ForumHolder::requireDefaultRecords()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 4
nop 0
1
<?php
2
3
/**
4
 * ForumHolder represents the top forum overview page. Its children
5
 * should be Forums. On this page you can also edit your global settings
6
 * for the entire forum.
7
 *
8
 * @package forum
9
 */
10
11
class ForumHolder extends Page
12
{
13
14
    private static $avatars_folder = 'forum/avatars/';
15
16
    private static $attachments_folder = 'forum/attachments/';
17
18
    private static $db = array(
19
        "HolderSubtitle" => "Varchar(200)",
20
        "ProfileSubtitle" => "Varchar(200)",
21
        "ForumSubtitle" => "Varchar(200)",
22
        "HolderAbstract" => "HTMLText",
23
        "ProfileAbstract" => "HTMLText",
24
        "ForumAbstract" => "HTMLText",
25
        "ProfileModify" => "HTMLText",
26
        "ProfileAdd" => "HTMLText",
27
        "DisplaySignatures" => "Boolean",
28
        "ShowInCategories" => "Boolean",
29
        "AllowGravatars" => "Boolean",
30
        "GravatarType" => "Varchar(10)",
31
        "ForbiddenWords" => "Text",
32
        "CanPostType" => "Enum('Anyone, LoggedInUsers, OnlyTheseUsers, NoOne', 'LoggedInUsers')",
33
    );
34
35
    private static $has_one = array();
36
37
    private static $has_many = array(
38
        "Categories" => "ForumCategory"
39
    );
40
41
    private static $allowed_children = array('Forum');
42
43
    private static $defaults = array(
44
        "HolderSubtitle" => "Welcome to our forum!",
45
        "ProfileSubtitle" => "Edit Your Profile",
46
        "ForumSubtitle" => "Start a new topic",
47
        "HolderAbstract" => "<p>If this is your first visit, you will need to <a class=\"broken\" title=\"Click here to register\" href=\"ForumMemberProfile/register\">register</a> before you can post. However, you can browse all messages below.</p>",
48
        "ProfileAbstract" => "<p>Please fill out the fields below. You can choose whether some are publically visible by using the checkbox for each one.</p>",
49
        "ForumAbstract" => "<p>From here you can start a new topic.</p>",
50
        "ProfileModify" => "<p>Thanks, your member profile has been modified.</p>",
51
        "ProfileAdd" => "<p>Thanks, you are now signed up to the forum.</p>",
52
    );
53
54
    /**
55
     * If the user has spam protection enabled and setup then we can provide spam
56
     * prevention for the forum. This stores whether we actually want the registration
57
     * form to have such protection
58
     *
59
     * @var bool
60
     */
61
    public static $use_spamprotection_on_register = true;
62
63
    /**
64
     * If the user has spam protection enabled and setup then we can provide spam
65
     * prevention for the forum. This stores whether we actually want the posting
66
     * form (adding, replying) to have such protection
67
     *
68
     * @var bool
69
     */
70
    public static $use_spamprotection_on_posts = false;
71
72
    /**
73
     * Add a hidden field to the form which should remain empty
74
     * If its filled out, we can assume that a spam bot is auto-filling fields.
75
     *
76
     * @var bool
77
     */
78
    public static $use_honeypot_on_register = false;
79
80
    /**
81
     * @var bool If TRUE, each logged in Member who visits a Forum will write the LastViewed field
82
     * which is for the "Currently online" functionality.
83
     */
84
    private static $currently_online_enabled = true;
85
86
    public function getCMSFields()
87
    {
88
        $self = $this;
89
90
        $this->beforeUpdateCMSFields(function ($fields) use ($self) {
91
92
            $fields->addFieldsToTab("Root.Messages", array(
93
                TextField::create("HolderSubtitle", "Forum Holder Subtitle"),
94
                HTMLEditorField::create("HolderAbstract", "Forum Holder Abstract"),
95
                TextField::create("ProfileSubtitle", "Member Profile Subtitle"),
96
                HTMLEditorField::create("ProfileAbstract", "Member Profile Abstract"),
97
                TextField::create("ForumSubtitle", "Create topic Subtitle"),
98
                HTMLEditorField::create("ForumAbstract", "Create topic Abstract"),
99
                HTMLEditorField::create("ProfileModify", "Create message after modifing forum member"),
100
                HTMLEditorField::create("ProfileAdd", "Create message after adding forum member")
101
            ));
102
            $fields->addFieldsToTab("Root.Settings", array(
103
                CheckboxField::create("DisplaySignatures", "Display Member Signatures?"),
104
                CheckboxField::create("ShowInCategories", "Show Forums In Categories?"),
105
                CheckboxField::create("AllowGravatars", "Allow <a href='http://www.gravatar.com/' target='_blank'>Gravatars</a>?"),
106
                DropdownField::create("GravatarType", "Gravatar Type", array(
107
                    "standard" => _t('Forum.STANDARD', 'Standard'),
108
                    "identicon" => _t('Forum.IDENTICON', 'Identicon'),
109
                    "wavatar" => _t('Forum.WAVATAR', 'Wavatar'),
110
                    "monsterid" => _t('Forum.MONSTERID', 'Monsterid'),
111
                    "retro" => _t('Forum.RETRO', 'Retro'),
112
                    "mm" => _t('Forum.MM', 'Mystery Man'),
113
                ))->setEmptyString('Use Forum Default')
114
            ));
115
116
            // add a grid field to the category tab with all the categories
117
            $categoryConfig = GridFieldConfig::create()
118
                ->addComponents(
119
                    new GridFieldSortableHeader(),
120
                    new GridFieldButtonRow(),
121
                    new GridFieldDataColumns(),
122
                    new GridFieldEditButton(),
123
                    new GridFieldViewButton(),
124
                    new GridFieldDeleteAction(),
125
                    new GridFieldAddNewButton('buttons-before-left'),
126
                    new GridFieldPaginator(),
127
                    new GridFieldDetailForm()
128
                );
129
130
            $categories = GridField::create(
131
                'Category',
132
                _t('Forum.FORUMCATEGORY', 'Forum Category'),
133
                $self->Categories(),
134
                $categoryConfig
135
            );
136
137
            $fields->addFieldsToTab("Root.Categories", $categories);
138
139
140
            $fields->addFieldsToTab("Root.LanguageFilter", array(
141
                TextField::create("ForbiddenWords", "Forbidden words (comma separated)"),
142
                LiteralField::create("FWLabel", "These words will be replaced by an asterisk")
143
            ));
144
145
            $fields->addFieldToTab("Root.Access", HeaderField::create(_t('Forum.ACCESSPOST', 'Who can post to the forum?'), 2));
146
            $fields->addFieldToTab("Root.Access", OptionsetField::create("CanPostType", "", array(
147
                "Anyone" => _t('Forum.READANYONE', 'Anyone'),
148
                "LoggedInUsers" => _t('Forum.READLOGGEDIN', 'Logged-in users'),
149
                "NoOne" => _t('Forum.READNOONE', 'Nobody. Make Forum Read Only')
150
            )));
151
        });
152
153
        $fields = parent::getCMSFields();
154
155
        return $fields;
156
    }
157
158
    public function canPost($member = null)
159
    {
160
        if (!$member) {
161
            $member = Member::currentUser();
162
        }
163
164
        if ($this->CanPostType == "NoOne") {
165
            return false;
166
        }
167
168
        if ($this->CanPostType == "Anyone" || $this->canEdit($member)) {
169
            return true;
170
        }
171
172 View Code Duplication
        if ($member) {
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...
173
            if ($member->IsSuspended()) {
174
                return false;
175
            }
176
            if ($member->IsBanned()) {
177
                return false;
178
            }
179
            if ($this->CanPostType == "LoggedInUsers") {
180
                return true;
181
            }
182
183
            if ($groups = $this->PosterGroups()) {
184
                foreach ($groups as $group) {
185
                    if ($member->inGroup($group)) {
186
                        return true;
187
                    }
188
                }
189
            }
190
        }
191
192
        return false;
193
    }
194
195
    /**
196
     * Ensure that any categories that exist with no forum holder are updated to be owned by the first forum holder
197
     * if there is one. This is required now that multiple forum holds are allowed, and categories belong to holders.
198
     *
199
     * @see sapphire/core/model/DataObject#requireDefaultRecords()
200
     */
201
    public function requireDefaultRecords()
202
    {
203
        parent::requireDefaultRecords();
204
205
        $forumCategories = ForumCategory::get()->filter('ForumHolderID', 0);
206
        if (!$forumCategories->exists()) {
207
            return;
208
        }
209
210
        $forumHolder = ForumHolder::get()->first();
211
        if (!$forumHolder) {
212
            return;
213
        }
214
215
        foreach ($forumCategories as $forumCategory) {
216
            $forumCategory->ForumHolderID = $forumHolder->ID;
217
            $forumCategory->write();
218
        }
219
    }
220
221
    /**
222
     * If we're on the search action, we need to at least show
223
     * a breadcrumb to get back to the ForumHolder page.
224
     * @return string
225
     */
226
    public function Breadcrumbs($maxDepth = 20, $unlinked = false, $stopAtPageType = false, $showHidden = false)
227
    {
228
        if (isset($this->urlParams['Action'])) {
229
            switch ($this->urlParams['Action']) {
230
                case 'search':
231
                    return '<a href="' . $this->Link() . '">' . $this->Title . '</a> &raquo; ' . _t('SEARCHBREADCRUMB', 'Search');
232
                case 'memberlist':
233
                    return '<a href="' . $this->Link() . '">' . $this->Title . '</a> &raquo; ' . _t('MEMBERLIST', 'Member List');
234
                case 'popularthreads':
235
                    return '<a href="' . $this->Link() . '">' . $this->Title . '</a> &raquo; ' . _t('MOSTPOPULARTHREADS', 'Most popular threads');
236
            }
237
        }
238
    }
239
240
241
    /**
242
     * Get the number of total posts
243
     *
244
     * @return int Returns the number of posts
245
     */
246 View Code Duplication
    public function getNumPosts()
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...
247
    {
248
            $sqlQuery = new SQLQuery();
0 ignored issues
show
Deprecated Code introduced by
The class SQLQuery has been deprecated with message: since version 4.0

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

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

Loading history...
249
            $sqlQuery->setFrom('"Post"');
250
            $sqlQuery->setSelect('COUNT("Post"."ID")');
251
            $sqlQuery->addInnerJoin('Member', '"Post"."AuthorID" = "Member"."ID"');
252
            $sqlQuery->addInnerJoin('SiteTree', '"Post"."ForumID" = "SiteTree"."ID"');
253
            $sqlQuery->addWhere('"Member"."ForumStatus" = \'Normal\'');
254
            $sqlQuery->addWhere('"SiteTree"."ParentID" = ' . $this->ID);
255
            return $sqlQuery->execute()->value();
256
    }
257
258
259
    /**
260
     * Get the number of total topics (threads)
261
     *
262
     * @return int Returns the number of topics (threads)
263
     */
264 View Code Duplication
    public function getNumTopics()
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...
265
    {
266
        $sqlQuery = new SQLQuery();
0 ignored issues
show
Deprecated Code introduced by
The class SQLQuery has been deprecated with message: since version 4.0

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

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

Loading history...
267
        $sqlQuery->setFrom('"Post"');
268
        $sqlQuery->setSelect('COUNT(DISTINCT("ThreadID"))');
269
        $sqlQuery->addInnerJoin('Member', '"Post"."AuthorID" = "Member"."ID"');
270
        $sqlQuery->addInnerJoin('SiteTree', '"Post"."ForumID" = "SiteTree"."ID"');
271
        $sqlQuery->addWhere('"Member"."ForumStatus" = \'Normal\'');
272
        $sqlQuery->addWhere('"SiteTree"."ParentID" = ' . $this->ID);
273
        return $sqlQuery->execute()->value();
274
    }
275
276
277
    /**
278
     * Get the number of distinct authors
279
     *
280
     * @return int Returns the number of distinct authors
281
     */
282 View Code Duplication
    public function getNumAuthors()
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...
283
    {
284
        $sqlQuery = new SQLQuery();
0 ignored issues
show
Deprecated Code introduced by
The class SQLQuery has been deprecated with message: since version 4.0

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

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

Loading history...
285
        $sqlQuery->setFrom('"Post"');
286
        $sqlQuery->setSelect('COUNT(DISTINCT("AuthorID"))');
287
        $sqlQuery->addInnerJoin('Member', '"Post"."AuthorID" = "Member"."ID"');
288
        $sqlQuery->addInnerJoin('SiteTree', '"Post"."ForumID" = "SiteTree"."ID"');
289
        $sqlQuery->addWhere('"Member"."ForumStatus" = \'Normal\'');
290
        $sqlQuery->addWhere('"SiteTree"."ParentID" = ' . $this->ID);
291
        return $sqlQuery->execute()->value();
292
    }
293
294
    /**
295
     * Is the "Currently Online" functionality enabled?
296
     * @return bool
297
     */
298
    public function CurrentlyOnlineEnabled()
299
    {
300
        return $this->config()->currently_online_enabled;
301
    }
302
303
    /**
304
     * Get a list of currently online users (last 15 minutes)
305
     * that belong to the "forum-members" code {@link Group}.
306
     *
307
     * @return DataList of {@link Member} objects
308
     */
309
    public function CurrentlyOnline()
310
    {
311
        if (!$this->CurrentlyOnlineEnabled()) {
312
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by ForumHolder::CurrentlyOnline of type DataList.

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...
313
        }
314
315
        $groupIDs = array();
316
317
        if ($forumGroup = Group::get()->filter('Code', 'forum-members')->first()) {
318
            $groupIDs[] = $forumGroup->ID;
319
        }
320
321
        if ($adminGroup = Group::get()->filter('Code', array('administrators', 'Administrators'))->first()) {
322
            $groupIDs[] = $adminGroup->ID;
323
        }
324
325
        return Member::get()
326
            ->leftJoin('Group_Members', '"Member"."ID" = "Group_Members"."MemberID"')
327
            ->filter('GroupID', $groupIDs)
328
            ->where('"Member"."LastViewed" > ' . DB::getConn()->datetimeIntervalClause('NOW', '-15 MINUTE'))
0 ignored issues
show
Deprecated Code introduced by
The method DB::getConn() has been deprecated with message: since version 4.0 Use DB::get_conn instead

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

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

Loading history...
329
            ->sort('"Member"."FirstName", "Member"."Surname"');
330
    }
331
332
    /**
333
     * @deprecated 0.5
334
     */
335
    public function LatestMember($limit = 1)
336
    {
337
        user_error('Please use LatestMembers($limit) instead of LatestMember', E_USER_NOTICE);
338
339
        return $this->LatestMembers($limit);
340
    }
341
342
    /**
343
     * Get the latest members from the forum group.
344
     *
345
     * @param int $limit Number of members to return
346
     * @return ArrayList
347
     */
348
    public function getLatestMembers($limit = null)
349
    {
350
        if (!is_null($limit)) {
351
            Deprecation::notice('1.0', '$limit parameter is deprecated, please chain the limit clause');
352
        }
353
        $groupID = DB::query('SELECT "ID" FROM "Group" WHERE "Code" = \'forum-members\'')->value();
354
355
        // if we're just looking for a single MemberID, do a quicker query on the join table.
356
        if ($limit == 1) {
357
            $latestMemberId = DB::query(sprintf(
358
                'SELECT MAX("MemberID")
359
				FROM "Group_Members"
360
				WHERE "Group_Members"."GroupID" = \'%s\'',
361
                $groupID
362
            ))->value();
363
364
            $latestMembers = Member::get()->byId($latestMemberId);
0 ignored issues
show
Bug Compatibility introduced by
The expression \Member::get()->byId($latestMemberId); of type DataObject|null adds the type DataObject to the return on line 375 which is incompatible with the return type documented by ForumHolder::getLatestMembers of type ArrayList|null.
Loading history...
365
        } else {
366
            $latestMembers = Member::get()
367
                ->leftJoin('Group_Members', '"Member"."ID" = "Group_Members"."MemberID"')
368
                ->filter('GroupID', $groupID)
369
                ->sort('"Member"."ID" DESC');
370
            if ($limit) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
371
                $latestMembers = $latestMembers->limit($limit);
372
            }
373
        }
374
375
        return $latestMembers;
376
    }
377
378
    /**
379
     * Get a list of Forum Categories
380
     * @return DataList
381
     */
382
    public function getShowInCategories()
383
    {
384
        $forumCategories = ForumCategory::get()->filter('ForumHolderID', $this->ID);
385
        $showInCategories = $this->getField('ShowInCategories');
386
        return $forumCategories->exists() && $showInCategories;
387
    }
388
389
    /**
390
     * Get the forums. Actually its a bit more complex than that
391
     * we need to group by the Forum Categories.
392
     *
393
     * @return ArrayList
394
     */
395
    public function Forums()
396
    {
397
        $categoryText = isset($_REQUEST['Category']) ? Convert::raw2xml($_REQUEST['Category']) : null;
398
        $holder = $this;
399
400
        if ($this->getShowInCategories()) {
401
            return ForumCategory::get()
402
                ->filter('ForumHolderID', $this->ID)
403
                ->filterByCallback(function ($category) use ($categoryText, $holder) {
404
                    // Don't include if we've specified a Category, and it doesn't match this one
405
                    if ($categoryText !== null && $category->Title != $categoryText) {
406
                        return false;
407
                    }
408
409
                    // Get a list of forums that live under this holder & category
410
                    $category->CategoryForums = Forum::get()
411
                        ->filter(array(
412
                            'CategoryID' => $category->ID,
413
                            'ParentID' => $holder->ID,
414
                            'ShowInMenus' => 1
415
                        ))
416
                        ->filterByCallback(function ($forum) {
417
                            return $forum->canView();
418
                        });
419
420
                    return $category->CategoryForums->exists();
421
                });
422
        } else {
423
            return Forum::get()
424
                ->filter(array(
425
                    'ParentID' => $this->ID,
426
                    'ShowInMenus' => 1
427
                ))
428
                ->filterByCallback(function ($forum) {
429
                    return $forum->canView();
430
                });
431
        }
432
    }
433
434
    /**
435
     * A function that returns the correct base table to use for custom forum queries. It uses the getVar stage to determine
436
     * what stage we are looking at, and determines whether to use SiteTree or SiteTree_Live (the general case). If the stage is
437
     * not specified, live is assumed (general case). It is a static function so it can be used for both ForumHolder and Forum.
438
     *
439
     * @return String
440
     */
441
    static function baseForumTable()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
442
    {
443
        $stage = (Controller::curr()->getRequest()) ? Controller::curr()->getRequest()->getVar('stage') : false;
444
        if (!$stage) {
445
            $stage = Versioned::get_live_stage();
446
        }
447
448
        if ((class_exists('SapphireTest', false) && SapphireTest::is_running_test())
449
            || $stage == "Stage"
450
        ) {
451
            return "SiteTree";
452
        } else {
453
            return "SiteTree_Live";
454
        }
455
    }
456
457
458
    /**
459
     * Is OpenID support available?
460
     *
461
     * This method checks if the {@link OpenIDAuthenticator} is available and
462
     * registered.
463
     *
464
     * @return bool Returns TRUE if OpenID is available, FALSE otherwise.
465
     */
466
    public function OpenIDAvailable()
467
    {
468
        if (class_exists('Authenticator') == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
469
            return false;
470
        }
471
472
        return Authenticator::is_registered("OpenIDAuthenticator");
473
    }
474
475
476
    /**
477
     * Get the latest posts
478
     *
479
     * @param int $limit Number of posts to return
480
     * @param int $forumID - Forum ID to limit it to
481
     * @param int $threadID - Thread ID to limit it to
482
     * @param int $lastVisit Optional: Unix timestamp of the last visit (GMT)
483
     * @param int $lastPostID Optional: ID of the last read post
484
     */
485
    public function getRecentPosts($limit = 50, $forumID = null, $threadID = null, $lastVisit = null, $lastPostID = null)
486
    {
487
        $filter = array();
488
489
        if ($lastVisit) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lastVisit of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
490
            $lastVisit = @date('Y-m-d H:i:s', $lastVisit);
491
        }
492
493
        $lastPostID = (int) $lastPostID;
494
495
        // last post viewed
496
        if ($lastPostID > 0) {
497
            $filter[] = "\"Post\".\"ID\" > '". Convert::raw2sql($lastPostID) ."'";
498
        }
499
500
        // last time visited
501
        if ($lastVisit) {
502
            $filter[] = "\"Post\".\"Created\" > '". Convert::raw2sql($lastVisit) ."'";
503
        }
504
505
        // limit to a forum
506
        if ($forumID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $forumID of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
507
            $filter[] = "\"Post\".\"ForumID\" = '". Convert::raw2sql($forumID) ."'";
508
        }
509
510
        // limit to a thread
511
        if ($threadID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $threadID of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
512
            $filter[] = "\"Post\".\"ThreadID\" = '". Convert::raw2sql($threadID) ."'";
513
        }
514
515
        // limit to just this forum install
516
        $filter[] = "\"ForumPage\".\"ParentID\"='{$this->ID}'";
517
518
        $posts = Post::get()
519
            ->leftJoin('ForumThread', '"Post"."ThreadID" = "ForumThread"."ID"')
520
            ->leftJoin(ForumHolder::baseForumTable(), '"ForumPage"."ID" = "Post"."ForumID"', 'ForumPage')
521
            ->limit($limit)
522
            ->sort('"Post"."ID"', 'DESC')
523
            ->where($filter);
524
525
        $recentPosts = new ArrayList();
526
        foreach ($posts as $post) {
527
            $recentPosts->push($post);
528
        }
529
        if ($recentPosts->count() > 0) {
530
            return $recentPosts;
531
        }
532
        return null;
533
    }
534
535
536
    /**
537
     * Are new posts available?
538
     *
539
     * @param int $id
540
     * @param array $data Optional: If an array is passed, the timestamp of
541
     *                    the last created post and it's ID will be stored in
542
     *                    it (keys: 'last_id', 'last_created')
543
     * @param int $lastVisit Unix timestamp of the last visit (GMT)
544
     * @param int $lastPostID ID of the last read post
545
     * @param int $thread ID of the relevant topic (set to NULL for all
0 ignored issues
show
Documentation introduced by
There is no parameter named $thread. Did you maybe mean $threadID?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
546
     *                     topics)
547
     * @return bool Returns TRUE if there are new posts available, otherwise
548
     *              FALSE.
549
     */
550
    public static function new_posts_available($id, &$data = array(), $lastVisit = null, $lastPostID = null, $forumID = null, $threadID = null)
551
    {
552
        $filter = array();
553
554
        // last post viewed
555
        $filter[] = "\"ForumPage\".\"ParentID\" = '". Convert::raw2sql($id) ."'";
556
        if ($lastPostID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lastPostID of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
557
            $filter[] = "\"Post\".\"ID\" > '". Convert::raw2sql($lastPostID) ."'";
558
        }
559
        if ($lastVisit) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lastVisit of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
560
            $filter[] = "\"Post\".\"Created\" > '". Convert::raw2sql($lastVisit) ."'";
561
        }
562
        if ($forumID) {
563
            $filter[] = "\"Post\".\"ForumID\" = '". Convert::raw2sql($forumID) ."'";
564
        }
565
        if ($threadID) {
566
            $filter[] = "\"ThreadID\" = '". Convert::raw2sql($threadID) ."'";
567
        }
568
569
        $filter = implode(" AND ", $filter);
570
571
        $version = DB::query("
572
			SELECT MAX(\"Post\".\"ID\") AS \"LastID\", MAX(\"Post\".\"Created\") AS \"LastCreated\"
573
			FROM \"Post\"
574
			JOIN \"" . ForumHolder::baseForumTable() . "\" AS \"ForumPage\" ON \"Post\".\"ForumID\"=\"ForumPage\".\"ID\"
575
			WHERE $filter")->first();
576
577
        if ($version == false) {
578
            return false;
579
        }
580
581
        if ($data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
582
            $data['last_id'] = (int)$version['LastID'];
583
            $data['last_created'] = strtotime($version['LastCreated']);
584
        }
585
586
        $lastVisit = (int) $lastVisit;
587
588
        if ($lastVisit <= 0) {
589
            $lastVisit = false;
590
        }
591
592
        $lastPostID = (int)$lastPostID;
593
        if ($lastPostID <= 0) {
594
            $lastPostID = false;
595
        }
596
597
        if (!$lastVisit && !$lastPostID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lastVisit of type false|integer is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
Bug Best Practice introduced by
The expression $lastPostID of type false|integer is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
598
            return true;
599
        }
600
        if ($lastVisit && (strtotime($version['LastCreated']) > $lastVisit)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lastVisit of type false|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...
601
            return true;
602
        }
603
604
        if ($lastPostID && ((int)$version['LastID'] > $lastPostID)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lastPostID of type false|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...
605
            return true;
606
        }
607
608
        return false;
609
    }
610
611
    /**
612
     * Helper Method from the template includes. Uses $ForumHolder so in order for it work
613
     * it needs to be included on this page
614
     *
615
     * @return ForumHolder
616
     */
617
    public function getForumHolder()
618
    {
619
        return $this;
620
    }
621
}
622
623
624
class ForumHolder_Controller extends Page_Controller
625
{
626
627
    private static $allowed_actions = array(
628
        'popularthreads',
629
        'login',
630
        'logout',
631
        'search',
632
        'rss',
633
    );
634
635
    public function init()
636
    {
637
        parent::init();
638
639
        Requirements::javascript(THIRDPARTY_DIR . "/jquery/jquery.js");
640
        Requirements::javascript("forum/javascript/jquery.MultiFile.js");
641
        Requirements::javascript("forum/javascript/forum.js");
642
643
        Requirements::themedCSS('Forum', 'forum', 'all');
644
645
        RSSFeed::linkToFeed($this->Link("rss"), _t('ForumHolder.POSTSTOALLFORUMS', "Posts to all forums"));
646
647
        // Set the back url
648 View Code Duplication
        if (isset($_SERVER['REQUEST_URI'])) {
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...
649
            Session::set('BackURL', $_SERVER['REQUEST_URI']);
650
        } else {
651
            Session::set('BackURL', $this->Link());
652
        }
653
    }
654
655
    /**
656
     * Generate a complete list of all the members data. Return a
657
     * set of all these members sorted by a GET variable
658
     *
659
     * @todo Sort via AJAX
660
     * @return DataObjectSet A DataObjectSet of all the members which are signed up
661
     */
662
    public function memberlist()
663
    {
664
        return $this->httpError(404);
665
666
        $forumGroupID = (int) DataObject::get_one('Group', "\"Code\" = 'forum-members'")->ID;
0 ignored issues
show
Unused Code introduced by
$forumGroupID = (int) \D...'forum-members\'')->ID; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
667
668
        // If sort has been defined then save it as in the session
669
        $order = (isset($_GET['order'])) ? $_GET['order']: "";
670
671
        if (!isset($_GET['start']) || !is_numeric($_GET['start']) || (int) $_GET['start'] < 1) {
672
            $_GET['start'] = 0;
673
        }
674
675
        $SQL_start = (int) $_GET['start'];
676
677
        switch ($order) {
678
            case "joined":
679
//				$members = DataObject::get("Member", "\"GroupID\" = '$forumGroupID'", "\"Member\".\"Created\" ASC", "LEFT JOIN \"Group_Members\" ON \"Member\".\"ID\" = \"Group_Members\".\"MemberID\"", "{$SQL_start},100");
680
                $members = Member::get()
681
                        ->filter('Member.GroupID', $forumGroupID)
682
                        ->leftJoin('Group_Members', '"Member"."ID" = "Group_Members"."MemberID"')
683
                        ->sort('"Member"."Created" ASC')
684
                        ->limit($SQL_start . ',100');
685
                break;
686 View Code Duplication
            case "name":
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...
687
//				$members = DataObject::get("Member", "\"GroupID\" = '$forumGroupID'", "\"Member\".\"Nickname\" ASC", "LEFT JOIN \"Group_Members\" ON \"Member\".\"ID\" = \"Group_Members\".\"MemberID\"", "{$SQL_start},100");
688
                $members = Member::get()
689
                        ->filter('Member.GroupID', $forumGroupID)
690
                        ->leftJoin('Group_Members', '"Member"."ID" = "Group_Members"."MemberID"')
691
                        ->sort('"Member"."Nickname" ASC')
692
                        ->limit($SQL_start . ',100');
693
                break;
694 View Code Duplication
            case "country":
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...
695
//				$members = DataObject::get("Member", "\"GroupID\" = '$forumGroupID' AND \"Member\".\"CountryPublic\" = TRUE", "\"Member\".\"Country\" ASC", "LEFT JOIN \"Group_Members\" ON \"Member\".\"ID\" = \"Group_Members\".\"MemberID\"", "{$SQL_start},100");
696
                $members = Member::get()
697
                        ->filter(array('Member.GroupID' => $forumGroupID, 'Member.CountryPublic' => true))
698
                        ->leftJoin('Group_Members', '"Member"."ID" = "Group_Members"."MemberID"')
699
                        ->sort('"Member"."Nickname" ASC')
700
                        ->limit($SQL_start . ',100');
701
                break;
702
            case "posts":
703
                $query = singleton('Member')->extendedSQL('', "\"NumPosts\" DESC", "{$SQL_start},100");
704
                $query->select[] = "(SELECT COUNT(*) FROM \"Post\" WHERE \"Post\".\"AuthorID\" = \"Member\".\"ID\") AS \"NumPosts\"";
705
                $records = $query->execute();
706
                $members = singleton('Member')->buildDataObjectSet($records, 'DataObjectSet', $query, 'Member');
707
                $members->parseQueryLimit($query);
708
                break;
709
            default:
710
                //$members = DataObject::get("Member", "\"GroupID\" = '$forumGroupID'", "\"Member\".\"Created\" DESC", "LEFT JOIN \"Group_Members\" ON \"Member\".\"ID\" = \"Group_Members\".\"MemberID\"", "{$SQL_start},100");
711
                $members = Member::get()
712
                        ->filter('Member.GroupID', $forumGroupID)
713
                        ->leftJoin('Group_Members', '"Member"."ID" = "Group_Members"."MemberID"')
714
                        ->sort('"Member"."Created" DESC')
715
                        ->limit($SQL_start . ',100');
716
                break;
717
        }
718
719
        return array(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array('Subtitle' ... 'Forum member List')); (array) is incompatible with the return type documented by ForumHolder_Controller::memberlist of type DataObjectSet.

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...
720
            'Subtitle' => _t('ForumHolder.MEMBERLIST', 'Forum member List'),
721
            'Abstract' => $this->MemberListAbstract,
722
            'Members' => $members,
723
            'Title' => _t('ForumHolder.MEMBERLIST', 'Forum member List')
724
        );
725
    }
726
727
    /**
728
     * Show the 20 most popular threads across all {@link Forum} children.
729
     *
730
     * Two configuration options are available:
731
     * 1. "posts" - most popular threads by posts
732
     * 2. "views" - most popular threads by views
733
     *
734
     * e.g. mysite.com/forums/popularthreads?by=posts
735
     *
736
     * @return array
737
     */
738
    public function popularthreads()
739
    {
740
        $start = isset($_GET['start']) ? (int) $_GET['start'] : 0;
741
        $limit = 20;
742
        $method = isset($_GET['by']) ? $_GET['by'] : null;
743
        if (!$method) {
744
            $method = 'posts';
745
        }
746
747
        if ($method == 'posts') {
748
            $threadsQuery = singleton('ForumThread')->buildSQL(
749
                "\"SiteTree\".\"ParentID\" = '" . $this->ID ."'",
750
                "\"PostCount\" DESC",
751
                "$start,$limit",
752
                "LEFT JOIN \"Post\" ON \"Post\".\"ThreadID\" = \"ForumThread\".\"ID\" LEFT JOIN \"SiteTree\" ON \"SiteTree\".\"ID\" = \"ForumThread\".\"ForumID\""
753
            );
754
            $threadsQuery->select[] = "COUNT(\"Post\".\"ID\") AS 'PostCount'";
755
            $threadsQuery->groupby[] = "\"ForumThread\".\"ID\"";
756
            $threads = singleton('ForumThread')->buildDataObjectSet($threadsQuery->execute());
757
            if ($threads) {
758
                $threads->setPageLimits($start, $limit, $threadsQuery->unlimitedRowCount());
759
            }
760
        } elseif ($method == 'views') {
761
            $threads = DataObject::get('ForumThread', '', "\"NumViews\" DESC", '', "$start,$limit");
762
        }
763
764
        return array(
765
            'Title' => _t('ForumHolder.POPULARTHREADS', 'Most popular forum threads'),
766
            'Subtitle' => _t('ForumHolder.POPULARTHREADS', 'Most popular forum threads'),
767
            'Method' => $method,
768
            'Threads' => $threads
0 ignored issues
show
Bug introduced by
The variable $threads does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
769
        );
770
    }
771
772
    /**
773
     * The login action
774
     *
775
     * It simple sets the return URL and forwards to the standard login form.
776
     */
777
    public function login()
778
    {
779
        Session::set('Security.Message.message', _t('Forum.CREDENTIALS'));
780
        Session::set('Security.Message.type', 'status');
781
        Session::set("BackURL", $this->Link());
782
783
        $this->redirect('Security/login');
784
    }
785
786
787
    public function logout()
788
    {
789
        if ($member = Member::currentUser()) {
790
            $member->logOut();
791
        }
792
793
        $this->redirect($this->Link());
794
    }
795
796
    /**
797
     * The search action
798
     *
799
     * @return array Returns an array to render the search results.
800
     */
801
    public function search()
802
    {
803
        $keywords   = (isset($_REQUEST['Search'])) ? Convert::raw2xml($_REQUEST['Search']) : null;
804
        $order      = (isset($_REQUEST['order'])) ? Convert::raw2xml($_REQUEST['order']) : null;
805
        $start      = (isset($_REQUEST['start'])) ? (int) $_REQUEST['start'] : 0;
806
807
        $abstract = ($keywords) ? "<p>" . sprintf(_t('ForumHolder.SEARCHEDFOR', "You searched for '%s'."), $keywords) . "</p>": null;
808
809
        // get the results of the query from the current search engine
810
        $search = ForumSearch::get_search_engine();
811
812
        if ($search) {
813
            $engine = new $search();
814
815
            $results = $engine->getResults($this->ID, $keywords, $order, $start);
816
        } else {
817
            $results = false;
818
        }
819
820
        //Paginate the results
821
        $results = PaginatedList::create(
822
            $results,
823
            $this->request->getVars()
824
        );
825
826
827
        // if the user has requested this search as an RSS feed then output the contents as xml
828
        // rather than passing it to the template
829
        if (isset($_REQUEST['rss'])) {
830
            $rss = new RSSFeed($results, $this->Link(), _t('ForumHolder.SEARCHRESULTS', 'Search results'), "", "Title", "RSSContent", "RSSAuthor");
831
832
            return $rss->outputToBrowser();
833
        }
834
835
        // attach a link to a RSS feed version of the search results
836
        $rssLink = $this->Link() ."search/?Search=".urlencode($keywords). "&amp;order=".urlencode($order)."&amp;rss";
837
        RSSFeed::linkToFeed($rssLink, _t('ForumHolder.SEARCHRESULTS', 'Search results'));
838
839
        return array(
840
            "Subtitle"      => DBField::create_field('Text', _t('ForumHolder.SEARCHRESULTS', 'Search results')),
841
            "Abstract"      => DBField::create_field('HTMLText', $abstract),
842
            "Query"             => DBField::create_field('Text', $_REQUEST['Search']),
843
            "Order"             => DBField::create_field('Text', ($order) ? $order : "relevance"),
844
            "RSSLink"       => DBField::create_field('HTMLText', $rssLink),
845
            "SearchResults"     => $results
846
        );
847
    }
848
849
    /**
850
     * Get the RSS feed
851
     *
852
     * This method will output the RSS feed with the last 50 posts to the
853
     * browser.
854
     */
855
    public function rss()
856
    {
857
        HTTP::set_cache_age(3600); // cache for one hour
858
859
        $threadID = null;
860
        $forumID = null;
861
862
        // optionally allow filtering of the forum posts by the url in the format
863
        // rss/thread/$ID or rss/forum/$ID
864
        if (isset($this->urlParams['ID']) && ($action = $this->urlParams['ID'])) {
865
            if (isset($this->urlParams['OtherID']) && ($id = $this->urlParams['OtherID'])) {
866
                switch ($action) {
867
                    case 'forum':
868
                        $forumID = (int) $id;
869
                        break;
870
                    case 'thread':
871
                        $threadID = (int) $id;
872
                }
873
            } else {
874
                // fallback is that it is the ID of a forum like it was in
875
                // previous versions
876
                $forumID = (int) $action;
877
            }
878
        }
879
880
        $data = array('last_created' => null, 'last_id' => null);
881
882
        if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && !isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
883
            // just to get the version data..
884
            $available = ForumHolder::new_posts_available($this->ID, $data, null, null, $forumID, $threadID);
0 ignored issues
show
Unused Code introduced by
$available is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
885
886
            // No information provided by the client, just return the last posts
887
            $rss = new RSSFeed(
888
                $this->getRecentPosts(50, $forumID, $threadID),
889
                $this->Link() . 'rss',
890
                sprintf(_t('Forum.RSSFORUMPOSTSTO'), $this->Title),
891
                "",
892
                "Title",
893
                "RSSContent",
894
                "RSSAuthor",
895
                $data['last_created'],
896
                $data['last_id']
897
            );
898
            return $rss->outputToBrowser();
899
        } else {
900
            // Return only new posts, check the request headers!
901
            $since = null;
902
            $etag = null;
903
904
            if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
905
                // Split the If-Modified-Since (Netscape < v6 gets this wrong)
906
                $since = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE']);
907
                // Turn the client request If-Modified-Since into a timestamp
908
                $since = @strtotime($since[0]);
909
                if (!$since) {
910
                    $since = null;
911
                }
912
            }
913
914
            if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && is_numeric($_SERVER['HTTP_IF_NONE_MATCH'])) {
915
                $etag = (int)$_SERVER['HTTP_IF_NONE_MATCH'];
916
            }
917
            if ($available = ForumHolder::new_posts_available($this->ID, $data, $since, $etag, $forumID, $threadID)) {
0 ignored issues
show
Unused Code introduced by
$available is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
918
                HTTP::register_modification_timestamp($data['last_created']);
919
                $rss = new RSSFeed(
920
                    $this->getRecentPosts(50, $forumID, $threadID, $etag),
921
                    $this->Link() . 'rss',
922
                    sprintf(_t('Forum.RSSFORUMPOSTSTO'), $this->Title),
923
                    "",
924
                    "Title",
925
                    "RSSContent",
926
                    "RSSAuthor",
927
                    $data['last_created'],
928
                    $data['last_id']
929
                );
930
                return $rss->outputToBrowser();
931
            } else {
932
                if ($data['last_created']) {
933
                    HTTP::register_modification_timestamp($data['last_created']);
934
                }
935
936
                if ($data['last_id']) {
937
                    HTTP::register_etag($data['last_id']);
938
                }
939
940
                // There are no new posts, just output an "304 Not Modified" message
941
                HTTP::add_cache_headers();
942
                header('HTTP/1.1 304 Not Modified');
943
            }
944
        }
945
        exit;
946
    }
947
948
    /**
949
     * Return the GlobalAnnouncements from the individual forums
950
     *
951
     * @return DataObjectSet
952
     */
953
    public function GlobalAnnouncements()
954
    {
955
        //dump(ForumHolder::baseForumTable());
956
957
        // Get all the forums with global sticky threads
958
        return ForumThread::get()
959
            ->filter('IsGlobalSticky', 1)
960
            ->innerJoin(ForumHolder::baseForumTable(), '"ForumThread"."ForumID"="ForumPage"."ID"', "ForumPage")
961
            ->where('"ForumPage"."ParentID" = '.$this->ID)
962
            ->filterByCallback(function ($thread) {
963
                if ($thread->canView()) {
964
                    $post = Post::get()->filter('ThreadID', $thread->ID)->sort('Post.Created DESC');
965
                    $thread->Post = $post;
966
                    return true;
967
                }
968
            });
969
    }
970
}
971