1 | <?php |
||||
2 | |||||
3 | namespace Sunnysideup\SiteWideSearch\Admin; |
||||
4 | |||||
5 | use SilverStripe\Admin\LeftAndMain; |
||||
6 | use SilverStripe\Assets\File; |
||||
7 | use SilverStripe\Control\HTTPResponse; |
||||
8 | use SilverStripe\Core\ClassInfo; |
||||
9 | use SilverStripe\Core\Environment; |
||||
10 | use SilverStripe\Core\Injector\Injector; |
||||
11 | use SilverStripe\Forms\CheckboxField; |
||||
12 | use SilverStripe\Forms\Form; |
||||
13 | use SilverStripe\Forms\FormAction; |
||||
14 | use SilverStripe\Forms\HiddenField; |
||||
15 | use SilverStripe\Forms\HTMLReadonlyField; |
||||
16 | use SilverStripe\Forms\OptionsetField; |
||||
17 | use SilverStripe\Forms\TextField; |
||||
18 | use SilverStripe\Forms\ToggleCompositeField; |
||||
19 | use SilverStripe\ORM\ArrayList; |
||||
20 | use SilverStripe\ORM\FieldType\DBField; |
||||
21 | use SilverStripe\Security\PermissionProvider; |
||||
22 | use Sunnysideup\SiteWideSearch\Api\SearchApi; |
||||
23 | use Sunnysideup\SiteWideSearch\QuickSearches\QuickSearchBaseClass; |
||||
24 | |||||
25 | /** |
||||
26 | * Class \Sunnysideup\SiteWideSearch\Admin\SearchAdmin |
||||
27 | * |
||||
28 | */ |
||||
29 | class SearchAdmin extends LeftAndMain implements PermissionProvider |
||||
30 | { |
||||
31 | protected $listHTML = ''; |
||||
32 | |||||
33 | protected $keywords = ''; |
||||
34 | |||||
35 | protected $replace = ''; |
||||
36 | |||||
37 | protected $applyReplace = false; |
||||
38 | |||||
39 | protected $quickSearchType = ''; |
||||
40 | |||||
41 | protected $searchWholePhrase = true; |
||||
42 | |||||
43 | protected $rawData; |
||||
44 | |||||
45 | private static $default_quick_search_type = 'limited'; |
||||
46 | |||||
47 | private static $url_segment = 'find'; |
||||
48 | |||||
49 | private static $menu_title = 'Search'; |
||||
50 | |||||
51 | private static $menu_icon_class = 'font-icon-p-search'; |
||||
52 | |||||
53 | private static $menu_priority = 9999999999; |
||||
54 | |||||
55 | private static $required_permission_codes = [ |
||||
56 | 'CMS_ACCESS_SITE_WIDE_SEARCH', |
||||
57 | ]; |
||||
58 | |||||
59 | protected function init() |
||||
60 | { |
||||
61 | if ($this->request->param('Action') && empty($this->request->postVars())) { |
||||
62 | $this->redirect('/admin/find'); |
||||
63 | } |
||||
64 | parent::init(); |
||||
65 | } |
||||
66 | |||||
67 | public function getEditForm($id = null, $fields = null) |
||||
68 | { |
||||
69 | $form = parent::getEditForm($id, $fields); |
||||
70 | $fields = $form->Fields(); |
||||
71 | |||||
72 | // if ($form instanceof HTTPResponse) { |
||||
73 | // return $form; |
||||
74 | // } |
||||
75 | $fields->push( |
||||
76 | (new TextField('Keywords', 'Keyword(s)', $this->keywords ?? '')) |
||||
77 | ->setAttribute('placeholder', 'e.g. agreement') |
||||
78 | ); |
||||
79 | $fields->push( |
||||
80 | (new HiddenField('IsSubmitHiddenField', 'IsSubmitHiddenField', 1)) |
||||
81 | ); |
||||
82 | |||||
83 | $options = QuickSearchBaseClass::get_list_of_quick_searches(); |
||||
84 | $fields->push( |
||||
85 | OptionsetField::create( |
||||
86 | 'QuickSearchType', |
||||
87 | 'Quick Search', |
||||
88 | $options |
||||
89 | )->setValue($this->bestSearchType()) |
||||
90 | ); |
||||
91 | |||||
92 | $fields->push( |
||||
93 | (new CheckboxField('SearchWholePhrase', 'Search exact phrase', $this->searchWholePhrase)) |
||||
94 | ->setDescription('If ticked, only items will be included that includes the whole phrase (e.g. "New Zealand", rather than anything that includes "New" OR "Zealand")') |
||||
95 | ); |
||||
96 | $fields->push( |
||||
97 | ToggleCompositeField::create( |
||||
98 | 'ReplaceToggle', |
||||
99 | _t(__CLASS__ . '.ReplaceToggle', 'Replace with ... (optional - make a backup first!)'), |
||||
100 | [ |
||||
101 | (new CheckboxField('ApplyReplace', 'Run replace (please make sure to make a backup first!)', $this->applyReplace)) |
||||
102 | ->setDescription('Check this to replace the searched value set above with its replacement value. Note that searches ignore uppercase / lowercase, but replace actions will only search and replace values with the same upper / lowercase.'), |
||||
103 | (new TextField('ReplaceWith', 'Replace (optional - careful!)', $this->replace ?? '')) |
||||
104 | ->setAttribute('placeholder', 'e.g. contract - make sure to also tick checkbox below'), |
||||
105 | ] |
||||
106 | )->setHeadingLevel(4) |
||||
107 | ); |
||||
108 | |||||
109 | if (! $this->getRequest()->requestVar('Keywords')) { |
||||
110 | $lastResults = $this->lastSearchResults(); |
||||
111 | $resultsTitle = $lastResults instanceof \SilverStripe\ORM\ArrayList ? 'Last Results' : 'Last Edited'; |
||||
112 | $this->listHTML = $this->renderWith(self::class . '_Results'); |
||||
113 | } else { |
||||
114 | $resultsTitle = 'Search Results'; |
||||
115 | } |
||||
116 | |||||
117 | $form->setFormMethod('get', false); |
||||
118 | |||||
119 | $fields->push( |
||||
120 | (new HTMLReadonlyField('List', $resultsTitle, DBField::create_field('HTMLText', $this->listHTML))) |
||||
121 | ); |
||||
122 | $form->Actions()->push( |
||||
123 | FormAction::create('search', 'Find') |
||||
124 | ->addExtraClass('btn-primary') |
||||
125 | ->setUseButtonTag(true) |
||||
126 | ); |
||||
127 | $form->addExtraClass('root-form cms-edit-form center fill-height'); |
||||
128 | // $form->disableSecurityToken(); |
||||
129 | // $form->setFormMethod('get'); |
||||
130 | |||||
131 | return $form; |
||||
132 | } |
||||
133 | |||||
134 | public function search(array $data, Form $form): HTTPResponse |
||||
135 | { |
||||
136 | if (empty($data['Keywords'])) { |
||||
137 | $form->sessionMessage('Please enter one or more keywords', 'bad'); |
||||
138 | |||||
139 | return $this->redirectBack(); |
||||
140 | } |
||||
141 | |||||
142 | $request = $this->getRequest(); |
||||
143 | |||||
144 | $this->rawData = $data; |
||||
145 | $this->listHTML = $this->renderWith(self::class . '_Results'); |
||||
146 | // Existing or new record? |
||||
147 | |||||
148 | return $this->getResponseNegotiator()->respond($request); |
||||
149 | } |
||||
150 | |||||
151 | /** |
||||
152 | * Only show first element, as the profile form is limited to editing |
||||
153 | * the current member it doesn't make much sense to show the member name |
||||
154 | * in the breadcrumbs. |
||||
155 | * |
||||
156 | * @param bool $unlinked |
||||
157 | * |
||||
158 | * @return ArrayList |
||||
159 | */ |
||||
160 | public function Breadcrumbs($unlinked = false) |
||||
161 | { |
||||
162 | $items = parent::Breadcrumbs($unlinked); |
||||
163 | |||||
164 | return new ArrayList([$items[0]]); |
||||
165 | } |
||||
166 | |||||
167 | public function IsQuickSearch(): bool |
||||
168 | { |
||||
169 | return $this->quickSearchType === 'limited'; |
||||
170 | } |
||||
171 | |||||
172 | public function SearchResults(): ?ArrayList |
||||
173 | { |
||||
174 | Environment::increaseTimeLimitTo(300); |
||||
175 | Environment::setMemoryLimitMax(-1); |
||||
176 | Environment::increaseMemoryLimitTo(-1); |
||||
177 | if (empty($this->rawData)) { |
||||
178 | $lastResults = $this->lastSearchResults(); |
||||
179 | if ($lastResults instanceof \SilverStripe\ORM\ArrayList) { |
||||
180 | return $lastResults; |
||||
181 | } |
||||
182 | } |
||||
183 | $this->keywords = $this->workOutString('Keywords', $this->rawData); |
||||
184 | $this->quickSearchType = $this->workOutString('QuickSearchType', $this->rawData, $this->bestSearchType()); |
||||
185 | $this->searchWholePhrase = $this->workOutBoolean('SearchWholePhrase', $this->rawData, false); |
||||
186 | $this->applyReplace = isset($this->rawData['ReplaceWith']) && $this->workOutBoolean('ApplyReplace', $this->rawData, false); |
||||
187 | $this->replace = $this->workOutString('ReplaceWith', $this->rawData); |
||||
188 | if ($this->applyReplace) { |
||||
189 | Injector::inst()->get(SearchApi::class) |
||||
190 | ->setQuickSearchType($this->quickSearchType) |
||||
191 | ->setSearchWholePhrase(true) // always true |
||||
192 | ->setWordsAsString($this->keywords) |
||||
193 | ->doReplacement($this->keywords, $this->replace) |
||||
194 | ; |
||||
195 | $this->applyReplace = false; |
||||
196 | } |
||||
197 | |||||
198 | $results = Injector::inst()->get(SearchApi::class) |
||||
199 | ->setQuickSearchType($this->quickSearchType) |
||||
200 | ->setSearchWholePhrase($this->searchWholePhrase) |
||||
201 | ->setWordsAsString($this->keywords) |
||||
202 | ->getLinks(); |
||||
203 | if ($results->count() === 1) { |
||||
204 | $result = $results->first(); |
||||
205 | // files do not re-redirect nicely... |
||||
206 | if ($result->HasCMSEditLink && $result->CMSEditLink && ! in_array(File::class, ClassInfo::ancestry($result->ClassName), true)) { |
||||
207 | // this is a variable, not a method! |
||||
208 | $this->redirect($result->CMSEditLink); |
||||
209 | } |
||||
210 | } |
||||
211 | // Accessing the session |
||||
212 | $session = $this->getRequest()->getSession(); |
||||
213 | if ($session) { |
||||
0 ignored issues
–
show
introduced
by
![]() |
|||||
214 | $session->set('QuickSearchLastResults', serialize($results->toArray())); |
||||
215 | } |
||||
216 | return $results; |
||||
217 | } |
||||
218 | |||||
219 | protected function workOutBoolean(string $fieldName, ?array $data = null, ?bool $default = false): bool |
||||
220 | { |
||||
221 | return (bool) (isset($data['IsSubmitHiddenField']) ? ! empty($data[$fieldName]) : $default); |
||||
222 | } |
||||
223 | |||||
224 | protected function workOutString(string $fieldName, ?array $data = null, ?string $default = ''): string |
||||
225 | { |
||||
226 | return trim($data[$fieldName] ?? $default); |
||||
0 ignored issues
–
show
It seems like
$data[$fieldName] ?? $default can also be of type null ; however, parameter $string of trim() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
227 | } |
||||
228 | |||||
229 | public function providePermissions() |
||||
230 | { |
||||
231 | return [ |
||||
232 | 'CMS_ACCESS_SITE_WIDE_SEARCH' => [ |
||||
233 | 'name' => 'Access to Search Website in the CMS', |
||||
234 | 'category' => _t('SilverStripe\\Security\\Permission.CMS_ACCESS_CATEGORY', 'CMS Access'), |
||||
235 | 'help' => 'Allow users to search for documents (all documents will also be checked to see if they are allowed to be viewed)', |
||||
236 | ], |
||||
237 | ]; |
||||
238 | } |
||||
239 | |||||
240 | protected function bestSearchType(): string |
||||
241 | { |
||||
242 | // Accessing the session |
||||
243 | $session = $this->getRequest()->getSession(); |
||||
244 | if ($this->quickSearchType) { |
||||
245 | $session->set('QuickSearchType', $this->quickSearchType); |
||||
246 | } elseif ($session) { |
||||
0 ignored issues
–
show
|
|||||
247 | $this->quickSearchType = $session->get('QuickSearchType'); |
||||
248 | if (isset($_GET['flush'])) { |
||||
249 | $this->quickSearchType = ''; |
||||
250 | $session->set('QuickSearchType', ''); |
||||
251 | } |
||||
252 | } |
||||
253 | if (! $this->quickSearchType) { |
||||
254 | $this->quickSearchType = $this->Config()->get('default_quick_search_type'); |
||||
255 | } |
||||
256 | return (string) $this->quickSearchType; |
||||
257 | } |
||||
258 | |||||
259 | protected function lastSearchResults(): ?ArrayList |
||||
260 | { |
||||
261 | // Accessing the session |
||||
262 | $session = $this->getRequest()->getSession(); |
||||
263 | if ($session) { |
||||
0 ignored issues
–
show
|
|||||
264 | if (isset($_GET['flush'])) { |
||||
265 | $session->clear('QuickSearchLastResults'); |
||||
266 | } else { |
||||
267 | $data = $session->get('QuickSearchLastResults'); |
||||
268 | if ($data) { |
||||
269 | $array = unserialize($data); |
||||
270 | $al = ArrayList::create(); |
||||
271 | foreach ($array as $item) { |
||||
272 | $al->push($item); |
||||
273 | } |
||||
274 | return $al; |
||||
275 | } |
||||
276 | } |
||||
277 | } |
||||
278 | return null; |
||||
279 | } |
||||
280 | } |
||||
281 |