Completed
Pull Request — master (#203)
by
unknown
40:36
created

Post   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 408
Duplicated Lines 2.7 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 55
lcom 1
cbo 0
dl 11
loc 408
rs 6.8
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A requireDefaultRecords() 0 17 4
A onBeforeDelete() 0 11 3
B canView() 0 14 5
B canEdit() 0 20 6
A canDelete() 0 12 3
A canCreate() 0 8 2
A AbsoluteLink() 0 4 1
A getTitle() 0 15 2
A getUpdated() 0 8 2
A isFirstPost() 0 15 3
A EditLink() 0 10 2
A DeleteLink() 0 15 3
A ReplyLink() 0 6 1
A ShowLink() 0 6 1
A MarkAsSpamLink() 0 18 4
A BanLink() 5 11 2
A GhostLink() 6 12 2
A getRSSContent() 0 4 1
A getRSSAuthor() 0 6 1
C Link() 0 22 7

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Post often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Post, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\Forum\Models;
4
5
use SilverStripe\Forum\Pages\Forum;
6
use SilverStripe\ORM\DB;
7
use SilverStripe\ORM\FieldType\DBDatetime;
8
use SilverStripe\ORM\FieldType\DBEnum;
9
use SilverStripe\ORM\FieldType\DBHTMLText;
10
use SilverStripe\ORM\FieldType\DBText;
11
use SilverStripe\ORM\HasManyList;
12
use SilverStripe\Security\Member;
13
use SilverStripe\Security\Permission;
14
use SilverStripe\Control\Director;
15
use SilverStripe\Control\Controller;
16
use SilverStripe\Security\SecurityToken;
17
use SilverStripe\ORM\DataObject;
18
use SilverStripe\Core\Convert;
19
use SilverStripe\Control\HTTPRequest;
20
use SilverStripe\Assets\File;
21
22
/**
23
 * Forum Post Object. Contains a single post by the user. A thread is generated
24
 * with multiple posts.
25
 *
26
 * @package forum
27
 * @property DBText Content
28
 * @property DBEnum Status
29
 * @method Member Author
30
 * @method ForumThread Thread
31
 * @method Forum Forum
32
 * @method HasManyList Attachments
33
 */
34
class Post extends DataObject
35
{
36
    /** @var array */
37
    private static $db = array(
38
        "Content" => "Text",
39
        "Status"  => "Enum('Awaiting, Moderated, Rejected, Archived', 'Moderated')",
40
    );
41
42
    /** @var array */
43
    private static $has_one = array(
44
        "Author" => Member::class,
45
        "Thread" => ForumThread::class,
46
        "Forum"  => Forum::class
47
    );
48
49
    /** @var array */
50
    private static $has_many = array(
51
        "Attachments" => PostAttachment::class
52
    );
53
54
    /** @var array */
55
    private static $casting = array(
56
        "Updated"    => "Datetime",
57
        "RSSContent" => "HTMLText",
58
        "RSSAuthor"  => "Varchar",
59
        "Content"    => "HTMLText"
60
    );
61
62
    /** @var array */
63
    private static $summary_fields = array(
64
        "Content.LimitWordCount" => "Summary",
65
        "Created"                => "Created",
66
        "Status"                 => "Status",
67
        "Thread.Title"           => "Thread",
68
        "Forum.Title"            => "Forum"
69
    );
70
71
    /**
72
     * Update all the posts to have a forum ID of their thread ID.
73
     *
74
     * @return void
75
     */
76
    public function requireDefaultRecords()
77
    {
78
        $posts = Post::get()->filter(array('ForumID' => 0, 'ThreadID:GreaterThan' => 0));
79
80
        if ($posts->exists()) {
81
            return;
82
        }
83
        /** @var Post $post */
84
        foreach ($posts as $post) {
85
            if ($post->ThreadID) {
86
                $post->ForumID = $post->Thread()->ForumID;
87
                $post->write();
88
            }
89
        }
90
91
        DB::alteration_message(_t('Forum.POSTSFORUMIDUPDATED', 'Forum posts forum ID added'), 'created');
92
    }
93
94
    /**
95
     * Before deleting a post make sure all attachments are also deleted
96
     *
97
     * @return void
98
     */
99
    public function onBeforeDelete()
100
    {
101
        parent::onBeforeDelete();
102
103
        if ($attachments = $this->Attachments()) {
104
            foreach ($attachments as $file) {
105
                $file->delete();
106
                $file->destroy();
107
            }
108
        }
109
    }
110
111
    /**
112
     * Check if user can see the post
113
     *
114
     * @param null|Member $member
115
     *
116
     * @return bool
117
     */
118
    public function canView($member = null)
119
    {
120
        if (!$member) {
121
            $member = Member::currentUser();
122
        }
123
124
        if ($this->Author()->ForumStatus != 'Normal') {
125
            if ($this->AuthorID != $member->ID || $member->ForumStatus != 'Ghost') {
126
                return false;
127
            }
128
        }
129
130
        return $this->Thread()->canView($member);
131
    }
132
133
    /**
134
     * Check if user can edit the post (only if it's his own, or he's an admin user)
135
     *
136
     * @param null|Member $member
137
     *
138
     * @return bool
139
     */
140
    public function canEdit($member = null)
141
    {
142
        if (!$member) {
143
            $member = Member::currentUser();
144
        }
145
146
        if ($member) {
147
            // Admins can always edit, regardless of thread/post ownership
148
            if (Permission::checkMember($member, 'ADMIN')) {
149
                return true;
150
            }
151
152
            // Otherwise check for thread permissions and ownership
153
            if ($this->Thread()->canPost($member) && $member->ID == $this->AuthorID) {
154
                return true;
155
            }
156
        }
157
158
        return false;
159
    }
160
161
    /**
162
     * Follow edit permissions for this, but additionally allow moderation even
163
     * if the thread is marked as readonly.
164
     *
165
     * @param null|Member $member
166
     *
167
     * @return bool
168
     */
169
    public function canDelete($member = null)
170
    {
171
        if (!$member) {
172
            $member = Member::currentUser();
173
        }
174
175
        if ($this->canEdit($member)) {
176
            return true;
177
        }
178
179
        return $this->Thread()->canModerate($member);
180
    }
181
182
    /**
183
     * Check if user can add new posts - hook up into canPost.
184
     *
185
     * @param null|Member $member
186
     *
187
     * @return bool
188
     */
189
    public function canCreate($member = null)
190
    {
191
        if (!$member) {
192
            $member = Member::currentUser();
193
        }
194
195
        return $this->Thread()->canPost($member);
196
    }
197
198
    /**
199
     * Returns the absolute url rather then relative. Used in Post RSS Feed
200
     *
201
     * @return string
202
     */
203
    public function AbsoluteLink()
204
    {
205
        return Director::absoluteURL($this->Link());
206
    }
207
208
    /**
209
     * Return the title of the post. Because we don't have to have the title
210
     * on individual posts check with the topic
211
     *
212
     * @return string
213
     */
214
    public function getTitle()
215
    {
216
        if ($this->isFirstPost()) {
217
            return $this->Thread()->Title;
218
        }
219
220
        return _t(
221
            'Post.RESPONSE',
222
            "Re: {title}",
223
            'Post Subject Prefix',
224
            [
0 ignored issues
show
Documentation introduced by
array('title' => $this->Thread()->Title) is of type array<string,?,{"title":"?"}>, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
225
                'title' => $this->Thread()->Title
226
            ]
227
        );
228
    }
229
230
    /**
231
     * Return the last edited date, if it's different from created
232
     *
233
     * @return DBDatetime|bool
234
     */
235
    public function getUpdated()
236
    {
237
        if ($this->LastEdited != $this->Created) {
238
            return false;
239
        }
240
241
        return $this->LastEdited;
242
    }
243
244
    /**
245
     * Is this post the first post in the thread. Check if their is a post with an ID less
246
     * than the one of this post in the same thread
247
     *
248
     * @return bool
249
     */
250
    public function isFirstPost()
251
    {
252
        if (empty($this->ThreadID) || empty($this->ID)) {
253
            return false;
254
        }
255
256
        $earlierPosts = Post::get()->filter(
257
            [
258
                'ThreadID'    => $this->ThreadID,
259
                'ID:LessThan' => $this->ID
260
            ]
261
        )->count();
262
263
        return (!$earlierPosts);
264
    }
265
266
    /**
267
     * Return a link to edit this post.
268
     *
269
     * @return string
270
     */
271
    public function EditLink()
272
    {
273
        if ($this->canEdit()) {
274
            $url = Controller::join_links($this->Link('editpost'), $this->ID);
275
276
            return '<a href="' . $url . '" class="editPostLink">' . _t('Post.EDIT', 'Edit') . '</a>';
277
        }
278
279
        return false;
280
    }
281
282
    /**
283
     * Return a link to delete this post.
284
     *
285
     * If the member is an admin of this forum, (ADMIN permissions
286
     * or a moderator) then they can delete the post.
287
     *
288
     * @return string
289
     */
290
    public function DeleteLink()
291
    {
292
        if ($this->canDelete()) {
293
            $url   = Controller::join_links($this->Link('deletepost'), $this->ID);
294
            $token = SecurityToken::inst();
295
            $url   = $token->addToUrl($url);
296
297
            $firstPost = ($this->isFirstPost()) ? ' firstPost' : '';
298
299
            return '<a class="deleteLink' . $firstPost . '" href="' . $url . '">' . _t('Post.DELETE',
300
                'Delete') . '</a>';
301
        }
302
303
        return false;
304
    }
305
306
    /**
307
     * Return a link to the reply form. Permission checking is handled on the actual URL
308
     * and not on this function
309
     *
310
     * @return string
311
     */
312
    public function ReplyLink()
313
    {
314
        $url = $this->Link('reply');
315
316
        return '<a href="' . $url . '" class="replyLink">' . _t('Post.REPLYLINK', 'Post Reply') . '</a>';
317
    }
318
319
    /**
320
     * Return a link to the post view.
321
     *
322
     * @return string
323
     */
324
    public function ShowLink()
325
    {
326
        $url = $this->Link('show');
327
328
        return '<a href="' . $url . '" class="showLink">' . _t('Post.SHOWLINK', 'Show Thread') . "</a>";
329
    }
330
331
    /**
332
     * Return a link to mark this post as spam. Used for the SpamProtection module
333
     *
334
     * @return string
335
     */
336
    public function MarkAsSpamLink()
337
    {
338
        if ($this->Thread()->canModerate()) {
339
            $member = Member::currentUser();
340
            if ($member->ID != $this->AuthorID) {
341
                $url   = Controller::join_links($this->Forum()->Link('markasspam'), $this->ID);
342
                $token = SecurityToken::inst();
343
                $url   = $token->addToUrl($url);
344
345
                $firstPost = ($this->isFirstPost()) ? ' firstPost' : '';
346
347
                return '<a href="' . $url . '" class="markAsSpamLink' . $firstPost . '" rel="' . $this->ID . '">' . _t('Post.MARKASSPAM',
348
                    'Mark as Spam') . '</a>';
349
            }
350
        }
351
352
        return false;
353
    }
354
355
    /**
356
     * Returns a ban link
357
     *
358
     * @return bool|string
359
     */
360
    public function BanLink()
361
    {
362
        $thread = $this->Thread();
363 View Code Duplication
        if ($thread->canModerate()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
364
            $link = $thread->Forum()->Link('ban') . '/' . $this->AuthorID;
365
366
            return "<a class='banLink' href=\"$link\" rel=\"$this->AuthorID\">" . _t('Post.BANUSER', 'Ban User') . "</a>";
367
        }
368
369
        return false;
370
    }
371
372
    /**
373
     * Returns a ghost link
374
     *
375
     * @return bool|string
376
     */
377
    public function GhostLink()
378
    {
379
        $thread = $this->Thread();
380 View Code Duplication
        if ($thread->canModerate()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
381
            $link = Controller::join_links($thread->Forum()->Link('ghost'), $this->AuthorID);
382
383
            return "<a class='ghostLink' href=\"$link\" rel=\"$this->AuthorID\">" . _t('Post.GHOSTUSER',
384
                'Ghost User') . "</a>";
385
        }
386
387
        return false;
388
    }
389
390
    /**
391
     * Return the parsed content and the information for the RSS feed
392
     *
393
     * @return DBHTMLText
394
     */
395
    public function getRSSContent()
396
    {
397
        return $this->renderWith('Includes/Post_rss');
398
    }
399
400
    /**
401
     * Get RSS Author
402
     *
403
     * @return string
404
     */
405
    public function getRSSAuthor()
406
    {
407
        $author = $this->Author();
408
409
        return $author->Nickname;
410
    }
411
412
    /**
413
     * Return a link to show this post
414
     *
415
     * @param string $action
416
     *
417
     * @return string
418
     */
419
    public function Link($action = "show")
420
    {
421
        // only include the forum thread ID in the URL if we're showing the thread either
422
        // by showing the posts or replying otherwise we only need to pass a single ID.
423
        $includeThreadID = ($action == "show" || $action == "reply") ? true : false;
424
        $link            = $this->Thread()->Link($action, $includeThreadID);
425
426
        // calculate what page results the post is on
427
        // the count is the position of the post in the thread
428
        $count = Post::get()->filter(
429
            [
430
                'ThreadID'    => $this->ThreadID,
431
                'Status'      => 'Moderated',
432
                'ID:LessThan' => $this->ID
433
            ]
434
        )->count();
435
436
        $start = ($count >= Forum::$postsPerPage) ? floor($count / Forum::$postsPerPage) * Forum::$postsPerPage : 0;
437
        $pos   = ($start == 0 ? '' : "?start=$start") . ($count == 0 ? '' : "#post{$this->ID}");
438
439
        return ($action == "show") ? $link . $pos : $link;
440
    }
441
}
442
443