1 | <?php |
||||||||||
2 | |||||||||||
3 | namespace LeKoala\CmsActions; |
||||||||||
4 | |||||||||||
5 | use Exception; |
||||||||||
6 | use ReflectionMethod; |
||||||||||
7 | use SilverStripe\Forms\Tab; |
||||||||||
8 | use SilverStripe\Forms\Form; |
||||||||||
9 | use SilverStripe\Forms\TabSet; |
||||||||||
10 | use SilverStripe\ORM\DataObject; |
||||||||||
11 | use SilverStripe\Core\Extensible; |
||||||||||
12 | use SilverStripe\Forms\FieldList; |
||||||||||
13 | use SilverStripe\Forms\FormField; |
||||||||||
14 | use SilverStripe\Control\Director; |
||||||||||
15 | use SilverStripe\Forms\FormAction; |
||||||||||
16 | use SilverStripe\Admin\LeftAndMain; |
||||||||||
17 | use SilverStripe\Forms\HiddenField; |
||||||||||
18 | use SilverStripe\Control\Controller; |
||||||||||
19 | use SilverStripe\Control\HTTPRequest; |
||||||||||
20 | use SilverStripe\Control\HTTPResponse; |
||||||||||
21 | use SilverStripe\Forms\CompositeField; |
||||||||||
22 | use SilverStripe\ORM\ValidationResult; |
||||||||||
23 | use SilverStripe\SiteConfig\SiteConfig; |
||||||||||
0 ignored issues
–
show
|
|||||||||||
24 | use SilverStripe\Core\Config\Configurable; |
||||||||||
25 | use SilverStripe\ORM\FieldType\DBHTMLText; |
||||||||||
26 | use SilverStripe\Control\HTTPResponse_Exception; |
||||||||||
27 | use SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest; |
||||||||||
28 | use ReflectionObject; |
||||||||||
29 | use SilverStripe\Admin\ModelAdmin; |
||||||||||
30 | use SilverStripe\Core\Extension; |
||||||||||
31 | use SilverStripe\View\ViewableData; |
||||||||||
32 | |||||||||||
33 | /** |
||||||||||
34 | * Decorates GridDetailForm_ItemRequest to use new form actions and buttons. |
||||||||||
35 | * |
||||||||||
36 | * This is also applied to LeftAndMain to allow actions on pages |
||||||||||
37 | * Warning: LeftAndMain doesn't call updateItemEditForm |
||||||||||
38 | * |
||||||||||
39 | * This is a lightweight version of BetterButtons that use default getCMSActions functionnality |
||||||||||
40 | * on DataObjects |
||||||||||
41 | * |
||||||||||
42 | * @link https://github.com/unclecheese/silverstripe-gridfield-betterbuttons |
||||||||||
43 | * @link https://github.com/unclecheese/silverstripe-gridfield-betterbuttons/blob/master/src/Extensions/GridFieldBetterButtonsItemRequest.php |
||||||||||
44 | * @property \SilverStripe\Admin\LeftAndMain|\SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest|ActionsGridFieldItemRequest $owner |
||||||||||
45 | * @extends \SilverStripe\Core\Extension<object> |
||||||||||
46 | */ |
||||||||||
47 | class ActionsGridFieldItemRequest extends Extension |
||||||||||
48 | { |
||||||||||
49 | use Configurable; |
||||||||||
50 | use Extensible; |
||||||||||
51 | |||||||||||
52 | /** |
||||||||||
53 | * @config |
||||||||||
54 | * @var boolean |
||||||||||
55 | */ |
||||||||||
56 | private static $enable_save_prev_next = true; |
||||||||||
57 | |||||||||||
58 | /** |
||||||||||
59 | * @config |
||||||||||
60 | * @var boolean |
||||||||||
61 | */ |
||||||||||
62 | private static $enable_save_close = true; |
||||||||||
63 | |||||||||||
64 | /** |
||||||||||
65 | * @config |
||||||||||
66 | * @var boolean |
||||||||||
67 | */ |
||||||||||
68 | private static $enable_delete_right = true; |
||||||||||
69 | |||||||||||
70 | /** |
||||||||||
71 | * @config |
||||||||||
72 | * @var boolean |
||||||||||
73 | */ |
||||||||||
74 | private static $enable_utils_prev_next = false; |
||||||||||
0 ignored issues
–
show
|
|||||||||||
75 | |||||||||||
76 | /** |
||||||||||
77 | * @var array<string> Allowed controller actions |
||||||||||
78 | */ |
||||||||||
79 | private static $allowed_actions = [ |
||||||||||
0 ignored issues
–
show
|
|||||||||||
80 | 'doSaveAndClose', |
||||||||||
81 | 'doSaveAndNext', |
||||||||||
82 | 'doSaveAndPrev', |
||||||||||
83 | 'doCustomAction', // For CustomAction |
||||||||||
84 | 'doCustomLink', // For CustomLink |
||||||||||
85 | ]; |
||||||||||
86 | |||||||||||
87 | /** |
||||||||||
88 | * @param FieldList $actions |
||||||||||
89 | * @return array<string> |
||||||||||
90 | */ |
||||||||||
91 | protected function getAvailableActions($actions) |
||||||||||
92 | { |
||||||||||
93 | $list = []; |
||||||||||
94 | foreach ($actions as $action) { |
||||||||||
95 | if (is_a($action, CompositeField::class)) { |
||||||||||
96 | $list = array_merge($list, $this->getAvailableActions($action->FieldList())); |
||||||||||
97 | } else { |
||||||||||
98 | $list[] = $action->getName(); |
||||||||||
99 | } |
||||||||||
100 | } |
||||||||||
101 | return $list; |
||||||||||
102 | } |
||||||||||
103 | |||||||||||
104 | /** |
||||||||||
105 | * This module does not interact with the /schema/SearchForm endpoint |
||||||||||
106 | * and therefore all requests for these urls don't need any special treatement |
||||||||||
107 | * |
||||||||||
108 | * @return bool |
||||||||||
109 | */ |
||||||||||
110 | protected function isSearchFormRequest(): bool |
||||||||||
111 | { |
||||||||||
112 | if (!Controller::has_curr()) { |
||||||||||
0 ignored issues
–
show
The function
SilverStripe\Control\Controller::has_curr() has been deprecated: 5.4.0 Will be removed without equivalent functionality to replace it in a future major release
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||||||||
113 | return false; |
||||||||||
114 | } |
||||||||||
115 | $curr = Controller::curr(); |
||||||||||
116 | if ($curr) { |
||||||||||
0 ignored issues
–
show
|
|||||||||||
117 | return str_contains($curr->getRequest()->getURL(), '/schema/SearchForm'); |
||||||||||
118 | } |
||||||||||
119 | return false; |
||||||||||
120 | } |
||||||||||
121 | |||||||||||
122 | /** |
||||||||||
123 | * Called by CMSMain, typically in the CMS or in the SiteConfig admin |
||||||||||
124 | * CMSMain already uses getCMSActions so we are good to go with anything defined there |
||||||||||
125 | * |
||||||||||
126 | * @param Form $form |
||||||||||
127 | * @return void |
||||||||||
128 | */ |
||||||||||
129 | public function updateEditForm(Form $form) |
||||||||||
130 | { |
||||||||||
131 | // Ignore search form requests |
||||||||||
132 | if ($this->isSearchFormRequest()) { |
||||||||||
133 | return; |
||||||||||
134 | } |
||||||||||
135 | |||||||||||
136 | $actions = $form->Actions(); |
||||||||||
137 | |||||||||||
138 | // We create a Drop-Up menu afterwards because it may already exist in the $CMSActions |
||||||||||
139 | // and we don't want to duplicate it |
||||||||||
140 | $this->processDropUpMenu($actions); |
||||||||||
141 | } |
||||||||||
142 | |||||||||||
143 | /** |
||||||||||
144 | * @return FieldList|false |
||||||||||
145 | */ |
||||||||||
146 | public function recordCmsUtils() |
||||||||||
147 | { |
||||||||||
148 | /** @var \SilverStripe\Versioned\VersionedGridFieldItemRequest|\SilverStripe\Admin\LeftAndMain $owner */ |
||||||||||
149 | $owner = $this->owner; |
||||||||||
150 | |||||||||||
151 | // At this stage, the get record could be from a gridfield item request, or from a more general left and main which requires an id |
||||||||||
152 | // maybe we could simply do: |
||||||||||
153 | // $record = DataObject::singleton($controller->getModelClass()); |
||||||||||
154 | $reflectionMethod = new ReflectionMethod($owner, 'getRecord'); |
||||||||||
155 | $record = count($reflectionMethod->getParameters()) > 0 ? $owner->getRecord(0) : $owner->getRecord(); |
||||||||||
0 ignored issues
–
show
The call to
SilverStripe\Forms\GridF...temRequest::getRecord() has too many arguments starting with 0 .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() The call to
SilverStripe\Admin\LeftAndMain::getRecord() has too few arguments starting with id .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||||||
156 | if ($record && $record->hasMethod('getCMSUtils')) { |
||||||||||
157 | //@phpstan-ignore-next-line |
||||||||||
158 | $utils = $record->getCMSUtils(); |
||||||||||
159 | $this->extend('onCMSUtils', $utils, $record); |
||||||||||
160 | $record->extend('onCMSUtils', $utils); |
||||||||||
161 | return $utils; |
||||||||||
162 | } |
||||||||||
163 | return false; |
||||||||||
164 | } |
||||||||||
165 | |||||||||||
166 | /** |
||||||||||
167 | * @param Form $form |
||||||||||
168 | * @return void |
||||||||||
169 | */ |
||||||||||
170 | public function updateItemEditForm($form) |
||||||||||
171 | { |
||||||||||
172 | /** @var ?DataObject $record */ |
||||||||||
173 | $record = $this->owner->getRecord(); |
||||||||||
0 ignored issues
–
show
The call to
SilverStripe\Admin\LeftAndMain::getRecord() has too few arguments starting with id .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() The method
getRecord() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||||
174 | if (!$record) { |
||||||||||
0 ignored issues
–
show
|
|||||||||||
175 | return; |
||||||||||
176 | } |
||||||||||
177 | |||||||||||
178 | // We get the actions as defined on our record |
||||||||||
179 | $CMSActions = $this->getCmsActionsFromRecord($record); |
||||||||||
180 | |||||||||||
181 | $FormActions = $form->Actions(); |
||||||||||
182 | |||||||||||
183 | // Push our actions that are otherwise ignored by SilverStripe |
||||||||||
184 | if ($CMSActions) { |
||||||||||
0 ignored issues
–
show
|
|||||||||||
185 | foreach ($CMSActions as $CMSAction) { |
||||||||||
186 | $action = $FormActions->fieldByName($CMSAction->getName()); |
||||||||||
187 | |||||||||||
188 | if ($action) { |
||||||||||
189 | // If it has been made readonly, revert |
||||||||||
190 | if ($CMSAction->isReadonly() != $action->isReadonly()) { |
||||||||||
191 | $FormActions->replaceField($action->getName(), $action->setReadonly($CMSAction->isReadonly())); |
||||||||||
192 | } |
||||||||||
193 | } |
||||||||||
194 | } |
||||||||||
195 | } |
||||||||||
196 | } |
||||||||||
197 | |||||||||||
198 | /** |
||||||||||
199 | * Called by GridField_ItemRequest |
||||||||||
200 | * We add our custom save&close, save&next and other tweaks |
||||||||||
201 | * Actions can be made readonly after this extension point |
||||||||||
202 | * @param FieldList $actions |
||||||||||
203 | * @return void |
||||||||||
204 | */ |
||||||||||
205 | public function updateFormActions($actions) |
||||||||||
206 | { |
||||||||||
207 | // Ignore search form requests |
||||||||||
208 | if ($this->isSearchFormRequest()) { |
||||||||||
209 | return; |
||||||||||
210 | } |
||||||||||
211 | |||||||||||
212 | /** @var DataObject|ViewableData|null $record */ |
||||||||||
213 | $record = $this->owner->getRecord(); |
||||||||||
0 ignored issues
–
show
The call to
SilverStripe\Admin\LeftAndMain::getRecord() has too few arguments starting with id .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||||||
214 | if (!$record) { |
||||||||||
215 | return; |
||||||||||
216 | } |
||||||||||
217 | |||||||||||
218 | // We get the actions as defined on our record |
||||||||||
219 | $CMSActions = $this->getCmsActionsFromRecord($record); |
||||||||||
220 | |||||||||||
221 | // The default button group that contains the Save or Create action |
||||||||||
222 | // @link https://docs.silverstripe.org/en/4/developer_guides/customising_the_admin_interface/how_tos/extend_cms_interface/#extending-the-cms-actions |
||||||||||
223 | $MajorActions = $actions->fieldByName('MajorActions'); |
||||||||||
224 | |||||||||||
225 | // If it doesn't exist, push to default group |
||||||||||
226 | if (!$MajorActions) { |
||||||||||
227 | $MajorActions = $actions; |
||||||||||
0 ignored issues
–
show
|
|||||||||||
228 | } |
||||||||||
229 | |||||||||||
230 | // Push our actions that are otherwise ignored by SilverStripe |
||||||||||
231 | if ($CMSActions) { |
||||||||||
232 | foreach ($CMSActions as $action) { |
||||||||||
233 | // Avoid duplicated actions (eg: when added by SilverStripe\Versioned\VersionedGridFieldItemRequest) |
||||||||||
234 | if ($actions->fieldByName($action->getName())) { |
||||||||||
235 | continue; |
||||||||||
236 | } |
||||||||||
237 | $actions->push($action); |
||||||||||
238 | } |
||||||||||
239 | } |
||||||||||
240 | |||||||||||
241 | // We create a Drop-Up menu afterwards because it may already exist in the $CMSActions |
||||||||||
242 | // and we don't want to duplicate it |
||||||||||
243 | $this->processDropUpMenu($actions); |
||||||||||
244 | |||||||||||
245 | // Add extension hook |
||||||||||
246 | $this->extend('onBeforeUpdateCMSActions', $actions, $record); |
||||||||||
247 | $record->extend('onBeforeUpdateCMSActions', $actions); |
||||||||||
248 | |||||||||||
249 | $ActionMenus = $actions->fieldByName('ActionMenus'); |
||||||||||
250 | // Re-insert ActionMenus to make sure they always follow the buttons |
||||||||||
251 | if ($ActionMenus) { |
||||||||||
252 | $actions->remove($ActionMenus); |
||||||||||
253 | $actions->push($ActionMenus); |
||||||||||
254 | } |
||||||||||
255 | |||||||||||
256 | // We have a 4.4 setup, before that there was no RightGroup |
||||||||||
257 | $RightGroup = $this->getRightGroupActions($actions); |
||||||||||
258 | |||||||||||
259 | // Insert again to make sure our actions are properly placed after apply changes |
||||||||||
260 | if ($RightGroup) { |
||||||||||
0 ignored issues
–
show
|
|||||||||||
261 | $actions->remove($RightGroup); |
||||||||||
262 | $actions->push($RightGroup); |
||||||||||
263 | } |
||||||||||
264 | |||||||||||
265 | $opts = [ |
||||||||||
266 | 'save_close' => self::config()->enable_save_close, |
||||||||||
267 | 'save_prev_next' => self::config()->enable_save_prev_next, |
||||||||||
268 | 'delete_right' => self::config()->enable_delete_right, |
||||||||||
269 | ]; |
||||||||||
270 | if ($record->hasMethod('getCMSActionsOptions')) { |
||||||||||
271 | $opts = array_merge($opts, $record->getCMSActionsOptions()); |
||||||||||
272 | } |
||||||||||
273 | |||||||||||
274 | if ($opts['save_close']) { |
||||||||||
275 | $this->addSaveAndClose($actions, $record); |
||||||||||
276 | } |
||||||||||
277 | |||||||||||
278 | if ($opts['save_prev_next']) { |
||||||||||
279 | $this->addSaveNextAndPrevious($actions, $record); |
||||||||||
280 | } |
||||||||||
281 | |||||||||||
282 | if ($opts['delete_right']) { |
||||||||||
283 | $this->moveCancelAndDelete($actions, $record); |
||||||||||
284 | } |
||||||||||
285 | |||||||||||
286 | // Fix gridstate being lost when running custom actions |
||||||||||
287 | if (method_exists($this->owner, 'getStateManager')) { |
||||||||||
288 | $request = $this->owner->getRequest(); |
||||||||||
0 ignored issues
–
show
The method
getRequest() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||||
289 | $stateManager = $this->owner->getStateManager(); |
||||||||||
0 ignored issues
–
show
The method
getStateManager() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||||
290 | $gridField = $this->owner->getGridField(); |
||||||||||
0 ignored issues
–
show
The method
getGridField() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||||
291 | $state = $stateManager->getStateFromRequest($gridField, $request); |
||||||||||
292 | $actions->push(new HiddenField($stateManager->getStateKey($gridField), null, $state)); |
||||||||||
293 | } |
||||||||||
294 | |||||||||||
295 | // Add extension hook |
||||||||||
296 | $this->extend('onAfterUpdateCMSActions', $actions, $record); |
||||||||||
297 | $record->extend('onAfterUpdateCMSActions', $actions); |
||||||||||
298 | } |
||||||||||
299 | |||||||||||
300 | /** |
||||||||||
301 | * Collect all Drop-Up actions into a menu. |
||||||||||
302 | * @param FieldList $actions |
||||||||||
303 | * @return void |
||||||||||
304 | */ |
||||||||||
305 | protected function processDropUpMenu($actions) |
||||||||||
306 | { |
||||||||||
307 | // The Drop-up container may already exist |
||||||||||
308 | /** @var ?Tab $dropUpContainer */ |
||||||||||
309 | $dropUpContainer = $actions->fieldByName('ActionMenus.MoreOptions'); |
||||||||||
310 | foreach ($actions as $action) { |
||||||||||
311 | //@phpstan-ignore-next-line |
||||||||||
312 | if ($action->hasMethod('getDropUp') && $action->getDropUp()) { |
||||||||||
313 | if (!$dropUpContainer) { |
||||||||||
314 | $dropUpContainer = $this->createDropUpContainer($actions); |
||||||||||
315 | } |
||||||||||
316 | $action->getContainerFieldList()->removeByName($action->getName()); |
||||||||||
317 | $dropUpContainer->push($action); |
||||||||||
0 ignored issues
–
show
It seems like
$action can also be of type SilverStripe\View\ArrayData ; however, parameter $field of SilverStripe\Forms\CompositeField::push() does only seem to accept SilverStripe\Forms\FormField , 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
![]() |
|||||||||||
318 | } |
||||||||||
319 | } |
||||||||||
320 | } |
||||||||||
321 | |||||||||||
322 | /** |
||||||||||
323 | * Prepares a Drop-Up menu |
||||||||||
324 | * @param FieldList $actions |
||||||||||
325 | * @return Tab |
||||||||||
326 | */ |
||||||||||
327 | protected function createDropUpContainer($actions) |
||||||||||
328 | { |
||||||||||
329 | $rootTabSet = new TabSet('ActionMenus'); |
||||||||||
330 | $dropUpContainer = new Tab( |
||||||||||
331 | 'MoreOptions', |
||||||||||
332 | _t(__CLASS__ . '.MoreOptions', 'More options', 'Expands a view for more buttons') |
||||||||||
333 | ); |
||||||||||
334 | $dropUpContainer->addExtraClass('popover-actions-simulate'); |
||||||||||
335 | $rootTabSet->push($dropUpContainer); |
||||||||||
336 | $rootTabSet->addExtraClass('ss-ui-action-tabset action-menus noborder'); |
||||||||||
337 | |||||||||||
338 | $actions->insertBefore('RightGroup', $rootTabSet); |
||||||||||
339 | |||||||||||
340 | return $dropUpContainer; |
||||||||||
341 | } |
||||||||||
342 | |||||||||||
343 | /** |
||||||||||
344 | * Check if a record can be edited/created/exists |
||||||||||
345 | * @param ViewableData $record |
||||||||||
346 | * @param bool $editOnly |
||||||||||
347 | * @return bool |
||||||||||
348 | */ |
||||||||||
349 | protected function checkCan($record, $editOnly = false) |
||||||||||
350 | { |
||||||||||
351 | // For ViewableData, we assume all methods should be implemented |
||||||||||
352 | // @link https://docs.silverstripe.org/en/5/developer_guides/forms/using_gridfield_with_arbitrary_data/#custom-edit |
||||||||||
353 | if (!method_exists($record, 'canEdit') || !method_exists($record, 'canCreate')) { |
||||||||||
354 | return false; |
||||||||||
355 | } |
||||||||||
356 | //@phpstan-ignore-next-line |
||||||||||
357 | if (!$record->ID && ($editOnly || !$record->canCreate())) { |
||||||||||
358 | return false; |
||||||||||
359 | } |
||||||||||
360 | if (!$record->canEdit()) { |
||||||||||
361 | return false; |
||||||||||
362 | } |
||||||||||
363 | |||||||||||
364 | return true; |
||||||||||
365 | } |
||||||||||
366 | |||||||||||
367 | /** |
||||||||||
368 | * @param ViewableData $record |
||||||||||
369 | * @return ?FieldList |
||||||||||
370 | */ |
||||||||||
371 | protected function getCmsActionsFromRecord(ViewableData $record) |
||||||||||
372 | { |
||||||||||
373 | if ($record instanceof DataObject) { |
||||||||||
374 | return $record->getCMSActions(); |
||||||||||
375 | } |
||||||||||
376 | if (method_exists($record, 'getCMSActions')) { |
||||||||||
377 | return $record->getCMSActions(); |
||||||||||
378 | } |
||||||||||
379 | return null; |
||||||||||
380 | } |
||||||||||
381 | |||||||||||
382 | /** |
||||||||||
383 | * @param FieldList $actions |
||||||||||
384 | * @param ViewableData $record |
||||||||||
385 | * @return void |
||||||||||
386 | */ |
||||||||||
387 | public function moveCancelAndDelete(FieldList $actions, ViewableData $record) |
||||||||||
388 | { |
||||||||||
389 | // We have a 4.4 setup, before that there was no RightGroup |
||||||||||
390 | $RightGroup = $actions->fieldByName('RightGroup'); |
||||||||||
391 | |||||||||||
392 | // Move delete at the end |
||||||||||
393 | $deleteAction = $actions->fieldByName('action_doDelete'); |
||||||||||
394 | if ($deleteAction) { |
||||||||||
395 | // Move at the end of the stack |
||||||||||
396 | $actions->remove($deleteAction); |
||||||||||
397 | $actions->push($deleteAction); |
||||||||||
398 | |||||||||||
399 | if (!$RightGroup) { |
||||||||||
400 | // Only necessary pre 4.4 |
||||||||||
401 | $deleteAction->addExtraClass('align-right'); |
||||||||||
402 | } |
||||||||||
403 | // Set custom title |
||||||||||
404 | if ($record->hasMethod('getDeleteButtonTitle')) { |
||||||||||
405 | //@phpstan-ignore-next-line |
||||||||||
406 | $deleteAction->setTitle($record->getDeleteButtonTitle()); |
||||||||||
407 | } |
||||||||||
408 | } |
||||||||||
409 | // Move cancel at the end |
||||||||||
410 | $cancelButton = $actions->fieldByName('cancelbutton'); |
||||||||||
411 | if ($cancelButton) { |
||||||||||
412 | // Move at the end of the stack |
||||||||||
413 | $actions->remove($cancelButton); |
||||||||||
414 | $actions->push($cancelButton); |
||||||||||
415 | if (!$RightGroup) { |
||||||||||
416 | // Only necessary pre 4.4 |
||||||||||
417 | $cancelButton->addExtraClass('align-right'); |
||||||||||
418 | } |
||||||||||
419 | // Set custom titlte |
||||||||||
420 | if ($record->hasMethod('getCancelButtonTitle')) { |
||||||||||
421 | //@phpstan-ignore-next-line |
||||||||||
422 | $cancelButton->setTitle($record->getCancelButtonTitle()); |
||||||||||
423 | } |
||||||||||
424 | } |
||||||||||
425 | } |
||||||||||
426 | |||||||||||
427 | /** |
||||||||||
428 | * @param ViewableData $record |
||||||||||
429 | * @return bool |
||||||||||
430 | */ |
||||||||||
431 | public function useCustomPrevNext(ViewableData $record): bool |
||||||||||
432 | { |
||||||||||
433 | if (self::config()->enable_custom_prevnext) { |
||||||||||
434 | return $record->hasMethod('PrevRecord') && $record->hasMethod('NextRecord'); |
||||||||||
435 | } |
||||||||||
436 | return false; |
||||||||||
437 | } |
||||||||||
438 | |||||||||||
439 | /** |
||||||||||
440 | * @param ViewableData $record |
||||||||||
441 | * @return int |
||||||||||
442 | */ |
||||||||||
443 | public function getCustomPreviousRecordID(ViewableData $record) |
||||||||||
444 | { |
||||||||||
445 | // This will overwrite state provided record |
||||||||||
446 | if ($this->useCustomPrevNext($record)) { |
||||||||||
447 | //@phpstan-ignore-next-line |
||||||||||
448 | return $record->PrevRecord()->ID ?? 0; |
||||||||||
449 | } |
||||||||||
450 | return $this->owner->getPreviousRecordID(); |
||||||||||
0 ignored issues
–
show
The method
getPreviousRecordID() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||||
451 | } |
||||||||||
452 | |||||||||||
453 | /** |
||||||||||
454 | * @param ViewableData $record |
||||||||||
455 | * @return int |
||||||||||
456 | */ |
||||||||||
457 | public function getCustomNextRecordID(ViewableData $record) |
||||||||||
458 | { |
||||||||||
459 | // This will overwrite state provided record |
||||||||||
460 | if ($this->useCustomPrevNext($record)) { |
||||||||||
461 | //@phpstan-ignore-next-line |
||||||||||
462 | return $record->NextRecord()->ID ?? 0; |
||||||||||
463 | } |
||||||||||
464 | return $this->owner->getNextRecordID(); |
||||||||||
0 ignored issues
–
show
The method
getNextRecordID() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||||
465 | } |
||||||||||
466 | |||||||||||
467 | /** |
||||||||||
468 | * @param FieldList $actions |
||||||||||
469 | * @return CompositeField|FieldList |
||||||||||
470 | */ |
||||||||||
471 | protected function getMajorActions(FieldList $actions) |
||||||||||
472 | { |
||||||||||
473 | /** @var ?CompositeField $MajorActions */ |
||||||||||
474 | $MajorActions = $actions->fieldByName('MajorActions'); |
||||||||||
475 | |||||||||||
476 | // If it doesn't exist, push to default group |
||||||||||
477 | if (!$MajorActions) { |
||||||||||
0 ignored issues
–
show
|
|||||||||||
478 | $MajorActions = $actions; |
||||||||||
479 | } |
||||||||||
480 | return $MajorActions; |
||||||||||
481 | } |
||||||||||
482 | |||||||||||
483 | /** |
||||||||||
484 | * @param FieldList $actions |
||||||||||
485 | * @return CompositeField |
||||||||||
486 | */ |
||||||||||
487 | protected function getRightGroupActions(FieldList $actions) |
||||||||||
488 | { |
||||||||||
489 | /** @var ?CompositeField $RightGroup */ |
||||||||||
490 | $RightGroup = $actions->fieldByName('RightGroup'); |
||||||||||
491 | return $RightGroup; |
||||||||||
492 | } |
||||||||||
493 | |||||||||||
494 | /** |
||||||||||
495 | * @param FieldList $actions |
||||||||||
496 | * @param ViewableData $record |
||||||||||
497 | * @return void |
||||||||||
498 | */ |
||||||||||
499 | public function addSaveNextAndPrevious(FieldList $actions, ViewableData $record) |
||||||||||
500 | { |
||||||||||
501 | if ($this->checkCan($record, true)) { |
||||||||||
502 | return; |
||||||||||
503 | } |
||||||||||
504 | |||||||||||
505 | $MajorActions = $this->getMajorActions($actions); |
||||||||||
506 | |||||||||||
507 | // @link https://github.com/silverstripe/silverstripe-framework/issues/10742 |
||||||||||
508 | $getPreviousRecordID = $this->getCustomPreviousRecordID($record); |
||||||||||
509 | $getNextRecordID = $this->getCustomNextRecordID($record); |
||||||||||
510 | $isCustom = $this->useCustomPrevNext($record); |
||||||||||
511 | |||||||||||
512 | // Coupling for HasPrevNextUtils |
||||||||||
513 | if (Controller::has_curr()) { |
||||||||||
0 ignored issues
–
show
The function
SilverStripe\Control\Controller::has_curr() has been deprecated: 5.4.0 Will be removed without equivalent functionality to replace it in a future major release
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||||||||
514 | $prevLink = $nextLink = null; |
||||||||||
515 | if (!$isCustom && $this->owner instanceof GridFieldDetailForm_ItemRequest) { |
||||||||||
516 | if ($getPreviousRecordID) { |
||||||||||
517 | $prevLink = $this->getPublicEditLinkForAdjacentRecord(-1); |
||||||||||
518 | } |
||||||||||
519 | if ($getNextRecordID) { |
||||||||||
520 | $nextLink = $this->getPublicEditLinkForAdjacentRecord(+1); |
||||||||||
521 | } |
||||||||||
522 | } |
||||||||||
523 | |||||||||||
524 | /** @var HTTPRequest $request */ |
||||||||||
525 | $request = Controller::curr()->getRequest(); |
||||||||||
526 | $routeParams = $request->routeParams(); |
||||||||||
527 | $recordClass = get_class($record); |
||||||||||
528 | $routeParams['cmsactions'][$recordClass]['PreviousRecordID'] = $getPreviousRecordID; |
||||||||||
529 | $routeParams['cmsactions'][$recordClass]['NextRecordID'] = $getNextRecordID; |
||||||||||
530 | $routeParams['cmsactions'][$recordClass]['PrevRecordLink'] = $prevLink; |
||||||||||
531 | $routeParams['cmsactions'][$recordClass]['NextRecordLink'] = $nextLink; |
||||||||||
532 | $request->setRouteParams($routeParams); |
||||||||||
533 | } |
||||||||||
534 | |||||||||||
535 | if ($getPreviousRecordID) { |
||||||||||
536 | $doSaveAndPrev = new FormAction('doSaveAndPrev', _t('ActionsGridFieldItemRequest.SAVEANDPREVIOUS', 'Save and Previous')); |
||||||||||
537 | $doSaveAndPrev->addExtraClass($this->getBtnClassForRecord($record)); |
||||||||||
538 | $doSaveAndPrev->addExtraClass('font-icon-angle-double-left btn-mobile-collapse'); |
||||||||||
539 | $doSaveAndPrev->setUseButtonTag(true); |
||||||||||
540 | $MajorActions->push($doSaveAndPrev); |
||||||||||
541 | } |
||||||||||
542 | if ($getNextRecordID) { |
||||||||||
543 | $doSaveAndNext = new FormAction('doSaveAndNext', _t('ActionsGridFieldItemRequest.SAVEANDNEXT', 'Save and Next')); |
||||||||||
544 | $doSaveAndNext->addExtraClass($this->getBtnClassForRecord($record)); |
||||||||||
545 | $doSaveAndNext->addExtraClass('font-icon-angle-double-right btn-mobile-collapse'); |
||||||||||
546 | $doSaveAndNext->setUseButtonTag(true); |
||||||||||
547 | $MajorActions->push($doSaveAndNext); |
||||||||||
548 | } |
||||||||||
549 | } |
||||||||||
550 | |||||||||||
551 | public function getPublicEditLinkForAdjacentRecord(int $offset): ?string |
||||||||||
552 | { |
||||||||||
553 | $this->owner->getStateManager(); |
||||||||||
554 | $reflObject = new ReflectionObject($this->owner); |
||||||||||
555 | $reflMethod = $reflObject->getMethod('getEditLinkForAdjacentRecord'); |
||||||||||
556 | $reflMethod->setAccessible(true); |
||||||||||
557 | |||||||||||
558 | try { |
||||||||||
559 | return $reflMethod->invoke($this->owner, $offset); |
||||||||||
560 | } catch (Exception $e) { |
||||||||||
561 | return null; |
||||||||||
562 | } |
||||||||||
563 | } |
||||||||||
564 | |||||||||||
565 | /** |
||||||||||
566 | * @param FieldList $actions |
||||||||||
567 | * @param ViewableData $record |
||||||||||
568 | * @return void |
||||||||||
569 | */ |
||||||||||
570 | public function addSaveAndClose(FieldList $actions, ViewableData $record) |
||||||||||
571 | { |
||||||||||
572 | if (!$this->checkCan($record)) { |
||||||||||
573 | return; |
||||||||||
574 | } |
||||||||||
575 | |||||||||||
576 | $MajorActions = $this->getMajorActions($actions); |
||||||||||
577 | |||||||||||
578 | //@phpstan-ignore-next-line |
||||||||||
579 | if ($record->ID) { |
||||||||||
580 | $label = _t('ActionsGridFieldItemRequest.SAVEANDCLOSE', 'Save and Close'); |
||||||||||
581 | } else { |
||||||||||
582 | $label = _t('ActionsGridFieldItemRequest.CREATEANDCLOSE', 'Create and Close'); |
||||||||||
583 | } |
||||||||||
584 | $saveAndClose = new FormAction('doSaveAndClose', $label); |
||||||||||
585 | $saveAndClose->addExtraClass($this->getBtnClassForRecord($record)); |
||||||||||
586 | $saveAndClose->setAttribute('data-text-alternate', $label); |
||||||||||
587 | |||||||||||
588 | if ($record->ID) { |
||||||||||
589 | $saveAndClose->setAttribute('data-btn-alternate-add', 'btn-primary'); |
||||||||||
590 | $saveAndClose->setAttribute('data-btn-alternate-remove', 'btn-outline-primary'); |
||||||||||
591 | } |
||||||||||
592 | $saveAndClose->addExtraClass('font-icon-level-up btn-mobile-collapse'); |
||||||||||
593 | $saveAndClose->setUseButtonTag(true); |
||||||||||
594 | $MajorActions->push($saveAndClose); |
||||||||||
595 | } |
||||||||||
596 | |||||||||||
597 | /** |
||||||||||
598 | * New and existing records have different classes |
||||||||||
599 | * |
||||||||||
600 | * @param ViewableData $record |
||||||||||
601 | * @return string |
||||||||||
602 | */ |
||||||||||
603 | protected function getBtnClassForRecord(ViewableData $record) |
||||||||||
604 | { |
||||||||||
605 | //@phpstan-ignore-next-line |
||||||||||
606 | if ($record->ID) { |
||||||||||
607 | return 'btn-outline-primary'; |
||||||||||
608 | } |
||||||||||
609 | return 'btn-primary'; |
||||||||||
610 | } |
||||||||||
611 | |||||||||||
612 | /** |
||||||||||
613 | * @param string $action |
||||||||||
614 | * @param array<FormField>|FieldList $definedActions |
||||||||||
615 | * @return FormField|null |
||||||||||
616 | */ |
||||||||||
617 | protected static function findAction($action, $definedActions) |
||||||||||
618 | { |
||||||||||
619 | $result = null; |
||||||||||
620 | |||||||||||
621 | foreach ($definedActions as $definedAction) { |
||||||||||
622 | if (is_a($definedAction, CompositeField::class)) { |
||||||||||
623 | $result = self::findAction($action, $definedAction->FieldList()); |
||||||||||
624 | if ($result) { |
||||||||||
625 | break; |
||||||||||
626 | } |
||||||||||
627 | } |
||||||||||
628 | |||||||||||
629 | $definedActionName = $definedAction->getName(); |
||||||||||
630 | |||||||||||
631 | if ($definedAction->hasMethod('actionName')) { |
||||||||||
632 | //@phpstan-ignore-next-line |
||||||||||
633 | $definedActionName = $definedAction->actionName(); |
||||||||||
634 | } |
||||||||||
635 | if ($definedActionName === $action) { |
||||||||||
636 | $result = $definedAction; |
||||||||||
637 | break; |
||||||||||
638 | } |
||||||||||
639 | } |
||||||||||
640 | |||||||||||
641 | return $result; |
||||||||||
642 | } |
||||||||||
643 | |||||||||||
644 | /** |
||||||||||
645 | * Forward a given action to a DataObject |
||||||||||
646 | * |
||||||||||
647 | * Action must be declared in getCMSActions to be called |
||||||||||
648 | * |
||||||||||
649 | * @param string $action |
||||||||||
650 | * @param array<string,mixed> $data |
||||||||||
651 | * @param Form $form |
||||||||||
652 | * @return HTTPResponse|DBHTMLText|string |
||||||||||
653 | * @throws HTTPResponse_Exception |
||||||||||
654 | */ |
||||||||||
655 | protected function forwardActionToRecord($action, $data = [], $form = null) |
||||||||||
656 | { |
||||||||||
657 | $controller = $this->getToplevelController(); |
||||||||||
658 | |||||||||||
659 | // We have an item request or a controller that can provide a record |
||||||||||
660 | $record = null; |
||||||||||
661 | if ($this->owner->hasMethod('ItemEditForm')) { |
||||||||||
662 | // It's a request handler. Don't check for a specific class as it may be subclassed |
||||||||||
663 | //@phpstan-ignore-next-line |
||||||||||
664 | $record = $this->owner->record; |
||||||||||
0 ignored issues
–
show
|
|||||||||||
665 | } elseif ($controller->hasMethod('save_siteconfig')) { |
||||||||||
666 | // Check for any type of siteconfig controller |
||||||||||
667 | $record = SiteConfig::current_site_config(); |
||||||||||
668 | } elseif (!empty($data['ClassName']) && !empty($data['ID'])) { |
||||||||||
669 | $record = DataObject::get_by_id($data['ClassName'], $data['ID']); |
||||||||||
670 | } elseif ($controller->hasMethod("getRecord")) { |
||||||||||
671 | // LeftAndMain requires an id |
||||||||||
672 | if ($controller instanceof LeftAndMain && !empty($data['ID'])) { |
||||||||||
673 | $record = $controller->getRecord($data['ID']); |
||||||||||
674 | } elseif ($controller instanceof ModelAdmin) { |
||||||||||
675 | // Otherwise fallback to singleton |
||||||||||
676 | $record = DataObject::singleton($controller->getModelClass()); |
||||||||||
677 | } |
||||||||||
678 | } |
||||||||||
679 | |||||||||||
680 | if (!$record) { |
||||||||||
681 | throw new Exception("No record to handle the action $action on " . get_class($controller)); |
||||||||||
682 | } |
||||||||||
683 | $CMSActions = $this->getCmsActionsFromRecord($record); |
||||||||||
684 | |||||||||||
685 | // Check if the action is indeed available |
||||||||||
686 | $clickedAction = null; |
||||||||||
687 | if (!empty($CMSActions)) { |
||||||||||
688 | $clickedAction = self::findAction($action, $CMSActions); |
||||||||||
689 | } |
||||||||||
690 | if (!$clickedAction) { |
||||||||||
691 | $class = get_class($record); |
||||||||||
692 | $availableActions = null; |
||||||||||
693 | if ($CMSActions) { |
||||||||||
694 | $availableActions = implode(',', $this->getAvailableActions($CMSActions)); |
||||||||||
695 | } |
||||||||||
696 | if (!$availableActions) { |
||||||||||
697 | $availableActions = "(no available actions, please check getCMSActions)"; |
||||||||||
698 | } |
||||||||||
699 | |||||||||||
700 | return $this->owner->httpError(403, sprintf( |
||||||||||
0 ignored issues
–
show
The method
httpError() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||||
701 | 'Action not available on %s. It must be one of : %s', |
||||||||||
702 | $class, |
||||||||||
703 | $availableActions |
||||||||||
704 | )); |
||||||||||
705 | } |
||||||||||
706 | |||||||||||
707 | if ($clickedAction->isReadonly() || $clickedAction->isDisabled()) { |
||||||||||
708 | return $this->owner->httpError(403, sprintf( |
||||||||||
709 | 'Action %s is disabled', |
||||||||||
710 | $clickedAction->getName(), |
||||||||||
711 | )); |
||||||||||
712 | } |
||||||||||
713 | |||||||||||
714 | $message = null; |
||||||||||
715 | $error = false; |
||||||||||
716 | |||||||||||
717 | // Check record BEFORE the action |
||||||||||
718 | // It can be deleted by the action, and it will return to the list |
||||||||||
719 | $isNewRecord = isset($record->ID) && $record->ID === 0; |
||||||||||
720 | |||||||||||
721 | $actionTitle = $clickedAction->getName(); |
||||||||||
722 | if (method_exists($clickedAction, 'getTitle')) { |
||||||||||
723 | $actionTitle = $clickedAction->getTitle(); |
||||||||||
724 | } |
||||||||||
725 | |||||||||||
726 | $recordName = $record instanceof DataObject ? $record->i18n_singular_name() : _t('ActionsGridFieldItemRequest.record', 'record'); |
||||||||||
727 | |||||||||||
728 | try { |
||||||||||
729 | $result = $record->$action($data, $form, $controller); |
||||||||||
730 | |||||||||||
731 | // We have a response |
||||||||||
732 | if ($result instanceof HTTPResponse) { |
||||||||||
733 | return $result; |
||||||||||
734 | } |
||||||||||
735 | |||||||||||
736 | if ($result === false) { |
||||||||||
737 | // Result returned an error (false) |
||||||||||
738 | $error = true; |
||||||||||
739 | $message = _t( |
||||||||||
740 | 'ActionsGridFieldItemRequest.FAILED', |
||||||||||
741 | 'Action {action} failed on {name}', |
||||||||||
742 | [ |
||||||||||
743 | 'action' => $actionTitle, |
||||||||||
744 | 'name' => $recordName, |
||||||||||
745 | ] |
||||||||||
746 | ); |
||||||||||
747 | } elseif (is_string($result)) { |
||||||||||
748 | // Result is a message |
||||||||||
749 | $message = $result; |
||||||||||
750 | } |
||||||||||
751 | } catch (Exception $ex) { |
||||||||||
752 | $result = null; |
||||||||||
753 | $error = true; |
||||||||||
754 | $message = $ex->getMessage(); |
||||||||||
755 | } |
||||||||||
756 | |||||||||||
757 | // Build default message |
||||||||||
758 | if (!$message) { |
||||||||||
759 | $message = _t( |
||||||||||
760 | 'ActionsGridFieldItemRequest.DONE', |
||||||||||
761 | 'Action {action} was done on {name}', |
||||||||||
762 | [ |
||||||||||
763 | 'action' => $actionTitle, |
||||||||||
764 | 'name' => $recordName, |
||||||||||
765 | ] |
||||||||||
766 | ); |
||||||||||
767 | } |
||||||||||
768 | $status = 'good'; |
||||||||||
769 | if ($error) { |
||||||||||
770 | $status = 'bad'; |
||||||||||
771 | } |
||||||||||
772 | |||||||||||
773 | // Progressive actions return array with json data |
||||||||||
774 | if (method_exists($clickedAction, 'getProgressive') && $clickedAction->getProgressive()) { |
||||||||||
775 | $response = $controller->getResponse(); |
||||||||||
776 | $response->addHeader('Content-Type', 'application/json'); |
||||||||||
777 | if ($result) { |
||||||||||
778 | $encodedResult = json_encode($result); |
||||||||||
779 | if (!$encodedResult) { |
||||||||||
780 | $encodedResult = json_last_error_msg(); |
||||||||||
781 | } |
||||||||||
782 | $response->setBody($encodedResult); |
||||||||||
783 | } |
||||||||||
784 | |||||||||||
785 | return $response; |
||||||||||
786 | } |
||||||||||
787 | |||||||||||
788 | // We don't have a form, simply return the result |
||||||||||
789 | if (!$form) { |
||||||||||
790 | if ($error) { |
||||||||||
791 | return $this->owner->httpError(403, $message); |
||||||||||
792 | } |
||||||||||
793 | |||||||||||
794 | return $message; |
||||||||||
795 | } |
||||||||||
796 | |||||||||||
797 | if (Director::is_ajax()) { |
||||||||||
798 | $controller->getResponse()->addHeader('X-Status', rawurlencode($message)); |
||||||||||
799 | |||||||||||
800 | if (method_exists($clickedAction, 'getShouldRefresh') && $clickedAction->getShouldRefresh()) { |
||||||||||
801 | self::addXReload($controller); |
||||||||||
802 | } |
||||||||||
803 | // 4xx status makes a red box |
||||||||||
804 | if ($error) { |
||||||||||
805 | $controller->getResponse()->setStatusCode(400); |
||||||||||
806 | } |
||||||||||
807 | } else { |
||||||||||
808 | // If the controller support sessionMessage, use it instead of form |
||||||||||
809 | if ($controller->hasMethod('sessionMessage')) { |
||||||||||
810 | //@phpstan-ignore-next-line |
||||||||||
811 | $controller->sessionMessage($message, $status, ValidationResult::CAST_HTML); |
||||||||||
812 | } else { |
||||||||||
813 | $form->sessionMessage($message, $status, ValidationResult::CAST_HTML); |
||||||||||
814 | } |
||||||||||
815 | } |
||||||||||
816 | |||||||||||
817 | // Custom redirect |
||||||||||
818 | /** @var CustomAction $clickedAction */ |
||||||||||
819 | if (method_exists($clickedAction, 'getRedirectURL') && $clickedAction->getRedirectURL()) { |
||||||||||
820 | // we probably need a full ui refresh |
||||||||||
821 | self::addXReload($controller, $clickedAction->getRedirectURL()); |
||||||||||
822 | return $controller->redirect($clickedAction->getRedirectURL()); |
||||||||||
823 | } |
||||||||||
824 | |||||||||||
825 | // Redirect after action |
||||||||||
826 | return $this->redirectAfterAction($isNewRecord, $record); |
||||||||||
827 | } |
||||||||||
828 | |||||||||||
829 | /** |
||||||||||
830 | * Requires a ControllerURL as well, see |
||||||||||
831 | * https://github.com/silverstripe/silverstripe-admin/blob/a3aa41cea4c4df82050eef65ad5efcfae7bfde69/client/src/legacy/LeftAndMain.js#L773-L780 |
||||||||||
832 | * |
||||||||||
833 | * @param Controller $controller |
||||||||||
834 | * @param string|null $url |
||||||||||
835 | * @return void |
||||||||||
836 | */ |
||||||||||
837 | public static function addXReload(Controller $controller, ?string $url = null): void |
||||||||||
838 | { |
||||||||||
839 | if (!$url) { |
||||||||||
840 | $url = $controller->getReferer(); |
||||||||||
841 | } |
||||||||||
842 | // Triggers a full reload. Without this header, it will use the pjax response |
||||||||||
843 | if ($url) { |
||||||||||
844 | $controller->getResponse()->addHeader('X-ControllerURL', $url); |
||||||||||
845 | } |
||||||||||
846 | $controller->getResponse()->addHeader('X-Reload', "true"); |
||||||||||
847 | } |
||||||||||
848 | |||||||||||
849 | /** |
||||||||||
850 | * Handles custom links |
||||||||||
851 | * |
||||||||||
852 | * Use CustomLink with default behaviour to trigger this |
||||||||||
853 | * |
||||||||||
854 | * See: |
||||||||||
855 | * DefaultLink::getModelLink |
||||||||||
856 | * GridFieldCustomLink::getLink |
||||||||||
857 | * |
||||||||||
858 | * @param HTTPRequest $request |
||||||||||
859 | * @return HTTPResponse|DBHTMLText|string |
||||||||||
860 | * @throws Exception |
||||||||||
861 | */ |
||||||||||
862 | public function doCustomLink(HTTPRequest $request) |
||||||||||
863 | { |
||||||||||
864 | $action = $request->getVar('CustomLink'); |
||||||||||
865 | return $this->forwardActionToRecord($action); |
||||||||||
866 | } |
||||||||||
867 | |||||||||||
868 | /** |
||||||||||
869 | * Handles custom actions |
||||||||||
870 | * |
||||||||||
871 | * Use CustomAction class to trigger this |
||||||||||
872 | * |
||||||||||
873 | * Nested actions are submitted like this |
||||||||||
874 | * [action_doCustomAction] => Array |
||||||||||
875 | * ( |
||||||||||
876 | * [doTestAction] => 1 |
||||||||||
877 | * ) |
||||||||||
878 | * |
||||||||||
879 | * @param array<string,mixed> $data The form data |
||||||||||
880 | * @param Form $form The form object |
||||||||||
881 | * @return HTTPResponse|DBHTMLText|string |
||||||||||
882 | * @throws Exception |
||||||||||
883 | */ |
||||||||||
884 | public function doCustomAction($data, $form) |
||||||||||
885 | { |
||||||||||
886 | $action = key($data['action_doCustomAction']); |
||||||||||
887 | return $this->forwardActionToRecord($action, $data, $form); |
||||||||||
888 | } |
||||||||||
889 | |||||||||||
890 | /** |
||||||||||
891 | * Saves the form and goes back to list view |
||||||||||
892 | * |
||||||||||
893 | * @param array<string,mixed> $data The form data |
||||||||||
894 | * @param Form $form The form object |
||||||||||
895 | * @return HTTPResponse |
||||||||||
896 | */ |
||||||||||
897 | public function doSaveAndClose($data, $form) |
||||||||||
898 | { |
||||||||||
899 | $this->owner->doSave($data, $form); |
||||||||||
0 ignored issues
–
show
The method
doSave() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||||
900 | // Redirect after save |
||||||||||
901 | $controller = $this->getToplevelController(); |
||||||||||
902 | |||||||||||
903 | $link = $this->getBackLink(); |
||||||||||
904 | |||||||||||
905 | // Doesn't seem to be needed anymore |
||||||||||
906 | // $link = $this->addGridState($link, $data); |
||||||||||
907 | |||||||||||
908 | $controller->getResponse()->addHeader("X-Pjax", "Content"); |
||||||||||
909 | |||||||||||
910 | return $controller->redirect($link); |
||||||||||
911 | } |
||||||||||
912 | |||||||||||
913 | /** |
||||||||||
914 | * @param string $dir prev|next |
||||||||||
915 | * @param array<string,mixed> $data The form data |
||||||||||
916 | * @param Form|null $form |
||||||||||
917 | * @return HTTPResponse |
||||||||||
918 | */ |
||||||||||
919 | protected function doSaveAndAdjacent(string $dir, array $data, ?Form $form) |
||||||||||
920 | { |
||||||||||
921 | //@phpstan-ignore-next-line |
||||||||||
922 | $record = $this->owner->record; |
||||||||||
0 ignored issues
–
show
|
|||||||||||
923 | $this->owner->doSave($data, $form); |
||||||||||
924 | // Redirect after save |
||||||||||
925 | $controller = $this->getToplevelController(); |
||||||||||
926 | $controller->getResponse()->addHeader("X-Pjax", "Content"); |
||||||||||
927 | |||||||||||
928 | if (!($record instanceof DataObject)) { |
||||||||||
929 | throw new Exception("Works only with DataObject"); |
||||||||||
930 | } |
||||||||||
931 | |||||||||||
932 | $class = get_class($record); |
||||||||||
933 | if (!$class) { |
||||||||||
934 | throw new Exception("Could not get class"); |
||||||||||
935 | } |
||||||||||
936 | |||||||||||
937 | if (!in_array($dir, ['prev', 'next'])) { |
||||||||||
938 | throw new Exception("Invalid dir $dir"); |
||||||||||
939 | } |
||||||||||
940 | |||||||||||
941 | $method = match ($dir) { |
||||||||||
942 | 'prev' => 'getCustomPreviousRecordID', |
||||||||||
943 | 'next' => 'getCustomNextRecordID', |
||||||||||
944 | }; |
||||||||||
945 | |||||||||||
946 | $offset = match ($dir) { |
||||||||||
947 | 'prev' => -1, |
||||||||||
948 | 'next' => +1, |
||||||||||
949 | }; |
||||||||||
950 | |||||||||||
951 | $adjRecordID = $this->$method($record); |
||||||||||
952 | |||||||||||
953 | /** @var ?DataObject $adj */ |
||||||||||
954 | $adj = $class::get()->byID($adjRecordID); |
||||||||||
955 | |||||||||||
956 | $useCustom = $this->useCustomPrevNext($record); |
||||||||||
957 | $link = $this->getPublicEditLinkForAdjacentRecord($offset); |
||||||||||
958 | if (!$link || $useCustom) { |
||||||||||
959 | $link = $this->owner->getEditLink($adjRecordID); |
||||||||||
0 ignored issues
–
show
The method
getEditLink() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||||
960 | $link = $this->addGridState($link, $data); |
||||||||||
0 ignored issues
–
show
The function
LeKoala\CmsActions\Actio...Request::addGridState() has been deprecated.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||||
961 | } |
||||||||||
962 | |||||||||||
963 | // Link to a specific tab if set, see cms-actions.js |
||||||||||
964 | if ($adj && !empty($data['_activetab'])) { |
||||||||||
965 | $link .= sprintf('#%s', $data['_activetab']); |
||||||||||
966 | } |
||||||||||
967 | |||||||||||
968 | return $controller->redirect($link); |
||||||||||
969 | } |
||||||||||
970 | |||||||||||
971 | /** |
||||||||||
972 | * Saves the form and goes back to the next item |
||||||||||
973 | * |
||||||||||
974 | * @param array<string,mixed> $data The form data |
||||||||||
975 | * @param Form $form The form object |
||||||||||
976 | * @return HTTPResponse |
||||||||||
977 | */ |
||||||||||
978 | public function doSaveAndNext($data, $form) |
||||||||||
979 | { |
||||||||||
980 | return $this->doSaveAndAdjacent('next', $data, $form); |
||||||||||
981 | } |
||||||||||
982 | |||||||||||
983 | /** |
||||||||||
984 | * Saves the form and goes to the previous item |
||||||||||
985 | * |
||||||||||
986 | * @param array<string,mixed> $data The form data |
||||||||||
987 | * @param Form $form The form object |
||||||||||
988 | * @return HTTPResponse |
||||||||||
989 | */ |
||||||||||
990 | public function doSaveAndPrev($data, $form) |
||||||||||
991 | { |
||||||||||
992 | return $this->doSaveAndAdjacent('prev', $data, $form); |
||||||||||
993 | } |
||||||||||
994 | |||||||||||
995 | /** |
||||||||||
996 | * Check if we can remove this safely |
||||||||||
997 | * @deprecated |
||||||||||
998 | * @param string $url |
||||||||||
999 | * @param array<mixed> $data |
||||||||||
1000 | * @return string |
||||||||||
1001 | */ |
||||||||||
1002 | protected function addGridState($url, $data) |
||||||||||
1003 | { |
||||||||||
1004 | // This should not be necessary at all if the state is correctly passed along |
||||||||||
1005 | $BackURL = $data['BackURL'] ?? null; |
||||||||||
1006 | if ($BackURL) { |
||||||||||
1007 | $query = parse_url($BackURL, PHP_URL_QUERY); |
||||||||||
1008 | if ($query) { |
||||||||||
1009 | $url = strtok($url, '?'); |
||||||||||
1010 | $url .= '?' . $query; |
||||||||||
1011 | } |
||||||||||
1012 | } |
||||||||||
1013 | return $url; |
||||||||||
1014 | } |
||||||||||
1015 | |||||||||||
1016 | /** |
||||||||||
1017 | * Gets the top level controller. |
||||||||||
1018 | * |
||||||||||
1019 | * @return Controller |
||||||||||
1020 | * @todo This had to be directly copied from {@link GridFieldDetailForm_ItemRequest} |
||||||||||
1021 | * because it is a protected method and not visible to a decorator! |
||||||||||
1022 | */ |
||||||||||
1023 | protected function getToplevelController() |
||||||||||
1024 | { |
||||||||||
1025 | if ($this->isLeftAndMain($this->owner)) { |
||||||||||
0 ignored issues
–
show
It seems like
$this->owner can also be of type LeKoala\CmsActions\ActionsGridFieldItemRequest and SilverStripe\Forms\GridF...dDetailForm_ItemRequest ; however, parameter $controller of LeKoala\CmsActions\Actio...equest::isLeftAndMain() does only seem to accept SilverStripe\Control\Controller , 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
![]() |
|||||||||||
1026 | return $this->owner; |
||||||||||
0 ignored issues
–
show
|
|||||||||||
1027 | } |
||||||||||
1028 | if (!$this->owner->hasMethod("getController")) { |
||||||||||
1029 | return Controller::curr(); |
||||||||||
1030 | } |
||||||||||
1031 | $controller = $this->owner->getController(); |
||||||||||
0 ignored issues
–
show
The method
getController() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||||
1032 | while ($controller instanceof GridFieldDetailForm_ItemRequest) { |
||||||||||
1033 | $controller = $controller->getController(); |
||||||||||
1034 | } |
||||||||||
1035 | |||||||||||
1036 | return $controller; |
||||||||||
1037 | } |
||||||||||
1038 | |||||||||||
1039 | /** |
||||||||||
1040 | * @param Controller $controller |
||||||||||
1041 | * @return boolean |
||||||||||
1042 | */ |
||||||||||
1043 | protected function isLeftAndMain($controller) |
||||||||||
1044 | { |
||||||||||
1045 | return is_subclass_of($controller, LeftAndMain::class); |
||||||||||
1046 | } |
||||||||||
1047 | |||||||||||
1048 | /** |
||||||||||
1049 | * Gets the back link |
||||||||||
1050 | * |
||||||||||
1051 | * @return string |
||||||||||
1052 | */ |
||||||||||
1053 | public function getBackLink() |
||||||||||
1054 | { |
||||||||||
1055 | $backlink = ''; |
||||||||||
1056 | $toplevelController = $this->getToplevelController(); |
||||||||||
1057 | // Check for LeftAndMain and alike controllers with a Backlink or Breadcrumbs methods |
||||||||||
1058 | if ($toplevelController->hasMethod('Backlink')) { |
||||||||||
1059 | //@phpstan-ignore-next-line |
||||||||||
1060 | $backlink = $toplevelController->Backlink(); |
||||||||||
1061 | } elseif ($this->owner->getController()->hasMethod('Breadcrumbs')) { |
||||||||||
1062 | //@phpstan-ignore-next-line |
||||||||||
1063 | $parents = $this->owner->getController()->Breadcrumbs(false)->items; |
||||||||||
1064 | $backlink = array_pop($parents)->Link; |
||||||||||
1065 | } |
||||||||||
1066 | if (!$backlink) { |
||||||||||
1067 | $backlink = $toplevelController->Link(); |
||||||||||
0 ignored issues
–
show
Are you sure the assignment to
$backlink is correct as $toplevelController->Link() targeting SilverStripe\Control\RequestHandler::Link() seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||||||||
1068 | } |
||||||||||
1069 | |||||||||||
1070 | return $backlink; |
||||||||||
1071 | } |
||||||||||
1072 | |||||||||||
1073 | /** |
||||||||||
1074 | * Response object for this request after a successful save |
||||||||||
1075 | * |
||||||||||
1076 | * @param bool $isNewRecord True if this record was just created |
||||||||||
1077 | * @param ViewableData $record |
||||||||||
1078 | * @return HTTPResponse|DBHTMLText|string |
||||||||||
1079 | * @todo This had to be directly copied from {@link GridFieldDetailForm_ItemRequest} |
||||||||||
1080 | * because it is a protected method and not visible to a decorator! |
||||||||||
1081 | */ |
||||||||||
1082 | protected function redirectAfterAction($isNewRecord, $record = null) |
||||||||||
1083 | { |
||||||||||
1084 | $controller = $this->getToplevelController(); |
||||||||||
1085 | |||||||||||
1086 | if ($this->isLeftAndMain($controller)) { |
||||||||||
1087 | // CMSMain => redirect to show |
||||||||||
1088 | if ($this->owner->hasMethod("LinkPageEdit")) { |
||||||||||
1089 | //@phpstan-ignore-next-line |
||||||||||
1090 | return $controller->redirect($this->owner->LinkPageEdit($record->ID)); |
||||||||||
0 ignored issues
–
show
The method
LinkPageEdit() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||||
1091 | } |
||||||||||
1092 | } |
||||||||||
1093 | |||||||||||
1094 | if ($isNewRecord) { |
||||||||||
1095 | return $controller->redirect($this->owner->Link()); |
||||||||||
0 ignored issues
–
show
The method
Link() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||||
1096 | } |
||||||||||
1097 | //@phpstan-ignore-next-line |
||||||||||
1098 | if ($this->owner->gridField && $this->owner->gridField->getList()->byID($this->owner->record->ID)) { |
||||||||||
0 ignored issues
–
show
|
|||||||||||
1099 | // Return new view, as we can't do a "virtual redirect" via the CMS Ajax |
||||||||||
1100 | // to the same URL (it assumes that its content is already current, and doesn't reload) |
||||||||||
1101 | return $this->owner->edit($controller->getRequest()); |
||||||||||
0 ignored issues
–
show
The method
edit() does not exist on LeKoala\CmsActions\ActionsGridFieldItemRequest . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||||
1102 | } |
||||||||||
1103 | // Changes to the record properties might've excluded the record from |
||||||||||
1104 | // a filtered list, so return back to the main view if it can't be found |
||||||||||
1105 | $noActionURL = $url = $controller->getRequest()->getURL(); |
||||||||||
1106 | if (!$url) { |
||||||||||
1107 | $url = ''; |
||||||||||
1108 | } |
||||||||||
1109 | |||||||||||
1110 | // The controller may not have these |
||||||||||
1111 | if ($controller->hasMethod('getAction')) { |
||||||||||
1112 | $action = $controller->getAction(); |
||||||||||
1113 | // Handle GridField detail form editing |
||||||||||
1114 | if (strpos($url, 'ItemEditForm') !== false) { |
||||||||||
1115 | $action = 'ItemEditForm'; |
||||||||||
1116 | } |
||||||||||
1117 | if ($action) { |
||||||||||
1118 | $noActionURL = $controller->removeAction($url, $action); |
||||||||||
1119 | } |
||||||||||
1120 | } else { |
||||||||||
1121 | // Simple fallback (last index of) |
||||||||||
1122 | $pos = strrpos($url, 'ItemEditForm'); |
||||||||||
1123 | if (is_int($pos)) { |
||||||||||
0 ignored issues
–
show
|
|||||||||||
1124 | $noActionURL = substr($url, 0, $pos); |
||||||||||
1125 | } |
||||||||||
1126 | } |
||||||||||
1127 | |||||||||||
1128 | $controller->getRequest()->addHeader('X-Pjax', 'Content'); |
||||||||||
1129 | return $controller->redirect($noActionURL, 302); |
||||||||||
1130 | } |
||||||||||
1131 | } |
||||||||||
1132 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths