SearchPlusPage_Controller::results()   F
last analyzed

Complexity

Conditions 18
Paths 513

Size

Total Lines 79
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 79
c 0
b 0
f 0
rs 3.1528
cc 18
eloc 57
nc 513
nop 1

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
0 ignored issues
show
Coding Style introduced by
File has mixed line endings; this may cause incorrect results
Loading history...
2
/*
3
 *@author: nicolaas [at] sunnysideup.co.nz
4
 *
5
 *
6
 **/
7
8
class SearchPlusPage extends Page
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
9
{
10
    private static $add_action = 'Search Plus Page';
0 ignored issues
show
Unused Code introduced by
The property $add_action is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
11
12
    private static $can_be_root = true;
0 ignored issues
show
Unused Code introduced by
The property $can_be_root is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
13
14
    private static $icon = 'searchplus/images/treeicons/SearchPlusPage';
0 ignored issues
show
Unused Code introduced by
The property $icon is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
15
16
    private static $db = array();
0 ignored issues
show
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
17
18
    private static $has_many = array(
0 ignored issues
show
Unused Code introduced by
The property $has_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
19
        "RecommendedSearchPlusSections" => "RecommendedSearchPlusSection"
20
    );
21
22
    public function canCreate($member = null)
23
    {
24
        return SearchPlusPage::get()->count ? false : true;
25
    }
26
27
    public function canDelete($member = null)
28
    {
29
        return false;
30
    }
31
32
    private static $result_length = 10;
0 ignored issues
show
Unused Code introduced by
The property $result_length is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
33
34
    public function getCMSFields($params = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
35
    {
36
        $fields = parent::getCMSFields($params);
37
        $fields->addFieldToTab(
38
            "Root.RecommendedSections",
39
            new GridField(
40
                $name = "RecommendedSearchPlusSections",
41
                $sourceClass = "Recommended Extras",
42
                RecommendedSearchPlusSection::get()
43
            )
44
        );
45
        $fields->addFieldToTab(
46
            "Root.PopularSearchPhrases",
47
            new LiteralField(
48
                "PopularSearchPhrasesLink",
49
                '<p>Please make sure to regular <a href="'.$this->Link().'popularsearchwords/100/10">review the most popular search phrases</a> and to add recommendations for each</a>.</p>'
50
            )
51
        );
52
        return $fields;
53
    }
54
}
55
56
class SearchPlusPage_Controller extends Page_Controller
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
57
{
58
    public function init()
59
    {
60
        parent::init();
61
        Requirements::javascript("searchplus/javascript/searchpluspage.js");
62
    }
63
64
    private static $search_history_object = null;
65
66
    public function Form()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
67
    {
68
        return $this->SearchPlusForm("MainSearchForm", "MainSearch", "");
69
    }
70
71
    public function results($data)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
Coding Style introduced by
results uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
results uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
72
    {
73
        if (isset($data["Search"]) || isset($data["MainSearch"])) {
74
            // there is a search
75
            Requirements::themedCSS("searchpluspage_searchresults", "searchplus");
76
            if (isset($data["MainSearch"]) || !isset($data["Search"])) {
77
                $data["Search"] = $data["MainSearch"];
78
            }
79
            //redirect if needed
80
            $data["Search"] = urldecode($data["Search"]);
81
            $form = $this->SearchPlusForm();
82
            if (!isset($_GET["redirect"])) {
83
                self::$search_history_object = SearchHistory::add_entry($data["Search"]);
84
                if (self::$search_history_object) {
85
                    if (self::$search_history_object->RedirectTo && self::$search_history_object->RedirectTo != self::$search_history_object->Title) {
86
                        $this->redirect(
87
                            str_replace(
88
                                "Search=".urlencode(self::$search_history_object->Title),
89
                                "Search=".urlencode(self::$search_history_object->RedirectTo),
90
                                HTTP::RAW_setGetVar('redirect', 1, null)
91
                            )
92
                        );
93
                    }
94
                }
95
            } else {
96
                self::$search_history_object = SearchHistory::find_entry($data["Search"]);
97
            }
98
            //load data for recommended pages
99
            $recommendationsSet = $this->Recommendations();
100
            $matchArrayRecommended = array();
101
            $matchArrayResults = array();
102
            if ($recommendationsSet) {
103
                foreach ($recommendationsSet as $rec) {
104
                    $matchArrayRecommended[$rec->ClassName.$rec->ID] = $rec->ClassName.$rec->ID;
105
                }
106
            }
107
            //work out positions
108
            $results = $form->getResults();
109
            $query = $form->getSearchQuery();
110
            $startingPosition = isset($_REQUEST["start"]) ? $_REQUEST["start"] : 0;
111
            $endingPosition = $startingPosition + Config::inst()->get("SearchPlusPage", "result_length");
112
            $startingPosition++;
113
            if ($results) {
114
                $total = $results->TotalItems();
115
            } else {
116
                $total = 0;
117
            }
118
            if ($endingPosition > $total) {
119
                $endingPosition = $total;
120
            }
121
            //highlight search text and check which ones are recommended
122
            if ($total) {
123
                foreach ($results as $result) {
124
                    $title = $result->getTitle();
125
                    $dbField = DBField::create_field($className = "Text", $title);
126
                    $result->HighlightedTitle = $dbField->ContextSummary();
127
                    $result->IsRecommended = false;
128
                    $matchArrayResults[$result->ClassName.$result->ID] = $result->ClassName.$result->ID;
129
                    if (isset($matchArrayRecommended[$result->ClassName.$result->ID])) {
130
                        $result->IsRecommended = true;
131
                    }
132
                }
133
            }
134
            $data = array(
135
                'Results' => $results,
136
                'Query' => $query,
137
                'From' => $startingPosition,
138
                'To' => $endingPosition,
139
                'Total' => $total,
140
                'HasResults' => $total ? true : false,
141
                'Recommendations' => $this->Recommendations(),
142
                'RecommendedSearchPlusSection' => $this->dataRecord->RecommendedSearchPlusSections(),
143
            );
144
            $this->Title = 'Search Results';
145
            $this->MenuTitle = 'Search Results';
146
            return $this->customise($data)->renderWith(array('SearchPlusPage_results', 'Page'));
147
        }
148
        return array();
149
    }
150
151
    public function Recommendations()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
152
    {
153
        if (self::$search_history_object) {
154
            return self::$search_history_object->Recommendations();
155
        }
156
    }
157
158
    public function HasPopularSearchWords()
159
    {
160
        return Permission::check("ADMIN");
161
    }
162
163
    public function PopularSearchWordsForAllUsers($days = 100, $limit = 7)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
164
    {
165
        $do = $this->getPopularSearchWords($days, $limit, $mergeRedirects = true);
166
        return $do->DataByCount;
167
    }
168
169
    public function popularsearchwords(HTTPRequest $HTTPRequest)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
170
    {
171
        if (!$this->HasPopularSearchWords()) {
172
            Security::permissionFailure($this, _t('Security.PERMFAILURE', ' This page is secured and you need administrator rights to access it. Enter your credentials below and we will send you right along.'));
173
            return;
174
        }
175
        Requirements::themedCSS("popularsearches", "searchplus");
176
        $days = intval($HTTPRequest->param("ID"));
177
        if (!$days) {
178
            $days = 100;
179
        }
180
        $limit = intval($HTTPRequest->param("OtherID")+0);
181
        if (!$limit) {
182
            $limit++;
183
        }
184
        $do = $this->getPopularSearchWords($days, $limit);
185
        $page->MenuTitle = $do->Title;
0 ignored issues
show
Bug introduced by
The variable $page does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
186
        $do->Title = $do->Title;
187
        return $this->customise($do)->renderWith(array('SearchPlusPage_popularsearches', 'Page'));
188
    }
189
190
    protected function getPopularSearchWords($days, $limit, $mergeRedirects = false)
191
    {
192
        $extraWhere = '';
193
        if ($mergeRedirects) {
194
            $extraWhere = " AND {$bt}RedirectTo{$bt} = '' OR {$bt}RedirectTo{$bt} IS NULL";
0 ignored issues
show
Bug introduced by
The variable $bt does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
195
        }
196
        $data = DB::query("
197
			SELECT COUNT(\"SearchHistoryLog\".\"ID\") count, \"SearchHistory\".\"RedirectTo\" RedirectTo, \"SearchHistory\".\"Title\" title, \"SearchHistory\".\"ID\" id
198
			FROM \"SearchHistoryLog\"
199
				INNER JOIN \"SearchHistory\" ON \"SearchHistory\".\"ID\" = \"SearchHistoryLog\".\"SearchedForID\"
200
			WHERE \"SearchHistoryLog\".\"Created\" > ( NOW() - INTERVAL $days DAY ) ".$extraWhere."
201
			GROUP BY \"SearchHistory\".\"ID\"
202
			ORDER BY count DESC
203
			LIMIT 0, $limit
204
		");
205
        $do = new DataObject();
206
        $do->Title = "Search phrase popularity, $days days $limit entries";
207
        $do->DataByCount = new ArrayList();
208
        $do->DataByTitle = new ArrayList();
209
        $do->Limit = $limit;
210
        $do->Days = $days;
211
        $list = array();
212
        foreach ($data as $key => $row) {
213
            if (!$key) {
214
                $max = $row["count"];
215
            }
216
            if ($mergeRedirects) {
217
                $data = DB::query("
218
					SELECT COUNT(\"SearchHistoryLog\".\"ID\") count
219
					FROM \"SearchHistoryLog\"
220
						INNER JOIN \"SearchHistory\"
221
							ON \"SearchHistory\".\"ID\" = \"SearchHistoryLog\".\"SearchedForID\"
222
					WHERE \"SearchHistoryLog\".\"Created\" > ( NOW() - INTERVAL $days DAY )
223
						AND \"SearchHistory\".\"RedirectTo\" = '".$row["title"]."'
224
					GROUP BY \"SearchHistory\".\"RedirectTo\"
225
					ORDER BY count
226
					DESC LIMIT 1
227
				");
228
                if ($data) {
229
                    $extraCounts = $data->value();
230
                }
231
                $row["count"] += $extraCounts;
0 ignored issues
show
Bug introduced by
The variable $extraCounts 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...
232
            }
233
            $percentage = floor(($row["count"]/$max)*100);
0 ignored issues
show
Bug introduced by
The variable $max 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...
234
            $subDataSet = new ArrayData(
235
                array(
236
                    "ParentID" => $row["id"],
237
                    "Title" => $row["title"],
238
                    "Width" => $percentage,
239
                    "Count" => $row["count"],
240
                    "Link" => $this->Link()."results/?Search=".urldecode($row["title"])."&amp;action_results=Search"
241
                )
242
            );
243
            $list[$row["title"]] = $subDataSet;
244
            $do->DataByCount->push($subDataSet);
245
        }
246
        ksort($list);
247
        foreach ($list as $subDataSet) {
248
            $do->DataByTitle->push($subDataSet);
249
        }
250
        return $do;
251
    }
252
}
253