Completed
Pull Request — master (#1758)
by Damian
02:19
created

SearchForm::getSearchQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace SilverStripe\CMS\Search;
4
5
use BadMethodCallException;
6
use SilverStripe\Assets\File;
7
use SilverStripe\CMS\Model\SiteTree;
8
use SilverStripe\Control\RequestHandler;
9
use SilverStripe\Forms\FieldList;
10
use SilverStripe\Forms\Form;
11
use SilverStripe\Forms\FormAction;
12
use SilverStripe\Forms\HiddenField;
13
use SilverStripe\Forms\TextField;
14
use SilverStripe\ORM\DB;
15
use SilverStripe\ORM\SS_List;
16
use Translatable;
17
18
/**
19
 * Standard basic search form which conducts a fulltext search on all {@link SiteTree}
20
 * objects.
21
 *
22
 * If multilingual content is enabled through the {@link Translatable} extension,
23
 * only pages the currently set language on the holder for this searchform are found.
24
 * The language is set through a hidden field in the form, which is prepoluated
25
 * with {@link Translatable::get_current_locale()} when then form is constructed.
26
 *
27
 * @see Use ModelController and SearchContext for a more generic search implementation based around DataObject
28
 */
29
class SearchForm extends Form
30
{
31
    /**
32
     * How many results are shown per page.
33
     * Relies on pagination being implemented in the search results template.
34
     *
35
     * @var int
36
     */
37
    protected $pageLength = 10;
38
39
    /**
40
     * Classes to search
41
     *
42
     * @var array
43
     */
44
    protected $classesToSearch = array(
45
        SiteTree::class,
46
        File::class
47
    );
48
49
    private static $casting = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
50
        'SearchQuery' => 'Text'
51
    );
52
53
    /**
54
     * @skipUpgrade
55
     * @param RequestHandler $controller
56
     * @param string $name The name of the form (used in URL addressing)
57
     * @param FieldList $fields Optional, defaults to a single field named "Search". Search logic needs to be customized
58
     *  if fields are added to the form.
59
     * @param FieldList $actions Optional, defaults to a single field named "Go".
60
     */
61
    public function __construct(
62
        RequestHandler $controller = null,
63
        $name = 'SearchForm',
64
        FieldList $fields = null,
65
        FieldList $actions = null
66
    ) {
67
        if (!$fields) {
68
            $fields = new FieldList(
69
                new TextField('Search', _t('SearchForm.SEARCH', 'Search'))
70
            );
71
        }
72
73
        if (class_exists('Translatable')
74
            && SiteTree::singleton()->hasExtension('Translatable')
75
        ) {
76
            $fields->push(new HiddenField('searchlocale', 'searchlocale', Translatable::get_current_locale()));
77
        }
78
79
        if (!$actions) {
80
            $actions = new FieldList(
81
                new FormAction("results", _t('SearchForm.GO', 'Go'))
82
            );
83
        }
84
85
        parent::__construct($controller, $name, $fields, $actions);
0 ignored issues
show
Documentation introduced by
$controller is of type null|object<SilverStripe\Control\RequestHandler>, but the function expects a object<SilverStripe\Control\Controller>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
86
87
        $this->setFormMethod('get');
88
89
        $this->disableSecurityToken();
90
    }
91
92
    /**
93
     * Set the classes to search.
94
     * Currently you can only choose from "SiteTree" and "File", but a future version might improve this.
95
     *
96
     * @param array $classes
97
     */
98
    public function classesToSearch($classes)
99
    {
100
        $supportedClasses = array(SiteTree::class, File::class);
101
        $illegalClasses = array_diff($classes, $supportedClasses);
102
        if ($illegalClasses) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $illegalClasses 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...
103
            throw new BadMethodCallException(
104
                "SearchForm::classesToSearch() passed illegal classes '" . implode("', '", $illegalClasses)
105
                . "'.  At this stage, only File and SiteTree are allowed"
106
            );
107
        }
108
        $legalClasses = array_intersect($classes, $supportedClasses);
109
        $this->classesToSearch = $legalClasses;
110
    }
111
112
    /**
113
     * Get the classes to search
114
     *
115
     * @return array
116
     */
117
    public function getClassesToSearch()
118
    {
119
        return $this->classesToSearch;
120
    }
121
122
    /**
123
     * Return dataObjectSet of the results using current request to get info from form.
124
     * Wraps around {@link searchEngine()}.
125
     *
126
     * @return SS_List
127
     */
128
    public function getResults()
129
    {
130
        // Get request data from request handler
131
        $request = $this->getRequestHandler()->getRequest();
0 ignored issues
show
Documentation Bug introduced by
The method getRequestHandler does not exist on object<SilverStripe\CMS\Search\SearchForm>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
132
133
        // set language (if present)
134
        $locale = null;
135
        $origLocale = null;
136
        if (class_exists('Translatable')) {
137
            $locale = $request->requestVar('searchlocale');
138 View Code Duplication
            if (SiteTree::singleton()->hasExtension('Translatable') && $locale) {
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...
139
                if ($locale === "ALL") {
140
                    Translatable::disable_locale_filter();
141
                } else {
142
                    $origLocale = Translatable::get_current_locale();
143
144
                    Translatable::set_current_locale($locale);
145
                }
146
            }
147
        }
148
149
        $keywords = $request->requestVar('Search');
150
151
        $andProcessor = create_function('$matches', '
152
	 		return " +" . $matches[2] . " +" . $matches[4] . " ";
153
	 	');
154
        $notProcessor = create_function('$matches', '
155
	 		return " -" . $matches[3];
156
	 	');
157
158
        $keywords = preg_replace_callback('/()("[^()"]+")( and )("[^"()]+")()/i', $andProcessor, $keywords);
159
        $keywords = preg_replace_callback('/(^| )([^() ]+)( and )([^ ()]+)( |$)/i', $andProcessor, $keywords);
160
        $keywords = preg_replace_callback('/(^| )(not )("[^"()]+")/i', $notProcessor, $keywords);
161
        $keywords = preg_replace_callback('/(^| )(not )([^() ]+)( |$)/i', $notProcessor, $keywords);
162
163
        $keywords = $this->addStarsToKeywords($keywords);
164
165
        $pageLength = $this->getPageLength();
166
        $start = $request->requestVar('start') ?: 0;
167
168
        $booleanSearch =
169
            strpos($keywords, '"') !== false ||
170
            strpos($keywords, '+') !== false ||
171
            strpos($keywords, '-') !== false ||
172
            strpos($keywords, '*') !== false;
173
        $results = DB::get_conn()->searchEngine($this->classesToSearch, $keywords, $start, $pageLength, "\"Relevance\" DESC", "", $booleanSearch);
174
175
        // filter by permission
176
        if ($results) {
177
            foreach ($results as $result) {
178
                if (!$result->canView()) {
179
                    $results->remove($result);
180
                }
181
            }
182
        }
183
184
        // reset locale
185 View Code Duplication
        if (class_exists('Translatable')) {
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...
186
            if (SiteTree::singleton()->hasExtension('Translatable') && $locale) {
187
                if ($locale == "ALL") {
188
                    Translatable::enable_locale_filter();
189
                } else {
190
                    Translatable::set_current_locale($origLocale);
191
                }
192
            }
193
        }
194
195
        return $results;
196
    }
197
198
    protected function addStarsToKeywords($keywords)
199
    {
200
        if (!trim($keywords)) {
201
            return "";
202
        }
203
        // Add * to each keyword
204
        $splitWords = preg_split("/ +/", trim($keywords));
205
        $newWords = [];
206
        while (list($i,$word) = each($splitWords)) {
0 ignored issues
show
Unused Code introduced by
The assignment to $i is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
207
            if ($word[0] == '"') {
208
                while (list($i,$subword) = each($splitWords)) {
0 ignored issues
show
Unused Code introduced by
The assignment to $i is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
209
                    $word .= ' ' . $subword;
210
                    if (substr($subword, -1) == '"') {
211
                        break;
212
                    }
213
                }
214
            } else {
215
                $word .= '*';
216
            }
217
            $newWords[] = $word;
218
        }
219
        return implode(" ", $newWords);
220
    }
221
222
    /**
223
     * Get the search query for display in a "You searched for ..." sentence.
224
     *
225
     * @return string
226
     */
227
    public function getSearchQuery()
228
    {
229
        return $this->getRequestHandler()->getRequest()->requestVar('Search');
0 ignored issues
show
Documentation Bug introduced by
The method getRequestHandler does not exist on object<SilverStripe\CMS\Search\SearchForm>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
230
    }
231
232
    /**
233
     * Set the maximum number of records shown on each page.
234
     *
235
     * @param int $length
236
     */
237
    public function setPageLength($length)
238
    {
239
        $this->pageLength = $length;
240
    }
241
242
    /**
243
     * @return int
244
     */
245
    public function getPageLength()
246
    {
247
        return $this->pageLength;
248
    }
249
}
250