Issues (10)

src/Admin/SearchAdmin.php (4 issues)

Labels
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
$session is of type SilverStripe\Control\Session, thus it always evaluated to true.
Loading history...
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 ignore-type  annotation

226
        return trim(/** @scrutinizer ignore-type */ $data[$fieldName] ?? $default);
Loading history...
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
$session is of type SilverStripe\Control\Session, thus it always evaluated to true.
Loading history...
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
$session is of type SilverStripe\Control\Session, thus it always evaluated to true.
Loading history...
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