Total Complexity | 139 |
Total Lines | 1014 |
Duplicated Lines | 0 % |
Changes | 24 | ||
Bugs | 7 | Features | 2 |
Complex classes like TabulatorGrid_ItemRequest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use TabulatorGrid_ItemRequest, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
31 | class TabulatorGrid_ItemRequest extends RequestHandler |
||
32 | { |
||
33 | |||
34 | private static $allowed_actions = [ |
||
35 | 'edit', |
||
36 | 'ajaxEdit', |
||
37 | 'ajaxMove', |
||
38 | 'view', |
||
39 | 'delete', |
||
40 | 'unlink', |
||
41 | 'customAction', |
||
42 | 'ItemEditForm', |
||
43 | ]; |
||
44 | |||
45 | protected TabulatorGrid $tabulatorGrid; |
||
46 | |||
47 | /** |
||
48 | * @var DataObject |
||
49 | */ |
||
50 | protected $record; |
||
51 | |||
52 | /** |
||
53 | * @var array |
||
54 | */ |
||
55 | protected $manipulatedData = null; |
||
56 | |||
57 | /** |
||
58 | * This represents the current parent RequestHandler (which does not necessarily need to be a Controller). |
||
59 | * It allows us to traverse the RequestHandler chain upwards to reach the Controller stack. |
||
60 | * |
||
61 | * @var RequestHandler |
||
62 | */ |
||
63 | protected $popupController; |
||
64 | |||
65 | protected string $hash = ''; |
||
66 | |||
67 | protected string $template = ''; |
||
68 | |||
69 | private static $url_handlers = [ |
||
70 | 'customAction/$CustomAction' => 'customAction', |
||
71 | '$Action!' => '$Action', |
||
72 | '' => 'edit', |
||
73 | ]; |
||
74 | |||
75 | /** |
||
76 | * |
||
77 | * @param TabulatorGrid $tabulatorGrid |
||
78 | * @param DataObject $record |
||
79 | * @param RequestHandler $requestHandler |
||
80 | */ |
||
81 | public function __construct($tabulatorGrid, $record, $requestHandler) |
||
82 | { |
||
83 | $this->tabulatorGrid = $tabulatorGrid; |
||
84 | $this->record = $record; |
||
85 | $this->popupController = $requestHandler; |
||
86 | parent::__construct(); |
||
87 | } |
||
88 | |||
89 | public function Link($action = null) |
||
90 | { |
||
91 | return Controller::join_links( |
||
92 | $this->tabulatorGrid->Link('item'), |
||
93 | $this->record->ID ? $this->record->ID : 'new', |
||
94 | $action |
||
95 | ); |
||
96 | } |
||
97 | |||
98 | public function AbsoluteLink($action = null) |
||
99 | { |
||
100 | return Director::absoluteURL($this->Link($action)); |
||
101 | } |
||
102 | |||
103 | protected function getManipulatedData(): array |
||
104 | { |
||
105 | if ($this->manipulatedData) { |
||
|
|||
106 | return $this->manipulatedData; |
||
107 | } |
||
108 | $grid = $this->getTabulatorGrid(); |
||
109 | |||
110 | $state = $grid->getState($this->popupController->getRequest()); |
||
111 | |||
112 | $currentPage = $state['page']; |
||
113 | $itemsPerPage = $state['limit']; |
||
114 | |||
115 | $limit = $itemsPerPage + 2; |
||
116 | $offset = max(0, $itemsPerPage * ($currentPage - 1) - 1); |
||
117 | |||
118 | $list = $grid->getManipulatedData($limit, $offset, $state['sort'], $state['filter']); |
||
119 | |||
120 | $this->manipulatedData = $list; |
||
121 | return $list; |
||
122 | } |
||
123 | |||
124 | public function index(HTTPRequest $request) |
||
125 | { |
||
126 | $controller = $this->getToplevelController(); |
||
127 | return $controller->redirect($this->Link('edit')); |
||
128 | } |
||
129 | |||
130 | protected function returnWithinContext(HTTPRequest $request, RequestHandler $controller, Form $form) |
||
131 | { |
||
132 | $data = $this->customise([ |
||
133 | 'Backlink' => $controller->hasMethod('Backlink') ? $controller->Backlink() : $controller->Link(), |
||
134 | 'ItemEditForm' => $form, |
||
135 | ]); |
||
136 | |||
137 | $return = $data->renderWith('LeKoala\\Tabulator\\TabulatorGrid_ItemEditForm'); |
||
138 | if ($request->isAjax()) { |
||
139 | return $return; |
||
140 | } |
||
141 | // If not requested by ajax, we need to render it within the controller context+template |
||
142 | return $controller->customise([ |
||
143 | 'Content' => $return, |
||
144 | ]); |
||
145 | } |
||
146 | |||
147 | protected function editFailure(): HTTPResponse |
||
148 | { |
||
149 | return $this->httpError(403, _t( |
||
150 | __CLASS__ . '.EditPermissionsFailure', |
||
151 | 'It seems you don\'t have the necessary permissions to edit "{ObjectTitle}"', |
||
152 | ['ObjectTitle' => $this->record->singular_name()] |
||
153 | )); |
||
154 | } |
||
155 | |||
156 | /** |
||
157 | * This is responsible to display an edit form, like GridFieldDetailForm, but much simpler |
||
158 | * |
||
159 | * @return mixed |
||
160 | */ |
||
161 | public function edit(HTTPRequest $request) |
||
162 | { |
||
163 | if (!$this->record->canEdit()) { |
||
164 | return $this->editFailure(); |
||
165 | } |
||
166 | $controller = $this->getToplevelController(); |
||
167 | |||
168 | $form = $this->ItemEditForm(); |
||
169 | |||
170 | return $this->returnWithinContext($request, $controller, $form); |
||
171 | } |
||
172 | |||
173 | public function ajaxEdit(HTTPRequest $request) |
||
174 | { |
||
175 | $SecurityID = $request->postVar('SecurityID'); |
||
176 | if (!SecurityToken::inst()->check($SecurityID)) { |
||
177 | return $this->httpError(404, "Invalid SecurityID"); |
||
178 | } |
||
179 | if (!$this->record->canEdit()) { |
||
180 | return $this->editFailure(); |
||
181 | } |
||
182 | |||
183 | $preventEmpty = []; |
||
184 | if ($this->record->hasMethod('tabulatorPreventEmpty')) { |
||
185 | $preventEmpty = $this->record->tabulatorPreventEmpty(); |
||
186 | } |
||
187 | |||
188 | $Data = $request->postVar("Data"); |
||
189 | $Column = $request->postVar("Column"); |
||
190 | $Value = $request->postVar("Value"); |
||
191 | |||
192 | if (!$Value && in_array($Column, $preventEmpty)) { |
||
193 | return $this->httpError(400, _t(__CLASS__ . '.ValueCannotBeEmpty', 'Value cannot be empty')); |
||
194 | } |
||
195 | |||
196 | try { |
||
197 | $updatedValue = $this->executeEdit($Column, $Value); |
||
198 | } catch (Exception $e) { |
||
199 | return $this->httpError(400, $e->getMessage()); |
||
200 | } |
||
201 | |||
202 | $response = new HTTPResponse(json_encode([ |
||
203 | 'success' => true, |
||
204 | 'message' => _t(__CLASS__ . '.RecordEdited', 'Record edited'), |
||
205 | 'value' => $updatedValue, |
||
206 | ])); |
||
207 | $response->addHeader('Content-Type', 'application/json'); |
||
208 | return $response; |
||
209 | } |
||
210 | |||
211 | public function executeEdit(string $Column, $Value) |
||
212 | { |
||
213 | $field = $Column; |
||
214 | $rel = $relField = null; |
||
215 | if (strpos($Column, ".") !== false) { |
||
216 | $parts = explode(".", $Column); |
||
217 | $rel = $parts[0]; |
||
218 | $relField = $parts[1]; |
||
219 | $field = $rel . "ID"; |
||
220 | if (!is_numeric($Value)) { |
||
221 | return $this->httpError(400, "ID must have a numerical value"); |
||
222 | } |
||
223 | } |
||
224 | if (!$field) { |
||
225 | return $this->httpError(400, "Field must not be empty"); |
||
226 | } |
||
227 | |||
228 | $grid = $this->tabulatorGrid; |
||
229 | |||
230 | $list = $grid->getDataList(); |
||
231 | |||
232 | if ($list instanceof ManyManyList) { |
||
233 | $extraData = $list->getExtraData($grid->getName(), $this->record->ID); |
||
234 | |||
235 | // Is it a many many field |
||
236 | // This is a bit basic but it works |
||
237 | if (isset($extraData[$Column])) { |
||
238 | $extra = [ |
||
239 | $Column => $Value |
||
240 | ]; |
||
241 | $list->add($this->record, $extra); |
||
242 | return $Value; |
||
243 | } |
||
244 | } |
||
245 | |||
246 | |||
247 | // Its on the object itself |
||
248 | $this->record->$field = $Value; |
||
249 | $this->record->write(); |
||
250 | $updatedValue = $this->record->$field; |
||
251 | if ($rel) { |
||
252 | /** @var DataObject $relObject */ |
||
253 | $relObject = $this->record->$rel(); |
||
254 | $updatedValue = $relObject->relField($relField); |
||
255 | } |
||
256 | return $updatedValue; |
||
257 | } |
||
258 | |||
259 | public function ajaxMove(HTTPRequest $request) |
||
260 | { |
||
261 | $SecurityID = $request->postVar('SecurityID'); |
||
262 | if (!SecurityToken::inst()->check($SecurityID)) { |
||
263 | return $this->httpError(404, "Invalid SecurityID"); |
||
264 | } |
||
265 | if (!$this->record->canEdit()) { |
||
266 | return $this->editFailure(); |
||
267 | } |
||
268 | $Data = $request->postVar("Data"); |
||
269 | if (is_string($Data)) { |
||
270 | $Data = json_decode($Data, JSON_OBJECT_AS_ARRAY); |
||
271 | } |
||
272 | $Sort = $request->postVar("Sort"); |
||
273 | |||
274 | try { |
||
275 | $updatedSort = $this->executeSort($Data, $Sort); |
||
276 | } catch (Exception $e) { |
||
277 | return $this->httpError(400, $e->getMessage()); |
||
278 | } |
||
279 | |||
280 | $response = new HTTPResponse(json_encode([ |
||
281 | 'success' => true, |
||
282 | 'message' => _t(__CLASS__ . '.RecordMove', 'Record moved'), |
||
283 | 'value' => $updatedSort, |
||
284 | ])); |
||
285 | $response->addHeader('Content-Type', 'application/json'); |
||
286 | return $response; |
||
287 | } |
||
288 | |||
289 | public function executeSort(array $Data, int $Sort, string $sortField = 'Sort'): int |
||
290 | { |
||
291 | $table = DataObject::getSchema()->baseDataTable(get_class($this->record)); |
||
292 | |||
293 | if (!isset($Data[$sortField])) { |
||
294 | return $this->httpError(403, _t( |
||
295 | __CLASS__ . '.UnableToResolveSort', |
||
296 | 'Unable to resolve previous sort order' |
||
297 | )); |
||
298 | } |
||
299 | |||
300 | $prevSort = $Data[$sortField]; |
||
301 | |||
302 | // Just make sure you don't have 0 (except first record) or equal sorts |
||
303 | if ($prevSort < $Sort) { |
||
304 | $set = "$sortField = $sortField - 1"; |
||
305 | $where = "$sortField > $prevSort and $sortField <= $Sort"; |
||
306 | } else { |
||
307 | $set = "$sortField = $sortField + 1"; |
||
308 | $where = "$sortField < $prevSort and $sortField >= $Sort"; |
||
309 | } |
||
310 | DB::query("UPDATE `$table` SET $set WHERE $where"); |
||
311 | $this->record->$sortField = $Sort; |
||
312 | $this->record->write(); |
||
313 | |||
314 | return $this->record->Sort; |
||
315 | } |
||
316 | |||
317 | |||
318 | /** |
||
319 | * Delete from the row level |
||
320 | * |
||
321 | * @param HTTPRequest $request |
||
322 | * @return void |
||
323 | */ |
||
324 | public function delete(HTTPRequest $request) |
||
325 | { |
||
326 | if (!$this->record->canDelete()) { |
||
327 | return $this->httpError(403, _t( |
||
328 | __CLASS__ . '.DeletePermissionsFailure', |
||
329 | 'It seems you don\'t have the necessary permissions to delete "{ObjectTitle}"', |
||
330 | ['ObjectTitle' => $this->record->singular_name()] |
||
331 | )); |
||
332 | } |
||
333 | |||
334 | $title = $this->record->getTitle(); |
||
335 | $this->record->delete(); |
||
336 | |||
337 | $message = _t( |
||
338 | 'SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Deleted', |
||
339 | 'Deleted {type} {name}', |
||
340 | [ |
||
341 | 'type' => $this->record->i18n_singular_name(), |
||
342 | 'name' => htmlspecialchars($title, ENT_QUOTES) |
||
343 | ] |
||
344 | ); |
||
345 | |||
346 | //when an item is deleted, redirect to the parent controller |
||
347 | $controller = $this->getToplevelController(); |
||
348 | |||
349 | if ($this->isSilverStripeAdmin($controller)) { |
||
350 | $controller->getRequest()->addHeader('X-Pjax', 'Content'); |
||
351 | } |
||
352 | |||
353 | //redirect back to admin section |
||
354 | $response = $controller->redirect($this->getBackLink(), 302); |
||
355 | |||
356 | $this->sessionMessage($message, "good"); |
||
357 | |||
358 | return $response; |
||
359 | } |
||
360 | |||
361 | /** |
||
362 | * Unlink from the row level |
||
363 | * |
||
364 | * @param HTTPRequest $request |
||
365 | * @return void |
||
366 | */ |
||
367 | public function unlink(HTTPRequest $request) |
||
368 | { |
||
369 | //TODO: isn't that too strict?? |
||
370 | if (!$this->record->canDelete()) { |
||
371 | return $this->httpError(403, _t( |
||
372 | __CLASS__ . '.UnlinkPermissionsFailure', |
||
373 | 'It seems you don\'t have the necessary permissions to unlink "{ObjectTitle}"', |
||
374 | ['ObjectTitle' => $this->record->singular_name()] |
||
375 | )); |
||
376 | } |
||
377 | |||
378 | $title = $this->record->getTitle(); |
||
379 | $this->tabulatorGrid->getDataList()->remove($this->record); |
||
380 | |||
381 | $message = _t( |
||
382 | 'SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Deleted', |
||
383 | 'Unlinked {type} {name}', |
||
384 | [ |
||
385 | 'type' => $this->record->i18n_singular_name(), |
||
386 | 'name' => htmlspecialchars($title, ENT_QUOTES) |
||
387 | ] |
||
388 | ); |
||
389 | |||
390 | //when an item is deleted, redirect to the parent controller |
||
391 | $controller = $this->getToplevelController(); |
||
392 | d($controller); |
||
393 | |||
394 | if ($this->isSilverStripeAdmin($controller)) { |
||
395 | $controller->getRequest()->addHeader('X-Pjax', 'Content'); |
||
396 | } |
||
397 | |||
398 | //redirect back to admin section |
||
399 | $response = $controller->redirect($this->getBackLink(), 302); |
||
400 | |||
401 | $this->sessionMessage($message, "good"); |
||
402 | |||
403 | return $response; |
||
404 | } |
||
405 | |||
406 | /** |
||
407 | * @return mixed |
||
408 | */ |
||
409 | public function view(HTTPRequest $request) |
||
410 | { |
||
411 | if (!$this->record->canView()) { |
||
412 | return $this->httpError(403, _t( |
||
413 | __CLASS__ . '.ViewPermissionsFailure', |
||
414 | 'It seems you don\'t have the necessary permissions to view "{ObjectTitle}"', |
||
415 | ['ObjectTitle' => $this->record->singular_name()] |
||
416 | )); |
||
417 | } |
||
418 | |||
419 | $controller = $this->getToplevelController(); |
||
420 | |||
421 | $form = $this->ItemEditForm(); |
||
422 | $form->makeReadonly(); |
||
423 | |||
424 | return $this->returnWithinContext($request, $controller, $form); |
||
425 | } |
||
426 | |||
427 | /** |
||
428 | * This is responsible to forward actions to the model if necessary |
||
429 | * @param HTTPRequest $request |
||
430 | * @return HTTPResponse |
||
431 | */ |
||
432 | public function customAction(HTTPRequest $request) |
||
433 | { |
||
434 | // This gets populated thanks to our updated URL handler |
||
435 | $params = $request->params(); |
||
436 | $customAction = $params['CustomAction'] ?? null; |
||
437 | $ID = $params['ID'] ?? 0; |
||
438 | |||
439 | $dataClass = $this->tabulatorGrid->getModelClass(); |
||
440 | $record = DataObject::get_by_id($dataClass, $ID); |
||
441 | $rowActions = $record->tabulatorRowActions(); |
||
442 | $validActions = array_keys($rowActions); |
||
443 | if (!$customAction || !in_array($customAction, $validActions)) { |
||
444 | return $this->httpError(403, _t( |
||
445 | __CLASS__ . '.CustomActionPermissionsFailure', |
||
446 | 'It seems you don\'t have the necessary permissions to {ActionName} "{ObjectTitle}"', |
||
447 | ['ActionName' => $customAction, 'ObjectTitle' => $this->record->singular_name()] |
||
448 | )); |
||
449 | } |
||
450 | |||
451 | $clickedAction = $rowActions[$customAction]; |
||
452 | |||
453 | $error = false; |
||
454 | try { |
||
455 | $result = $record->$customAction(); |
||
456 | } catch (Exception $e) { |
||
457 | $error = true; |
||
458 | $result = $e->getMessage(); |
||
459 | } |
||
460 | |||
461 | // Maybe it's a custom redirect or a file ? |
||
462 | if ($result && $result instanceof HTTPResponse) { |
||
463 | return $result; |
||
464 | } |
||
465 | |||
466 | // Show message on controller or in form |
||
467 | $controller = $this->getToplevelController(); |
||
468 | $response = $controller->getResponse(); |
||
469 | if (Director::is_ajax()) { |
||
470 | $responseData = [ |
||
471 | 'message' => $result, |
||
472 | 'status' => $error ? 'error' : 'success', |
||
473 | ]; |
||
474 | if (!empty($clickedAction['reload'])) { |
||
475 | $responseData['reload'] = true; |
||
476 | } |
||
477 | if (!empty($clickedAction['refresh'])) { |
||
478 | $responseData['refresh'] = true; |
||
479 | } |
||
480 | $response->setBody(json_encode($responseData)); |
||
481 | // 4xx status makes a red box |
||
482 | if ($error) { |
||
483 | $response->setStatusCode(400); |
||
484 | } |
||
485 | return $response; |
||
486 | } |
||
487 | |||
488 | $url = $this->getDefaultBackLink(); |
||
489 | $response = $this->redirect($url); |
||
490 | |||
491 | $this->sessionMessage($result, $error ? "error" : "good", "html"); |
||
492 | |||
493 | return $response; |
||
494 | } |
||
495 | |||
496 | public function sessionMessage($message, $type = ValidationResult::TYPE_ERROR, $cast = ValidationResult::CAST_TEXT) |
||
497 | { |
||
498 | $controller = $this->getToplevelController(); |
||
499 | if ($controller->hasMethod('sessionMessage')) { |
||
500 | $controller->sessionMessage($message, $type, $cast); |
||
501 | } else { |
||
502 | $form = $this->ItemEditForm(); |
||
503 | if ($form) { |
||
504 | $form->sessionMessage($message, $type, $cast); |
||
505 | } |
||
506 | } |
||
507 | } |
||
508 | |||
509 | /** |
||
510 | * Builds an item edit form |
||
511 | * |
||
512 | * @return Form|HTTPResponse |
||
513 | */ |
||
514 | public function ItemEditForm() |
||
515 | { |
||
516 | $list = $this->tabulatorGrid->getList(); |
||
517 | $controller = $this->getToplevelController(); |
||
518 | |||
519 | try { |
||
520 | $record = $this->getRecord(); |
||
521 | } catch (Exception $e) { |
||
522 | $url = $controller->getRequest()->getURL(); |
||
523 | $noActionURL = $controller->removeAction($url); |
||
524 | //clear the existing redirect |
||
525 | $controller->getResponse()->removeHeader('Location'); |
||
526 | return $controller->redirect($noActionURL, 302); |
||
527 | } |
||
528 | |||
529 | // If we are creating a new record in a has-many list, then |
||
530 | // pre-populate the record's foreign key. |
||
531 | if ($list instanceof HasManyList && !$this->record->isInDB()) { |
||
532 | $key = $list->getForeignKey(); |
||
533 | $id = $list->getForeignID(); |
||
534 | $record->$key = $id; |
||
535 | } |
||
536 | |||
537 | if (!$record->canView()) { |
||
538 | return $controller->httpError(403, _t( |
||
539 | __CLASS__ . '.ViewPermissionsFailure', |
||
540 | 'It seems you don\'t have the necessary permissions to view "{ObjectTitle}"', |
||
541 | ['ObjectTitle' => $this->record->singular_name()] |
||
542 | )); |
||
543 | } |
||
544 | |||
545 | if ($record->hasMethod("tabulatorCMSFields")) { |
||
546 | $fields = $record->tabulatorCMSFields(); |
||
547 | } else { |
||
548 | $fields = $record->getCMSFields(); |
||
549 | } |
||
550 | |||
551 | // If we are creating a new record in a has-many list, then |
||
552 | // Disable the form field as it has no effect. |
||
553 | if ($list instanceof HasManyList && !$this->record->isInDB()) { |
||
554 | $key = $list->getForeignKey(); |
||
555 | |||
556 | if ($field = $fields->dataFieldByName($key)) { |
||
557 | $fields->makeFieldReadonly($field); |
||
558 | } |
||
559 | } |
||
560 | |||
561 | $compatLayer = $this->tabulatorGrid->getCompatLayer($controller); |
||
562 | |||
563 | $actions = $compatLayer->getFormActions($this); |
||
564 | $this->extend('updateFormActions', $actions); |
||
565 | |||
566 | $validator = null; |
||
567 | |||
568 | $form = new Form( |
||
569 | $this, |
||
570 | 'ItemEditForm', |
||
571 | $fields, |
||
572 | $actions, |
||
573 | $validator |
||
574 | ); |
||
575 | |||
576 | $form->loadDataFrom($record, $record->ID == 0 ? Form::MERGE_IGNORE_FALSEISH : Form::MERGE_DEFAULT); |
||
577 | |||
578 | if ($record->ID && !$record->canEdit()) { |
||
579 | // Restrict editing of existing records |
||
580 | $form->makeReadonly(); |
||
581 | // Hack to re-enable delete button if user can delete |
||
582 | if ($record->canDelete()) { |
||
583 | $form->Actions()->fieldByName('action_doDelete')->setReadonly(false); |
||
584 | } |
||
585 | } |
||
586 | $cannotCreate = !$record->ID && !$record->canCreate(null, $this->getCreateContext()); |
||
587 | if ($cannotCreate || $this->tabulatorGrid->isViewOnly()) { |
||
588 | // Restrict creation of new records |
||
589 | $form->makeReadonly(); |
||
590 | } |
||
591 | |||
592 | // Load many_many extraData for record. |
||
593 | // Fields with the correct 'ManyMany' namespace need to be added manually through getCMSFields(). |
||
594 | if ($list instanceof ManyManyList) { |
||
595 | $extraData = $list->getExtraData('', $this->record->ID); |
||
596 | $form->loadDataFrom(['ManyMany' => $extraData]); |
||
597 | } |
||
598 | |||
599 | // Coupling with CMS |
||
600 | $compatLayer->adjustItemEditForm($this, $form); |
||
601 | |||
602 | $this->extend("updateItemEditForm", $form); |
||
603 | |||
604 | return $form; |
||
605 | } |
||
606 | |||
607 | /** |
||
608 | * Build context for verifying canCreate |
||
609 | * |
||
610 | * @return array |
||
611 | */ |
||
612 | protected function getCreateContext() |
||
613 | { |
||
614 | $grid = $this->tabulatorGrid; |
||
615 | $context = []; |
||
616 | if ($grid->getList() instanceof RelationList) { |
||
617 | $record = $grid->getForm()->getRecord(); |
||
618 | if ($record && $record instanceof DataObject) { |
||
619 | $context['Parent'] = $record; |
||
620 | } |
||
621 | } |
||
622 | return $context; |
||
623 | } |
||
624 | |||
625 | /** |
||
626 | * @return \SilverStripe\Control\Controller|\SilverStripe\Admin\LeftAndMain|TabulatorGrid_ItemRequest |
||
627 | */ |
||
628 | public function getToplevelController(): RequestHandler |
||
629 | { |
||
630 | $c = $this->popupController; |
||
631 | // Maybe our field is included in a GridField or in a TabulatorGrid? |
||
632 | while ($c && ($c instanceof GridFieldDetailForm_ItemRequest || $c instanceof TabulatorGrid_ItemRequest)) { |
||
633 | $c = $c->getController(); |
||
634 | } |
||
635 | return $c; |
||
636 | } |
||
637 | |||
638 | public function getDefaultBackLink(): string |
||
639 | { |
||
640 | $url = $this->getBackURL() |
||
641 | ?: $this->getReturnReferer() |
||
642 | ?: $this->AbsoluteLink(); |
||
643 | return $url; |
||
644 | } |
||
645 | |||
646 | public function getBackLink(): string |
||
647 | { |
||
648 | $backlink = ''; |
||
649 | $toplevelController = $this->getToplevelController(); |
||
650 | if ($this->popupController->hasMethod('Breadcrumbs')) { |
||
651 | $parents = $this->popupController->Breadcrumbs(false); |
||
652 | if ($parents && $parents = $parents->items) { |
||
653 | $backlink = array_pop($parents)->Link; |
||
654 | } |
||
655 | } |
||
656 | if ($toplevelController && $toplevelController->hasMethod('Backlink')) { |
||
657 | $backlink = $toplevelController->Backlink(); |
||
658 | } |
||
659 | if (!$backlink) { |
||
660 | $backlink = $toplevelController->Link(); |
||
661 | } |
||
662 | return $backlink; |
||
663 | } |
||
664 | |||
665 | /** |
||
666 | * Get the list of extra data from the $record as saved into it by |
||
667 | * {@see Form::saveInto()} |
||
668 | * |
||
669 | * Handles detection of falsey values explicitly saved into the |
||
670 | * DataObject by formfields |
||
671 | * |
||
672 | * @param DataObject $record |
||
673 | * @param SS_List $list |
||
674 | * @return array List of data to write to the relation |
||
675 | */ |
||
676 | protected function getExtraSavedData($record, $list) |
||
677 | { |
||
678 | // Skip extra data if not ManyManyList |
||
679 | if (!($list instanceof ManyManyList)) { |
||
680 | return null; |
||
681 | } |
||
682 | |||
683 | $data = []; |
||
684 | foreach ($list->getExtraFields() as $field => $dbSpec) { |
||
685 | $savedField = "ManyMany[{$field}]"; |
||
686 | if ($record->hasField($savedField)) { |
||
687 | $data[$field] = $record->getField($savedField); |
||
688 | } |
||
689 | } |
||
690 | return $data; |
||
691 | } |
||
692 | |||
693 | public function doSave($data, $form) |
||
736 | } |
||
737 | |||
738 | /** |
||
739 | * Gets the edit link for a record |
||
740 | * |
||
741 | * @param int $id The ID of the record in the GridField |
||
742 | * @return string |
||
743 | */ |
||
744 | public function getEditLink($id) |
||
745 | { |
||
746 | $link = Controller::join_links( |
||
747 | $this->tabulatorGrid->Link(), |
||
748 | 'item', |
||
749 | $id |
||
750 | ); |
||
751 | |||
752 | return $link; |
||
753 | } |
||
754 | |||
755 | /** |
||
756 | * @param int $offset The offset from the current record |
||
757 | * @return int|bool |
||
758 | */ |
||
759 | private function getAdjacentRecordID($offset) |
||
760 | { |
||
761 | $list = $this->getManipulatedData(); |
||
762 | $map = array_column($list['data'], "ID"); |
||
763 | $index = array_search($this->record->ID, $map); |
||
764 | return isset($map[$index + $offset]) ? $map[$index + $offset] : false; |
||
765 | } |
||
766 | |||
767 | /** |
||
768 | * Gets the ID of the previous record in the list. |
||
769 | */ |
||
770 | public function getPreviousRecordID(): int |
||
771 | { |
||
772 | return $this->getAdjacentRecordID(-1); |
||
773 | } |
||
774 | |||
775 | /** |
||
776 | * Gets the ID of the next record in the list. |
||
777 | */ |
||
778 | public function getNextRecordID(): int |
||
779 | { |
||
780 | return $this->getAdjacentRecordID(1); |
||
781 | } |
||
782 | |||
783 | /** |
||
784 | * This is expected in lekoala/silverstripe-cms-actions ActionsGridFieldItemRequest |
||
785 | * @return HTTPResponse |
||
786 | */ |
||
787 | public function getResponse() |
||
788 | { |
||
789 | return $this->getToplevelController()->getResponse(); |
||
790 | } |
||
791 | |||
792 | /** |
||
793 | * Response object for this request after a successful save |
||
794 | * |
||
795 | * @param bool $isNewRecord True if this record was just created |
||
796 | * @return HTTPResponse|DBHTMLText |
||
797 | */ |
||
798 | protected function redirectAfterSave($isNewRecord) |
||
799 | { |
||
800 | $controller = $this->getToplevelController(); |
||
801 | if ($isNewRecord) { |
||
802 | return $this->redirect($this->Link()); |
||
803 | } elseif ($this->tabulatorGrid->hasByIDList() && $this->tabulatorGrid->getByIDList()->byID($this->record->ID)) { |
||
804 | return $this->redirect($this->getDefaultBackLink()); |
||
805 | } else { |
||
806 | // Changes to the record properties might've excluded the record from |
||
807 | // a filtered list, so return back to the main view if it can't be found |
||
808 | $url = $controller->getRequest()->getURL(); |
||
809 | $noActionURL = $controller->removeAction($url); |
||
810 | if ($this->isSilverStripeAdmin($controller)) { |
||
811 | $controller->getRequest()->addHeader('X-Pjax', 'Content'); |
||
812 | } |
||
813 | return $controller->redirect($noActionURL, 302); |
||
814 | } |
||
815 | } |
||
816 | |||
817 | protected function getHashValue() |
||
818 | { |
||
819 | if ($this->hash) { |
||
820 | $hash = $this->hash; |
||
821 | } else { |
||
822 | $hash = Cookie::get('hash'); |
||
823 | } |
||
824 | if ($hash) { |
||
825 | $hash = '#' . ltrim($hash, '#'); |
||
826 | } |
||
827 | return $hash; |
||
828 | } |
||
829 | |||
830 | /** |
||
831 | * Redirect to the given URL. |
||
832 | * |
||
833 | * @param string $url |
||
834 | * @param int $code |
||
835 | * @return HTTPResponse |
||
836 | */ |
||
837 | public function redirect($url, $code = 302): HTTPResponse |
||
838 | { |
||
839 | $hash = $this->getHashValue(); |
||
840 | if ($hash) { |
||
841 | $url .= $hash; |
||
842 | } |
||
843 | |||
844 | $controller = $this->getToplevelController(); |
||
845 | $response = $controller->redirect($url, $code); |
||
846 | |||
847 | // if ($hash) { |
||
848 | // We also pass it as a hash |
||
849 | // @link https://github.com/whatwg/fetch/issues/1167 |
||
850 | // $response = $response->addHeader('X-Hash', $hash); |
||
851 | // } |
||
852 | |||
853 | return $response; |
||
854 | } |
||
855 | |||
856 | public function httpError($errorCode, $errorMessage = null) |
||
857 | { |
||
858 | $controller = $this->getToplevelController(); |
||
859 | return $controller->httpError($errorCode, $errorMessage); |
||
860 | } |
||
861 | |||
862 | /** |
||
863 | * Loads the given form data into the underlying dataobject and relation |
||
864 | * |
||
865 | * @param array $data |
||
866 | * @param Form $form |
||
867 | * @throws ValidationException On error |
||
868 | * @return DataObject Saved record |
||
869 | */ |
||
870 | protected function saveFormIntoRecord($data, $form) |
||
871 | { |
||
872 | $list = $this->tabulatorGrid->getList(); |
||
873 | |||
874 | // Check object matches the correct classname |
||
875 | if (isset($data['ClassName']) && $data['ClassName'] != $this->record->ClassName) { |
||
876 | $newClassName = $data['ClassName']; |
||
877 | // The records originally saved attribute was overwritten by $form->saveInto($record) before. |
||
878 | // This is necessary for newClassInstance() to work as expected, and trigger change detection |
||
879 | // on the ClassName attribute |
||
880 | $this->record->setClassName($this->record->ClassName); |
||
881 | // Replace $record with a new instance |
||
882 | $this->record = $this->record->newClassInstance($newClassName); |
||
883 | } |
||
884 | |||
885 | // Save form and any extra saved data into this dataobject. |
||
886 | // Set writeComponents = true to write has-one relations / join records |
||
887 | $form->saveInto($this->record); |
||
888 | // https://github.com/silverstripe/silverstripe-assets/issues/365 |
||
889 | $this->record->write(); |
||
890 | $this->extend('onAfterSave', $this->record); |
||
891 | |||
892 | $extraData = $this->getExtraSavedData($this->record, $list); |
||
893 | $list->add($this->record, $extraData); |
||
894 | |||
895 | return $this->record; |
||
896 | } |
||
897 | |||
898 | /** |
||
899 | * Delete from ItemRequest action |
||
900 | * |
||
901 | * @param array $data |
||
902 | * @param Form $form |
||
903 | * @return HTTPResponse |
||
904 | * @throws ValidationException |
||
905 | */ |
||
906 | public function doDelete($data, $form) |
||
907 | { |
||
908 | $title = $this->record->Title; |
||
909 | if (!$this->record->canDelete()) { |
||
910 | throw new ValidationException( |
||
911 | _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.DeletePermissionsFailure', "No delete permissions") |
||
912 | ); |
||
913 | } |
||
914 | |||
915 | $this->record->delete(); |
||
916 | |||
917 | $message = _t( |
||
918 | 'SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Deleted', |
||
919 | 'Deleted {type} {name}', |
||
920 | [ |
||
921 | 'type' => $this->record->i18n_singular_name(), |
||
922 | 'name' => htmlspecialchars($title, ENT_QUOTES) |
||
923 | ] |
||
924 | ); |
||
925 | |||
926 | |||
927 | $backForm = $form; |
||
928 | $toplevelController = $this->getToplevelController(); |
||
929 | if ($this->isSilverStripeAdmin($toplevelController)) { |
||
930 | $backForm = $toplevelController->getEditForm(); |
||
931 | } |
||
932 | //when an item is deleted, redirect to the parent controller |
||
933 | $controller = $this->getToplevelController(); |
||
934 | |||
935 | if ($this->isSilverStripeAdmin($toplevelController)) { |
||
936 | $controller->getRequest()->addHeader('X-Pjax', 'Content'); |
||
937 | } |
||
938 | $response = $controller->redirect($this->getBackLink(), 302); //redirect back to admin section |
||
939 | $this->sessionMessage($message, "good"); |
||
940 | |||
941 | return $response; |
||
942 | } |
||
943 | |||
944 | public function isSilverStripeAdmin($controller) |
||
945 | { |
||
946 | if ($controller) { |
||
947 | return is_subclass_of($controller, \SilverStripe\Admin\LeftAndMain::class); |
||
948 | } |
||
949 | return false; |
||
950 | } |
||
951 | |||
952 | /** |
||
953 | * @param string $template |
||
954 | * @return $this |
||
955 | */ |
||
956 | public function setTemplate($template) |
||
960 | } |
||
961 | |||
962 | /** |
||
963 | * @return string |
||
964 | */ |
||
965 | public function getTemplate() |
||
966 | { |
||
967 | return $this->template; |
||
968 | } |
||
969 | |||
970 | /** |
||
971 | * Get list of templates to use |
||
972 | * |
||
973 | * @return array |
||
974 | */ |
||
975 | public function getTemplates() |
||
976 | { |
||
977 | $templates = SSViewer::get_templates_by_class($this, '', __CLASS__); |
||
978 | // Prefer any custom template |
||
979 | if ($this->getTemplate()) { |
||
980 | array_unshift($templates, $this->getTemplate()); |
||
981 | } |
||
982 | return $templates; |
||
983 | } |
||
984 | |||
985 | /** |
||
986 | * @return Controller |
||
987 | */ |
||
988 | public function getController() |
||
989 | { |
||
990 | return $this->popupController; |
||
991 | } |
||
992 | |||
993 | /** |
||
994 | * @return TabulatorGrid |
||
995 | */ |
||
996 | public function getTabulatorGrid() |
||
997 | { |
||
998 | return $this->tabulatorGrid; |
||
999 | } |
||
1000 | |||
1001 | /** |
||
1002 | * @return DataObject |
||
1003 | */ |
||
1004 | public function getRecord() |
||
1005 | { |
||
1006 | return $this->record; |
||
1007 | } |
||
1008 | |||
1009 | /** |
||
1010 | * CMS-specific functionality: Passes through navigation breadcrumbs |
||
1011 | * to the template, and includes the currently edited record (if any). |
||
1012 | * see {@link LeftAndMain->Breadcrumbs()} for details. |
||
1013 | * |
||
1014 | * @param boolean $unlinked |
||
1015 | * @return ArrayList |
||
1016 | */ |
||
1017 | public function Breadcrumbs($unlinked = false) |
||
1045 | } |
||
1046 | } |
||
1047 |
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.