Completed
Push — master ( 9cc98c...049e77 )
by Daniel
13s
created

BlogPost::fieldLabels()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
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\Parsers\ShortcodeParser;
26
use SilverStripe\View\Requirements;
27
28
/**
29
 * An individual blog post.
30
 *
31
 * @method ManyManyList Categories()
32
 * @method ManyManyList Tags()
33
 * @method ManyManyList Authors()
34
 * @method Blog Parent()
35
 *
36
 * @property string $PublishDate
37
 * @property string $AuthorNames
38
 * @property int $ParentID
39
 */
40
class BlogPost extends Page
41
{
42
    /**
43
     * Same as above, but for list of users that can be
44
     * given credit in the author field for blog posts
45
     * @var string|bool false or group code
46
     */
47
    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...
48
49
    /**
50
     * {@inheritDoc}
51
     * @var string
52
     */
53
    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...
54
55
    /**
56
     * @var array
57
     */
58
    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...
59
        'PublishDate' => 'Datetime',
60
        'AuthorNames' => 'Varchar(1024)',
61
        'Summary'     => 'HTMLText'
62
    ];
63
64
    /**
65
     * @var array
66
     */
67
    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...
68
        'FeaturedImage' => Image::class
69
    ];
70
71
    /**
72
     * @var array
73
     */
74
    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...
75
        'FeaturedImage',
76
    ];
77
78
    /**
79
     * @var array
80
     */
81
    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...
82
        'Categories' => BlogCategory::class,
83
        'Tags'       => BlogTag::class,
84
        'Authors'    => Member::class
85
    ];
86
87
    /**
88
     * @var array
89
     */
90
    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...
91
        'ShowInMenus'     => false,
92
        'InheritSideBar'  => true,
93
        'ProvideComments' => true
94
    ];
95
96
    /**
97
     * @var array
98
     */
99
    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...
100
        BlogPostFilter::class
101
    ];
102
103
    /**
104
     * @var array
105
     */
106
    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...
107
        'Title'
108
    ];
109
110
    /**
111
     * @var array
112
     */
113
    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...
114
        'Title'
115
    ];
116
117
    /**
118
     * @var array
119
     */
120
    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...
121
        'Excerpt' => 'HTMLText',
122
        'Date' => 'DBDatetime'
123
    ];
124
125
    /**
126
     * @var array
127
     */
128
    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...
129
130
    /**
131
     * The default sorting lists BlogPosts with an empty PublishDate at the top.
132
     *
133
     * @var string
134
     */
135
    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...
136
137
    /**
138
     * @var bool
139
     */
140
    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...
141
142
    /**
143
     * This will display or hide the current class from the SiteTree. This variable can be
144
     * configured using YAML.
145
     *
146
     * @var bool
147
     */
148
    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...
149
150
    /**
151
     * This helps estimate how long an article will take to read, if your target audience
152
     * is elderly then you should lower this value. See {@link getMinutesToRead()}
153
     *
154
     * @var int
155
     */
156
    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...
157
158
    /**
159
     * Determine the role of the given member.
160
     *
161
     * Call be called via template to determine the current user.
162
     *
163
     * @example "Hello $RoleOf($CurrentMember.ID)"
164
     *
165
     * @param null|int|Member $member
166
     *
167
     * @return null|string
168
     */
169
    public function RoleOf($member = null)
170
    {
171
        $member = $this->getMember($member);
172
173
        if (!$member) {
174
            return null;
175
        }
176
177
        if ($this->isAuthor($member)) {
178
            return _t(__CLASS__ . '.AUTHOR', 'Author');
179
        }
180
181
        $parent = $this->Parent();
182
183
        if ($parent instanceof Blog) {
184
            return $parent->RoleOf($member);
185
        }
186
187
        return null;
188
    }
189
190
    /**
191
     * Determine if the given member is an author of this post.
192
     *
193
     * @param null|Member $member
194
     *
195
     * @return bool
196
     */
197 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...
198
    {
199
        if (!$member || !$member->exists()) {
200
            return false;
201
        }
202
203
        $list = $this->Authors();
204
205
        if ($list instanceof UnsavedRelationList) {
206
            return in_array($member->ID, $list->getIDList());
207
        }
208
209
        return $list->byID($member->ID) !== null;
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215
    public function getCMSFields()
216
    {
217
        Requirements::css('silverstripe/blog:client/dist/styles/main.css');
218
        Requirements::javascript('silverstripe/blog:client/dist/js/main.bundle.js');
219
220
        $this->beforeUpdateCMSFields(function ($fields) {
221
            $uploadField = UploadField::create('FeaturedImage', _t(__CLASS__ . '.FeaturedImage', 'Featured Image'));
222
            $uploadField->getValidator()->setAllowedExtensions(['jpg', 'jpeg', 'png', 'gif']);
223
224
            /**
225
             * @var FieldList $fields
226
             */
227
            $fields->insertAfter($uploadField, 'Content');
228
229
            $summary = HtmlEditorField::create('Summary', false);
230
            $summary->setRows(5);
231
            $summary->setDescription(_t(
232
                __CLASS__ . '.SUMMARY_DESCRIPTION',
233
                'If no summary is specified the first 30 words will be used.'
234
            ));
235
236
            $summaryHolder = ToggleCompositeField::create(
237
                'CustomSummary',
238
                _t(__CLASS__ . '.CUSTOMSUMMARY', 'Add A Custom Summary'),
239
                [
240
                    $summary,
241
                ]
242
            );
243
            $summaryHolder->setHeadingLevel(4);
244
            $summaryHolder->addExtraClass('custom-summary');
245
246
            $fields->insertAfter($summaryHolder, 'FeaturedImage');
247
248
            $urlSegment = $fields->dataFieldByName('URLSegment');
249
            $urlSegment->setURLPrefix($this->Parent()->RelativeLink());
250
251
            $fields->removeFieldsFromTab('Root.Main', [
252
                'MenuTitle',
253
                'URLSegment',
254
            ]);
255
256
            $authorField = ListboxField::create(
257
                'Authors',
258
                _t(__CLASS__ . '.Authors', 'Authors'),
259
                $this->getCandidateAuthors()->map()->toArray()
260
            );
261
262
            $authorNames = TextField::create(
263
                'AuthorNames',
264
                _t(__CLASS__ . '.AdditionalCredits', 'Additional Credits'),
265
                null,
266
                1024
267
            )->setDescription(
268
                _t(
269
                    __CLASS__ . '.AdditionalCredits_Description',
270
                    'If some authors of this post don\'t have CMS access, enter their name(s) here. '.
271
                    'You can separate multiple names with a comma.'
272
                )
273
            );
274
275
            if (!$this->canEditAuthors()) {
276
                $authorField = $authorField->performDisabledTransformation();
277
                $authorNames = $authorNames->performDisabledTransformation();
278
            }
279
280
            $publishDate = DatetimeField::create('PublishDate', _t(__CLASS__ . '.PublishDate', 'Publish Date'));
281
282
            if (!$this->PublishDate) {
283
                $publishDate->setDescription(
284
                    _t(
285
                        __CLASS__ . '.PublishDate_Description',
286
                        'Will be set to "now" if published without a value.'
287
                    )
288
                );
289
            }
290
291
            // Get categories and tags
292
            $parent = $this->Parent();
293
            $categories = $parent instanceof Blog
294
                ? $parent->Categories()
295
                : BlogCategory::get();
296
            $tags = $parent instanceof Blog
297
                ? $parent->Tags()
298
                : BlogTag::get();
299
300
            // @todo: Reimplement the sidebar
301
            // $options = BlogAdminSidebar::create(
302
            $fields->addFieldsToTab(
303
                'Root.PostOptions',
304
                [
305
                    $publishDate,
306
                    $urlSegment,
307
                    TagField::create(
308
                        'Categories',
309
                        _t(__CLASS__ . '.Categories', 'Categories'),
310
                        $categories,
311
                        $this->Categories()
312
                    )
313
                        ->setCanCreate($this->canCreateCategories())
314
                        ->setShouldLazyLoad(true),
315
                    TagField::create(
316
                        'Tags',
317
                        _t(__CLASS__ . '.Tags', 'Tags'),
318
                        $tags,
319
                        $this->Tags()
320
                    )
321
                        ->setCanCreate($this->canCreateTags())
322
                        ->setShouldLazyLoad(true),
323
                    $authorField,
324
                    $authorNames
325
                ]
326
            );
327
            // )->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...
328
            // $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...
329
            // $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...
330
        });
331
332
        $fields = parent::getCMSFields();
333
334
        $fields->fieldByName('Root')->setTemplate('TabSet_holder');
335
336
        $fields->fieldByName('Root.PostOptions')
337
            ->setTitle(_t(__CLASS__ . '.PostOptions', 'Post Options'));
338
339
        return $fields;
340
    }
341
342
    /**
343
     * Gets the list of author candidates to be assigned as authors of this blog post.
344
     *
345
     * @return SS_List
346
     */
347
    public function getCandidateAuthors()
348
    {
349
        if ($this->config()->get('restrict_authors_to_group')) {
350
            return Group::get()->filter('Code', $this->config()->get('restrict_authors_to_group'))->first()->Members();
351
        }
352
353
        $list = Member::get();
354
        $this->extend('updateCandidateAuthors', $list);
355
        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...
356
    }
357
358
    /**
359
     * Determine if this user can edit the authors list.
360
     *
361
     * @param null|int|Member $member
362
     *
363
     * @return bool
364
     */
365
    public function canEditAuthors($member = null)
366
    {
367
        $member = $this->getMember($member);
368
369
        $extended = $this->extendedCan('canEditAuthors', $member);
370
371
        if ($extended !== null) {
372
            return $extended;
373
        }
374
375
        $parent = $this->Parent();
376
377
        if ($parent instanceof Blog && $parent->exists()) {
378
            if ($parent->isEditor($member)) {
379
                return true;
380
            }
381
382
            if ($parent->isWriter($member) && $this->isAuthor($member)) {
383
                return true;
384
            }
385
        }
386
387
        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 387 which is incompatible with the return type documented by SilverStripe\Blog\Model\BlogPost::canEditAuthors of type boolean.
Loading history...
388
    }
389
390
    /**
391
     * @param null|int|Member $member
392
     *
393
     * @return null|Member
394
     */
395 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...
396
    {
397
        if (!$member) {
398
            $member = Security::getCurrentUser();
399
        }
400
401
        if (is_numeric($member)) {
402
            $member = Member::get()->byID($member);
403
        }
404
405
        return $member;
406
    }
407
408
    /**
409
     * Determine whether user can create new categories.
410
     *
411
     * @param null|int|Member $member
412
     *
413
     * @return bool
414
     */
415 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...
416
    {
417
        $member = $this->getMember($member);
418
419
        $parent = $this->Parent();
420
421
        if (!$parent || !$parent->exists() || !($parent instanceof Blog)) {
422
            return false;
423
        }
424
425
        if ($parent->isEditor($member)) {
426
            return true;
427
        }
428
429
        return Permission::checkMember($member, 'ADMIN');
430
    }
431
432
    /**
433
     * Determine whether user can create new tags.
434
     *
435
     * @param null|int|Member $member
436
     *
437
     * @return bool
438
     */
439 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...
440
    {
441
        $member = $this->getMember($member);
442
443
        $parent = $this->Parent();
444
445
        if (!$parent || !$parent->exists() || !($parent instanceof Blog)) {
446
            return false;
447
        }
448
449
        if ($parent->isEditor($member)) {
450
            return true;
451
        }
452
453
        if ($parent->isWriter($member)) {
454
            return true;
455
        }
456
457
        return Permission::checkMember($member, 'ADMIN');
458
    }
459
460
    /**
461
     * {@inheritdoc}
462
     *
463
     * Update the PublishDate to now if the BlogPost would otherwise be published without a date.
464
     */
465
    public function onBeforePublish()
466
    {
467
        /**
468
         * @var DBDatetime $publishDate
469
         */
470
        $publishDate = $this->dbObject('PublishDate');
471
472
        if (!$publishDate->getValue()) {
473
            $this->PublishDate = DBDatetime::now()->getValue();
474
            $this->write();
475
        }
476
    }
477
478
    /**
479
     * {@inheritdoc}
480
     *
481
     * Sets blog relationship on all categories and tags assigned to this post.
482
     */
483
    public function onAfterWrite()
484
    {
485
        parent::onAfterWrite();
486
487
        foreach ($this->Categories() as $category) {
488
            /**
489
             * @var BlogCategory $category
490
             */
491
            $category->BlogID = $this->ParentID;
492
            $category->write();
493
        }
494
495
        foreach ($this->Tags() as $tag) {
496
            /**
497
             * @var BlogTag $tag
498
             */
499
            $tag->BlogID = $this->ParentID;
500
            $tag->write();
501
        }
502
    }
503
504
    /**
505
     * {@inheritdoc}
506
     */
507
    public function canView($member = null)
508
    {
509
        $member = $this->getMember($member);
510
511
        if (!parent::canView($member)) {
512
            return false;
513
        }
514
515
        if ($this->canEdit($member)) {
516
            return true;
517
        }
518
519
        // If on draft stage, user has permission to view draft, so show it
520
        if (Versioned::get_stage() === Versioned::DRAFT) {
521
            return true;
522
        }
523
524
        /**
525
         * @var DBDatetime $publishDate
526
         */
527
        $publishDate = $this->dbObject('PublishDate');
528
        if (!$publishDate->exists()) {
529
            return false;
530
        }
531
532
        return !$publishDate->InFuture();
533
    }
534
535
    /**
536
     * {@inheritdoc}
537
     */
538
    public function canPublish($member = null)
539
    {
540
        $member = $this->getMember($member);
541
542
        if (Permission::checkMember($member, 'ADMIN')) {
543
            return true;
544
        }
545
546
        $extended = $this->extendedCan('canPublish', $member);
547
548
        if ($extended !== null) {
549
            return $extended;
550
        }
551
552
        $parent = $this->Parent();
553
554
        if ($parent instanceof Blog && $parent->exists()) {
555
            if ($parent->isEditor($member)) {
556
                return true;
557
            }
558
559
            if ($parent->isWriter($member) && $this->isAuthor($member)) {
560
                return true;
561
            }
562
563
            if ($parent->isContributor($member)) {
564
                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...
565
            }
566
        }
567
568
        return $this->canEdit($member);
569
    }
570
571
    /**
572
     * {@inheritdoc}
573
     */
574
    public function canEdit($member = null)
575
    {
576
        $member = $this->getMember($member);
577
578
        if (parent::canEdit($member)) {
579
            return true;
580
        }
581
582
        $parent = $this->Parent();
583
584
        if (!$parent || !$parent->exists() || !($parent instanceof Blog)) {
585
            return false;
586
        }
587
588
        if ($parent->isEditor($member)) {
589
            return true;
590
        }
591
592
        if (!$parent->isWriter($member) && !$parent->isContributor($member)) {
593
            return false;
594
        }
595
596
        return $this->isAuthor($member);
597
    }
598
599
    /**
600
     * Returns the post excerpt.
601
     *
602
     * @param int $wordsToDisplay
603
     *
604
     * @return string
605
     */
606
    public function Excerpt($wordsToDisplay = 30)
607
    {
608
        /** @var HTMLText $content */
609
        $content = $this->dbObject('Content');
610
611
        return $content->Summary($wordsToDisplay);
612
    }
613
614
    /**
615
     * Returns a monthly archive link for the current blog post.
616
     *
617
     * @param string $type
618
     *
619
     * @return string
620
     */
621
    public function getMonthlyArchiveLink($type = 'day')
622
    {
623
        /**
624
         * @var DBDatetime $date
625
         */
626
        $date = $this->dbObject('PublishDate');
627
628
        if ($type != 'year') {
629
            if ($type == 'day') {
630
                return Controller::join_links(
631
                    $this->Parent()->Link('archive'),
632
                    $date->format('Y'),
633
                    $date->format('m'),
634
                    $date->format('d')
635
                );
636
            }
637
638
            return Controller::join_links($this->Parent()->Link('archive'), $date->format('Y'), $date->format('m'));
639
        }
640
641
        return Controller::join_links($this->Parent()->Link('archive'), $date->format('Y'));
642
    }
643
644
    /**
645
     * Returns a yearly archive link for the current blog post.
646
     *
647
     * @return string
648
     */
649
    public function getYearlyArchiveLink()
650
    {
651
        /**
652
         * @var DBDatetime $date
653
         */
654
        $date = $this->dbObject('PublishDate');
655
656
        return Controller::join_links($this->Parent()->Link('archive'), $date->format('Y'));
657
    }
658
659
    /**
660
     * Resolves static and dynamic authors linked to this post.
661
     *
662
     * @return ArrayList
663
     */
664
    public function getCredits()
665
    {
666
        $list = ArrayList::create();
667
668
        $list->merge($this->getDynamicCredits());
669
        $list->merge($this->getStaticCredits());
670
671
        return $list->sort('Name');
672
    }
673
674
    /**
675
     * Resolves dynamic authors linked to this post.
676
     *
677
     * @return ArrayList
678
     */
679
    protected function getDynamicCredits()
680
    {
681
        // Find best page to host user profiles
682
        $parent = $this->Parent();
683
        if (! ($parent instanceof Blog)) {
684
            $parent = Blog::get()->first();
685
        }
686
687
        // If there is no parent blog, return list undecorated
688
        if (!$parent) {
689
            $items = $this->Authors()->toArray();
690
            return ArrayList::create($items);
691
        }
692
693
        // Update all authors
694
        $items = ArrayList::create();
695
        foreach ($this->Authors() as $author) {
696
            // Add link for each author
697
            $author = $author->customise([
698
                'URL' => $parent->ProfileLink($author->URLSegment),
699
            ]);
700
            $items->push($author);
701
        }
702
703
        return $items;
704
    }
705
706
    /**
707
     * Resolves static authors linked to this post.
708
     *
709
     * @return ArrayList
710
     */
711
    protected function getStaticCredits()
712
    {
713
        $items = ArrayList::create();
714
715
        $authors = array_filter(preg_split('/\s*,\s*/', $this->AuthorNames));
716
717
        foreach ($authors as $author) {
718
            $item = ArrayData::create([
719
                'Name' => $author,
720
            ]);
721
722
            $items->push($item);
723
        }
724
725
        return $items;
726
    }
727
728
    /**
729
     * Checks to see if User Profiles has been disabled via config
730
     *
731
     * @return bool
732
     */
733
    public function getProfilesDisabled()
734
    {
735
        return Config::inst()->get(BlogController::class, 'disable_profiles');
736
    }
737
738
    /**
739
     * Sets the label for BlogPost.Title to 'Post Title' (Rather than 'Page name').
740
     *
741
     * @param bool $includeRelations
742
     *
743
     * @return array
744
     */
745
    public function fieldLabels($includeRelations = true)
746
    {
747
        $labels = parent::fieldLabels($includeRelations);
748
749
        $labels['Title'] = _t(__CLASS__ . '.PageTitleLabel', 'Post Title');
750
751
        return $labels;
752
    }
753
754
    /**
755
     * Proxy method for displaying the publish date in rss feeds.
756
     * @see https://github.com/silverstripe/silverstripe-blog/issues/394
757
     *
758
     * @return string|null
759
     */
760
    public function getDate()
761
    {
762
        if ($this->hasDatabaseField('Date')) {
763
            return $this->getField('Date');
764
        }
765
        return !empty($this->PublishDate) ? $this->PublishDate : null;
766
    }
767
768
    /**
769
     * Provides a rough estimate of how long this post will take to read based on wikipedias answer to "How fast can a
770
     * human read" of 200wpm. Source https://en.wikipedia.org/wiki/Speed_reading
771
     *
772
     * @param null|integer $wpm
773
     *
774
     * @return string
775
     */
776
    public function MinutesToRead($wpm = null)
777
    {
778
        $wpm = $wpm ?: $this->config()->get('minutes_to_read_wpm');
779
780
        if (!is_numeric($wpm)) {
781
            throw new \InvalidArgumentException(sprintf("Expecting integer but got %s instead", gettype($wpm)));
782
        }
783
784
        $wordCount = str_word_count(strip_tags($this->Content));
785
786
        if ($wordCount < $wpm) {
787
            return 0;
788
        }
789
790
        return round($wordCount / $wpm, 0);
791
    }
792
793
    /**
794
     * {@inheritdoc}
795
     */
796
    protected function onBeforeWrite()
797
    {
798
        parent::onBeforeWrite();
799
800
        if (!$this->exists() && ($member = Security::getCurrentUser())) {
801
            $this->Authors()->add($member);
802
        }
803
    }
804
}
805