Completed
Pull Request — master (#460)
by Daniel
09:57
created

Blog_Controller::isRSS()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 3
eloc 6
nc 2
nop 0
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();
0 ignored issues
show
Bug introduced by
The method toArray cannot be called on $this->getCandidateUsers()->map() (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
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
        $conn = DB::getConn();
0 ignored issues
show
Deprecated Code introduced by
The method DB::getConn() has been deprecated with message: since version 4.0 Use DB::get_conn instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
477
478
        // Filter by year
479
        $yearCond = $conn->formattedDatetimeClause('"BlogPost"."PublishDate"', '%Y');
480
        $query->where(sprintf('%s = \'%04d\'', $yearCond, Convert::raw2sql($year)));
481
482
        // Filter by month (if given)
483
        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...
484
            $monthCond = $conn->formattedDatetimeClause('"BlogPost"."PublishDate"', '%m');
485
            $query->where(sprintf('%s = \'%02d\'', $monthCond, Convert::raw2sql($month)));
486
487
            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...
488
                $dayCond = $conn->formattedDatetimeClause('"BlogPost"."PublishDate"', '%d');
489
                $query->where(sprintf('%s = \'%02d\'', $dayCond, Convert::raw2sql($day)));
490
            }
491
        }
492
493
494
        return $this->getBlogPosts()->setDataQuery($query);
495
    }
496
497
    /**
498
     * Return blog posts.
499
     *
500
     * @return DataList of BlogPost objects
501
     */
502
    public function getBlogPosts()
503
    {
504
        $blogPosts = BlogPost::get()->filter('ParentID', $this->ID);
505
506
        $this->extend('updateGetBlogPosts', $blogPosts);
507
508
        return $blogPosts;
509
    }
510
511
    /**
512
     * Get a link to a Member profile.
513
     *
514
     * @param string $urlSegment
515
     *
516
     * @return string
517
     */
518
    public function ProfileLink($urlSegment)
519
    {
520
        return Controller::join_links($this->Link(), 'profile', $urlSegment);
521
    }
522
523
    /**
524
     * This sets the title for our gridfield.
525
     *
526
     * @return string
527
     */
528
    public function getLumberjackTitle()
529
    {
530
        return _t('Blog.LumberjackTitle', 'Blog Posts');
531
    }
532
533
    /**
534
     * This overwrites lumberjacks default gridfield config.
535
     *
536
     * @return GridFieldConfig
537
     */
538
    public function getLumberjackGridFieldConfig()
539
    {
540
        return GridFieldConfig_BlogPost::create();
541
    }
542
543
    /**
544
     * {@inheritdoc}
545
     */
546
    public function providePermissions()
547
    {
548
        return array(
549
            Blog::MANAGE_USERS => array(
550
                'name' => _t(
551
                    'Blog.PERMISSION_MANAGE_USERS_DESCRIPTION',
552
                    'Manage users for individual blogs'
553
                ),
554
                'help' => _t(
555
                    'Blog.PERMISSION_MANAGE_USERS_HELP',
556
                    'Allow assignment of Editors, Writers, or Contributors to blogs'
557
                ),
558
                'category' => _t('Blog.PERMISSIONS_CATEGORY', 'Blog permissions'),
559
                'sort' => 100
560
            )
561
        );
562
    }
563
564
    /**
565
     * {@inheritdoc}
566
     */
567
    protected function onBeforeWrite()
568
    {
569
        parent::onBeforeWrite();
570
        $this->assignGroup();
571
    }
572
573
    /**
574
     * Assign users as necessary to the blog group.
575
     */
576
    protected function assignGroup()
577
    {
578
        if (!$this->config()->grant_user_access) {
579
            return;
580
        }
581
582
        $group = $this->getUserGroup();
583
584
        // Must check if the method exists or else an error occurs when changing page type
585
        if ($this->hasMethod('Editors')) {
586
            foreach (array($this->Editors(), $this->Writers(), $this->Contributors()) as $levels) {
587
                foreach ($levels as $user) {
588
                    if (!$user->inGroup($group)) {
589
                        $user->Groups()->add($group);
590
                    }
591
                }
592
            }
593
        }
594
    }
595
596
    /**
597
     * Gets or creates the group used to assign CMS access.
598
     *
599
     * @return Group
600
     */
601
    protected function getUserGroup()
602
    {
603
        $code = $this->config()->grant_user_group;
604
605
        $group = Group::get()->filter('Code', $code)->first();
606
607
        if ($group) {
608
            return $group;
609
        }
610
611
        $group = new Group();
612
        $group->Title = 'Blog users';
613
        $group->Code = $code;
614
615
        $group->write();
616
617
        $permission = new Permission();
618
        $permission->Code = $this->config()->grant_user_permission;
619
620
        $group->Permissions()->add($permission);
621
622
        return $group;
623
    }
624
}
625
626
/**
627
 * @package silverstripe
628
 * @subpackage blog
629
 */
630
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...
631
{
632
    /**
633
     * @var array
634
     */
635
    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...
636
        'archive',
637
        'tag',
638
        'category',
639
        'rss',
640
        'profile',
641
    );
642
643
    /**
644
     * @var array
645
     */
646
    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...
647
        'tag/$Tag!/$Rss' => 'tag',
648
        'category/$Category!/$Rss' => 'category',
649
        'archive/$Year!/$Month/$Day' => 'archive',
650
        'profile/$URLSegment!' => 'profile',
651
    );
652
653
    /**
654
     * @var array
655
     */
656
    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...
657
        'MetaTitle' => 'Text',
658
        'FilterDescription' => 'Text',
659
    );
660
661
    /**
662
     * The current Blog Post DataList query.
663
     *
664
     * @var DataList
665
     */
666
    protected $blogPosts;
667
668
    /**
669
     * @return string
670
     */
671
    public function index()
672
    {
673
        /**
674
         * @var Blog $dataRecord
675
         */
676
        $dataRecord = $this->dataRecord;
677
678
        $this->blogPosts = $dataRecord->getBlogPosts();
679
680
        return $this->render();
681
    }
682
683
    /**
684
     * Renders a Blog Member's profile.
685
     *
686
     * @return SS_HTTPResponse
687
     */
688
    public function profile()
689
    {
690
        $profile = $this->getCurrentProfile();
691
692
        if (!$profile) {
693
            return $this->httpError(404, 'Not Found');
694
        }
695
696
        $this->blogPosts = $this->getCurrentProfilePosts();
697
698
        return $this->render();
699
    }
700
701
    /**
702
     * Get the Member associated with the current URL segment.
703
     *
704
     * @return null|Member
705
     */
706
    public function getCurrentProfile()
707
    {
708
        $urlSegment = $this->request->param('URLSegment');
709
710
        if ($urlSegment) {
711
            return Member::get()
712
                ->filter('URLSegment', $urlSegment)
713
                ->first();
714
        }
715
716
        return null;
717
    }
718
719
    /**
720
     * Get posts related to the current Member profile.
721
     *
722
     * @return null|DataList
723
     */
724
    public function getCurrentProfilePosts()
725
    {
726
        $profile = $this->getCurrentProfile();
727
728
        if ($profile) {
729
            return $profile->BlogPosts()->filter('ParentID', $this->ID);
730
        }
731
732
        return null;
733
    }
734
735
    /**
736
     * Renders an archive for a specified date. This can be by year or year/month.
737
     *
738
     * @return null|SS_HTTPResponse
739
     */
740
    public function archive()
741
    {
742
        /**
743
         * @var Blog $dataRecord
744
         */
745
        $dataRecord = $this->dataRecord;
746
747
        $year = $this->getArchiveYear();
748
        $month = $this->getArchiveMonth();
749
        $day = $this->getArchiveDay();
750
751
        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...
752
            $this->httpError(404, 'Not Found');
753
        }
754
755
        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...
756
            $this->httpError(404, 'Not Found');
757
        }
758
759
        if ($year) {
760
            $this->blogPosts = $dataRecord->getArchivedBlogPosts($year, $month, $day);
761
762
            return $this->render();
763
        }
764
765
        $this->httpError(404, 'Not Found');
766
767
        return null;
768
    }
769
770
    /**
771
     * Fetches the archive year from the url.
772
     *
773
     * @return int
774
     */
775
    public function getArchiveYear()
776
    {
777
        if ($this->request->param('Year')) {
778
            if (preg_match('/^[0-9]{4}$/', $year = $this->request->param('Year'))) {
779
                return (int) $year;
780
            }
781
        } elseif ($this->request->param('Action') == 'archive') {
782
            return SS_Datetime::now()->Year();
783
        }
784
785
        return null;
786
    }
787
788
    /**
789
     * Fetches the archive money from the url.
790
     *
791
     * @return null|int
792
     */
793
    public function getArchiveMonth()
794
    {
795
        $month = $this->request->param('Month');
796
797
        if (preg_match('/^[0-9]{1,2}$/', $month)) {
798
            if ($month > 0 && $month < 13) {
799
                if (checkdate($month, 01, $this->getArchiveYear())) {
800
                    return (int) $month;
801
                }
802
            }
803
        }
804
805
        return null;
806
    }
807
808
    /**
809
     * Fetches the archive day from the url.
810
     *
811
     * @return null|int
812
     */
813
    public function getArchiveDay()
814
    {
815
        $day = $this->request->param('Day');
816
817
        if (preg_match('/^[0-9]{1,2}$/', $day)) {
818
            if (checkdate($this->getArchiveMonth(), $day, $this->getArchiveYear())) {
819
                return (int) $day;
820
            }
821
        }
822
823
        return null;
824
    }
825
826
    /**
827
     * Renders the blog posts for a given tag.
828
     *
829
     * @return null|SS_HTTPResponse
830
     */
831 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...
832
    {
833
        $tag = $this->getCurrentTag();
834
835
        if ($tag) {
836
            $this->blogPosts = $tag->BlogPosts();
837
838
            if($this->isRSS()) {
839
            	return $this->rssFeed($this->blogPosts, $tag->getLink());
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->rssFeed($t...osts, $tag->getLink()); (HTMLText) is incompatible with the return type documented by Blog_Controller::tag of type null|SS_HTTPResponse.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
840
            } else {
841
            	return $this->render();
842
            }
843
        }
844
845
        $this->httpError(404, 'Not Found');
846
847
        return null;
848
    }
849
850
    /**
851
     * Tag Getter for use in templates.
852
     *
853
     * @return null|BlogTag
854
     */
855 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...
856
    {
857
        /**
858
         * @var Blog $dataRecord
859
         */
860
        $dataRecord = $this->dataRecord;
861
        $tag = $this->request->param('Tag');
862
        if ($tag) {
863
            return $dataRecord->Tags()
864
                ->filter('URLSegment', array($tag, rawurlencode($tag)))
865
                ->first();
866
        }
867
        return null;
868
    }
869
870
    /**
871
     * Renders the blog posts for a given category.
872
     *
873
     * @return null|SS_HTTPResponse
874
     */
875 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...
876
    {
877
        $category = $this->getCurrentCategory();
878
879
        if ($category) {
880
            $this->blogPosts = $category->BlogPosts();
881
882
            if($this->isRSS()) {
883
            	return $this->rssFeed($this->blogPosts, $category->getLink());
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->rssFeed($t... $category->getLink()); (HTMLText) is incompatible with the return type documented by Blog_Controller::category of type null|SS_HTTPResponse.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
884
            } else {
885
            	return $this->render();
886
            }
887
        }
888
889
        $this->httpError(404, 'Not Found');
890
891
        return null;
892
    }
893
894
    /**
895
     * Category Getter for use in templates.
896
     *
897
     * @return null|BlogCategory
898
     */
899 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...
900
    {
901
        /**
902
         * @var Blog $dataRecord
903
         */
904
        $dataRecord = $this->dataRecord;
905
        $category = $this->request->param('Category');
906
        if ($category) {
907
            return $dataRecord->Categories()
908
                ->filter('URLSegment', array($category, rawurlencode($category)))
909
                ->first();
910
        }
911
        return null;
912
    }
913
914
    /**
915
     * Get the meta title for the current action.
916
     *
917
     * @return string
918
     */
919
    public function getMetaTitle()
920
    {
921
        $title = $this->data()->getTitle();
922
        $filter = $this->getFilterDescription();
923
924
        if ($filter) {
925
            $title = sprintf('%s - %s', $title, $filter);
926
        }
927
928
        $this->extend('updateMetaTitle', $title);
929
930
        return $title;
931
    }
932
933
    /**
934
     * Returns a description of the current filter.
935
     *
936
     * @return string
937
     */
938
    public function getFilterDescription()
939
    {
940
        $items = array();
941
942
        $list = $this->PaginatedList();
943
        $currentPage = $list->CurrentPage();
944
945
        if ($currentPage > 1) {
946
            $items[] = _t(
947
                'Blog.FILTERDESCRIPTION_PAGE',
948
                'Page {page}',
949
                null,
950
                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...
951
                    'page' => $currentPage,
952
                )
953
            );
954
        }
955
956
        if ($author = $this->getCurrentProfile()) {
957
            $items[] = _t(
958
                'Blog.FILTERDESCRIPTION_AUTHOR',
959
                'By {author}',
960
                null,
961
                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...
962
                    'author' => $author->Title,
963
                )
964
            );
965
        }
966
967
        if ($tag = $this->getCurrentTag()) {
968
            $items[] = _t(
969
                'Blog.FILTERDESCRIPTION_TAG',
970
                'Tagged with {tag}',
971
                null,
972
                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...
973
                    'tag' => $tag->Title,
974
                )
975
            );
976
        }
977
978
        if ($category = $this->getCurrentCategory()) {
979
            $items[] = _t(
980
                'Blog.FILTERDESCRIPTION_CATEGORY',
981
                'In category {category}',
982
                null,
983
                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...
984
                    'category' => $category->Title,
985
                )
986
            );
987
        }
988
989
        if ($this->owner->getArchiveYear()) {
990
            if ($this->owner->getArchiveDay()) {
991
                $date = $this->owner->getArchiveDate()->Nice();
992
            } elseif ($this->owner->getArchiveMonth()) {
993
                $date = $this->owner->getArchiveDate()->format('F, Y');
994
            } else {
995
                $date = $this->owner->getArchiveDate()->format('Y');
996
            }
997
998
            $items[] = _t(
999
                'Blog.FILTERDESCRIPTION_DATE',
1000
                'In {date}',
1001
                null,
1002
                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...
1003
                    'date' => $date,
1004
                )
1005
            );
1006
        }
1007
1008
        $result = '';
1009
1010
        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...
1011
            $result = implode(', ', $items);
1012
        }
1013
1014
        $this->extend('updateFilterDescription', $result);
1015
1016
        return $result;
1017
    }
1018
1019
    /**
1020
     * Returns a list of paginated blog posts based on the BlogPost dataList.
1021
     *
1022
     * @return PaginatedList
1023
     */
1024
    public function PaginatedList()
1025
    {
1026
        $allPosts = $this->blogPosts ?: new ArrayList();
1027
1028
        $posts = new PaginatedList($allPosts);
1029
1030
        // Set appropriate page size
1031
        if ($this->PostsPerPage > 0) {
1032
            $pageSize = $this->PostsPerPage;
1033
        } elseif ($count = $allPosts->count()) {
1034
            $pageSize = $count;
1035
        } else {
1036
            $pageSize = 99999;
1037
        }
1038
        $posts->setPageLength($pageSize);
1039
1040
        // Set current page
1041
        $start = $this->request->getVar($posts->getPaginationGetVar());
1042
        $posts->setPageStart($start);
1043
1044
        return $posts;
1045
    }
1046
1047
    /**
1048
     * Displays an RSS feed of blog posts.
1049
     *
1050
     * @return string
1051
     */
1052
    public function rss()
1053
    {
1054
        /**
1055
         * @var Blog $dataRecord
1056
         */
1057
        $dataRecord = $this->dataRecord;
1058
1059
        $this->blogPosts = $dataRecord->getBlogPosts();
1060
1061
        return $this->rssFeed($this->blogPosts, $this->Link());
1062
    }
1063
1064
    /**
1065
     * Returns the current archive date.
1066
     *
1067
     * @return null|Date
1068
     */
1069
    public function getArchiveDate()
1070
    {
1071
        $year = $this->getArchiveYear();
1072
        $month = $this->getArchiveMonth();
1073
        $day = $this->getArchiveDay();
1074
1075
        if ($year) {
1076
            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...
1077
                $date = sprintf('%s-%s-01', $year, $month);
1078
1079
                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...
1080
                    $date = sprintf('%s-%s-%s', $year, $month, $day);
1081
                }
1082
            } else {
1083
                $date = sprintf('%s-01-01', $year);
1084
            }
1085
1086
            return DBField::create_field('Date', $date);
1087
        }
1088
1089
        return null;
1090
    }
1091
1092
    /**
1093
     * Returns a link to the RSS feed.
1094
     *
1095
     * @return string
1096
     */
1097
    public function getRSSLink()
1098
    {
1099
        return $this->Link('rss');
1100
    }
1101
    
1102
    /**
1103
     * Displays an RSS feed of the given blog posts.
1104
     *
1105
     * @param DataList $blogPosts
1106
     * @param string $link
1107
     *
1108
     * @return string
1109
     */
1110
    protected function rssFeed($blogPosts, $link)
1111
    {
1112
        $rss = new RSSFeed($blogPosts, $link, $this->MetaTitle, $this->MetaDescription);
1113
1114
        $this->extend('updateRss', $rss);
1115
1116
        return $rss->outputToBrowser();
1117
    }
1118
    
1119
    /** 
1120
     * Returns true if the $Rss sub-action for categories/tags has been set to "rss"
1121
     */
1122
    private function isRSS() 
1123
    {
1124
        $rss = $this->request->param('Rss');
1125
        if(is_string($rss) && strcasecmp($rss, "rss") == 0) {
1126
            return true;
1127
        } else {
1128
            return false;
1129
        }
1130
    }
1131
}
1132