Issues (110)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Model/BlogPost.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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