Completed
Pull Request — master (#512)
by
unknown
01:44
created

BlogPost::getMinutesToRead()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 18
rs 8.8571
cc 6
eloc 9
nc 7
nop 1
1
<?php
2
3
namespace SilverStripe\Blog\Model;
4
5
use Page;
6
use SilverStripe\AssetAdmin\Forms\UploadField;
7
use SilverStripe\Assets\Image;
8
use SilverStripe\Control\Controller;
9
use SilverStripe\Core\Config\Config;
10
use SilverStripe\Forms\DatetimeField;
11
use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
12
use SilverStripe\Forms\ListboxField;
13
use SilverStripe\Forms\TextField;
14
use SilverStripe\Forms\ToggleCompositeField;
15
use SilverStripe\ORM\ArrayList;
16
use SilverStripe\ORM\FieldType\DBDatetime;
17
use SilverStripe\ORM\UnsavedRelationList;
18
use SilverStripe\Security\Group;
19
use SilverStripe\Security\Member;
20
use SilverStripe\Security\Permission;
21
use SilverStripe\Security\Security;
22
use SilverStripe\TagField\TagField;
23
use SilverStripe\Versioned\Versioned;
24
use SilverStripe\View\ArrayData;
25
use SilverStripe\View\Requirements;
26
27
/**
28
 * An individual blog post.
29
 *
30
 * @method ManyManyList Categories()
31
 * @method ManyManyList Tags()
32
 * @method ManyManyList Authors()
33
 * @method Blog Parent()
34
 *
35
 * @property string $PublishDate
36
 * @property string $AuthorNames
37
 * @property int $ParentID
38
 */
39
class BlogPost extends Page
40
{
41
    /**
42
     * Same as above, but for list of users that can be
43
     * given credit in the author field for blog posts
44
     * @var string|bool false or group code
45
     */
46
    private static $restrict_authors_to_group = false;
0 ignored issues
show
Unused Code introduced by
The property $restrict_authors_to_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...
47
48
    /**
49
     * {@inheritDoc}
50
     * @var string
51
     */
52
    private static $table_name = 'BlogPost';
0 ignored issues
show
Unused Code introduced by
The property $table_name 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...
53
54
    /**
55
     * @var array
56
     */
57
    private static $db = [
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...
58
        'PublishDate' => 'Datetime',
59
        'AuthorNames' => 'Varchar(1024)',
60
        'Summary'     => 'HTMLText'
61
    ];
62
63
    /**
64
     * @var array
65
     */
66
    private static $has_one = [
0 ignored issues
show
Unused Code introduced by
The property $has_one 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...
67
        'FeaturedImage' => Image::class
68
    ];
69
70
    /**
71
     * @var array
72
     */
73
    private static $owns = [
0 ignored issues
show
Unused Code introduced by
The property $owns 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...
74
        'FeaturedImage',
75
    ];
76
77
    /**
78
     * @var array
79
     */
80
    private static $many_many = [
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...
81
        'Categories' => BlogCategory::class,
82
        'Tags'       => BlogTag::class,
83
        'Authors'    => Member::class
84
    ];
85
86
    /**
87
     * @var array
88
     */
89
    private static $defaults = [
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...
90
        'ShowInMenus'     => false,
91
        'InheritSideBar'  => true,
92
        'ProvideComments' => true
93
    ];
94
95
    /**
96
     * @var array
97
     */
98
    private static $extensions = [
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...
99
        BlogPostFilter::class
100
    ];
101
102
    /**
103
     * @var array
104
     */
105
    private static $searchable_fields = [
0 ignored issues
show
Unused Code introduced by
The property $searchable_fields 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...
106
        'Title'
107
    ];
108
109
    /**
110
     * @var array
111
     */
112
    private static $summary_fields = [
0 ignored issues
show
Unused Code introduced by
The property $summary_fields 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...
113
        'Title'
114
    ];
115
116
    /**
117
     * @var array
118
     */
119
    private static $casting = [
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...
120
        'Excerpt' => 'HTMLText',
121
        'Date' => 'DBDatetime'
122
    ];
123
124
    /**
125
     * @var array
126
     */
127
    private static $allowed_children = [];
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...
128
129
    /**
130
     * The default sorting lists BlogPosts with an empty PublishDate at the top.
131
     *
132
     * @var string
133
     */
134
    private static $default_sort = '"PublishDate" IS NULL DESC, "PublishDate" DESC';
0 ignored issues
show
Unused Code introduced by
The property $default_sort 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...
135
136
    /**
137
     * @var bool
138
     */
139
    private static $can_be_root = false;
0 ignored issues
show
Unused Code introduced by
The property $can_be_root 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...
140
141
    /**
142
     * This will display or hide the current class from the SiteTree. This variable can be
143
     * configured using YAML.
144
     *
145
     * @var bool
146
     */
147
    private static $show_in_sitetree = false;
0 ignored issues
show
Unused Code introduced by
The property $show_in_sitetree 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...
148
149
    /**
150
     * This helps estimate how long an article will take to read, if your target audience
151
     * is elderly then you should lower this value. See {@link getMinutesToRead()}
152
     *
153
     * @var int
154
     */
155
    private static $minutes_to_read_wpm = 200;
0 ignored issues
show
Unused Code introduced by
The property $minutes_to_read_wpm 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...
156
157
    /**
158
     * Determine the role of the given member.
159
     *
160
     * Call be called via template to determine the current user.
161
     *
162
     * @example "Hello $RoleOf($CurrentMember.ID)"
163
     *
164
     * @param null|int|Member $member
165
     *
166
     * @return null|string
167
     */
168
    public function RoleOf($member = null)
169
    {
170
        $member = $this->getMember($member);
171
172
        if (!$member) {
173
            return null;
174
        }
175
176
        if ($this->isAuthor($member)) {
177
            return _t(__CLASS__ . '.AUTHOR', 'Author');
178
        }
179
180
        $parent = $this->Parent();
181
182
        if ($parent instanceof Blog) {
183
            return $parent->RoleOf($member);
184
        }
185
186
        return null;
187
    }
188
189
    /**
190
     * Determine if the given member is an author of this post.
191
     *
192
     * @param null|Member $member
193
     *
194
     * @return bool
195
     */
196 View Code Duplication
    public function isAuthor($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...
197
    {
198
        if (!$member || !$member->exists()) {
199
            return false;
200
        }
201
202
        $list = $this->Authors();
203
204
        if ($list instanceof UnsavedRelationList) {
205
            return in_array($member->ID, $list->getIDList());
206
        }
207
208
        return $list->byID($member->ID) !== null;
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function getCMSFields()
215
    {
216
        Requirements::css('silverstripe/blog:client/dist/styles/main.css');
217
        Requirements::javascript('silverstripe/blog:client/dist/js/main.bundle.js');
218
219
        $this->beforeUpdateCMSFields(function ($fields) {
220
            $uploadField = UploadField::create('FeaturedImage', _t(__CLASS__ . '.FeaturedImage', 'Featured Image'));
221
            $uploadField->getValidator()->setAllowedExtensions(['jpg', 'jpeg', 'png', 'gif']);
222
223
            /**
224
             * @var FieldList $fields
225
             */
226
            $fields->insertAfter($uploadField, 'Content');
227
228
            $summary = HtmlEditorField::create('Summary', false);
229
            $summary->setRows(5);
230
            $summary->setDescription(_t(
231
                __CLASS__ . '.SUMMARY_DESCRIPTION',
232
                'If no summary is specified the first 30 words will be used.'
233
            ));
234
235
            $summaryHolder = ToggleCompositeField::create(
236
                'CustomSummary',
237
                _t(__CLASS__ . '.CUSTOMSUMMARY', 'Add A Custom Summary'),
238
                [
239
                    $summary,
240
                ]
241
            );
242
            $summaryHolder->setHeadingLevel(4);
243
            $summaryHolder->addExtraClass('custom-summary');
244
245
            $fields->insertAfter($summaryHolder, 'FeaturedImage');
246
247
            $urlSegment = $fields->dataFieldByName('URLSegment');
248
            $urlSegment->setURLPrefix($this->Parent()->RelativeLink());
249
250
            $fields->removeFieldsFromTab('Root.Main', [
251
                'MenuTitle',
252
                'URLSegment',
253
            ]);
254
255
            $authorField = ListboxField::create(
256
                'Authors',
257
                _t(__CLASS__ . '.Authors', 'Authors'),
258
                $this->getCandidateAuthors()->map()->toArray()
259
            );
260
261
            $authorNames = TextField::create(
262
                'AuthorNames',
263
                _t(__CLASS__ . '.AdditionalCredits', 'Additional Credits'),
264
                null,
265
                1024
266
            )->setDescription(
267
                _t(
268
                    __CLASS__ . '.AdditionalCredits_Description',
269
                    'If some authors of this post don\'t have CMS access, enter their name(s) here. '.
270
                    'You can separate multiple names with a comma.'
271
                )
272
            );
273
274
            if (!$this->canEditAuthors()) {
275
                $authorField = $authorField->performDisabledTransformation();
276
                $authorNames = $authorNames->performDisabledTransformation();
277
            }
278
279
            $publishDate = DatetimeField::create('PublishDate', _t(__CLASS__ . '.PublishDate', 'Publish Date'));
280
281
            if (!$this->PublishDate) {
282
                $publishDate->setDescription(
283
                    _t(
284
                        __CLASS__ . '.PublishDate_Description',
285
                        'Will be set to "now" if published without a value.'
286
                    )
287
                );
288
            }
289
290
            // Get categories and tags
291
            $parent = $this->Parent();
292
            $categories = $parent instanceof Blog
293
                ? $parent->Categories()
294
                : BlogCategory::get();
295
            $tags = $parent instanceof Blog
296
                ? $parent->Tags()
297
                : BlogTag::get();
298
299
            // @todo: Reimplement the sidebar
300
            // $options = BlogAdminSidebar::create(
301
            $fields->addFieldsToTab(
302
                'Root.PostOptions',
303
                [
304
                    $publishDate,
305
                    $urlSegment,
306
                    TagField::create(
307
                        'Categories',
308
                        _t(__CLASS__ . '.Categories', 'Categories'),
309
                        $categories,
310
                        $this->Categories()
311
                    )
312
                        ->setCanCreate($this->canCreateCategories())
313
                        ->setShouldLazyLoad(true),
314
                    TagField::create(
315
                        'Tags',
316
                        _t(__CLASS__ . '.Tags', 'Tags'),
317
                        $tags,
318
                        $this->Tags()
319
                    )
320
                        ->setCanCreate($this->canCreateTags())
321
                        ->setShouldLazyLoad(true),
322
                    $authorField,
323
                    $authorNames
324
                ]
325
            );
326
            // )->setTitle('Post Options');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
327
            // $options->setName('blog-admin-sidebar');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
328
            // $fields->insertBefore($options, 'Root');
0 ignored issues
show
Unused Code Comprehensibility introduced by
73% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
329
        });
330
331
        $fields = parent::getCMSFields();
332
333
        $fields->fieldByName('Root')->setTemplate('TabSet_holder');
334
335
        $fields->fieldByName('Root.PostOptions')
336
            ->setTitle(_t(__CLASS__ . '.PostOptions', 'Post Options'));
337
338
        return $fields;
339
    }
340
341
    /**
342
     * Gets the list of author candidates to be assigned as authors of this blog post.
343
     *
344
     * @return SS_List
345
     */
346
    public function getCandidateAuthors()
347
    {
348
        if ($this->config()->get('restrict_authors_to_group')) {
349
            return Group::get()->filter('Code', $this->config()->get('restrict_authors_to_group'))->first()->Members();
350
        }
351
352
        $list = Member::get();
353
        $this->extend('updateCandidateAuthors', $list);
354
        return $list;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $list; (SilverStripe\ORM\DataList) is incompatible with the return type documented by SilverStripe\Blog\Model\...st::getCandidateAuthors of type SilverStripe\Blog\Model\SS_List.

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...
355
    }
356
357
    /**
358
     * Determine if this user can edit the authors list.
359
     *
360
     * @param null|int|Member $member
361
     *
362
     * @return bool
363
     */
364
    public function canEditAuthors($member = null)
365
    {
366
        $member = $this->getMember($member);
367
368
        $extended = $this->extendedCan('canEditAuthors', $member);
369
370
        if ($extended !== null) {
371
            return $extended;
372
        }
373
374
        $parent = $this->Parent();
375
376
        if ($parent instanceof Blog && $parent->exists()) {
377
            if ($parent->isEditor($member)) {
378
                return true;
379
            }
380
381
            if ($parent->isWriter($member) && $this->isAuthor($member)) {
382
                return true;
383
            }
384
        }
385
386
        return Permission::checkMember($member, Blog::MANAGE_USERS);
0 ignored issues
show
Bug Compatibility introduced by
The expression \SilverStripe\Security\P...el\Blog::MANAGE_USERS); of type boolean|string adds the type string to the return on line 386 which is incompatible with the return type documented by SilverStripe\Blog\Model\BlogPost::canEditAuthors of type boolean.
Loading history...
387
    }
388
389
    /**
390
     * @param null|int|Member $member
391
     *
392
     * @return null|Member
393
     */
394 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...
395
    {
396
        if (!$member) {
397
            $member = Security::getCurrentUser();
398
        }
399
400
        if (is_numeric($member)) {
401
            $member = Member::get()->byID($member);
402
        }
403
404
        return $member;
405
    }
406
407
    /**
408
     * Determine whether user can create new categories.
409
     *
410
     * @param null|int|Member $member
411
     *
412
     * @return bool
413
     */
414 View Code Duplication
    public function canCreateCategories($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
        $parent = $this->Parent();
419
420
        if (!$parent || !$parent->exists() || !($parent instanceof Blog)) {
421
            return false;
422
        }
423
424
        if ($parent->isEditor($member)) {
425
            return true;
426
        }
427
428
        return Permission::checkMember($member, 'ADMIN');
429
    }
430
431
    /**
432
     * Determine whether user can create new tags.
433
     *
434
     * @param null|int|Member $member
435
     *
436
     * @return bool
437
     */
438 View Code Duplication
    public function canCreateTags($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
        $parent = $this->Parent();
443
444
        if (!$parent || !$parent->exists() || !($parent instanceof Blog)) {
445
            return false;
446
        }
447
448
        if ($parent->isEditor($member)) {
449
            return true;
450
        }
451
452
        if ($parent->isWriter($member)) {
453
            return true;
454
        }
455
456
        return Permission::checkMember($member, 'ADMIN');
457
    }
458
459
    /**
460
     * {@inheritdoc}
461
     *
462
     * Update the PublishDate to now if the BlogPost would otherwise be published without a date.
463
     */
464
    public function onBeforePublish()
465
    {
466
        /**
467
         * @var DBDatetime $publishDate
468
         */
469
        $publishDate = $this->dbObject('PublishDate');
470
471
        if (!$publishDate->getValue()) {
472
            $this->PublishDate = DBDatetime::now()->getValue();
473
            $this->write();
474
        }
475
    }
476
477
    /**
478
     * {@inheritdoc}
479
     *
480
     * Sets blog relationship on all categories and tags assigned to this post.
481
     */
482
    public function onAfterWrite()
483
    {
484
        parent::onAfterWrite();
485
486
        foreach ($this->Categories() as $category) {
487
            /**
488
             * @var BlogCategory $category
489
             */
490
            $category->BlogID = $this->ParentID;
491
            $category->write();
492
        }
493
494
        foreach ($this->Tags() as $tag) {
495
            /**
496
             * @var BlogTag $tag
497
             */
498
            $tag->BlogID = $this->ParentID;
499
            $tag->write();
500
        }
501
    }
502
503
    /**
504
     * {@inheritdoc}
505
     */
506
    public function canView($member = null)
507
    {
508
        $member = $this->getMember($member);
509
510
        if (!parent::canView($member)) {
511
            return false;
512
        }
513
514
        if ($this->canEdit($member)) {
515
            return true;
516
        }
517
518
        // If on draft stage, user has permission to view draft, so show it
519
        if (Versioned::get_stage() === Versioned::DRAFT) {
520
            return true;
521
        }
522
523
        /**
524
         * @var DBDatetime $publishDate
525
         */
526
        $publishDate = $this->dbObject('PublishDate');
527
        if (!$publishDate->exists()) {
528
            return false;
529
        }
530
531
        return !$publishDate->InFuture();
532
    }
533
534
    /**
535
     * {@inheritdoc}
536
     */
537
    public function canPublish($member = null)
538
    {
539
        $member = $this->getMember($member);
540
541
        if (Permission::checkMember($member, 'ADMIN')) {
542
            return true;
543
        }
544
545
        $extended = $this->extendedCan('canPublish', $member);
546
547
        if ($extended !== null) {
548
            return $extended;
549
        }
550
551
        $parent = $this->Parent();
552
553
        if ($parent instanceof Blog && $parent->exists()) {
554
            if ($parent->isEditor($member)) {
555
                return true;
556
            }
557
558
            if ($parent->isWriter($member) && $this->isAuthor($member)) {
559
                return true;
560
            }
561
562
            if ($parent->isContributor($member)) {
563
                return parent::canEdit($member);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (canEdit() instead of canPublish()). Are you sure this is correct? If so, you might want to change this to $this->canEdit().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
564
            }
565
        }
566
567
        return $this->canEdit($member);
568
    }
569
570
    /**
571
     * {@inheritdoc}
572
     */
573
    public function canEdit($member = null)
574
    {
575
        $member = $this->getMember($member);
576
577
        if (parent::canEdit($member)) {
578
            return true;
579
        }
580
581
        $parent = $this->Parent();
582
583
        if (!$parent || !$parent->exists() || !($parent instanceof Blog)) {
584
            return false;
585
        }
586
587
        if ($parent->isEditor($member)) {
588
            return true;
589
        }
590
591
        if (!$parent->isWriter($member) && !$parent->isContributor($member)) {
592
            return false;
593
        }
594
595
        return $this->isAuthor($member);
596
    }
597
598
    /**
599
     * Returns the post excerpt.
600
     *
601
     * @param int $wordsToDisplay
602
     *
603
     * @return string
604
     */
605
    public function Excerpt($wordsToDisplay = 30)
606
    {
607
        /** @var HTMLText $content */
608
        $content = $this->dbObject('Content');
609
610
        return $content->Summary($wordsToDisplay);
611
    }
612
613
    /**
614
     * Returns a monthly archive link for the current blog post.
615
     *
616
     * @param string $type
617
     *
618
     * @return string
619
     */
620
    public function getMonthlyArchiveLink($type = 'day')
621
    {
622
        /**
623
         * @var DBDatetime $date
624
         */
625
        $date = $this->dbObject('PublishDate');
626
627
        if ($type != 'year') {
628
            if ($type == 'day') {
629
                return Controller::join_links(
630
                    $this->Parent()->Link('archive'),
631
                    $date->format('Y'),
632
                    $date->format('m'),
633
                    $date->format('d')
634
                );
635
            }
636
637
            return Controller::join_links($this->Parent()->Link('archive'), $date->format('Y'), $date->format('m'));
638
        }
639
640
        return Controller::join_links($this->Parent()->Link('archive'), $date->format('Y'));
641
    }
642
643
    /**
644
     * Returns a yearly archive link for the current blog post.
645
     *
646
     * @return string
647
     */
648
    public function getYearlyArchiveLink()
649
    {
650
        /**
651
         * @var DBDatetime $date
652
         */
653
        $date = $this->dbObject('PublishDate');
654
655
        return Controller::join_links($this->Parent()->Link('archive'), $date->format('Y'));
656
    }
657
658
    /**
659
     * Resolves static and dynamic authors linked to this post.
660
     *
661
     * @return ArrayList
662
     */
663
    public function getCredits()
664
    {
665
        $list = ArrayList::create();
666
667
        $list->merge($this->getDynamicCredits());
668
        $list->merge($this->getStaticCredits());
669
670
        return $list->sort('Name');
671
    }
672
673
    /**
674
     * Resolves dynamic authors linked to this post.
675
     *
676
     * @return ArrayList
677
     */
678
    protected function getDynamicCredits()
679
    {
680
        // Find best page to host user profiles
681
        $parent = $this->Parent();
682
        if (! ($parent instanceof Blog)) {
683
            $parent = Blog::get()->first();
684
        }
685
686
        // If there is no parent blog, return list undecorated
687
        if (!$parent) {
688
            $items = $this->Authors()->toArray();
689
            return ArrayList::create($items);
690
        }
691
692
        // Update all authors
693
        $items = ArrayList::create();
694
        foreach ($this->Authors() as $author) {
695
            // Add link for each author
696
            $author = $author->customise([
697
                'URL' => $parent->ProfileLink($author->URLSegment),
698
            ]);
699
            $items->push($author);
700
        }
701
702
        return $items;
703
    }
704
705
    /**
706
     * Resolves static authors linked to this post.
707
     *
708
     * @return ArrayList
709
     */
710
    protected function getStaticCredits()
711
    {
712
        $items = ArrayList::create();
713
714
        $authors = array_filter(preg_split('/\s*,\s*/', $this->AuthorNames));
715
716
        foreach ($authors as $author) {
717
            $item = ArrayData::create([
718
                'Name' => $author,
719
            ]);
720
721
            $items->push($item);
722
        }
723
724
        return $items;
725
    }
726
727
    /**
728
     * Checks to see if User Profiles has been disabled via config
729
     *
730
     * @return bool
731
     */
732
    public function getProfilesDisabled()
733
    {
734
        return Config::inst()->get(BlogController::class, 'disable_profiles');
735
    }
736
737
    /**
738
     * Sets the label for BlogPost.Title to 'Post Title' (Rather than 'Page name').
739
     *
740
     * @param bool $includeRelations
741
     *
742
     * @return array
743
     */
744
    public function fieldLabels($includeRelations = true)
745
    {
746
        $labels = parent::fieldLabels($includeRelations);
747
748
        $labels['Title'] = _t(__CLASS__ . '.PageTitleLabel', 'Post Title');
749
750
        return $labels;
751
    }
752
753
    /**
754
     * Proxy method for displaying the publish date in rss feeds.
755
     * @see https://github.com/silverstripe/silverstripe-blog/issues/394
756
     *
757
     * @return string|null
758
     */
759
    public function getDate()
760
    {
761
        if ($this->hasDatabaseField('Date')) {
762
            return $this->getField('Date');
763
        }
764
        return !empty($this->PublishDate) ? $this->PublishDate : null;
765
    }
766
767
    /**
768
     * Provides a rough estimate of how long this post will take to read based on wikipedias answer to "How fast can a
769
     * human read" of 200wpm. Source https://en.wikipedia.org/wiki/Speed_reading
770
     *
771
     * @param null|integer $wpm
772
     *
773
     * @return string
774
     */
775
    public function getMinutesToRead($wpm = null)
776
    {
777
        if ($wpm && !is_numeric($wpm)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $wpm 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...
778
            throw new \InvalidArgumentException(sprintf("Expecting integer but got %s instead", gettype($wpm)));
779
        }
780
781
        if (!$wpm) {
782
            $wpm = $this->config()->get('minutes_to_read_wpm') ?: 200;
783
        }
784
785
        $wordCount = count(array_filter(explode(" ", strip_tags($this->Content))));
786
787
        if ($wordCount < $wpm) {
788
            return 0;
789
        }
790
791
        return round($wordCount / $wpm, 0);
792
    }
793
794
    /**
795
     * {@inheritdoc}
796
     */
797
    protected function onBeforeWrite()
798
    {
799
        parent::onBeforeWrite();
800
801
        if (!$this->exists() && ($member = Security::getCurrentUser())) {
802
            $this->Authors()->add($member);
803
        }
804
    }
805
}
806