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

ForumHolder::new_posts_available()   F

Complexity

Conditions 15
Paths 528

Size

Total Lines 60
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 60
rs 3.9341
c 0
b 0
f 0
cc 15
eloc 33
nc 528
nop 6

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverStripe\Forum\Pages;
4
5
use SilverStripe\Forms\TextField;
6
use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
7
use SilverStripe\Forms\CheckboxField;
8
use SilverStripe\Forms\DropdownField;
9
use SilverStripe\Forms\GridField\GridFieldConfig;
10
use SilverStripe\Forms\GridField\GridFieldSortableHeader;
11
use SilverStripe\Forms\GridField\GridFieldButtonRow;
12
use SilverStripe\Forms\GridField\GridFieldDataColumns;
13
use SilverStripe\Forms\GridField\GridFieldEditButton;
14
use SilverStripe\Forms\GridField\GridFieldViewButton;
15
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
16
use SilverStripe\Forms\GridField\GridFieldAddNewButton;
17
use SilverStripe\Forms\GridField\GridFieldPaginator;
18
use SilverStripe\Forms\GridField\GridFieldDetailForm;
19
use SilverStripe\Forms\GridField\GridField;
20
use SilverStripe\Forms\LiteralField;
21
use SilverStripe\Forms\HeaderField;
22
use SilverStripe\Forms\OptionsetField;
23
use SilverStripe\Security\Member;
24
use SilverStripe\Security\Group;
25
use SilverStripe\ORM\DB;
26
use SilverStripe\Dev\Deprecation;
27
use SilverStripe\Core\Convert;
28
use SilverStripe\Control\Controller;
29
use SilverStripe\ORM\Versioning\Versioned;
30
use SilverStripe\Dev\SapphireTest;
31
use SilverStripe\Security\Authenticator;
32
use SilverStripe\ORM\ArrayList;
33
use SilverStripe\View\Requirements;
34
use SilverStripe\Control\RSS\RSSFeed;
35
use SilverStripe\Control\Session;
36
use SilverStripe\ORM\DataObject;
37
use SilverStripe\ORM\PaginatedList;
38
use SilverStripe\ORM\FieldType\DBField;
39
use SilverStripe\Control\HTTP;
40
use PageController;
41
use Page;
42
use ForumCategory;
43
use SQLQuery;
44
use Post;
45
use ForumSearch;
46
use ForumThread;
47
48
/**
49
 * ForumHolder represents the top forum overview page. Its children
50
 * should be Forums. On this page you can also edit your global settings
51
 * for the entire forum.
52
 *
53
 * @package forum
54
 */
55
56
class ForumHolder extends Page
57
{
58
59
    private static $avatars_folder = 'forum/avatars/';
60
61
    private static $attachments_folder = 'forum/attachments/';
62
63
    private static $db = array(
64
        "HolderSubtitle" => "Varchar(200)",
65
        "ProfileSubtitle" => "Varchar(200)",
66
        "ForumSubtitle" => "Varchar(200)",
67
        "HolderAbstract" => "HTMLText",
68
        "ProfileAbstract" => "HTMLText",
69
        "ForumAbstract" => "HTMLText",
70
        "ProfileModify" => "HTMLText",
71
        "ProfileAdd" => "HTMLText",
72
        "DisplaySignatures" => "Boolean",
73
        "ShowInCategories" => "Boolean",
74
        "AllowGravatars" => "Boolean",
75
        "GravatarType" => "Varchar(10)",
76
        "ForbiddenWords" => "Text",
77
        "CanPostType" => "Enum('Anyone, LoggedInUsers, OnlyTheseUsers, NoOne', 'LoggedInUsers')",
78
    );
79
80
    private static $has_one = array();
81
82
    private static $has_many = array(
83
        "Categories" => "ForumCategory"
84
    );
85
86
    private static $allowed_children = array('Forum');
87
88
    private static $defaults = array(
89
        "HolderSubtitle" => "Welcome to our forum!",
90
        "ProfileSubtitle" => "Edit Your Profile",
91
        "ForumSubtitle" => "Start a new topic",
92
        "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>",
93
        "ProfileAbstract" => "<p>Please fill out the fields below. You can choose whether some are publically visible by using the checkbox for each one.</p>",
94
        "ForumAbstract" => "<p>From here you can start a new topic.</p>",
95
        "ProfileModify" => "<p>Thanks, your member profile has been modified.</p>",
96
        "ProfileAdd" => "<p>Thanks, you are now signed up to the forum.</p>",
97
    );
98
99
    /**
100
     * If the user has spam protection enabled and setup then we can provide spam
101
     * prevention for the forum. This stores whether we actually want the registration
102
     * form to have such protection
103
     *
104
     * @var bool
105
     */
106
    public static $use_spamprotection_on_register = true;
107
108
    /**
109
     * If the user has spam protection enabled and setup then we can provide spam
110
     * prevention for the forum. This stores whether we actually want the posting
111
     * form (adding, replying) to have such protection
112
     *
113
     * @var bool
114
     */
115
    public static $use_spamprotection_on_posts = false;
116
117
    /**
118
     * Add a hidden field to the form which should remain empty
119
     * If its filled out, we can assume that a spam bot is auto-filling fields.
120
     *
121
     * @var bool
122
     */
123
    public static $use_honeypot_on_register = false;
124
125
    /**
126
     * @var bool If TRUE, each logged in Member who visits a Forum will write the LastViewed field
127
     * which is for the "Currently online" functionality.
128
     */
129
    private static $currently_online_enabled = true;
130
131
    public function getCMSFields()
132
    {
133
        $self = $this;
134
135
        $this->beforeUpdateCMSFields(function ($fields) use ($self) {
136
137
            $fields->addFieldsToTab("Root.Messages", array(
138
                TextField::create("HolderSubtitle", "Forum Holder Subtitle"),
139
                HTMLEditorField::create("HolderAbstract", "Forum Holder Abstract"),
140
                TextField::create("ProfileSubtitle", "Member Profile Subtitle"),
141
                HTMLEditorField::create("ProfileAbstract", "Member Profile Abstract"),
142
                TextField::create("ForumSubtitle", "Create topic Subtitle"),
143
                HTMLEditorField::create("ForumAbstract", "Create topic Abstract"),
144
                HTMLEditorField::create("ProfileModify", "Create message after modifing forum member"),
145
                HTMLEditorField::create("ProfileAdd", "Create message after adding forum member")
146
            ));
147
            $fields->addFieldsToTab("Root.Settings", array(
148
                CheckboxField::create("DisplaySignatures", "Display Member Signatures?"),
149
                CheckboxField::create("ShowInCategories", "Show Forums In Categories?"),
150
                CheckboxField::create("AllowGravatars", "Allow <a href='http://www.gravatar.com/' target='_blank'>Gravatars</a>?"),
151
                DropdownField::create("GravatarType", "Gravatar Type", array(
152
                    "standard" => _t('Forum.STANDARD', 'Standard'),
153
                    "identicon" => _t('Forum.IDENTICON', 'Identicon'),
154
                    "wavatar" => _t('Forum.WAVATAR', 'Wavatar'),
155
                    "monsterid" => _t('Forum.MONSTERID', 'Monsterid'),
156
                    "retro" => _t('Forum.RETRO', 'Retro'),
157
                    "mm" => _t('Forum.MM', 'Mystery Man'),
158
                ))->setEmptyString('Use Forum Default')
159
            ));
160
161
            // add a grid field to the category tab with all the categories
162
            $categoryConfig = GridFieldConfig::create()
163
                ->addComponents(
164
                    new GridFieldSortableHeader(),
165
                    new GridFieldButtonRow(),
166
                    new GridFieldDataColumns(),
167
                    new GridFieldEditButton(),
168
                    new GridFieldViewButton(),
169
                    new GridFieldDeleteAction(),
170
                    new GridFieldAddNewButton('buttons-before-left'),
171
                    new GridFieldPaginator(),
172
                    new GridFieldDetailForm()
173
                );
174
175
            $categories = GridField::create(
176
                'Category',
177
                _t('Forum.FORUMCATEGORY', 'Forum Category'),
178
                $self->Categories(),
179
                $categoryConfig
180
            );
181
182
            $fields->addFieldsToTab("Root.Categories", $categories);
183
184
185
            $fields->addFieldsToTab("Root.LanguageFilter", array(
186
                TextField::create("ForbiddenWords", "Forbidden words (comma separated)"),
187
                LiteralField::create("FWLabel", "These words will be replaced by an asterisk")
188
            ));
189
190
            $fields->addFieldToTab("Root.Access", HeaderField::create(_t('Forum.ACCESSPOST', 'Who can post to the forum?'), 2));
191
            $fields->addFieldToTab("Root.Access", OptionsetField::create("CanPostType", "", array(
192
                "Anyone" => _t('Forum.READANYONE', 'Anyone'),
193
                "LoggedInUsers" => _t('Forum.READLOGGEDIN', 'Logged-in users'),
194
                "NoOne" => _t('Forum.READNOONE', 'Nobody. Make Forum Read Only')
195
            )));
196
        });
197
198
        $fields = parent::getCMSFields();
199
200
        return $fields;
201
    }
202
203
    public function canPost($member = null)
204
    {
205
        if (!$member) {
206
            $member = Member::currentUser();
207
        }
208
209
        if ($this->CanPostType == "NoOne") {
210
            return false;
211
        }
212
213
        if ($this->CanPostType == "Anyone" || $this->canEdit($member)) {
214
            return true;
215
        }
216
217 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...
218
            if ($member->IsSuspended()) {
219
                return false;
220
            }
221
            if ($member->IsBanned()) {
222
                return false;
223
            }
224
            if ($this->CanPostType == "LoggedInUsers") {
225
                return true;
226
            }
227
228
            if ($groups = $this->PosterGroups()) {
229
                foreach ($groups as $group) {
230
                    if ($member->inGroup($group)) {
231
                        return true;
232
                    }
233
                }
234
            }
235
        }
236
237
        return false;
238
    }
239
240
    /**
241
     * Ensure that any categories that exist with no forum holder are updated to be owned by the first forum holder
242
     * if there is one. This is required now that multiple forum holds are allowed, and categories belong to holders.
243
     *
244
     * @see sapphire/core/model/DataObject#requireDefaultRecords()
245
     */
246
    public function requireDefaultRecords()
247
    {
248
        parent::requireDefaultRecords();
249
250
        $forumCategories = ForumCategory::get()->filter('ForumHolderID', 0);
251
        if (!$forumCategories->exists()) {
252
            return;
253
        }
254
255
        $forumHolder = ForumHolder::get()->first();
256
        if (!$forumHolder) {
257
            return;
258
        }
259
260
        foreach ($forumCategories as $forumCategory) {
261
            $forumCategory->ForumHolderID = $forumHolder->ID;
262
            $forumCategory->write();
263
        }
264
    }
265
266
    /**
267
     * If we're on the search action, we need to at least show
268
     * a breadcrumb to get back to the ForumHolder page.
269
     * @return string
270
     */
271
    public function Breadcrumbs($maxDepth = 20, $unlinked = false, $stopAtPageType = false, $showHidden = false)
272
    {
273
        if (isset($this->urlParams['Action'])) {
274
            switch ($this->urlParams['Action']) {
275
                case 'search':
276
                    return '<a href="' . $this->Link() . '">' . $this->Title . '</a> &raquo; ' . _t('SEARCHBREADCRUMB', 'Search');
277
                case 'memberlist':
278
                    return '<a href="' . $this->Link() . '">' . $this->Title . '</a> &raquo; ' . _t('MEMBERLIST', 'Member List');
279
                case 'popularthreads':
280
                    return '<a href="' . $this->Link() . '">' . $this->Title . '</a> &raquo; ' . _t('MOSTPOPULARTHREADS', 'Most popular threads');
281
            }
282
        }
283
    }
284
285
286
    /**
287
     * Get the number of total posts
288
     *
289
     * @return int Returns the number of posts
290
     */
291 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...
292
    {
293
            $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...
294
            $sqlQuery->setFrom('"Post"');
295
            $sqlQuery->setSelect('COUNT("Post"."ID")');
296
            $sqlQuery->addInnerJoin('SilverStripe\\Security\\Member', '"Post"."AuthorID" = "Member"."ID"');
297
            $sqlQuery->addInnerJoin('SilverStripe\\CMS\\Model\\SiteTree', '"Post"."ForumID" = "SiteTree"."ID"');
298
            $sqlQuery->addWhere('"Member"."ForumStatus" = \'Normal\'');
299
            $sqlQuery->addWhere('"SiteTree"."ParentID" = ' . $this->ID);
300
            return $sqlQuery->execute()->value();
301
    }
302
303
304
    /**
305
     * Get the number of total topics (threads)
306
     *
307
     * @return int Returns the number of topics (threads)
308
     */
309 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...
310
    {
311
        $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...
312
        $sqlQuery->setFrom('"Post"');
313
        $sqlQuery->setSelect('COUNT(DISTINCT("ThreadID"))');
314
        $sqlQuery->addInnerJoin('SilverStripe\\Security\\Member', '"Post"."AuthorID" = "Member"."ID"');
315
        $sqlQuery->addInnerJoin('SilverStripe\\CMS\\Model\\SiteTree', '"Post"."ForumID" = "SiteTree"."ID"');
316
        $sqlQuery->addWhere('"Member"."ForumStatus" = \'Normal\'');
317
        $sqlQuery->addWhere('"SiteTree"."ParentID" = ' . $this->ID);
318
        return $sqlQuery->execute()->value();
319
    }
320
321
322
    /**
323
     * Get the number of distinct authors
324
     *
325
     * @return int Returns the number of distinct authors
326
     */
327 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...
328
    {
329
        $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...
330
        $sqlQuery->setFrom('"Post"');
331
        $sqlQuery->setSelect('COUNT(DISTINCT("AuthorID"))');
332
        $sqlQuery->addInnerJoin('SilverStripe\\Security\\Member', '"Post"."AuthorID" = "Member"."ID"');
333
        $sqlQuery->addInnerJoin('SilverStripe\\CMS\\Model\\SiteTree', '"Post"."ForumID" = "SiteTree"."ID"');
334
        $sqlQuery->addWhere('"Member"."ForumStatus" = \'Normal\'');
335
        $sqlQuery->addWhere('"SiteTree"."ParentID" = ' . $this->ID);
336
        return $sqlQuery->execute()->value();
337
    }
338
339
    /**
340
     * Is the "Currently Online" functionality enabled?
341
     * @return bool
342
     */
343
    public function CurrentlyOnlineEnabled()
344
    {
345
        return $this->config()->currently_online_enabled;
346
    }
347
348
    /**
349
     * Get a list of currently online users (last 15 minutes)
350
     * that belong to the "forum-members" code {@link Group}.
351
     *
352
     * @return DataList of {@link Member} objects
353
     */
354
    public function CurrentlyOnline()
355
    {
356
        if (!$this->CurrentlyOnlineEnabled()) {
357
            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 SilverStripe\Forum\Pages...Holder::CurrentlyOnline of type SilverStripe\Forum\Pages\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...
358
        }
359
360
        $groupIDs = array();
361
362
        if ($forumGroup = Group::get()->filter('Code', 'forum-members')->first()) {
363
            $groupIDs[] = $forumGroup->ID;
364
        }
365
366
        if ($adminGroup = Group::get()->filter('Code', array('administrators', 'Administrators'))->first()) {
367
            $groupIDs[] = $adminGroup->ID;
368
        }
369
370
        return Member::get()
371
            ->leftJoin('Group_Members', '"Member"."ID" = "Group_Members"."MemberID"')
372
            ->filter('GroupID', $groupIDs)
373
            ->where('"Member"."LastViewed" > ' . DB::getConn()->datetimeIntervalClause('NOW', '-15 MINUTE'))
374
            ->sort('"Member"."FirstName", "Member"."Surname"');
375
    }
376
377
    /**
378
     * @deprecated 0.5
379
     */
380
    public function LatestMember($limit = 1)
381
    {
382
        user_error('Please use LatestMembers($limit) instead of LatestMember', E_USER_NOTICE);
383
384
        return $this->LatestMembers($limit);
385
    }
386
387
    /**
388
     * Get the latest members from the forum group.
389
     *
390
     * @param int $limit Number of members to return
391
     * @return ArrayList
392
     */
393
    public function getLatestMembers($limit = null)
394
    {
395
        if (!is_null($limit)) {
396
            Deprecation::notice('1.0', '$limit parameter is deprecated, please chain the limit clause');
397
        }
398
        $groupID = DB::query('SELECT "ID" FROM "Group" WHERE "Code" = \'forum-members\'')->value();
399
400
        // if we're just looking for a single MemberID, do a quicker query on the join table.
401
        if ($limit == 1) {
402
            $latestMemberId = DB::query(sprintf(
403
                'SELECT MAX("MemberID")
404
				FROM "Group_Members"
405
				WHERE "Group_Members"."GroupID" = \'%s\'',
406
                $groupID
407
            ))->value();
408
409
            $latestMembers = Member::get()->byId($latestMemberId);
410
        } else {
411
            $latestMembers = Member::get()
412
                ->leftJoin('Group_Members', '"Member"."ID" = "Group_Members"."MemberID"')
413
                ->filter('GroupID', $groupID)
414
                ->sort('"Member"."ID" DESC');
415
            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...
416
                $latestMembers = $latestMembers->limit($limit);
417
            }
418
        }
419
420
        return $latestMembers;
421
    }
422
423
    /**
424
     * Get a list of Forum Categories
425
     * @return DataList
426
     */
427
    public function getShowInCategories()
428
    {
429
        $forumCategories = ForumCategory::get()->filter('ForumHolderID', $this->ID);
430
        $showInCategories = $this->getField('ShowInCategories');
431
        return $forumCategories->exists() && $showInCategories;
432
    }
433
434
    /**
435
     * Get the forums. Actually its a bit more complex than that
436
     * we need to group by the Forum Categories.
437
     *
438
     * @return ArrayList
439
     */
440
    public function Forums()
441
    {
442
        $categoryText = isset($_REQUEST['Category']) ? Convert::raw2xml($_REQUEST['Category']) : null;
443
        $holder = $this;
444
445
        if ($this->getShowInCategories()) {
446
            return ForumCategory::get()
447
                ->filter('ForumHolderID', $this->ID)
448
                ->filterByCallback(function ($category) use ($categoryText, $holder) {
449
                    // Don't include if we've specified a Category, and it doesn't match this one
450
                    if ($categoryText !== null && $category->Title != $categoryText) {
451
                        return false;
452
                    }
453
454
                    // Get a list of forums that live under this holder & category
455
                    $category->CategoryForums = Forum::get()
456
                        ->filter(array(
457
                            'CategoryID' => $category->ID,
458
                            'ParentID' => $holder->ID,
459
                            'ShowInMenus' => 1
460
                        ))
461
                        ->filterByCallback(function ($forum) {
462
                            return $forum->canView();
463
                        });
464
465
                    return $category->CategoryForums->exists();
466
                });
467
        } else {
468
            return Forum::get()
469
                ->filter(array(
470
                    'ParentID' => $this->ID,
471
                    'ShowInMenus' => 1
472
                ))
473
                ->filterByCallback(function ($forum) {
474
                    return $forum->canView();
475
                });
476
        }
477
    }
478
479
    /**
480
     * A function that returns the correct base table to use for custom forum queries. It uses the getVar stage to determine
481
     * what stage we are looking at, and determines whether to use SiteTree or SiteTree_Live (the general case). If the stage is
482
     * not specified, live is assumed (general case). It is a static function so it can be used for both ForumHolder and Forum.
483
     *
484
     * @return String
485
     */
486
    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...
487
    {
488
        $stage = (Controller::curr()->getRequest()) ? Controller::curr()->getRequest()->getVar('stage') : false;
489
        if (!$stage) {
490
            $stage = Versioned::get_live_stage();
491
        }
492
493
        if ((class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test())
494
            || $stage == "Stage"
495
        ) {
496
            return "SilverStripe\\CMS\\Model\\SiteTree";
497
        } else {
498
            return "SiteTree_Live";
499
        }
500
    }
501
502
503
    /**
504
     * Is OpenID support available?
505
     *
506
     * This method checks if the {@link OpenIDAuthenticator} is available and
507
     * registered.
508
     *
509
     * @return bool Returns TRUE if OpenID is available, FALSE otherwise.
510
     */
511
    public function OpenIDAvailable()
512
    {
513
        if (class_exists('SilverStripe\\Security\\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...
514
            return false;
515
        }
516
517
        return Authenticator::is_registered("OpenIDAuthenticator");
518
    }
519
520
521
    /**
522
     * Get the latest posts
523
     *
524
     * @param int $limit Number of posts to return
525
     * @param int $forumID - Forum ID to limit it to
526
     * @param int $threadID - Thread ID to limit it to
527
     * @param int $lastVisit Optional: Unix timestamp of the last visit (GMT)
528
     * @param int $lastPostID Optional: ID of the last read post
529
     */
530
    public function getRecentPosts($limit = 50, $forumID = null, $threadID = null, $lastVisit = null, $lastPostID = null)
531
    {
532
        $filter = array();
533
534
        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...
535
            $lastVisit = @date('Y-m-d H:i:s', $lastVisit);
536
        }
537
538
        $lastPostID = (int) $lastPostID;
539
540
        // last post viewed
541
        if ($lastPostID > 0) {
542
            $filter[] = "\"Post\".\"ID\" > '". Convert::raw2sql($lastPostID) ."'";
543
        }
544
545
        // last time visited
546
        if ($lastVisit) {
547
            $filter[] = "\"Post\".\"Created\" > '". Convert::raw2sql($lastVisit) ."'";
548
        }
549
550
        // limit to a forum
551
        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...
552
            $filter[] = "\"Post\".\"ForumID\" = '". Convert::raw2sql($forumID) ."'";
553
        }
554
555
        // limit to a thread
556
        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...
557
            $filter[] = "\"Post\".\"ThreadID\" = '". Convert::raw2sql($threadID) ."'";
558
        }
559
560
        // limit to just this forum install
561
        $filter[] = "\"ForumPage\".\"ParentID\"='{$this->ID}'";
562
563
        $posts = Post::get()
564
            ->leftJoin('ForumThread', '"Post"."ThreadID" = "ForumThread"."ID"')
565
            ->leftJoin(ForumHolder::baseForumTable(), '"ForumPage"."ID" = "Post"."ForumID"', 'ForumPage')
566
            ->limit($limit)
567
            ->sort('"Post"."ID"', 'DESC')
568
            ->where($filter);
569
570
        $recentPosts = new ArrayList();
571
        foreach ($posts as $post) {
572
            $recentPosts->push($post);
573
        }
574
        if ($recentPosts->count() > 0) {
575
            return $recentPosts;
576
        }
577
        return null;
578
    }
579
580
581
    /**
582
     * Are new posts available?
583
     *
584
     * @param int $id
585
     * @param array $data Optional: If an array is passed, the timestamp of
586
     *                    the last created post and it's ID will be stored in
587
     *                    it (keys: 'last_id', 'last_created')
588
     * @param int $lastVisit Unix timestamp of the last visit (GMT)
589
     * @param int $lastPostID ID of the last read post
590
     * @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...
591
     *                     topics)
592
     * @return bool Returns TRUE if there are new posts available, otherwise
593
     *              FALSE.
594
     */
595
    public static function new_posts_available($id, &$data = array(), $lastVisit = null, $lastPostID = null, $forumID = null, $threadID = null)
596
    {
597
        $filter = array();
598
599
        // last post viewed
600
        $filter[] = "\"ForumPage\".\"ParentID\" = '". Convert::raw2sql($id) ."'";
601
        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...
602
            $filter[] = "\"Post\".\"ID\" > '". Convert::raw2sql($lastPostID) ."'";
603
        }
604
        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...
605
            $filter[] = "\"Post\".\"Created\" > '". Convert::raw2sql($lastVisit) ."'";
606
        }
607
        if ($forumID) {
608
            $filter[] = "\"Post\".\"ForumID\" = '". Convert::raw2sql($forumID) ."'";
609
        }
610
        if ($threadID) {
611
            $filter[] = "\"ThreadID\" = '". Convert::raw2sql($threadID) ."'";
612
        }
613
614
        $filter = implode(" AND ", $filter);
615
616
        $version = DB::query("
617
			SELECT MAX(\"Post\".\"ID\") AS \"LastID\", MAX(\"Post\".\"Created\") AS \"LastCreated\"
618
			FROM \"Post\"
619
			JOIN \"" . ForumHolder::baseForumTable() . "\" AS \"ForumPage\" ON \"Post\".\"ForumID\"=\"ForumPage\".\"ID\"
620
			WHERE $filter")->first();
621
622
        if ($version == false) {
623
            return false;
624
        }
625
626
        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...
627
            $data['last_id'] = (int)$version['LastID'];
628
            $data['last_created'] = strtotime($version['LastCreated']);
629
        }
630
631
        $lastVisit = (int) $lastVisit;
632
633
        if ($lastVisit <= 0) {
634
            $lastVisit = false;
635
        }
636
637
        $lastPostID = (int)$lastPostID;
638
        if ($lastPostID <= 0) {
639
            $lastPostID = false;
640
        }
641
642
        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...
643
            return true;
644
        }
645
        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...
646
            return true;
647
        }
648
649
        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...
650
            return true;
651
        }
652
653
        return false;
654
    }
655
656
    /**
657
     * Helper Method from the template includes. Uses $ForumHolder so in order for it work
658
     * it needs to be included on this page
659
     *
660
     * @return ForumHolder
661
     */
662
    public function getForumHolder()
663
    {
664
        return $this;
665
    }
666
}
667
668
669
class ForumHolder_Controller extends PageController
670
{
671
672
    private static $allowed_actions = array(
673
        'popularthreads',
674
        'login',
675
        'logout',
676
        'search',
677
        'rss',
678
    );
679
680
    public function init()
681
    {
682
        parent::init();
683
684
        Requirements::javascript(THIRDPARTY_DIR . "/jquery/jquery.js");
685
        Requirements::javascript("forum/javascript/jquery.MultiFile.js");
686
        Requirements::javascript("forum/javascript/forum.js");
687
688
        Requirements::themedCSS('Forum', 'forum', 'all');
689
690
        RSSFeed::linkToFeed($this->Link("rss"), _t('ForumHolder.POSTSTOALLFORUMS', "Posts to all forums"));
691
692
        // Set the back url
693 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...
694
            Session::set('BackURL', $_SERVER['REQUEST_URI']);
695
        } else {
696
            Session::set('BackURL', $this->Link());
697
        }
698
    }
699
700
    /**
701
     * Generate a complete list of all the members data. Return a
702
     * set of all these members sorted by a GET variable
703
     *
704
     * @todo Sort via AJAX
705
     * @return DataObjectSet A DataObjectSet of all the members which are signed up
706
     */
707
    public function memberlist()
708
    {
709
        return $this->httpError(404);
710
711
        $forumGroupID = (int) DataObject::get_one('SilverStripe\\Security\\Group', "\"Code\" = 'forum-members'")->ID;
0 ignored issues
show
Unused Code introduced by
$forumGroupID = (int) \S...'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...
712
713
        // If sort has been defined then save it as in the session
714
        $order = (isset($_GET['order'])) ? $_GET['order']: "";
715
716
        if (!isset($_GET['start']) || !is_numeric($_GET['start']) || (int) $_GET['start'] < 1) {
717
            $_GET['start'] = 0;
718
        }
719
720
        $SQL_start = (int) $_GET['start'];
721
722
        switch ($order) {
723
            case "joined":
724
//				$members = DataObject::get("Member", "\"GroupID\" = '$forumGroupID'", "\"Member\".\"Created\" ASC", "LEFT JOIN \"Group_Members\" ON \"Member\".\"ID\" = \"Group_Members\".\"MemberID\"", "{$SQL_start},100");
725
                $members = Member::get()
726
                        ->filter('Member.GroupID', $forumGroupID)
727
                        ->leftJoin('Group_Members', '"Member"."ID" = "Group_Members"."MemberID"')
728
                        ->sort('"Member"."Created" ASC')
729
                        ->limit($SQL_start . ',100');
730
                break;
731 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...
732
//				$members = DataObject::get("Member", "\"GroupID\" = '$forumGroupID'", "\"Member\".\"Nickname\" ASC", "LEFT JOIN \"Group_Members\" ON \"Member\".\"ID\" = \"Group_Members\".\"MemberID\"", "{$SQL_start},100");
733
                $members = Member::get()
734
                        ->filter('Member.GroupID', $forumGroupID)
735
                        ->leftJoin('Group_Members', '"Member"."ID" = "Group_Members"."MemberID"')
736
                        ->sort('"Member"."Nickname" ASC')
737
                        ->limit($SQL_start . ',100');
738
                break;
739 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...
740
//				$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");
741
                $members = Member::get()
742
                        ->filter(array('Member.GroupID' => $forumGroupID, 'Member.CountryPublic' => true))
743
                        ->leftJoin('Group_Members', '"Member"."ID" = "Group_Members"."MemberID"')
744
                        ->sort('"Member"."Nickname" ASC')
745
                        ->limit($SQL_start . ',100');
746
                break;
747
            case "posts":
748
                $query = singleton('SilverStripe\\Security\\Member')->extendedSQL('', "\"NumPosts\" DESC", "{$SQL_start},100");
749
                $query->select[] = "(SELECT COUNT(*) FROM \"Post\" WHERE \"Post\".\"AuthorID\" = \"Member\".\"ID\") AS \"NumPosts\"";
750
                $records = $query->execute();
751
                $members = singleton('SilverStripe\\Security\\Member')->buildDataObjectSet($records, 'DataObjectSet', $query, 'SilverStripe\\Security\\Member');
752
                $members->parseQueryLimit($query);
753
                break;
754
            default:
755
                //$members = DataObject::get("Member", "\"GroupID\" = '$forumGroupID'", "\"Member\".\"Created\" DESC", "LEFT JOIN \"Group_Members\" ON \"Member\".\"ID\" = \"Group_Members\".\"MemberID\"", "{$SQL_start},100");
756
                $members = Member::get()
757
                        ->filter('Member.GroupID', $forumGroupID)
758
                        ->leftJoin('Group_Members', '"Member"."ID" = "Group_Members"."MemberID"')
759
                        ->sort('"Member"."Created" DESC')
760
                        ->limit($SQL_start . ',100');
761
                break;
762
        }
763
764
        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 SilverStripe\Forum\Pages..._Controller::memberlist of type SilverStripe\Forum\Pages\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...
765
            'Subtitle' => _t('ForumHolder.MEMBERLIST', 'Forum member List'),
766
            'Abstract' => $this->MemberListAbstract,
767
            'Members' => $members,
768
            'Title' => _t('ForumHolder.MEMBERLIST', 'Forum member List')
769
        );
770
    }
771
772
    /**
773
     * Show the 20 most popular threads across all {@link Forum} children.
774
     *
775
     * Two configuration options are available:
776
     * 1. "posts" - most popular threads by posts
777
     * 2. "views" - most popular threads by views
778
     *
779
     * e.g. mysite.com/forums/popularthreads?by=posts
780
     *
781
     * @return array
782
     */
783
    public function popularthreads()
784
    {
785
        $start = isset($_GET['start']) ? (int) $_GET['start'] : 0;
786
        $limit = 20;
787
        $method = isset($_GET['by']) ? $_GET['by'] : null;
788
        if (!$method) {
789
            $method = 'posts';
790
        }
791
792
        if ($method == 'posts') {
793
            $threadsQuery = singleton('ForumThread')->buildSQL(
794
                "\"SiteTree\".\"ParentID\" = '" . $this->ID ."'",
795
                "\"PostCount\" DESC",
796
                "$start,$limit",
797
                "LEFT JOIN \"Post\" ON \"Post\".\"ThreadID\" = \"ForumThread\".\"ID\" LEFT JOIN \"SiteTree\" ON \"SiteTree\".\"ID\" = \"ForumThread\".\"ForumID\""
798
            );
799
            $threadsQuery->select[] = "COUNT(\"Post\".\"ID\") AS 'PostCount'";
800
            $threadsQuery->groupby[] = "\"ForumThread\".\"ID\"";
801
            $threads = singleton('ForumThread')->buildDataObjectSet($threadsQuery->execute());
802
            if ($threads) {
803
                $threads->setPageLimits($start, $limit, $threadsQuery->unlimitedRowCount());
804
            }
805
        } elseif ($method == 'views') {
806
            $threads = DataObject::get('ForumThread', '', "\"NumViews\" DESC", '', "$start,$limit");
807
        }
808
809
        return array(
810
            'Title' => _t('ForumHolder.POPULARTHREADS', 'Most popular forum threads'),
811
            'Subtitle' => _t('ForumHolder.POPULARTHREADS', 'Most popular forum threads'),
812
            'Method' => $method,
813
            '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...
814
        );
815
    }
816
817
    /**
818
     * The login action
819
     *
820
     * It simple sets the return URL and forwards to the standard login form.
821
     */
822
    public function login()
823
    {
824
        Session::set('Security.Message.message', _t('Forum.CREDENTIALS'));
825
        Session::set('Security.Message.type', 'status');
826
        Session::set("BackURL", $this->Link());
827
828
        $this->redirect('Security/login');
829
    }
830
831
832
    public function logout()
833
    {
834
        if ($member = Member::currentUser()) {
835
            $member->logOut();
836
        }
837
838
        $this->redirect($this->Link());
839
    }
840
841
    /**
842
     * The search action
843
     *
844
     * @return array Returns an array to render the search results.
845
     */
846
    public function search()
847
    {
848
        $keywords   = (isset($_REQUEST['Search'])) ? Convert::raw2xml($_REQUEST['Search']) : null;
849
        $order      = (isset($_REQUEST['order'])) ? Convert::raw2xml($_REQUEST['order']) : null;
850
        $start      = (isset($_REQUEST['start'])) ? (int) $_REQUEST['start'] : 0;
851
852
        $abstract = ($keywords) ? "<p>" . sprintf(_t('ForumHolder.SEARCHEDFOR', "You searched for '%s'."), $keywords) . "</p>": null;
853
854
        // get the results of the query from the current search engine
855
        $search = ForumSearch::get_search_engine();
856
857
        if ($search) {
858
            $engine = new $search();
859
860
            $results = $engine->getResults($this->ID, $keywords, $order, $start);
861
        } else {
862
            $results = false;
863
        }
864
865
        //Paginate the results
866
        $results = PaginatedList::create(
867
            $results,
868
            $this->request->getVars()
869
        );
870
871
872
        // if the user has requested this search as an RSS feed then output the contents as xml
873
        // rather than passing it to the template
874
        if (isset($_REQUEST['rss'])) {
875
            $rss = new RSSFeed($results, $this->Link(), _t('ForumHolder.SEARCHRESULTS', 'Search results'), "", "Title", "RSSContent", "RSSAuthor");
876
877
            return $rss->outputToBrowser();
878
        }
879
880
        // attach a link to a RSS feed version of the search results
881
        $rssLink = $this->Link() ."search/?Search=".urlencode($keywords). "&amp;order=".urlencode($order)."&amp;rss";
882
        RSSFeed::linkToFeed($rssLink, _t('ForumHolder.SEARCHRESULTS', 'Search results'));
883
884
        return array(
885
            "Subtitle"      => DBField::create_field('Text', _t('ForumHolder.SEARCHRESULTS', 'Search results')),
886
            "Abstract"      => DBField::create_field('HTMLText', $abstract),
887
            "Query"             => DBField::create_field('Text', $_REQUEST['Search']),
888
            "Order"             => DBField::create_field('Text', ($order) ? $order : "relevance"),
889
            "RSSLink"       => DBField::create_field('HTMLText', $rssLink),
890
            "SearchResults"     => $results
891
        );
892
    }
893
894
    /**
895
     * Get the RSS feed
896
     *
897
     * This method will output the RSS feed with the last 50 posts to the
898
     * browser.
899
     */
900
    public function rss()
901
    {
902
        HTTP::set_cache_age(3600); // cache for one hour
903
904
        $threadID = null;
905
        $forumID = null;
906
907
        // optionally allow filtering of the forum posts by the url in the format
908
        // rss/thread/$ID or rss/forum/$ID
909
        if (isset($this->urlParams['ID']) && ($action = $this->urlParams['ID'])) {
910
            if (isset($this->urlParams['OtherID']) && ($id = $this->urlParams['OtherID'])) {
911
                switch ($action) {
912
                    case 'forum':
913
                        $forumID = (int) $id;
914
                        break;
915
                    case 'thread':
916
                        $threadID = (int) $id;
917
                }
918
            } else {
919
                // fallback is that it is the ID of a forum like it was in
920
                // previous versions
921
                $forumID = (int) $action;
922
            }
923
        }
924
925
        $data = array('last_created' => null, 'last_id' => null);
926
927
        if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && !isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
928
            // just to get the version data..
929
            $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...
930
931
            // No information provided by the client, just return the last posts
932
            $rss = new RSSFeed(
933
                $this->getRecentPosts(50, $forumID, $threadID),
934
                $this->Link() . 'rss',
935
                sprintf(_t('Forum.RSSFORUMPOSTSTO'), $this->Title),
936
                "",
937
                "Title",
938
                "RSSContent",
939
                "RSSAuthor",
940
                $data['last_created'],
941
                $data['last_id']
942
            );
943
            return $rss->outputToBrowser();
944
        } else {
945
            // Return only new posts, check the request headers!
946
            $since = null;
947
            $etag = null;
948
949
            if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
950
                // Split the If-Modified-Since (Netscape < v6 gets this wrong)
951
                $since = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE']);
952
                // Turn the client request If-Modified-Since into a timestamp
953
                $since = @strtotime($since[0]);
954
                if (!$since) {
955
                    $since = null;
956
                }
957
            }
958
959
            if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && is_numeric($_SERVER['HTTP_IF_NONE_MATCH'])) {
960
                $etag = (int)$_SERVER['HTTP_IF_NONE_MATCH'];
961
            }
962
            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...
963
                HTTP::register_modification_timestamp($data['last_created']);
964
                $rss = new RSSFeed(
965
                    $this->getRecentPosts(50, $forumID, $threadID, $etag),
966
                    $this->Link() . 'rss',
967
                    sprintf(_t('Forum.RSSFORUMPOSTSTO'), $this->Title),
968
                    "",
969
                    "Title",
970
                    "RSSContent",
971
                    "RSSAuthor",
972
                    $data['last_created'],
973
                    $data['last_id']
974
                );
975
                return $rss->outputToBrowser();
976
            } else {
977
                if ($data['last_created']) {
978
                    HTTP::register_modification_timestamp($data['last_created']);
979
                }
980
981
                if ($data['last_id']) {
982
                    HTTP::register_etag($data['last_id']);
983
                }
984
985
                // There are no new posts, just output an "304 Not Modified" message
986
                HTTP::add_cache_headers();
987
                header('HTTP/1.1 304 Not Modified');
988
            }
989
        }
990
        exit;
991
    }
992
993
    /**
994
     * Return the GlobalAnnouncements from the individual forums
995
     *
996
     * @return DataObjectSet
997
     */
998
    public function GlobalAnnouncements()
999
    {
1000
        //dump(ForumHolder::baseForumTable());
1001
1002
        // Get all the forums with global sticky threads
1003
        return ForumThread::get()
1004
            ->filter('IsGlobalSticky', 1)
1005
            ->innerJoin(ForumHolder::baseForumTable(), '"ForumThread"."ForumID"="ForumPage"."ID"', "ForumPage")
1006
            ->where('"ForumPage"."ParentID" = '.$this->ID)
1007
            ->filterByCallback(function ($thread) {
1008
                if ($thread->canView()) {
1009
                    $post = Post::get()->filter('ThreadID', $thread->ID)->sort('Post.Created DESC');
1010
                    $thread->Post = $post;
1011
                    return true;
1012
                }
1013
            });
1014
    }
1015
}
1016