Completed
Pull Request — master (#362)
by Gordon
17:10 queued 17:10
created

Blog::isMemberOf()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 11
Ratio 91.67 %
Metric Value
dl 11
loc 12
rs 9.2
cc 4
eloc 6
nc 3
nop 2
1
<?php
2
3
/**
4
 * Blog Holder
5
 *
6
 * @package silverstripe
7
 * @subpackage blog
8
 *
9
 * @method HasManyList Tags() List of tags in this blog
10
 * @method HasManyList Categories() List of categories in this blog
11
 * @method ManyManyList Editors() List of editors
12
 * @method ManyManyList Writers() List of writers
13
 * @method ManyManyList Contributors() List of contributors
14
 */
15
class Blog extends Page implements PermissionProvider
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
16
{
17
    /**
18
     * Permission for user management.
19
     *
20
     * @var string
21
     */
22
    const MANAGE_USERS = 'BLOG_MANAGE_USERS';
23
24
    /**
25
     * If true, users assigned as editor, writer, or contributor will be automatically granted
26
     * CMS_ACCESS_CMSMain permission. If false, only users with this permission already may be
27
     * assigned.
28
     *
29
     * @config
30
     *
31
     * @var boolean
32
     */
33
    private static $grant_user_access = true;
0 ignored issues
show
Unused Code introduced by
The property $grant_user_access is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
34
35
    /**
36
     * Permission to either require, or grant to users assigned to work on this blog.
37
     *
38
     * @config
39
     *
40
     * @var string
41
     */
42
    private static $grant_user_permission = 'CMS_ACCESS_CMSMain';
0 ignored issues
show
Unused Code introduced by
The property $grant_user_permission is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
43
44
    /**
45
     * Group code to assign newly granted users to.
46
     *
47
     * @config
48
     *
49
     * @var string
50
     */
51
    private static $grant_user_group = 'blog-users';
0 ignored issues
show
Unused Code introduced by
The property $grant_user_group is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
52
53
    /**
54
     * @var array
55
     */
56
    private static $db = array(
0 ignored issues
show
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
57
        'PostsPerPage' => 'Int',
58
    );
59
60
    /**
61
     * @var array
62
     */
63
    private static $has_many = array(
0 ignored issues
show
Unused Code introduced by
The property $has_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
64
        'Tags' => 'BlogTag',
65
        'Categories' => 'BlogCategory',
66
    );
67
68
    /**
69
     * @var array
70
     */
71
    private static $many_many = array(
0 ignored issues
show
Unused Code introduced by
The property $many_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
72
        'Editors' => 'Member',
73
        'Writers' => 'Member',
74
        'Contributors' => 'Member',
75
    );
76
77
    /**
78
     * @var array
79
     */
80
    private static $allowed_children = array(
0 ignored issues
show
Unused Code introduced by
The property $allowed_children is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
81
        'BlogPost',
82
    );
83
84
    /**
85
     * @var array
86
     */
87
    private static $extensions = array(
0 ignored issues
show
Unused Code introduced by
The property $extensions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
88
        'BlogFilter',
89
    );
90
91
    /**
92
     * @var array
93
     */
94
    private static $defaults = array(
0 ignored issues
show
Unused Code introduced by
The property $defaults is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
95
        'ProvideComments' => false,
96
        'PostsPerPage' => 10,
97
    );
98
99
    /**
100
     * @var string
101
     */
102
    private static $description = 'Adds a blog to your website.';
0 ignored issues
show
Unused Code introduced by
The property $description is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
103
104
    private static $icon = 'blog/images/site-tree-icon.png';
0 ignored issues
show
Unused Code introduced by
The property $icon is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
105
106
    /**
107
     * {@inheritdoc}
108
     */
109
    public function getCMSFields()
110
    {
111
        Requirements::css(BLOGGER_DIR . '/css/cms.css');
112
        Requirements::javascript(BLOGGER_DIR . '/js/cms.js');
113
114
        $self =& $this;
115
116
        $this->beforeUpdateCMSFields(function ($fields) use ($self) {
117
            if (!$self->canEdit()) {
118
                return;
119
            }
120
121
            $categories = GridField::create(
122
                'Categories',
123
                _t('Blog.Categories', 'Categories'),
124
                $self->Categories(),
125
                GridFieldCategorisationConfig::create(15, $self->Categories()->sort('Title'), 'BlogCategory', 'Categories', 'BlogPosts')
126
            );
127
128
            $tags = GridField::create(
129
                'Tags',
130
                _t('Blog.Tags', 'Tags'),
131
                $self->Tags(),
132
                GridFieldCategorisationConfig::create(15, $self->Tags()->sort('Title'), 'BlogTag', 'Tags', 'BlogPosts')
133
            );
134
135
            /**
136
             * @var FieldList $fields
137
             */
138
            $fields->addFieldsToTab('Root.Categorisation', array(
139
                $categories,
140
                $tags
141
            ));
142
143
            $fields->findOrMakeTab('Root.Categorisation')->addExtraClass('blog-cms-categorisation');
144
        });
145
146
        return parent::getCMSFields();
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152
    public function canEdit($member = null)
153
    {
154
        $member = $this->getMember($member);
155
156
        if ($this->isEditor($member)) {
157
            return true;
158
        }
159
160
        return parent::canEdit($member);
161
    }
162
163
    /**
164
     * @param null|int|Member $member
165
     *
166
     * @return null|Member
167
     */
168 View Code Duplication
    protected function getMember($member = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
169
    {
170
        if (!$member) {
171
            $member = Member::currentUser();
172
        }
173
174
        if (is_numeric($member)) {
175
            $member = Member::get()->byID($member);
176
        }
177
178
        return $member;
179
    }
180
181
    /**
182
     * Check if this member is an editor of the blog.
183
     *
184
     * @param Member $member
185
     *
186
     * @return bool
187
     */
188
    public function isEditor($member)
189
    {
190
        $isEditor = $this->isMemberOf($member, $this->Editors());
191
        $this->extend('updateIsEditor', $isEditor, $member);
192
193
        return $isEditor;
194
    }
195
196
    /**
197
     * Determine if the given member belongs to the given relation.
198
     *
199
     * @param Member $member
200
     * @param DataList $relation
201
     *
202
     * @return bool
203
     */
204 View Code Duplication
    protected function isMemberOf($member, $relation)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
205
    {
206
        if (!$member || !$member->exists()) {
207
            return false;
208
        }
209
210
        if ($relation instanceof UnsavedRelationList) {
211
            return in_array($member->ID, $relation->getIDList());
212
        }
213
214
        return $relation->byID($member->ID) !== null;
215
    }
216
217
    /**
218
     * Determine the role of the given member.
219
     *
220
     * Call be called via template to determine the current user.
221
     *
222
     * @example "Hello $RoleOf($CurrentMember.ID)"
223
     *
224
     * @param int|Member $member
225
     *
226
     * @return null|string
227
     */
228
    public function RoleOf($member)
229
    {
230
        if (is_numeric($member)) {
231
            $member = DataObject::get_by_id('Member', $member);
232
        }
233
234
        if (!$member) {
235
            return null;
236
        }
237
238
        if ($this->isEditor($member)) {
239
            return _t('Blog.EDITOR', 'Editor');
240
        }
241
242
        if ($this->isWriter($member)) {
243
            return _t('Blog.WRITER', 'Writer');
244
        }
245
246
        if ($this->isContributor($member)) {
247
            return _t('Blog.CONTRIBUTOR', 'Contributor');
248
        }
249
250
        return null;
251
    }
252
253
    /**
254
     * Check if this member is a writer of the blog.
255
     *
256
     * @param Member $member
257
     *
258
     * @return bool
259
     */
260
    public function isWriter($member)
261
    {
262
        $isWriter = $this->isMemberOf($member, $this->Writers());
263
        $this->extend('updateIsWriter', $isWriter, $member);
264
265
        return $isWriter;
266
    }
267
268
    /**
269
     * Check if this member is a contributor of the blog.
270
     *
271
     * @param Member $member
272
     *
273
     * @return bool
274
     */
275
    public function isContributor($member)
276
    {
277
        $isContributor = $this->isMemberOf($member, $this->Contributors());
278
        $this->extend('updateIsContributor', $isContributor, $member);
279
280
        return $isContributor;
281
    }
282
283
    /**
284
     * {@inheritdoc}
285
     */
286
    public function canAddChildren($member = null)
287
    {
288
        $member = $this->getMember($member);
289
290
        if ($this->isEditor($member) || $this->isWriter($member) || $this->isContributor($member)) {
291
            return true;
292
        }
293
294
        return parent::canAddChildren($member);
295
    }
296
297
    /**
298
     * {@inheritdoc}
299
     */
300
    public function getSettingsFields()
301
    {
302
        $fields = parent::getSettingsFields();
303
304
        $fields->addFieldToTab('Root.Settings',
305
            NumericField::create('PostsPerPage', _t('Blog.PostsPerPage', 'Posts Per Page'))
306
        );
307
308
        $members = $this->getCandidateUsers()->map()->toArray();
309
310
        $editorField = ListboxField::create('Editors', 'Editors', $members)
311
            ->setMultiple(true)
312
            ->setRightTitle('<a class="toggle-description">help</a>')
313
            ->setDescription('
314
				An editor has control over specific Blogs, and all posts included within it. Short of being able to assign other editors to a blog, they are able to handle most changes to their assigned blog.<br />
315
				<br />
316
				Editors have these permissions:<br />
317
				<br />
318
				Update or publish any BlogPost in their Blog<br />
319
				Update or publish their Blog<br />
320
				Assign/unassign writers to their Blog<br />
321
				Assign/unassign contributors to their Blog<br />
322
				Assign/unassign any member as an author of a particular BlogPost
323
			');
324
325
        if (!$this->canEditEditors()) {
326
            $editorField = $editorField->performDisabledTransformation();
327
        }
328
329
        $writerField = ListboxField::create('Writers', 'Writers', $members)
330
            ->setMultiple(true)
331
            ->setRightTitle('<a class="toggle-description">help</a>')
332
            ->setDescription('
333
				A writer has full control over creating, editing and publishing BlogPosts they have authored or have been assigned to. Writers are unable to edit BlogPosts to which they are not assigned.<br />
334
				<br />
335
				Writers have these permissions:<br />
336
				<br />
337
				Update or publish any BlogPost they have authored or have been assigned to<br />
338
				Assign/unassign any member as an author of a particular BlogPost they have authored or have been assigned to
339
			');
340
341
        if (!$this->canEditWriters()) {
342
            $writerField = $writerField->performDisabledTransformation();
343
        }
344
345
        $contributorField = ListboxField::create('Contributors', 'Contributors', $members)
346
            ->setMultiple(true)
347
            ->setRightTitle('<a class="toggle-description">help</a>')
348
            ->setDescription('
349
				Contributors have the ability to create or edit BlogPosts, but are unable to publish without authorisation of an editor. They are also unable to assign other contributing authors to any of their BlogPosts.<br />
350
				<br />
351
				Contributors have these permissions:<br />
352
				<br />
353
				Update any BlogPost they have authored or have been assigned to
354
			');
355
356
        if (!$this->canEditContributors()) {
357
            $contributorField = $contributorField->performDisabledTransformation();
358
        }
359
360
        $fields->addFieldsToTab('Root.Users', array(
361
            $editorField,
362
            $writerField,
363
            $contributorField
364
        ));
365
366
        return $fields;
367
    }
368
369
    /**
370
     * Gets the list of user candidates to be assigned to assist with this blog.
371
     *
372
     * @return SS_List
373
     */
374
    protected function getCandidateUsers()
375
    {
376
        if ($this->config()->grant_user_access) {
377
            $list = Member::get();
378
            $this->extend('updateCandidateUsers', $list);
379
            return $list;
380
        } else {
381
            return Permission::get_members_by_permission(
382
                $this->config()->grant_user_permission
383
            );
384
        }
385
    }
386
387
    /**
388
     * Determine if this user can edit the editors list.
389
     *
390
     * @param int|Member $member
391
     *
392
     * @return bool
393
     */
394
    public function canEditEditors($member = null)
395
    {
396
        $member = $this->getMember($member);
397
398
        $extended = $this->extendedCan('canEditEditors', $member);
399
400
        if ($extended !== null) {
401
            return $extended;
402
        }
403
404
        return Permission::checkMember($member, self::MANAGE_USERS);
0 ignored issues
show
Bug Compatibility introduced by
The expression \Permission::checkMember...r, self::MANAGE_USERS); of type boolean|string|null adds the type string to the return on line 404 which is incompatible with the return type documented by Blog::canEditEditors of type boolean.
Loading history...
405
    }
406
407
    /**
408
     * Determine if this user can edit writers list.
409
     *
410
     * @param int|Member $member
411
     *
412
     * @return boolean
413
     */
414 View Code Duplication
    public function canEditWriters($member = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
415
    {
416
        $member = $this->getMember($member);
417
418
        $extended = $this->extendedCan('canEditWriters', $member);
419
420
        if ($extended !== null) {
421
            return $extended;
422
        }
423
424
        if ($this->isEditor($member)) {
425
            return true;
426
        }
427
428
        return Permission::checkMember($member, self::MANAGE_USERS);
0 ignored issues
show
Bug Compatibility introduced by
The expression \Permission::checkMember...r, self::MANAGE_USERS); of type boolean|string|null adds the type string to the return on line 428 which is incompatible with the return type documented by Blog::canEditWriters of type boolean.
Loading history...
429
    }
430
431
    /**
432
     * Determines if this user can edit the contributors list.
433
     *
434
     * @param int|Member $member
435
     *
436
     * @return boolean
437
     */
438 View Code Duplication
    public function canEditContributors($member = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
439
    {
440
        $member = $this->getMember($member);
441
442
        $extended = $this->extendedCan('canEditContributors', $member);
443
444
        if ($extended !== null) {
445
            return $extended;
446
        }
447
448
        if ($this->isEditor($member)) {
449
            return true;
450
        }
451
452
        return Permission::checkMember($member, self::MANAGE_USERS);
0 ignored issues
show
Bug Compatibility introduced by
The expression \Permission::checkMember...r, self::MANAGE_USERS); of type boolean|string|null adds the type string to the return on line 452 which is incompatible with the return type documented by Blog::canEditContributors of type boolean.
Loading history...
453
    }
454
455
    /**
456
     * Returns BlogPosts for a given date period.
457
     *
458
     * @param int $year
459
     * @param null|int $month
460
     * @param null|int $day
461
     *
462
     * @return DataList
463
     */
464
    public function getArchivedBlogPosts($year, $month = null, $day = null)
465
    {
466
        $query = $this->getBlogPosts()->dataQuery();
467
468
        $stage = $query->getQueryParam('Versioned.stage');
469
470
        if ($stage) {
471
            $stage = '_' . $stage;
472
        }
473
474
        $query->innerJoin('BlogPost', sprintf('"SiteTree%s"."ID" = "BlogPost%s"."ID"', $stage, $stage));
475
476
        // getConn is deprecated, but not get_conn in 3.1
477
        $getConnectionMethod = 'getConn';
478
        if (method_exists('DB','get_conn')) {
479
            $getConnectionMethod = 'get_conn';
480
        };
481
482
483
        if (DB::$getConnectionMethod() instanceof MySQLDatabase) {
484
            $query->where(sprintf('YEAR("PublishDate") = \'%s\'', Convert::raw2sql($year)));
485
486 View Code Duplication
            if ($month) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $month of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
487
                $query->where(sprintf('MONTH("PublishDate") = \'%s\'', Convert::raw2sql($month)));
488
489
                if ($day) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $day of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
490
                    $query->where(sprintf('DAY("PublishDate") = \'%s\'', Convert::raw2sql($day)));
491
                }
492
            }
493
        } elseif (DB::$getConnectionMethod() instanceof PostgreSQLDatabase) {
0 ignored issues
show
Bug introduced by
The class PostgreSQLDatabase does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
494
            $where = sprintf('EXTRACT(YEAR FROM "PublishDate") = \'%s\'', Convert::raw2sql($year));
495
496 View Code Duplication
            if ($month) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $month of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
497
                $where .= sprintf(' AND EXTRACT(MONTH FROM "PublishDate") = \'%s\'', Convert::raw2sql($month));
498
499
                if ($day) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $day of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
500
                    $where .= sprintf(' AND EXTRACT(DAY FROM "PublishDate") = \'%s\'', Convert::raw2sql($day));
501
                }
502
            }
503
504
            $query->where($where);
505
        }
506
507
508
        return $this->getBlogPosts()->setDataQuery($query);
509
    }
510
511
    /**
512
     * Return blog posts.
513
     *
514
     * @return DataList of BlogPost objects
515
     */
516
    public function getBlogPosts()
517
    {
518
        $blogPosts = BlogPost::get()->filter('ParentID', $this->ID);
519
520
        $this->extend('updateGetBlogPosts', $blogPosts);
521
522
        return $blogPosts;
523
    }
524
525
    /**
526
     * Get a link to a Member profile.
527
     *
528
     * @param string $urlSegment
529
     *
530
     * @return string
531
     */
532
    public function ProfileLink($urlSegment)
533
    {
534
        return Controller::join_links($this->Link(), 'profile', $urlSegment);
535
    }
536
537
    /**
538
     * This sets the title for our gridfield.
539
     *
540
     * @return string
541
     */
542
    public function getLumberjackTitle()
543
    {
544
        return _t('Blog.LumberjackTitle', 'Blog Posts');
545
    }
546
547
    /**
548
     * This overwrites lumberjacks default gridfield config.
549
     *
550
     * @return GridFieldConfig
551
     */
552
    public function getLumberjackGridFieldConfig()
553
    {
554
        return GridFieldConfig_BlogPost::create();
555
    }
556
557
    /**
558
     * {@inheritdoc}
559
     */
560
    public function providePermissions()
561
    {
562
        return array(
563
            Blog::MANAGE_USERS => array(
564
                'name' => _t(
565
                    'Blog.PERMISSION_MANAGE_USERS_DESCRIPTION',
566
                    'Manage users for individual blogs'
567
                ),
568
                'help' => _t(
569
                    'Blog.PERMISSION_MANAGE_USERS_HELP',
570
                    'Allow assignment of Editors, Writers, or Contributors to blogs'
571
                ),
572
                'category' => _t('Blog.PERMISSIONS_CATEGORY', 'Blog permissions'),
573
                'sort' => 100
574
            )
575
        );
576
    }
577
578
    /**
579
     * {@inheritdoc}
580
     */
581
    protected function onBeforeWrite()
582
    {
583
        parent::onBeforeWrite();
584
        $this->assignGroup();
585
    }
586
587
    /**
588
     * Assign users as necessary to the blog group.
589
     */
590
    protected function assignGroup()
591
    {
592
        if (!$this->config()->grant_user_access) {
593
            return;
594
        }
595
596
        $group = $this->getUserGroup();
597
598
        // Must check if the method exists or else an error occurs when changing page type
599
        if ($this->hasMethod('Editors')) {
600
            foreach (array($this->Editors(), $this->Writers(), $this->Contributors()) as $levels) {
601
                foreach ($levels as $user) {
602
                    if (!$user->inGroup($group)) {
603
                        $user->Groups()->add($group);
604
                    }
605
                }
606
            }
607
        }
608
    }
609
610
    /**
611
     * Gets or creates the group used to assign CMS access.
612
     *
613
     * @return Group
614
     */
615
    protected function getUserGroup()
616
    {
617
        $code = $this->config()->grant_user_group;
618
619
        $group = Group::get()->filter('Code', $code)->first();
620
621
        if ($group) {
622
            return $group;
623
        }
624
625
        $group = new Group();
626
        $group->Title = 'Blog users';
627
        $group->Code = $code;
628
629
        $group->write();
630
631
        $permission = new Permission();
632
        $permission->Code = $this->config()->grant_user_permission;
633
634
        $group->Permissions()->add($permission);
635
636
        return $group;
637
    }
638
}
639
640
/**
641
 * @package silverstripe
642
 * @subpackage blog
643
 */
644
class Blog_Controller extends Page_Controller
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
645
{
646
    /**
647
     * @var array
648
     */
649
    private static $allowed_actions = array(
0 ignored issues
show
Unused Code introduced by
The property $allowed_actions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
650
        'archive',
651
        'tag',
652
        'category',
653
        'rss',
654
        'profile',
655
    );
656
657
    /**
658
     * @var array
659
     */
660
    private static $url_handlers = array(
0 ignored issues
show
Unused Code introduced by
The property $url_handlers is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
661
        'tag/$Tag!' => 'tag',
662
        'category/$Category!' => 'category',
663
        'archive/$Year!/$Month/$Day' => 'archive',
664
        'profile/$URLSegment!' => 'profile',
665
    );
666
667
    /**
668
     * @var array
669
     */
670
    private static $casting = array(
0 ignored issues
show
Unused Code introduced by
The property $casting is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
671
        'MetaTitle' => 'Text',
672
        'FilterDescription' => 'Text',
673
    );
674
675
    /**
676
     * The current Blog Post DataList query.
677
     *
678
     * @var DataList
679
     */
680
    protected $blogPosts;
681
682
    /**
683
     * @return string
684
     */
685
    public function index()
686
    {
687
        /**
688
         * @var Blog $dataRecord
689
         */
690
        $dataRecord = $this->dataRecord;
691
692
        $this->blogPosts = $dataRecord->getBlogPosts();
693
694
        return $this->render();
695
    }
696
697
    /**
698
     * Renders a Blog Member's profile.
699
     *
700
     * @return SS_HTTPResponse
701
     */
702
    public function profile()
703
    {
704
        $profile = $this->getCurrentProfile();
705
706
        if (!$profile) {
707
            return $this->httpError(404, 'Not Found');
708
        }
709
710
        $this->blogPosts = $this->getCurrentProfilePosts();
711
712
        return $this->render();
713
    }
714
715
    /**
716
     * Get the Member associated with the current URL segment.
717
     *
718
     * @return null|Member
719
     */
720
    public function getCurrentProfile()
721
    {
722
        $urlSegment = $this->request->param('URLSegment');
723
724
        if ($urlSegment) {
725
            return Member::get()
726
                ->filter('URLSegment', $urlSegment)
727
                ->first();
728
        }
729
730
        return null;
731
    }
732
733
    /**
734
     * Get posts related to the current Member profile.
735
     *
736
     * @return null|DataList
737
     */
738
    public function getCurrentProfilePosts()
739
    {
740
        $profile = $this->getCurrentProfile();
741
742
        if ($profile) {
743
            return $profile->BlogPosts()->filter('ParentID', $this->ID);
744
        }
745
746
        return null;
747
    }
748
749
    /**
750
     * Renders an archive for a specified date. This can be by year or year/month.
751
     *
752
     * @return null|SS_HTTPResponse
753
     */
754
    public function archive()
755
    {
756
        /**
757
         * @var Blog $dataRecord
758
         */
759
        $dataRecord = $this->dataRecord;
760
761
        $year = $this->getArchiveYear();
762
        $month = $this->getArchiveMonth();
763
        $day = $this->getArchiveDay();
764
765
        if ($this->request->param('Month') && !$month) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $month of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
766
            $this->httpError(404, 'Not Found');
767
        }
768
769
        if ($month && $this->request->param('Day') && !$day) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $month of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
Bug Best Practice introduced by
The expression $day of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
770
            $this->httpError(404, 'Not Found');
771
        }
772
773
        if ($year) {
774
            $this->blogPosts = $dataRecord->getArchivedBlogPosts($year, $month, $day);
775
776
            return $this->render();
777
        }
778
779
        $this->httpError(404, 'Not Found');
780
781
        return null;
782
    }
783
784
    /**
785
     * Fetches the archive year from the url.
786
     *
787
     * @return int
788
     */
789
    public function getArchiveYear()
790
    {
791
        if ($this->request->param('Year')) {
792
            if (preg_match('/^[0-9]{4}$/', $year = $this->request->param('Year'))) {
793
                return (int) $year;
794
            }
795
        } elseif ($this->request->param('Action') == 'archive') {
796
            return SS_Datetime::now()->Year();
797
        }
798
799
        return null;
800
    }
801
802
    /**
803
     * Fetches the archive money from the url.
804
     *
805
     * @return null|int
806
     */
807
    public function getArchiveMonth()
808
    {
809
        $month = $this->request->param('Month');
810
811
        if (preg_match('/^[0-9]{1,2}$/', $month)) {
812
            if ($month > 0 && $month < 13) {
813
                if (checkdate($month, 01, $this->getArchiveYear())) {
814
                    return (int) $month;
815
                }
816
            }
817
        }
818
819
        return null;
820
    }
821
822
    /**
823
     * Fetches the archive day from the url.
824
     *
825
     * @return null|int
826
     */
827
    public function getArchiveDay()
828
    {
829
        $day = $this->request->param('Day');
830
831
        if (preg_match('/^[0-9]{1,2}$/', $day)) {
832
            if (checkdate($this->getArchiveMonth(), $day, $this->getArchiveYear())) {
833
                return (int) $day;
834
            }
835
        }
836
837
        return null;
838
    }
839
840
    /**
841
     * Renders the blog posts for a given tag.
842
     *
843
     * @return null|SS_HTTPResponse
844
     */
845 View Code Duplication
    public function tag()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
846
    {
847
        $tag = $this->getCurrentTag();
848
849
        if ($tag) {
850
            $this->blogPosts = $tag->BlogPosts();
851
            return $this->render();
852
        }
853
854
        $this->httpError(404, 'Not Found');
855
856
        return null;
857
    }
858
859
    /**
860
     * Tag Getter for use in templates.
861
     *
862
     * @return null|BlogTag
863
     */
864 View Code Duplication
    public function getCurrentTag()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
865
    {
866
        /**
867
         * @var Blog $dataRecord
868
         */
869
        $dataRecord = $this->dataRecord;
870
        $tag = $this->request->param('Tag');
871
        if ($tag) {
872
            return $dataRecord->Tags()
873
                ->filter('URLSegment', array($tag, rawurlencode($tag)))
874
                ->first();
875
        }
876
        return null;
877
    }
878
879
    /**
880
     * Renders the blog posts for a given category.
881
     *
882
     * @return null|SS_HTTPResponse
883
     */
884 View Code Duplication
    public function category()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
885
    {
886
        $category = $this->getCurrentCategory();
887
888
        if ($category) {
889
            $this->blogPosts = $category->BlogPosts();
890
891
            return $this->render();
892
        }
893
894
        $this->httpError(404, 'Not Found');
895
896
        return null;
897
    }
898
899
    /**
900
     * Category Getter for use in templates.
901
     *
902
     * @return null|BlogCategory
903
     */
904 View Code Duplication
    public function getCurrentCategory()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
905
    {
906
        /**
907
         * @var Blog $dataRecord
908
         */
909
        $dataRecord = $this->dataRecord;
910
        $category = $this->request->param('Category');
911
        if ($category) {
912
            return $dataRecord->Categories()
913
                ->filter('URLSegment', array($category, rawurlencode($category)))
914
                ->first();
915
        }
916
        return null;
917
    }
918
919
    /**
920
     * Get the meta title for the current action.
921
     *
922
     * @return string
923
     */
924
    public function getMetaTitle()
925
    {
926
        $title = $this->data()->getTitle();
927
        $filter = $this->getFilterDescription();
928
929
        if ($filter) {
930
            $title = sprintf('%s - %s', $title, $filter);
931
        }
932
933
        $this->extend('updateMetaTitle', $title);
934
935
        return $title;
936
    }
937
938
    /**
939
     * Returns a description of the current filter.
940
     *
941
     * @return string
942
     */
943
    public function getFilterDescription()
944
    {
945
        $items = array();
946
947
        $list = $this->PaginatedList();
948
        $currentPage = $list->CurrentPage();
949
950
        if ($currentPage > 1) {
951
            $items[] = _t(
952
                'Blog.FILTERDESCRIPTION_PAGE',
953
                'Page {page}',
954
                null,
955
                array(
0 ignored issues
show
Documentation introduced by
array('page' => $currentPage) is of type array<string,double|inte...age":"double|integer"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
956
                    'page' => $currentPage,
957
                )
958
            );
959
        }
960
961
        if ($author = $this->getCurrentProfile()) {
962
            $items[] = _t(
963
                'Blog.FILTERDESCRIPTION_AUTHOR',
964
                'By {author}',
965
                null,
966
                array(
0 ignored issues
show
Documentation introduced by
array('author' => $author->Title) is of type array<string,?,{"author":"?"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
967
                    'author' => $author->Title,
968
                )
969
            );
970
        }
971
972
        if ($tag = $this->getCurrentTag()) {
973
            $items[] = _t(
974
                'Blog.FILTERDESCRIPTION_TAG',
975
                'Tagged with {tag}',
976
                null,
977
                array(
0 ignored issues
show
Documentation introduced by
array('tag' => $tag->Title) is of type array<string,?,{"tag":"?"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
978
                    'tag' => $tag->Title,
979
                )
980
            );
981
        }
982
983
        if ($category = $this->getCurrentCategory()) {
984
            $items[] = _t(
985
                'Blog.FILTERDESCRIPTION_CATEGORY',
986
                'In category {category}',
987
                null,
988
                array(
0 ignored issues
show
Documentation introduced by
array('category' => $category->Title) is of type array<string,?,{"category":"?"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
989
                    'category' => $category->Title,
990
                )
991
            );
992
        }
993
994
        if ($this->owner->getArchiveYear()) {
995
            if ($this->owner->getArchiveDay()) {
996
                $date = $this->owner->getArchiveDate()->Nice();
997
            } elseif ($this->owner->getArchiveMonth()) {
998
                $date = $this->owner->getArchiveDate()->format('F, Y');
999
            } else {
1000
                $date = $this->owner->getArchiveDate()->format('Y');
1001
            }
1002
1003
            $items[] = _t(
1004
                'Blog.FILTERDESCRIPTION_DATE',
1005
                'In {date}',
1006
                null,
1007
                array(
0 ignored issues
show
Documentation introduced by
array('date' => $date) is of type array<string,?,{"date":"?"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1008
                    'date' => $date,
1009
                )
1010
            );
1011
        }
1012
1013
        $result = '';
1014
1015
        if ($items) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $items of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1016
            $result = implode(', ', $items);
1017
        }
1018
1019
        $this->extend('updateFilterDescription', $result);
1020
1021
        return $result;
1022
    }
1023
1024
    /**
1025
     * Returns a list of paginated blog posts based on the BlogPost dataList.
1026
     *
1027
     * @return PaginatedList
1028
     */
1029
    public function PaginatedList()
1030
    {
1031
        $allPosts = $this->blogPosts ?: new ArrayList();
1032
1033
        $posts = new PaginatedList($allPosts);
1034
1035
        // Set appropriate page size
1036
        if ($this->PostsPerPage > 0) {
1037
            $pageSize = $this->PostsPerPage;
1038
        } elseif ($count = $allPosts->count()) {
1039
            $pageSize = $count;
1040
        } else {
1041
            $pageSize = 99999;
1042
        }
1043
        $posts->setPageLength($pageSize);
1044
1045
        // Set current page
1046
        $start = $this->request->getVar($posts->getPaginationGetVar());
1047
        $posts->setPageStart($start);
1048
1049
        return $posts;
1050
    }
1051
1052
    /**
1053
     * Displays an RSS feed of blog posts.
1054
     *
1055
     * @return string
1056
     */
1057
    public function rss()
1058
    {
1059
        /**
1060
         * @var Blog $dataRecord
1061
         */
1062
        $dataRecord = $this->dataRecord;
1063
1064
        $this->blogPosts = $dataRecord->getBlogPosts();
1065
1066
        $rss = new RSSFeed($this->blogPosts, $this->Link(), $this->MetaTitle, $this->MetaDescription);
1067
1068
        $this->extend('updateRss', $rss);
1069
1070
        return $rss->outputToBrowser();
1071
    }
1072
1073
    /**
1074
     * Returns the current archive date.
1075
     *
1076
     * @return null|Date
1077
     */
1078
    public function getArchiveDate()
1079
    {
1080
        $year = $this->getArchiveYear();
1081
        $month = $this->getArchiveMonth();
1082
        $day = $this->getArchiveDay();
1083
1084
        if ($year) {
1085
            if ($month) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $month of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1086
                $date = sprintf('%s-%s-01', $year, $month);
1087
1088
                if ($day) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $day of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1089
                    $date = sprintf('%s-%s-%s', $year, $month, $day);
1090
                }
1091
            } else {
1092
                $date = sprintf('%s-01-01', $year);
1093
            }
1094
1095
            return DBField::create_field('Date', $date);
1096
        }
1097
1098
        return null;
1099
    }
1100
1101
    /**
1102
     * Returns a link to the RSS feed.
1103
     *
1104
     * @return string
1105
     */
1106
    public function getRSSLink()
1107
    {
1108
        return $this->Link('rss');
1109
    }
1110
}
1111