Completed
Push — master ( 0cbb9f...012867 )
by Damian
02:58
created

SearchForm::classesToSearch()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 13
rs 9.4285
c 1
b 0
f 0
cc 2
eloc 10
nc 2
nop 1
1
<?php
2
3
namespace SilverStripe\CMS\Search;
4
5
use SilverStripe\CMS\Model\SiteTree;
6
use SilverStripe\Control\Controller;
7
use SilverStripe\Forms\FieldList;
8
use SilverStripe\Forms\Form;
9
use SilverStripe\Forms\FormAction;
10
use SilverStripe\Forms\HiddenField;
11
use SilverStripe\Forms\TextField;
12
use SilverStripe\ORM\DB;
13
use SilverStripe\ORM\SS_List;
14
use Translatable;
15
16
/**
17
 * Standard basic search form which conducts a fulltext search on all {@link SiteTree}
18
 * objects.
19
 *
20
 * If multilingual content is enabled through the {@link Translatable} extension,
21
 * only pages the currently set language on the holder for this searchform are found.
22
 * The language is set through a hidden field in the form, which is prepoluated
23
 * with {@link Translatable::get_current_locale()} when then form is constructed.
24
 *
25
 * @see Use ModelController and SearchContext for a more generic search implementation based around DataObject
26
 */
27
class SearchForm extends Form {
28
29
	/**
30
	 * @var int $pageLength How many results are shown per page.
31
	 * Relies on pagination being implemented in the search results template.
32
	 */
33
	protected $pageLength = 10;
34
35
	/**
36
	 * Classes to search
37
	 */
38
 	protected $classesToSearch = array(
39
		"SilverStripe\\CMS\\Model\\SiteTree",
40
		"SilverStripe\\Assets\\File"
41
	);
42
43
	private static $casting = array(
44
		'SearchQuery' => 'Text'
45
	);
46
47
	/**
48
	 *
49
	 * @param Controller $controller
50
	 * @param string $name The name of the form (used in URL addressing)
51
	 * @param FieldList $fields Optional, defaults to a single field named "Search". Search logic needs to be customized
52
	 *  if fields are added to the form.
53
	 * @param FieldList $actions Optional, defaults to a single field named "Go".
54
	 */
55
	public function __construct($controller, $name, $fields = null, $actions = null) {
56
		if(!$fields) {
57
			$fields = new FieldList(
58
				new TextField('Search', _t('SearchForm.SEARCH', 'Search')
59
			));
60
		}
61
62
		if(class_exists('Translatable')
63
			&& SiteTree::singleton()->hasExtension('Translatable')
64
		) {
65
			$fields->push(new HiddenField('searchlocale', 'searchlocale', Translatable::get_current_locale()));
66
		}
67
68
		if(!$actions) {
69
			$actions = new FieldList(
70
				new FormAction("getResults", _t('SearchForm.GO', 'Go'))
71
			);
72
		}
73
74
		parent::__construct($controller, $name, $fields, $actions);
75
76
		$this->setFormMethod('get');
77
78
		$this->disableSecurityToken();
79
	}
80
81
	/**
82
	 * Set the classes to search.
83
	 * Currently you can only choose from "SiteTree" and "File", but a future version might improve this.
84
	 *
85
	 * @param array $classes
86
	 */
87
	public function classesToSearch($classes) {
88
		$supportedClasses = array('SilverStripe\\CMS\\Model\\SiteTree', 'SilverStripe\\Assets\\File');
89
		$illegalClasses = array_diff($classes, $supportedClasses);
90
		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...
91
			user_error(
92
				"SearchForm::classesToSearch() passed illegal classes '" . implode("', '", $illegalClasses)
93
				. "'.  At this stage, only File and SiteTree are allowed",
94
				E_USER_WARNING
95
			);
96
		}
97
		$legalClasses = array_intersect($classes, $supportedClasses);
98
		$this->classesToSearch = $legalClasses;
99
	}
100
101
	/**
102
	 * Get the classes to search
103
	 *
104
	 * @return array
105
	 */
106
	public function getClassesToSearch() {
107
		return $this->classesToSearch;
108
	}
109
110
	/**
111
	 * Return dataObjectSet of the results using $_REQUEST to get info from form.
112
	 * Wraps around {@link searchEngine()}.
113
	 *
114
	 * @param int $pageLength DEPRECATED 2.3 Use SearchForm->pageLength
115
	 * @param array $data Request data as an associative array. Should contain at least a key 'Search' with all searched keywords.
116
	 * @return SS_List
117
	 */
118
	public function getResults($pageLength = null, $data = null){
119
	 	// legacy usage: $data was defaulting to $_REQUEST, parameter not passed in doc.silverstripe.org tutorials
120
		if(!isset($data) || !is_array($data)) $data = $_REQUEST;
121
122
		// set language (if present)
123 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...
124
			if(SiteTree::singleton()->hasExtension('Translatable') && isset($data['searchlocale'])) {
125
				if($data['searchlocale'] == "ALL") {
126
					Translatable::disable_locale_filter();
127
				} else {
128
					$origLocale = Translatable::get_current_locale();
129
130
					Translatable::set_current_locale($data['searchlocale']);
131
				}
132
			}
133
		}
134
135
		$keywords = $data['Search'];
136
137
	 	$andProcessor = create_function('$matches','
138
	 		return " +" . $matches[2] . " +" . $matches[4] . " ";
139
	 	');
140
	 	$notProcessor = create_function('$matches', '
141
	 		return " -" . $matches[3];
142
	 	');
143
144
	 	$keywords = preg_replace_callback('/()("[^()"]+")( and )("[^"()]+")()/i', $andProcessor, $keywords);
145
	 	$keywords = preg_replace_callback('/(^| )([^() ]+)( and )([^ ()]+)( |$)/i', $andProcessor, $keywords);
146
		$keywords = preg_replace_callback('/(^| )(not )("[^"()]+")/i', $notProcessor, $keywords);
147
		$keywords = preg_replace_callback('/(^| )(not )([^() ]+)( |$)/i', $notProcessor, $keywords);
148
149
		$keywords = $this->addStarsToKeywords($keywords);
150
151
		if(!$pageLength) $pageLength = $this->pageLength;
0 ignored issues
show
Bug Best Practice introduced by
The expression $pageLength of type integer|null 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...
152
		$start = isset($_GET['start']) ? (int)$_GET['start'] : 0;
153
154
		if(strpos($keywords, '"') !== false || strpos($keywords, '+') !== false || strpos($keywords, '-') !== false || strpos($keywords, '*') !== false) {
155
			$results = DB::get_conn()->searchEngine($this->classesToSearch, $keywords, $start, $pageLength, "\"Relevance\" DESC", "", true);
156
		} else {
157
			$results = DB::get_conn()->searchEngine($this->classesToSearch, $keywords, $start, $pageLength);
158
		}
159
160
		// filter by permission
161
		if($results) foreach($results as $result) {
162
			if(!$result->canView()) $results->remove($result);
163
		}
164
165
		// reset locale
166 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...
167
			if(SiteTree::singleton()->hasExtension('Translatable') && isset($data['searchlocale'])) {
168
				if($data['searchlocale'] == "ALL") {
169
					Translatable::enable_locale_filter();
170
				} else {
171
					Translatable::set_current_locale($origLocale);
0 ignored issues
show
Bug introduced by
The variable $origLocale 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...
172
				}
173
			}
174
		}
175
176
		return $results;
177
	}
178
179
	protected function addStarsToKeywords($keywords) {
180
		if(!trim($keywords)) return "";
181
		// Add * to each keyword
182
		$splitWords = preg_split("/ +/" , trim($keywords));
183
		$newWords = [];
184
		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...
185
			if($word[0] == '"') {
186
				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...
187
					$word .= ' ' . $subword;
188
					if(substr($subword,-1) == '"') break;
189
				}
190
			} else {
191
				$word .= '*';
192
			}
193
			$newWords[] = $word;
194
		}
195
		return implode(" ", $newWords);
196
	}
197
198
	/**
199
	 * Get the search query for display in a "You searched for ..." sentence.
200
	 *
201
	 * @param array $data
202
	 * @return string
203
	 */
204
	public function getSearchQuery($data = null) {
205
		// legacy usage: $data was defaulting to $_REQUEST, parameter not passed in doc.silverstripe.org tutorials
206
		if(!isset($data)) {
207
			$data = $_REQUEST;
208
		}
209
210
		// The form could be rendered without the search being done, so check for that.
211
		if (isset($data['Search'])) {
212
			return $data['Search'];
213
		}
214
215
		return null;
216
	}
217
218
	/**
219
	 * Set the maximum number of records shown on each page.
220
	 *
221
	 * @param int $length
222
	 */
223
	public function setPageLength($length) {
224
		$this->pageLength = $length;
225
	}
226
227
	/**
228
	 * @return int
229
	 */
230
	public function getPageLength() {
231
		return $this->pageLength;
232
	}
233
234
}
235
236
237