1
|
|
|
<?php |
|
|
|
|
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/* |
6
|
|
|
* This file is part of the Sonata Project package. |
7
|
|
|
* |
8
|
|
|
* (c) Thomas Rabaix <[email protected]> |
9
|
|
|
* |
10
|
|
|
* For the full copyright and license information, please view the LICENSE |
11
|
|
|
* file that was distributed with this source code. |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
namespace Sonata\AdminBundle\Controller; |
15
|
|
|
|
16
|
|
|
use Doctrine\Common\Inflector\Inflector; |
17
|
|
|
use Psr\Log\LoggerInterface; |
18
|
|
|
use Psr\Log\NullLogger; |
19
|
|
|
use Sonata\AdminBundle\Admin\AdminInterface; |
20
|
|
|
use Sonata\AdminBundle\Admin\FieldDescriptionCollection; |
21
|
|
|
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface; |
22
|
|
|
use Sonata\AdminBundle\Exception\LockException; |
23
|
|
|
use Sonata\AdminBundle\Exception\ModelManagerException; |
24
|
|
|
use Sonata\AdminBundle\Templating\TemplateRegistryInterface; |
25
|
|
|
use Sonata\AdminBundle\Util\AdminObjectAclData; |
26
|
|
|
use Sonata\AdminBundle\Util\AdminObjectAclManipulator; |
27
|
|
|
use Symfony\Bridge\Twig\AppVariable; |
28
|
|
|
use Symfony\Bridge\Twig\Command\DebugCommand; |
29
|
|
|
use Symfony\Bridge\Twig\Extension\FormExtension; |
30
|
|
|
use Symfony\Bridge\Twig\Form\TwigRenderer; |
31
|
|
|
use Symfony\Bundle\FrameworkBundle\Controller\ControllerTrait; |
32
|
|
|
use Symfony\Component\DependencyInjection\ContainerAwareInterface; |
33
|
|
|
use Symfony\Component\DependencyInjection\ContainerAwareTrait; |
34
|
|
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
35
|
|
|
use Symfony\Component\Form\FormRenderer; |
36
|
|
|
use Symfony\Component\Form\FormView; |
37
|
|
|
use Symfony\Component\HttpFoundation\JsonResponse; |
38
|
|
|
use Symfony\Component\HttpFoundation\RedirectResponse; |
39
|
|
|
use Symfony\Component\HttpFoundation\Request; |
40
|
|
|
use Symfony\Component\HttpFoundation\Response; |
41
|
|
|
use Symfony\Component\HttpKernel\Exception\HttpException; |
42
|
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; |
43
|
|
|
use Symfony\Component\PropertyAccess\PropertyAccess; |
44
|
|
|
use Symfony\Component\PropertyAccess\PropertyPath; |
45
|
|
|
use Symfony\Component\Security\Core\Exception\AccessDeniedException; |
46
|
|
|
use Symfony\Component\Security\Csrf\CsrfToken; |
47
|
|
|
|
48
|
|
|
// BC for Symfony < 3.3 where this trait does not exist |
49
|
|
|
// NEXT_MAJOR: Remove the polyfill and inherit from \Symfony\Bundle\FrameworkBundle\Controller\Controller again |
50
|
|
|
if (!trait_exists(ControllerTrait::class)) { |
51
|
|
|
require_once __DIR__.'/PolyfillControllerTrait.php'; |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @author Thomas Rabaix <[email protected]> |
56
|
|
|
*/ |
57
|
|
|
class CRUDController implements ContainerAwareInterface |
58
|
|
|
{ |
59
|
|
|
// NEXT_MAJOR: Don't use these traits anymore (inherit from Controller instead) |
60
|
|
|
use ControllerTrait, ContainerAwareTrait { |
61
|
|
|
ControllerTrait::render as originalRender; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* The related Admin class. |
66
|
|
|
* |
67
|
|
|
* @var AdminInterface |
68
|
|
|
*/ |
69
|
|
|
protected $admin; |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* The template registry of the related Admin class. |
73
|
|
|
* |
74
|
|
|
* @var TemplateRegistryInterface |
75
|
|
|
*/ |
76
|
|
|
private $templateRegistry; |
77
|
|
|
|
78
|
|
|
// BC for Symfony 3.3 where ControllerTrait exists but does not contain get() and has() methods. |
79
|
|
|
public function __call($method, $arguments) |
80
|
|
|
{ |
81
|
|
|
if (\in_array($method, ['get', 'has'], true)) { |
82
|
|
|
return $this->container->$method(...$arguments); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
if (method_exists($this, 'proxyToControllerClass')) { |
86
|
|
|
return $this->proxyToControllerClass($method, $arguments); |
|
|
|
|
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
throw new \LogicException('Call to undefined method '.__CLASS__.'::'.$method); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
public function setContainer(ContainerInterface $container = null): void |
93
|
|
|
{ |
94
|
|
|
$this->container = $container; |
95
|
|
|
|
96
|
|
|
$this->configure(); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* NEXT_MAJOR: Remove this method. |
101
|
|
|
* |
102
|
|
|
* @see renderWithExtraParams() |
103
|
|
|
* |
104
|
|
|
* @param string $view The view name |
105
|
|
|
* @param array $parameters An array of parameters to pass to the view |
106
|
|
|
* |
107
|
|
|
* @return Response A Response instance |
108
|
|
|
* |
109
|
|
|
* @deprecated since version 3.27, to be removed in 4.0. Use Sonata\AdminBundle\Controller\CRUDController::renderWithExtraParams() instead. |
110
|
|
|
*/ |
111
|
|
|
public function render($view, array $parameters = [], Response $response = null) |
112
|
|
|
{ |
113
|
|
|
@trigger_error( |
|
|
|
|
114
|
|
|
'Method '.__CLASS__.'::render has been renamed to '.__CLASS__.'::renderWithExtraParams.', |
115
|
|
|
E_USER_DEPRECATED |
116
|
|
|
); |
117
|
|
|
|
118
|
|
|
return $this->renderWithExtraParams($view, $parameters, $response); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Renders a view while passing mandatory parameters on to the template. |
123
|
|
|
* |
124
|
|
|
* @param string $view The view name |
125
|
|
|
* |
126
|
|
|
* @return Response A Response instance |
127
|
|
|
*/ |
128
|
|
|
public function renderWithExtraParams($view, array $parameters = [], Response $response = null) |
129
|
|
|
{ |
130
|
|
|
if (!$this->isXmlHttpRequest()) { |
131
|
|
|
$parameters['breadcrumbs_builder'] = $this->get('sonata.admin.breadcrumbs_builder'); |
132
|
|
|
} |
133
|
|
|
$parameters['admin'] = $parameters['admin'] ?? |
134
|
|
|
$this->admin; |
135
|
|
|
|
136
|
|
|
$parameters['base_template'] = $parameters['base_template'] ?? |
137
|
|
|
$this->getBaseTemplate(); |
138
|
|
|
|
139
|
|
|
$parameters['admin_pool'] = $this->get('sonata.admin.pool'); |
140
|
|
|
|
141
|
|
|
//NEXT_MAJOR: Remove method alias and use $this->render() directly. |
142
|
|
|
return $this->originalRender($view, $parameters, $response); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* List action. |
147
|
|
|
* |
148
|
|
|
* @throws AccessDeniedException If access is not granted |
149
|
|
|
* |
150
|
|
|
* @return Response |
151
|
|
|
*/ |
152
|
|
|
public function listAction() |
153
|
|
|
{ |
154
|
|
|
$request = $this->getRequest(); |
155
|
|
|
|
156
|
|
|
$this->admin->checkAccess('list'); |
157
|
|
|
|
158
|
|
|
$preResponse = $this->preList($request); |
159
|
|
|
if (null !== $preResponse) { |
160
|
|
|
return $preResponse; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
if ($listMode = $request->get('_list_mode')) { |
164
|
|
|
$this->admin->setListMode($listMode); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
$datagrid = $this->admin->getDatagrid(); |
168
|
|
|
$formView = $datagrid->getForm()->createView(); |
169
|
|
|
|
170
|
|
|
// set the theme for the current Admin Form |
171
|
|
|
$this->setFormTheme($formView, $this->admin->getFilterTheme()); |
172
|
|
|
|
173
|
|
|
// NEXT_MAJOR: Remove this line and use commented line below it instead |
174
|
|
|
$template = $this->admin->getTemplate('list'); |
|
|
|
|
175
|
|
|
// $template = $this->templateRegistry->getTemplate('list'); |
176
|
|
|
|
177
|
|
|
return $this->renderWithExtraParams($template, [ |
178
|
|
|
'action' => 'list', |
179
|
|
|
'form' => $formView, |
180
|
|
|
'datagrid' => $datagrid, |
181
|
|
|
'csrf_token' => $this->getCsrfToken('sonata.batch'), |
182
|
|
|
'export_formats' => $this->has('sonata.admin.admin_exporter') ? |
183
|
|
|
$this->get('sonata.admin.admin_exporter')->getAvailableFormats($this->admin) : |
184
|
|
|
$this->admin->getExportFormats(), |
185
|
|
|
], null); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Execute a batch delete. |
190
|
|
|
* |
191
|
|
|
* @throws AccessDeniedException If access is not granted |
192
|
|
|
* |
193
|
|
|
* @return RedirectResponse |
194
|
|
|
*/ |
195
|
|
|
public function batchActionDelete(ProxyQueryInterface $query) |
196
|
|
|
{ |
197
|
|
|
$this->admin->checkAccess('batchDelete'); |
198
|
|
|
|
199
|
|
|
$modelManager = $this->admin->getModelManager(); |
200
|
|
|
|
201
|
|
|
try { |
202
|
|
|
$modelManager->batchDelete($this->admin->getClass(), $query); |
203
|
|
|
$this->addFlash( |
204
|
|
|
'sonata_flash_success', |
205
|
|
|
$this->trans('flash_batch_delete_success', [], 'SonataAdminBundle') |
206
|
|
|
); |
207
|
|
|
} catch (ModelManagerException $e) { |
208
|
|
|
$this->handleModelManagerException($e); |
209
|
|
|
$this->addFlash( |
210
|
|
|
'sonata_flash_error', |
211
|
|
|
$this->trans('flash_batch_delete_error', [], 'SonataAdminBundle') |
212
|
|
|
); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
return $this->redirectToList(); |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Delete action. |
220
|
|
|
* |
221
|
|
|
* @param int|string|null $id |
222
|
|
|
* |
223
|
|
|
* @throws NotFoundHttpException If the object does not exist |
224
|
|
|
* @throws AccessDeniedException If access is not granted |
225
|
|
|
* |
226
|
|
|
* @return Response|RedirectResponse |
227
|
|
|
*/ |
228
|
|
|
public function deleteAction($id) |
|
|
|
|
229
|
|
|
{ |
230
|
|
|
$request = $this->getRequest(); |
231
|
|
|
$id = $request->get($this->admin->getIdParameter()); |
232
|
|
|
$object = $this->admin->getObject($id); |
233
|
|
|
|
234
|
|
|
if (!$object) { |
235
|
|
|
throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id)); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
$this->checkParentChildAssociation($request, $object); |
239
|
|
|
|
240
|
|
|
$this->admin->checkAccess('delete', $object); |
241
|
|
|
|
242
|
|
|
$preResponse = $this->preDelete($request, $object); |
243
|
|
|
if (null !== $preResponse) { |
244
|
|
|
return $preResponse; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
if ('DELETE' === $this->getRestMethod()) { |
248
|
|
|
// check the csrf token |
249
|
|
|
$this->validateCsrfToken('sonata.delete'); |
250
|
|
|
|
251
|
|
|
$objectName = $this->admin->toString($object); |
252
|
|
|
|
253
|
|
|
try { |
254
|
|
|
$this->admin->delete($object); |
255
|
|
|
|
256
|
|
|
if ($this->isXmlHttpRequest()) { |
257
|
|
|
return $this->renderJson(['result' => 'ok'], 200, []); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
$this->addFlash( |
261
|
|
|
'sonata_flash_success', |
262
|
|
|
$this->trans( |
263
|
|
|
'flash_delete_success', |
264
|
|
|
['%name%' => $this->escapeHtml($objectName)], |
265
|
|
|
'SonataAdminBundle' |
266
|
|
|
) |
267
|
|
|
); |
268
|
|
|
} catch (ModelManagerException $e) { |
269
|
|
|
$this->handleModelManagerException($e); |
270
|
|
|
|
271
|
|
|
if ($this->isXmlHttpRequest()) { |
272
|
|
|
return $this->renderJson(['result' => 'error'], 200, []); |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
$this->addFlash( |
276
|
|
|
'sonata_flash_error', |
277
|
|
|
$this->trans( |
278
|
|
|
'flash_delete_error', |
279
|
|
|
['%name%' => $this->escapeHtml($objectName)], |
280
|
|
|
'SonataAdminBundle' |
281
|
|
|
) |
282
|
|
|
); |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
return $this->redirectTo($object); |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
// NEXT_MAJOR: Remove this line and use commented line below it instead |
289
|
|
|
$template = $this->admin->getTemplate('delete'); |
|
|
|
|
290
|
|
|
// $template = $this->templateRegistry->getTemplate('delete'); |
291
|
|
|
|
292
|
|
|
return $this->renderWithExtraParams($template, [ |
293
|
|
|
'object' => $object, |
294
|
|
|
'action' => 'delete', |
295
|
|
|
'csrf_token' => $this->getCsrfToken('sonata.delete'), |
296
|
|
|
], null); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Edit action. |
301
|
|
|
* |
302
|
|
|
* @param int|string|null $id |
303
|
|
|
* |
304
|
|
|
* @throws NotFoundHttpException If the object does not exist |
305
|
|
|
* @throws \RuntimeException If no editable field is defined |
306
|
|
|
* @throws AccessDeniedException If access is not granted |
307
|
|
|
* |
308
|
|
|
* @return Response|RedirectResponse |
309
|
|
|
*/ |
310
|
|
|
public function editAction($id = null) |
|
|
|
|
311
|
|
|
{ |
312
|
|
|
$request = $this->getRequest(); |
313
|
|
|
// the key used to lookup the template |
314
|
|
|
$templateKey = 'edit'; |
315
|
|
|
|
316
|
|
|
$id = $request->get($this->admin->getIdParameter()); |
317
|
|
|
$existingObject = $this->admin->getObject($id); |
318
|
|
|
|
319
|
|
|
if (!$existingObject) { |
320
|
|
|
throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id)); |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
$this->checkParentChildAssociation($request, $existingObject); |
324
|
|
|
|
325
|
|
|
$this->admin->checkAccess('edit', $existingObject); |
326
|
|
|
|
327
|
|
|
$preResponse = $this->preEdit($request, $existingObject); |
328
|
|
|
if (null !== $preResponse) { |
329
|
|
|
return $preResponse; |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
$this->admin->setSubject($existingObject); |
333
|
|
|
$objectId = $this->admin->getNormalizedIdentifier($existingObject); |
334
|
|
|
|
335
|
|
|
$form = $this->admin->getForm(); |
336
|
|
|
|
337
|
|
|
if (!\is_array($fields = $form->all()) || 0 === \count($fields)) { |
338
|
|
|
throw new \RuntimeException( |
339
|
|
|
'No editable field defined. Did you forget to implement the "configureFormFields" method?' |
340
|
|
|
); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
$form->setData($existingObject); |
344
|
|
|
$form->handleRequest($request); |
345
|
|
|
|
346
|
|
|
if ($form->isSubmitted()) { |
347
|
|
|
$isFormValid = $form->isValid(); |
348
|
|
|
|
349
|
|
|
// persist if the form was valid and if in preview mode the preview was approved |
350
|
|
|
if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) { |
351
|
|
|
$submittedObject = $form->getData(); |
352
|
|
|
$this->admin->setSubject($submittedObject); |
353
|
|
|
|
354
|
|
|
try { |
355
|
|
|
$existingObject = $this->admin->update($submittedObject); |
356
|
|
|
|
357
|
|
|
if ($this->isXmlHttpRequest()) { |
358
|
|
|
return $this->renderJson([ |
359
|
|
|
'result' => 'ok', |
360
|
|
|
'objectId' => $objectId, |
361
|
|
|
'objectName' => $this->escapeHtml($this->admin->toString($existingObject)), |
362
|
|
|
], 200, []); |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
$this->addFlash( |
366
|
|
|
'sonata_flash_success', |
367
|
|
|
$this->trans( |
368
|
|
|
'flash_edit_success', |
369
|
|
|
['%name%' => $this->escapeHtml($this->admin->toString($existingObject))], |
370
|
|
|
'SonataAdminBundle' |
371
|
|
|
) |
372
|
|
|
); |
373
|
|
|
|
374
|
|
|
// redirect to edit mode |
375
|
|
|
return $this->redirectTo($existingObject); |
376
|
|
|
} catch (ModelManagerException $e) { |
377
|
|
|
$this->handleModelManagerException($e); |
378
|
|
|
|
379
|
|
|
$isFormValid = false; |
380
|
|
|
} catch (LockException $e) { |
381
|
|
|
$this->addFlash('sonata_flash_error', $this->trans('flash_lock_error', [ |
382
|
|
|
'%name%' => $this->escapeHtml($this->admin->toString($existingObject)), |
383
|
|
|
'%link_start%' => '<a href="'.$this->admin->generateObjectUrl('edit', $existingObject).'">', |
384
|
|
|
'%link_end%' => '</a>', |
385
|
|
|
], 'SonataAdminBundle')); |
386
|
|
|
} |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
// show an error message if the form failed validation |
390
|
|
|
if (!$isFormValid) { |
391
|
|
|
if (!$this->isXmlHttpRequest()) { |
392
|
|
|
$this->addFlash( |
393
|
|
|
'sonata_flash_error', |
394
|
|
|
$this->trans( |
395
|
|
|
'flash_edit_error', |
396
|
|
|
['%name%' => $this->escapeHtml($this->admin->toString($existingObject))], |
397
|
|
|
'SonataAdminBundle' |
398
|
|
|
) |
399
|
|
|
); |
400
|
|
|
} |
401
|
|
|
} elseif ($this->isPreviewRequested()) { |
402
|
|
|
// enable the preview template if the form was valid and preview was requested |
403
|
|
|
$templateKey = 'preview'; |
404
|
|
|
$this->admin->getShow(); |
405
|
|
|
} |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
$formView = $form->createView(); |
409
|
|
|
// set the theme for the current Admin Form |
410
|
|
|
$this->setFormTheme($formView, $this->admin->getFormTheme()); |
411
|
|
|
|
412
|
|
|
// NEXT_MAJOR: Remove this line and use commented line below it instead |
413
|
|
|
$template = $this->admin->getTemplate($templateKey); |
|
|
|
|
414
|
|
|
// $template = $this->templateRegistry->getTemplate($templateKey); |
415
|
|
|
|
416
|
|
|
return $this->renderWithExtraParams($template, [ |
417
|
|
|
'action' => 'edit', |
418
|
|
|
'form' => $formView, |
419
|
|
|
'object' => $existingObject, |
420
|
|
|
'objectId' => $objectId, |
421
|
|
|
], null); |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
/** |
425
|
|
|
* Batch action. |
426
|
|
|
* |
427
|
|
|
* @throws NotFoundHttpException If the HTTP method is not POST |
428
|
|
|
* @throws \RuntimeException If the batch action is not defined |
429
|
|
|
* |
430
|
|
|
* @return Response|RedirectResponse |
431
|
|
|
*/ |
432
|
|
|
public function batchAction() |
433
|
|
|
{ |
434
|
|
|
$request = $this->getRequest(); |
435
|
|
|
$restMethod = $this->getRestMethod(); |
436
|
|
|
|
437
|
|
|
if ('POST' !== $restMethod) { |
438
|
|
|
throw $this->createNotFoundException(sprintf('Invalid request type "%s", POST expected', $restMethod)); |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
// check the csrf token |
442
|
|
|
$this->validateCsrfToken('sonata.batch'); |
443
|
|
|
|
444
|
|
|
$confirmation = $request->get('confirmation', false); |
445
|
|
|
|
446
|
|
|
if ($data = json_decode($request->get('data', ''), true)) { |
447
|
|
|
$action = $data['action']; |
448
|
|
|
$idx = $data['idx']; |
449
|
|
|
$allElements = $data['all_elements']; |
450
|
|
|
$request->request->replace(array_merge($request->request->all(), $data)); |
451
|
|
|
} else { |
452
|
|
|
$request->request->set('idx', $request->get('idx', [])); |
453
|
|
|
$request->request->set('all_elements', $request->get('all_elements', false)); |
454
|
|
|
|
455
|
|
|
$action = $request->get('action'); |
456
|
|
|
$idx = $request->get('idx'); |
457
|
|
|
$allElements = $request->get('all_elements'); |
458
|
|
|
$data = $request->request->all(); |
459
|
|
|
|
460
|
|
|
unset($data['_sonata_csrf_token']); |
461
|
|
|
} |
462
|
|
|
|
463
|
|
|
$batchActions = $this->admin->getBatchActions(); |
464
|
|
|
if (!\array_key_exists($action, $batchActions)) { |
465
|
|
|
throw new \RuntimeException(sprintf('The `%s` batch action is not defined', $action)); |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
$camelizedAction = Inflector::classify($action); |
469
|
|
|
$isRelevantAction = sprintf('batchAction%sIsRelevant', ucfirst($camelizedAction)); |
470
|
|
|
|
471
|
|
|
if (method_exists($this, $isRelevantAction)) { |
472
|
|
|
$nonRelevantMessage = $this->$isRelevantAction($idx, $allElements, $request); |
473
|
|
|
} else { |
474
|
|
|
$nonRelevantMessage = 0 !== \count($idx) || $allElements; // at least one item is selected |
475
|
|
|
} |
476
|
|
|
|
477
|
|
|
if (!$nonRelevantMessage) { // default non relevant message (if false of null) |
478
|
|
|
$nonRelevantMessage = 'flash_batch_empty'; |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
$datagrid = $this->admin->getDatagrid(); |
482
|
|
|
$datagrid->buildPager(); |
483
|
|
|
|
484
|
|
|
if (true !== $nonRelevantMessage) { |
485
|
|
|
$this->addFlash( |
486
|
|
|
'sonata_flash_info', |
487
|
|
|
$this->trans($nonRelevantMessage, [], 'SonataAdminBundle') |
488
|
|
|
); |
489
|
|
|
|
490
|
|
|
return $this->redirectToList(); |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
$askConfirmation = $batchActions[$action]['ask_confirmation'] ?? |
494
|
|
|
true; |
495
|
|
|
|
496
|
|
|
if ($askConfirmation && 'ok' !== $confirmation) { |
497
|
|
|
$actionLabel = $batchActions[$action]['label']; |
498
|
|
|
$batchTranslationDomain = $batchActions[$action]['translation_domain'] ?? |
499
|
|
|
$this->admin->getTranslationDomain(); |
500
|
|
|
|
501
|
|
|
$formView = $datagrid->getForm()->createView(); |
502
|
|
|
$this->setFormTheme($formView, $this->admin->getFilterTheme()); |
503
|
|
|
|
504
|
|
|
// NEXT_MAJOR: Remove these lines and use commented lines below them instead |
505
|
|
|
$template = !empty($batchActions[$action]['template']) ? |
506
|
|
|
$batchActions[$action]['template'] : |
507
|
|
|
$this->admin->getTemplate('batch_confirmation'); |
|
|
|
|
508
|
|
|
// $template = !empty($batchActions[$action]['template']) ? |
509
|
|
|
// $batchActions[$action]['template'] : |
510
|
|
|
// $this->templateRegistry->getTemplate('batch_confirmation'); |
511
|
|
|
|
512
|
|
|
return $this->renderWithExtraParams($template, [ |
513
|
|
|
'action' => 'list', |
514
|
|
|
'action_label' => $actionLabel, |
515
|
|
|
'batch_translation_domain' => $batchTranslationDomain, |
516
|
|
|
'datagrid' => $datagrid, |
517
|
|
|
'form' => $formView, |
518
|
|
|
'data' => $data, |
519
|
|
|
'csrf_token' => $this->getCsrfToken('sonata.batch'), |
520
|
|
|
], null); |
521
|
|
|
} |
522
|
|
|
|
523
|
|
|
// execute the action, batchActionXxxxx |
524
|
|
|
$finalAction = sprintf('batchAction%s', $camelizedAction); |
525
|
|
|
if (!method_exists($this, $finalAction)) { |
526
|
|
|
throw new \RuntimeException(sprintf('A `%s::%s` method must be callable', \get_class($this), $finalAction)); |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
$query = $datagrid->getQuery(); |
530
|
|
|
|
531
|
|
|
$query->setFirstResult(null); |
532
|
|
|
$query->setMaxResults(null); |
533
|
|
|
|
534
|
|
|
$this->admin->preBatchAction($action, $query, $idx, $allElements); |
535
|
|
|
|
536
|
|
|
if (\count($idx) > 0) { |
537
|
|
|
$this->admin->getModelManager()->addIdentifiersToQuery($this->admin->getClass(), $query, $idx); |
538
|
|
|
} elseif (!$allElements) { |
539
|
|
|
$this->addFlash( |
540
|
|
|
'sonata_flash_info', |
541
|
|
|
$this->trans('flash_batch_no_elements_processed', [], 'SonataAdminBundle') |
542
|
|
|
); |
543
|
|
|
|
544
|
|
|
return $this->redirectToList(); |
545
|
|
|
} |
546
|
|
|
|
547
|
|
|
return $this->$finalAction($query, $request); |
548
|
|
|
} |
549
|
|
|
|
550
|
|
|
/** |
551
|
|
|
* Create action. |
552
|
|
|
* |
553
|
|
|
* @throws AccessDeniedException If access is not granted |
554
|
|
|
* @throws \RuntimeException If no editable field is defined |
555
|
|
|
* |
556
|
|
|
* @return Response |
557
|
|
|
*/ |
558
|
|
|
public function createAction() |
559
|
|
|
{ |
560
|
|
|
$request = $this->getRequest(); |
561
|
|
|
// the key used to lookup the template |
562
|
|
|
$templateKey = 'edit'; |
563
|
|
|
|
564
|
|
|
$this->admin->checkAccess('create'); |
565
|
|
|
|
566
|
|
|
$class = new \ReflectionClass($this->admin->hasActiveSubClass() ? $this->admin->getActiveSubClass() : $this->admin->getClass()); |
567
|
|
|
|
568
|
|
|
if ($class->isAbstract()) { |
569
|
|
|
return $this->renderWithExtraParams( |
570
|
|
|
'@SonataAdmin/CRUD/select_subclass.html.twig', |
571
|
|
|
[ |
572
|
|
|
'base_template' => $this->getBaseTemplate(), |
573
|
|
|
'admin' => $this->admin, |
574
|
|
|
'action' => 'create', |
575
|
|
|
], |
576
|
|
|
null |
577
|
|
|
); |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
$newObject = $this->admin->getNewInstance(); |
581
|
|
|
|
582
|
|
|
$preResponse = $this->preCreate($request, $newObject); |
583
|
|
|
if (null !== $preResponse) { |
584
|
|
|
return $preResponse; |
585
|
|
|
} |
586
|
|
|
|
587
|
|
|
$this->admin->setSubject($newObject); |
588
|
|
|
|
589
|
|
|
$form = $this->admin->getForm(); |
590
|
|
|
|
591
|
|
|
if (!\is_array($fields = $form->all()) || 0 === \count($fields)) { |
592
|
|
|
throw new \RuntimeException( |
593
|
|
|
'No editable field defined. Did you forget to implement the "configureFormFields" method?' |
594
|
|
|
); |
595
|
|
|
} |
596
|
|
|
|
597
|
|
|
$form->setData($newObject); |
598
|
|
|
$form->handleRequest($request); |
599
|
|
|
|
600
|
|
|
if ($form->isSubmitted()) { |
601
|
|
|
$isFormValid = $form->isValid(); |
602
|
|
|
|
603
|
|
|
// persist if the form was valid and if in preview mode the preview was approved |
604
|
|
|
if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) { |
605
|
|
|
$submittedObject = $form->getData(); |
606
|
|
|
$this->admin->setSubject($submittedObject); |
607
|
|
|
$this->admin->checkAccess('create', $submittedObject); |
608
|
|
|
|
609
|
|
|
try { |
610
|
|
|
$newObject = $this->admin->create($submittedObject); |
611
|
|
|
|
612
|
|
|
if ($this->isXmlHttpRequest()) { |
613
|
|
|
return $this->renderJson([ |
614
|
|
|
'result' => 'ok', |
615
|
|
|
'objectId' => $this->admin->getNormalizedIdentifier($newObject), |
616
|
|
|
'objectName' => $this->escapeHtml($this->admin->toString($newObject)), |
617
|
|
|
], 200, []); |
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
$this->addFlash( |
621
|
|
|
'sonata_flash_success', |
622
|
|
|
$this->trans( |
623
|
|
|
'flash_create_success', |
624
|
|
|
['%name%' => $this->escapeHtml($this->admin->toString($newObject))], |
625
|
|
|
'SonataAdminBundle' |
626
|
|
|
) |
627
|
|
|
); |
628
|
|
|
|
629
|
|
|
// redirect to edit mode |
630
|
|
|
return $this->redirectTo($newObject); |
631
|
|
|
} catch (ModelManagerException $e) { |
632
|
|
|
$this->handleModelManagerException($e); |
633
|
|
|
|
634
|
|
|
$isFormValid = false; |
635
|
|
|
} |
636
|
|
|
} |
637
|
|
|
|
638
|
|
|
// show an error message if the form failed validation |
639
|
|
|
if (!$isFormValid) { |
640
|
|
|
if (!$this->isXmlHttpRequest()) { |
641
|
|
|
$this->addFlash( |
642
|
|
|
'sonata_flash_error', |
643
|
|
|
$this->trans( |
644
|
|
|
'flash_create_error', |
645
|
|
|
['%name%' => $this->escapeHtml($this->admin->toString($newObject))], |
646
|
|
|
'SonataAdminBundle' |
647
|
|
|
) |
648
|
|
|
); |
649
|
|
|
} |
650
|
|
|
} elseif ($this->isPreviewRequested()) { |
651
|
|
|
// pick the preview template if the form was valid and preview was requested |
652
|
|
|
$templateKey = 'preview'; |
653
|
|
|
$this->admin->getShow(); |
654
|
|
|
} |
655
|
|
|
} |
656
|
|
|
|
657
|
|
|
$formView = $form->createView(); |
658
|
|
|
// set the theme for the current Admin Form |
659
|
|
|
$this->setFormTheme($formView, $this->admin->getFormTheme()); |
660
|
|
|
|
661
|
|
|
// NEXT_MAJOR: Remove this line and use commented line below it instead |
662
|
|
|
$template = $this->admin->getTemplate($templateKey); |
|
|
|
|
663
|
|
|
// $template = $this->templateRegistry->getTemplate($templateKey); |
664
|
|
|
|
665
|
|
|
return $this->renderWithExtraParams($template, [ |
666
|
|
|
'action' => 'create', |
667
|
|
|
'form' => $formView, |
668
|
|
|
'object' => $newObject, |
669
|
|
|
'objectId' => null, |
670
|
|
|
], null); |
671
|
|
|
} |
672
|
|
|
|
673
|
|
|
/** |
674
|
|
|
* Show action. |
675
|
|
|
* |
676
|
|
|
* @param int|string|null $id |
677
|
|
|
* |
678
|
|
|
* @throws NotFoundHttpException If the object does not exist |
679
|
|
|
* @throws AccessDeniedException If access is not granted |
680
|
|
|
* |
681
|
|
|
* @return Response |
682
|
|
|
*/ |
683
|
|
|
public function showAction($id = null) |
|
|
|
|
684
|
|
|
{ |
685
|
|
|
$request = $this->getRequest(); |
686
|
|
|
$id = $request->get($this->admin->getIdParameter()); |
687
|
|
|
|
688
|
|
|
$object = $this->admin->getObject($id); |
689
|
|
|
|
690
|
|
|
if (!$object) { |
691
|
|
|
throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id)); |
692
|
|
|
} |
693
|
|
|
|
694
|
|
|
$this->checkParentChildAssociation($request, $object); |
695
|
|
|
|
696
|
|
|
$this->admin->checkAccess('show', $object); |
697
|
|
|
|
698
|
|
|
$preResponse = $this->preShow($request, $object); |
699
|
|
|
if (null !== $preResponse) { |
700
|
|
|
return $preResponse; |
701
|
|
|
} |
702
|
|
|
|
703
|
|
|
$this->admin->setSubject($object); |
704
|
|
|
|
705
|
|
|
$fields = $this->admin->getShow(); |
706
|
|
|
\assert($fields instanceof FieldDescriptionCollection); |
707
|
|
|
|
708
|
|
|
// NEXT_MAJOR: replace deprecation with exception |
709
|
|
|
if (!\is_array($fields->getElements()) || 0 === $fields->count()) { |
710
|
|
|
@trigger_error( |
|
|
|
|
711
|
|
|
'Calling this method without implementing "configureShowFields"' |
712
|
|
|
.' is not supported since 3.40.0' |
713
|
|
|
.' and will no longer be possible in 4.0', |
714
|
|
|
E_USER_DEPRECATED |
715
|
|
|
); |
716
|
|
|
} |
717
|
|
|
|
718
|
|
|
// NEXT_MAJOR: Remove this line and use commented line below it instead |
719
|
|
|
$template = $this->admin->getTemplate('show'); |
|
|
|
|
720
|
|
|
//$template = $this->templateRegistry->getTemplate('show'); |
721
|
|
|
|
722
|
|
|
return $this->renderWithExtraParams($template, [ |
723
|
|
|
'action' => 'show', |
724
|
|
|
'object' => $object, |
725
|
|
|
'elements' => $fields, |
726
|
|
|
], null); |
727
|
|
|
} |
728
|
|
|
|
729
|
|
|
/** |
730
|
|
|
* Show history revisions for object. |
731
|
|
|
* |
732
|
|
|
* @param int|string|null $id |
733
|
|
|
* |
734
|
|
|
* @throws AccessDeniedException If access is not granted |
735
|
|
|
* @throws NotFoundHttpException If the object does not exist or the audit reader is not available |
736
|
|
|
* |
737
|
|
|
* @return Response |
738
|
|
|
*/ |
739
|
|
|
public function historyAction($id = null) |
|
|
|
|
740
|
|
|
{ |
741
|
|
|
$request = $this->getRequest(); |
742
|
|
|
$id = $request->get($this->admin->getIdParameter()); |
743
|
|
|
|
744
|
|
|
$object = $this->admin->getObject($id); |
745
|
|
|
|
746
|
|
|
if (!$object) { |
747
|
|
|
throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id)); |
748
|
|
|
} |
749
|
|
|
|
750
|
|
|
$this->admin->checkAccess('history', $object); |
751
|
|
|
|
752
|
|
|
$manager = $this->get('sonata.admin.audit.manager'); |
753
|
|
|
|
754
|
|
|
if (!$manager->hasReader($this->admin->getClass())) { |
755
|
|
|
throw $this->createNotFoundException( |
756
|
|
|
sprintf( |
757
|
|
|
'unable to find the audit reader for class : %s', |
758
|
|
|
$this->admin->getClass() |
759
|
|
|
) |
760
|
|
|
); |
761
|
|
|
} |
762
|
|
|
|
763
|
|
|
$reader = $manager->getReader($this->admin->getClass()); |
764
|
|
|
|
765
|
|
|
$revisions = $reader->findRevisions($this->admin->getClass(), $id); |
766
|
|
|
|
767
|
|
|
// NEXT_MAJOR: Remove this line and use commented line below it instead |
768
|
|
|
$template = $this->admin->getTemplate('history'); |
|
|
|
|
769
|
|
|
// $template = $this->templateRegistry->getTemplate('history'); |
770
|
|
|
|
771
|
|
|
return $this->renderWithExtraParams($template, [ |
772
|
|
|
'action' => 'history', |
773
|
|
|
'object' => $object, |
774
|
|
|
'revisions' => $revisions, |
775
|
|
|
'currentRevision' => $revisions ? current($revisions) : false, |
776
|
|
|
], null); |
777
|
|
|
} |
778
|
|
|
|
779
|
|
|
/** |
780
|
|
|
* View history revision of object. |
781
|
|
|
* |
782
|
|
|
* @param int|string|null $id |
783
|
|
|
* @param string|null $revision |
784
|
|
|
* |
785
|
|
|
* @throws AccessDeniedException If access is not granted |
786
|
|
|
* @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available |
787
|
|
|
* |
788
|
|
|
* @return Response |
789
|
|
|
*/ |
790
|
|
|
public function historyViewRevisionAction($id = null, $revision = null) |
|
|
|
|
791
|
|
|
{ |
792
|
|
|
$request = $this->getRequest(); |
793
|
|
|
$id = $request->get($this->admin->getIdParameter()); |
794
|
|
|
|
795
|
|
|
$object = $this->admin->getObject($id); |
796
|
|
|
|
797
|
|
|
if (!$object) { |
798
|
|
|
throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id)); |
799
|
|
|
} |
800
|
|
|
|
801
|
|
|
$this->admin->checkAccess('historyViewRevision', $object); |
802
|
|
|
|
803
|
|
|
$manager = $this->get('sonata.admin.audit.manager'); |
804
|
|
|
|
805
|
|
|
if (!$manager->hasReader($this->admin->getClass())) { |
806
|
|
|
throw $this->createNotFoundException( |
807
|
|
|
sprintf( |
808
|
|
|
'unable to find the audit reader for class : %s', |
809
|
|
|
$this->admin->getClass() |
810
|
|
|
) |
811
|
|
|
); |
812
|
|
|
} |
813
|
|
|
|
814
|
|
|
$reader = $manager->getReader($this->admin->getClass()); |
815
|
|
|
|
816
|
|
|
// retrieve the revisioned object |
817
|
|
|
$object = $reader->find($this->admin->getClass(), $id, $revision); |
818
|
|
|
|
819
|
|
|
if (!$object) { |
820
|
|
|
throw $this->createNotFoundException( |
821
|
|
|
sprintf( |
822
|
|
|
'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`', |
823
|
|
|
$id, |
824
|
|
|
$revision, |
825
|
|
|
$this->admin->getClass() |
826
|
|
|
) |
827
|
|
|
); |
828
|
|
|
} |
829
|
|
|
|
830
|
|
|
$this->admin->setSubject($object); |
831
|
|
|
|
832
|
|
|
// NEXT_MAJOR: Remove this line and use commented line below it instead |
833
|
|
|
$template = $this->admin->getTemplate('show'); |
|
|
|
|
834
|
|
|
// $template = $this->templateRegistry->getTemplate('show'); |
835
|
|
|
|
836
|
|
|
return $this->renderWithExtraParams($template, [ |
837
|
|
|
'action' => 'show', |
838
|
|
|
'object' => $object, |
839
|
|
|
'elements' => $this->admin->getShow(), |
840
|
|
|
], null); |
841
|
|
|
} |
842
|
|
|
|
843
|
|
|
/** |
844
|
|
|
* Compare history revisions of object. |
845
|
|
|
* |
846
|
|
|
* @param int|string|null $id |
847
|
|
|
* @param int|string|null $base_revision |
848
|
|
|
* @param int|string|null $compare_revision |
849
|
|
|
* |
850
|
|
|
* @throws AccessDeniedException If access is not granted |
851
|
|
|
* @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available |
852
|
|
|
* |
853
|
|
|
* @return Response |
854
|
|
|
*/ |
855
|
|
|
public function historyCompareRevisionsAction($id = null, $base_revision = null, $compare_revision = null) |
|
|
|
|
856
|
|
|
{ |
857
|
|
|
$request = $this->getRequest(); |
858
|
|
|
|
859
|
|
|
$this->admin->checkAccess('historyCompareRevisions'); |
860
|
|
|
|
861
|
|
|
$id = $request->get($this->admin->getIdParameter()); |
862
|
|
|
|
863
|
|
|
$object = $this->admin->getObject($id); |
864
|
|
|
|
865
|
|
|
if (!$object) { |
866
|
|
|
throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id)); |
867
|
|
|
} |
868
|
|
|
|
869
|
|
|
$manager = $this->get('sonata.admin.audit.manager'); |
870
|
|
|
|
871
|
|
|
if (!$manager->hasReader($this->admin->getClass())) { |
872
|
|
|
throw $this->createNotFoundException( |
873
|
|
|
sprintf( |
874
|
|
|
'unable to find the audit reader for class : %s', |
875
|
|
|
$this->admin->getClass() |
876
|
|
|
) |
877
|
|
|
); |
878
|
|
|
} |
879
|
|
|
|
880
|
|
|
$reader = $manager->getReader($this->admin->getClass()); |
881
|
|
|
|
882
|
|
|
// retrieve the base revision |
883
|
|
|
$base_object = $reader->find($this->admin->getClass(), $id, $base_revision); |
884
|
|
|
if (!$base_object) { |
885
|
|
|
throw $this->createNotFoundException( |
886
|
|
|
sprintf( |
887
|
|
|
'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`', |
888
|
|
|
$id, |
889
|
|
|
$base_revision, |
890
|
|
|
$this->admin->getClass() |
891
|
|
|
) |
892
|
|
|
); |
893
|
|
|
} |
894
|
|
|
|
895
|
|
|
// retrieve the compare revision |
896
|
|
|
$compare_object = $reader->find($this->admin->getClass(), $id, $compare_revision); |
897
|
|
|
if (!$compare_object) { |
898
|
|
|
throw $this->createNotFoundException( |
899
|
|
|
sprintf( |
900
|
|
|
'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`', |
901
|
|
|
$id, |
902
|
|
|
$compare_revision, |
903
|
|
|
$this->admin->getClass() |
904
|
|
|
) |
905
|
|
|
); |
906
|
|
|
} |
907
|
|
|
|
908
|
|
|
$this->admin->setSubject($base_object); |
909
|
|
|
|
910
|
|
|
// NEXT_MAJOR: Remove this line and use commented line below it instead |
911
|
|
|
$template = $this->admin->getTemplate('show_compare'); |
|
|
|
|
912
|
|
|
// $template = $this->templateRegistry->getTemplate('show_compare'); |
913
|
|
|
|
914
|
|
|
return $this->renderWithExtraParams($template, [ |
915
|
|
|
'action' => 'show', |
916
|
|
|
'object' => $base_object, |
917
|
|
|
'object_compare' => $compare_object, |
918
|
|
|
'elements' => $this->admin->getShow(), |
919
|
|
|
], null); |
920
|
|
|
} |
921
|
|
|
|
922
|
|
|
/** |
923
|
|
|
* Export data to specified format. |
924
|
|
|
* |
925
|
|
|
* @throws AccessDeniedException If access is not granted |
926
|
|
|
* @throws \RuntimeException If the export format is invalid |
927
|
|
|
* |
928
|
|
|
* @return Response |
929
|
|
|
*/ |
930
|
|
|
public function exportAction(Request $request) |
931
|
|
|
{ |
932
|
|
|
$this->admin->checkAccess('export'); |
933
|
|
|
|
934
|
|
|
$format = $request->get('format'); |
935
|
|
|
|
936
|
|
|
// NEXT_MAJOR: remove the check |
937
|
|
|
if (!$this->has('sonata.admin.admin_exporter')) { |
938
|
|
|
@trigger_error( |
|
|
|
|
939
|
|
|
'Not registering the exporter bundle is deprecated since version 3.14.' |
940
|
|
|
.' You must register it to be able to use the export action in 4.0.', |
941
|
|
|
E_USER_DEPRECATED |
942
|
|
|
); |
943
|
|
|
$allowedExportFormats = (array) $this->admin->getExportFormats(); |
944
|
|
|
|
945
|
|
|
$class = (string) $this->admin->getClass(); |
946
|
|
|
$filename = sprintf( |
947
|
|
|
'export_%s_%s.%s', |
948
|
|
|
strtolower((string) substr($class, strripos($class, '\\') + 1)), |
949
|
|
|
date('Y_m_d_H_i_s', strtotime('now')), |
950
|
|
|
$format |
951
|
|
|
); |
952
|
|
|
$exporter = $this->get('sonata.admin.exporter'); |
953
|
|
|
} else { |
954
|
|
|
$adminExporter = $this->get('sonata.admin.admin_exporter'); |
955
|
|
|
$allowedExportFormats = $adminExporter->getAvailableFormats($this->admin); |
956
|
|
|
$filename = $adminExporter->getExportFilename($this->admin, $format); |
957
|
|
|
$exporter = $this->get('sonata.exporter.exporter'); |
958
|
|
|
} |
959
|
|
|
|
960
|
|
|
if (!\in_array($format, $allowedExportFormats, true)) { |
961
|
|
|
throw new \RuntimeException( |
962
|
|
|
sprintf( |
963
|
|
|
'Export in format `%s` is not allowed for class: `%s`. Allowed formats are: `%s`', |
964
|
|
|
$format, |
965
|
|
|
$this->admin->getClass(), |
966
|
|
|
implode(', ', $allowedExportFormats) |
967
|
|
|
) |
968
|
|
|
); |
969
|
|
|
} |
970
|
|
|
|
971
|
|
|
return $exporter->getResponse( |
972
|
|
|
$format, |
973
|
|
|
$filename, |
974
|
|
|
$this->admin->getDataSourceIterator() |
975
|
|
|
); |
976
|
|
|
} |
977
|
|
|
|
978
|
|
|
/** |
979
|
|
|
* Returns the Response object associated to the acl action. |
980
|
|
|
* |
981
|
|
|
* @param int|string|null $id |
982
|
|
|
* |
983
|
|
|
* @throws AccessDeniedException If access is not granted |
984
|
|
|
* @throws NotFoundHttpException If the object does not exist or the ACL is not enabled |
985
|
|
|
* |
986
|
|
|
* @return Response|RedirectResponse |
987
|
|
|
*/ |
988
|
|
|
public function aclAction($id = null) |
|
|
|
|
989
|
|
|
{ |
990
|
|
|
$request = $this->getRequest(); |
991
|
|
|
|
992
|
|
|
if (!$this->admin->isAclEnabled()) { |
993
|
|
|
throw $this->createNotFoundException('ACL are not enabled for this admin'); |
994
|
|
|
} |
995
|
|
|
|
996
|
|
|
$id = $request->get($this->admin->getIdParameter()); |
997
|
|
|
|
998
|
|
|
$object = $this->admin->getObject($id); |
999
|
|
|
|
1000
|
|
|
if (!$object) { |
1001
|
|
|
throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id)); |
1002
|
|
|
} |
1003
|
|
|
|
1004
|
|
|
$this->admin->checkAccess('acl', $object); |
1005
|
|
|
|
1006
|
|
|
$this->admin->setSubject($object); |
1007
|
|
|
$aclUsers = $this->getAclUsers(); |
1008
|
|
|
$aclRoles = $this->getAclRoles(); |
1009
|
|
|
|
1010
|
|
|
$adminObjectAclManipulator = $this->get('sonata.admin.object.manipulator.acl.admin'); |
1011
|
|
|
$adminObjectAclData = new AdminObjectAclData( |
1012
|
|
|
$this->admin, |
1013
|
|
|
$object, |
1014
|
|
|
$aclUsers, |
1015
|
|
|
$adminObjectAclManipulator->getMaskBuilderClass(), |
1016
|
|
|
$aclRoles |
1017
|
|
|
); |
1018
|
|
|
|
1019
|
|
|
$aclUsersForm = $adminObjectAclManipulator->createAclUsersForm($adminObjectAclData); |
1020
|
|
|
$aclRolesForm = $adminObjectAclManipulator->createAclRolesForm($adminObjectAclData); |
1021
|
|
|
|
1022
|
|
|
if ('POST' === $request->getMethod()) { |
1023
|
|
|
if ($request->request->has(AdminObjectAclManipulator::ACL_USERS_FORM_NAME)) { |
1024
|
|
|
$form = $aclUsersForm; |
1025
|
|
|
$updateMethod = 'updateAclUsers'; |
1026
|
|
|
} elseif ($request->request->has(AdminObjectAclManipulator::ACL_ROLES_FORM_NAME)) { |
1027
|
|
|
$form = $aclRolesForm; |
1028
|
|
|
$updateMethod = 'updateAclRoles'; |
1029
|
|
|
} |
1030
|
|
|
|
1031
|
|
|
if (isset($form)) { |
1032
|
|
|
$form->handleRequest($request); |
1033
|
|
|
|
1034
|
|
|
if ($form->isValid()) { |
1035
|
|
|
$adminObjectAclManipulator->$updateMethod($adminObjectAclData); |
|
|
|
|
1036
|
|
|
$this->addFlash( |
1037
|
|
|
'sonata_flash_success', |
1038
|
|
|
$this->trans('flash_acl_edit_success', [], 'SonataAdminBundle') |
1039
|
|
|
); |
1040
|
|
|
|
1041
|
|
|
return new RedirectResponse($this->admin->generateObjectUrl('acl', $object)); |
1042
|
|
|
} |
1043
|
|
|
} |
1044
|
|
|
} |
1045
|
|
|
|
1046
|
|
|
// NEXT_MAJOR: Remove this line and use commented line below it instead |
1047
|
|
|
$template = $this->admin->getTemplate('acl'); |
|
|
|
|
1048
|
|
|
// $template = $this->templateRegistry->getTemplate('acl'); |
1049
|
|
|
|
1050
|
|
|
return $this->renderWithExtraParams($template, [ |
1051
|
|
|
'action' => 'acl', |
1052
|
|
|
'permissions' => $adminObjectAclData->getUserPermissions(), |
1053
|
|
|
'object' => $object, |
1054
|
|
|
'users' => $aclUsers, |
1055
|
|
|
'roles' => $aclRoles, |
1056
|
|
|
'aclUsersForm' => $aclUsersForm->createView(), |
1057
|
|
|
'aclRolesForm' => $aclRolesForm->createView(), |
1058
|
|
|
], null); |
1059
|
|
|
} |
1060
|
|
|
|
1061
|
|
|
/** |
1062
|
|
|
* @return Request |
1063
|
|
|
*/ |
1064
|
|
|
public function getRequest() |
1065
|
|
|
{ |
1066
|
|
|
return $this->container->get('request_stack')->getCurrentRequest(); |
1067
|
|
|
} |
1068
|
|
|
|
1069
|
|
|
/** |
1070
|
|
|
* Gets a container configuration parameter by its name. |
1071
|
|
|
* |
1072
|
|
|
* @param string $name The parameter name |
1073
|
|
|
* |
1074
|
|
|
* @return mixed |
1075
|
|
|
*/ |
1076
|
|
|
protected function getParameter($name) |
1077
|
|
|
{ |
1078
|
|
|
return $this->container->getParameter($name); |
|
|
|
|
1079
|
|
|
} |
1080
|
|
|
|
1081
|
|
|
/** |
1082
|
|
|
* Render JSON. |
1083
|
|
|
* |
1084
|
|
|
* @param mixed $data |
1085
|
|
|
* @param int $status |
1086
|
|
|
* @param array $headers |
1087
|
|
|
* |
1088
|
|
|
* @return Response with json encoded data |
1089
|
|
|
*/ |
1090
|
|
|
protected function renderJson($data, $status = 200, $headers = []) |
1091
|
|
|
{ |
1092
|
|
|
return new JsonResponse($data, $status, $headers); |
1093
|
|
|
} |
1094
|
|
|
|
1095
|
|
|
/** |
1096
|
|
|
* Returns true if the request is a XMLHttpRequest. |
1097
|
|
|
* |
1098
|
|
|
* @return bool True if the request is an XMLHttpRequest, false otherwise |
1099
|
|
|
*/ |
1100
|
|
|
protected function isXmlHttpRequest() |
1101
|
|
|
{ |
1102
|
|
|
$request = $this->getRequest(); |
1103
|
|
|
|
1104
|
|
|
return $request->isXmlHttpRequest() || $request->get('_xml_http_request'); |
1105
|
|
|
} |
1106
|
|
|
|
1107
|
|
|
/** |
1108
|
|
|
* Returns the correct RESTful verb, given either by the request itself or |
1109
|
|
|
* via the "_method" parameter. |
1110
|
|
|
* |
1111
|
|
|
* @return string HTTP method, either |
1112
|
|
|
*/ |
1113
|
|
|
protected function getRestMethod() |
1114
|
|
|
{ |
1115
|
|
|
$request = $this->getRequest(); |
1116
|
|
|
|
1117
|
|
|
if (Request::getHttpMethodParameterOverride() || !$request->request->has('_method')) { |
1118
|
|
|
return $request->getMethod(); |
1119
|
|
|
} |
1120
|
|
|
|
1121
|
|
|
return $request->request->get('_method'); |
1122
|
|
|
} |
1123
|
|
|
|
1124
|
|
|
/** |
1125
|
|
|
* Contextualize the admin class depends on the current request. |
1126
|
|
|
* |
1127
|
|
|
* @throws \RuntimeException |
1128
|
|
|
*/ |
1129
|
|
|
protected function configure(): void |
1130
|
|
|
{ |
1131
|
|
|
$request = $this->getRequest(); |
1132
|
|
|
|
1133
|
|
|
$adminCode = $request->get('_sonata_admin'); |
1134
|
|
|
|
1135
|
|
|
if (!$adminCode) { |
1136
|
|
|
throw new \RuntimeException(sprintf( |
1137
|
|
|
'There is no `_sonata_admin` defined for the controller `%s` and the current route `%s`', |
1138
|
|
|
\get_class($this), |
1139
|
|
|
$request->get('_route') |
1140
|
|
|
)); |
1141
|
|
|
} |
1142
|
|
|
|
1143
|
|
|
$this->admin = $this->container->get('sonata.admin.pool')->getAdminByAdminCode($adminCode); |
1144
|
|
|
|
1145
|
|
|
if (!$this->admin) { |
1146
|
|
|
throw new \RuntimeException(sprintf( |
1147
|
|
|
'Unable to find the admin class related to the current controller (%s)', |
1148
|
|
|
\get_class($this) |
1149
|
|
|
)); |
1150
|
|
|
} |
1151
|
|
|
|
1152
|
|
|
$this->templateRegistry = $this->container->get($this->admin->getCode().'.template_registry'); |
1153
|
|
|
if (!$this->templateRegistry instanceof TemplateRegistryInterface) { |
1154
|
|
|
throw new \RuntimeException(sprintf( |
1155
|
|
|
'Unable to find the template registry related to the current admin (%s)', |
1156
|
|
|
$this->admin->getCode() |
1157
|
|
|
)); |
1158
|
|
|
} |
1159
|
|
|
|
1160
|
|
|
$rootAdmin = $this->admin; |
1161
|
|
|
|
1162
|
|
|
while ($rootAdmin->isChild()) { |
1163
|
|
|
$rootAdmin->setCurrentChild(true); |
1164
|
|
|
$rootAdmin = $rootAdmin->getParent(); |
1165
|
|
|
} |
1166
|
|
|
|
1167
|
|
|
$rootAdmin->setRequest($request); |
1168
|
|
|
|
1169
|
|
|
if ($request->get('uniqid')) { |
1170
|
|
|
$this->admin->setUniqid($request->get('uniqid')); |
1171
|
|
|
} |
1172
|
|
|
} |
1173
|
|
|
|
1174
|
|
|
/** |
1175
|
|
|
* Proxy for the logger service of the container. |
1176
|
|
|
* If no such service is found, a NullLogger is returned. |
1177
|
|
|
* |
1178
|
|
|
* @return LoggerInterface |
1179
|
|
|
*/ |
1180
|
|
|
protected function getLogger() |
1181
|
|
|
{ |
1182
|
|
|
if ($this->container->has('logger')) { |
1183
|
|
|
$logger = $this->container->get('logger'); |
1184
|
|
|
\assert($logger instanceof LoggerInterface); |
1185
|
|
|
|
1186
|
|
|
return $logger; |
1187
|
|
|
} |
1188
|
|
|
|
1189
|
|
|
return new NullLogger(); |
1190
|
|
|
} |
1191
|
|
|
|
1192
|
|
|
/** |
1193
|
|
|
* Returns the base template name. |
1194
|
|
|
* |
1195
|
|
|
* @return string The template name |
1196
|
|
|
*/ |
1197
|
|
|
protected function getBaseTemplate() |
1198
|
|
|
{ |
1199
|
|
|
if ($this->isXmlHttpRequest()) { |
1200
|
|
|
// NEXT_MAJOR: Remove this line and use commented line below it instead |
1201
|
|
|
return $this->admin->getTemplate('ajax'); |
|
|
|
|
1202
|
|
|
// return $this->templateRegistry->getTemplate('ajax'); |
1203
|
|
|
} |
1204
|
|
|
|
1205
|
|
|
// NEXT_MAJOR: Remove this line and use commented line below it instead |
1206
|
|
|
return $this->admin->getTemplate('layout'); |
|
|
|
|
1207
|
|
|
// return $this->templateRegistry->getTemplate('layout'); |
1208
|
|
|
} |
1209
|
|
|
|
1210
|
|
|
/** |
1211
|
|
|
* @throws \Exception |
1212
|
|
|
*/ |
1213
|
|
|
protected function handleModelManagerException(\Exception $e): void |
1214
|
|
|
{ |
1215
|
|
|
if ($this->get('kernel')->isDebug()) { |
1216
|
|
|
throw $e; |
1217
|
|
|
} |
1218
|
|
|
|
1219
|
|
|
$context = ['exception' => $e]; |
1220
|
|
|
if ($e->getPrevious()) { |
1221
|
|
|
$context['previous_exception_message'] = $e->getPrevious()->getMessage(); |
1222
|
|
|
} |
1223
|
|
|
$this->getLogger()->error($e->getMessage(), $context); |
1224
|
|
|
} |
1225
|
|
|
|
1226
|
|
|
/** |
1227
|
|
|
* Redirect the user depend on this choice. |
1228
|
|
|
* |
1229
|
|
|
* @param object $object |
1230
|
|
|
* |
1231
|
|
|
* @return RedirectResponse |
1232
|
|
|
*/ |
1233
|
|
|
protected function redirectTo($object) |
1234
|
|
|
{ |
1235
|
|
|
$request = $this->getRequest(); |
1236
|
|
|
|
1237
|
|
|
$url = false; |
1238
|
|
|
|
1239
|
|
|
if (null !== $request->get('btn_update_and_list')) { |
1240
|
|
|
return $this->redirectToList(); |
1241
|
|
|
} |
1242
|
|
|
if (null !== $request->get('btn_create_and_list')) { |
1243
|
|
|
return $this->redirectToList(); |
1244
|
|
|
} |
1245
|
|
|
|
1246
|
|
|
if (null !== $request->get('btn_create_and_create')) { |
1247
|
|
|
$params = []; |
1248
|
|
|
if ($this->admin->hasActiveSubClass()) { |
1249
|
|
|
$params['subclass'] = $request->get('subclass'); |
|
|
|
|
1250
|
|
|
} |
1251
|
|
|
$url = $this->admin->generateUrl('create', $params); |
1252
|
|
|
} |
1253
|
|
|
|
1254
|
|
|
if ('DELETE' === $this->getRestMethod()) { |
1255
|
|
|
return $this->redirectToList(); |
1256
|
|
|
} |
1257
|
|
|
|
1258
|
|
|
if (!$url) { |
|
|
|
|
1259
|
|
|
foreach (['edit', 'show'] as $route) { |
1260
|
|
|
if ($this->admin->hasRoute($route) && $this->admin->hasAccess($route, $object)) { |
1261
|
|
|
$url = $this->admin->generateObjectUrl( |
1262
|
|
|
$route, |
1263
|
|
|
$object, |
1264
|
|
|
$request->request->has('_tab') ? ['_tab' => $request->request->get('_tab')] : [] |
1265
|
|
|
); |
1266
|
|
|
|
1267
|
|
|
break; |
1268
|
|
|
} |
1269
|
|
|
} |
1270
|
|
|
} |
1271
|
|
|
|
1272
|
|
|
if (!$url) { |
|
|
|
|
1273
|
|
|
return $this->redirectToList(); |
1274
|
|
|
} |
1275
|
|
|
|
1276
|
|
|
return new RedirectResponse($url); |
1277
|
|
|
} |
1278
|
|
|
|
1279
|
|
|
/** |
1280
|
|
|
* Redirects the user to the list view. |
1281
|
|
|
* |
1282
|
|
|
* @return RedirectResponse |
1283
|
|
|
*/ |
1284
|
|
|
final protected function redirectToList() |
1285
|
|
|
{ |
1286
|
|
|
$parameters = []; |
1287
|
|
|
|
1288
|
|
|
if ($filter = $this->admin->getFilterParameters()) { |
1289
|
|
|
$parameters['filter'] = $filter; |
1290
|
|
|
} |
1291
|
|
|
|
1292
|
|
|
return $this->redirect($this->admin->generateUrl('list', $parameters)); |
1293
|
|
|
} |
1294
|
|
|
|
1295
|
|
|
/** |
1296
|
|
|
* Returns true if the preview is requested to be shown. |
1297
|
|
|
* |
1298
|
|
|
* @return bool |
1299
|
|
|
*/ |
1300
|
|
|
protected function isPreviewRequested() |
1301
|
|
|
{ |
1302
|
|
|
$request = $this->getRequest(); |
1303
|
|
|
|
1304
|
|
|
return null !== $request->get('btn_preview'); |
1305
|
|
|
} |
1306
|
|
|
|
1307
|
|
|
/** |
1308
|
|
|
* Returns true if the preview has been approved. |
1309
|
|
|
* |
1310
|
|
|
* @return bool |
1311
|
|
|
*/ |
1312
|
|
|
protected function isPreviewApproved() |
1313
|
|
|
{ |
1314
|
|
|
$request = $this->getRequest(); |
1315
|
|
|
|
1316
|
|
|
return null !== $request->get('btn_preview_approve'); |
1317
|
|
|
} |
1318
|
|
|
|
1319
|
|
|
/** |
1320
|
|
|
* Returns true if the request is in the preview workflow. |
1321
|
|
|
* |
1322
|
|
|
* That means either a preview is requested or the preview has already been shown |
1323
|
|
|
* and it got approved/declined. |
1324
|
|
|
* |
1325
|
|
|
* @return bool |
1326
|
|
|
*/ |
1327
|
|
|
protected function isInPreviewMode() |
1328
|
|
|
{ |
1329
|
|
|
return $this->admin->supportsPreviewMode() |
1330
|
|
|
&& ($this->isPreviewRequested() |
1331
|
|
|
|| $this->isPreviewApproved() |
1332
|
|
|
|| $this->isPreviewDeclined()); |
1333
|
|
|
} |
1334
|
|
|
|
1335
|
|
|
/** |
1336
|
|
|
* Returns true if the preview has been declined. |
1337
|
|
|
* |
1338
|
|
|
* @return bool |
1339
|
|
|
*/ |
1340
|
|
|
protected function isPreviewDeclined() |
1341
|
|
|
{ |
1342
|
|
|
$request = $this->getRequest(); |
1343
|
|
|
|
1344
|
|
|
return null !== $request->get('btn_preview_decline'); |
1345
|
|
|
} |
1346
|
|
|
|
1347
|
|
|
/** |
1348
|
|
|
* Gets ACL users. |
1349
|
|
|
* |
1350
|
|
|
* @return \Traversable |
1351
|
|
|
*/ |
1352
|
|
|
protected function getAclUsers() |
1353
|
|
|
{ |
1354
|
|
|
$aclUsers = []; |
1355
|
|
|
|
1356
|
|
|
$userManagerServiceName = $this->container->getParameter('sonata.admin.security.acl_user_manager'); |
|
|
|
|
1357
|
|
|
if (null !== $userManagerServiceName && $this->has($userManagerServiceName)) { |
1358
|
|
|
$userManager = $this->get($userManagerServiceName); |
1359
|
|
|
|
1360
|
|
|
if (method_exists($userManager, 'findUsers')) { |
1361
|
|
|
$aclUsers = $userManager->findUsers(); |
1362
|
|
|
} |
1363
|
|
|
} |
1364
|
|
|
|
1365
|
|
|
return \is_array($aclUsers) ? new \ArrayIterator($aclUsers) : $aclUsers; |
1366
|
|
|
} |
1367
|
|
|
|
1368
|
|
|
/** |
1369
|
|
|
* Gets ACL roles. |
1370
|
|
|
* |
1371
|
|
|
* @return \Traversable |
1372
|
|
|
*/ |
1373
|
|
|
protected function getAclRoles() |
1374
|
|
|
{ |
1375
|
|
|
$aclRoles = []; |
1376
|
|
|
$roleHierarchy = $this->container->getParameter('security.role_hierarchy.roles'); |
|
|
|
|
1377
|
|
|
$pool = $this->container->get('sonata.admin.pool'); |
1378
|
|
|
|
1379
|
|
|
foreach ($pool->getAdminServiceIds() as $id) { |
1380
|
|
|
try { |
1381
|
|
|
$admin = $pool->getInstance($id); |
1382
|
|
|
} catch (\Exception $e) { |
1383
|
|
|
continue; |
1384
|
|
|
} |
1385
|
|
|
|
1386
|
|
|
$baseRole = $admin->getSecurityHandler()->getBaseRole($admin); |
1387
|
|
|
foreach ($admin->getSecurityInformation() as $role => $permissions) { |
1388
|
|
|
$role = sprintf($baseRole, $role); |
1389
|
|
|
$aclRoles[] = $role; |
1390
|
|
|
} |
1391
|
|
|
} |
1392
|
|
|
|
1393
|
|
|
foreach ($roleHierarchy as $name => $roles) { |
1394
|
|
|
$aclRoles[] = $name; |
1395
|
|
|
$aclRoles = array_merge($aclRoles, $roles); |
1396
|
|
|
} |
1397
|
|
|
|
1398
|
|
|
$aclRoles = array_unique($aclRoles); |
1399
|
|
|
|
1400
|
|
|
return \is_array($aclRoles) ? new \ArrayIterator($aclRoles) : $aclRoles; |
1401
|
|
|
} |
1402
|
|
|
|
1403
|
|
|
/** |
1404
|
|
|
* Validate CSRF token for action without form. |
1405
|
|
|
* |
1406
|
|
|
* @param string $intention |
1407
|
|
|
* |
1408
|
|
|
* @throws HttpException |
1409
|
|
|
*/ |
1410
|
|
|
protected function validateCsrfToken($intention): void |
1411
|
|
|
{ |
1412
|
|
|
$request = $this->getRequest(); |
1413
|
|
|
$token = $request->request->get('_sonata_csrf_token', false); |
1414
|
|
|
|
1415
|
|
|
if ($this->container->has('security.csrf.token_manager')) { |
1416
|
|
|
$valid = $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($intention, $token)); |
1417
|
|
|
} else { |
1418
|
|
|
return; |
1419
|
|
|
} |
1420
|
|
|
|
1421
|
|
|
if (!$valid) { |
1422
|
|
|
throw new HttpException(400, 'The csrf token is not valid, CSRF attack?'); |
1423
|
|
|
} |
1424
|
|
|
} |
1425
|
|
|
|
1426
|
|
|
/** |
1427
|
|
|
* Escape string for html output. |
1428
|
|
|
* |
1429
|
|
|
* @param string $s |
1430
|
|
|
* |
1431
|
|
|
* @return string |
1432
|
|
|
*/ |
1433
|
|
|
protected function escapeHtml($s) |
1434
|
|
|
{ |
1435
|
|
|
return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); |
1436
|
|
|
} |
1437
|
|
|
|
1438
|
|
|
/** |
1439
|
|
|
* Get CSRF token. |
1440
|
|
|
* |
1441
|
|
|
* @param string $intention |
1442
|
|
|
* |
1443
|
|
|
* @return string|false |
1444
|
|
|
*/ |
1445
|
|
|
protected function getCsrfToken($intention) |
1446
|
|
|
{ |
1447
|
|
|
if ($this->container->has('security.csrf.token_manager')) { |
1448
|
|
|
return $this->container->get('security.csrf.token_manager')->getToken($intention)->getValue(); |
1449
|
|
|
} |
1450
|
|
|
|
1451
|
|
|
return false; |
1452
|
|
|
} |
1453
|
|
|
|
1454
|
|
|
/** |
1455
|
|
|
* This method can be overloaded in your custom CRUD controller. |
1456
|
|
|
* It's called from createAction. |
1457
|
|
|
* |
1458
|
|
|
* @param mixed $object |
1459
|
|
|
* |
1460
|
|
|
* @return Response|null |
1461
|
|
|
*/ |
1462
|
|
|
protected function preCreate(Request $request, $object) |
|
|
|
|
1463
|
|
|
{ |
1464
|
|
|
} |
1465
|
|
|
|
1466
|
|
|
/** |
1467
|
|
|
* This method can be overloaded in your custom CRUD controller. |
1468
|
|
|
* It's called from editAction. |
1469
|
|
|
* |
1470
|
|
|
* @param mixed $object |
1471
|
|
|
* |
1472
|
|
|
* @return Response|null |
1473
|
|
|
*/ |
1474
|
|
|
protected function preEdit(Request $request, $object) |
|
|
|
|
1475
|
|
|
{ |
1476
|
|
|
} |
1477
|
|
|
|
1478
|
|
|
/** |
1479
|
|
|
* This method can be overloaded in your custom CRUD controller. |
1480
|
|
|
* It's called from deleteAction. |
1481
|
|
|
* |
1482
|
|
|
* @param mixed $object |
1483
|
|
|
* |
1484
|
|
|
* @return Response|null |
1485
|
|
|
*/ |
1486
|
|
|
protected function preDelete(Request $request, $object) |
|
|
|
|
1487
|
|
|
{ |
1488
|
|
|
} |
1489
|
|
|
|
1490
|
|
|
/** |
1491
|
|
|
* This method can be overloaded in your custom CRUD controller. |
1492
|
|
|
* It's called from showAction. |
1493
|
|
|
* |
1494
|
|
|
* @param mixed $object |
1495
|
|
|
* |
1496
|
|
|
* @return Response|null |
1497
|
|
|
*/ |
1498
|
|
|
protected function preShow(Request $request, $object) |
|
|
|
|
1499
|
|
|
{ |
1500
|
|
|
} |
1501
|
|
|
|
1502
|
|
|
/** |
1503
|
|
|
* This method can be overloaded in your custom CRUD controller. |
1504
|
|
|
* It's called from listAction. |
1505
|
|
|
* |
1506
|
|
|
* @return Response|null |
1507
|
|
|
*/ |
1508
|
|
|
protected function preList(Request $request) |
|
|
|
|
1509
|
|
|
{ |
1510
|
|
|
} |
1511
|
|
|
|
1512
|
|
|
/** |
1513
|
|
|
* Translate a message id. |
1514
|
|
|
* |
1515
|
|
|
* @param string $id |
1516
|
|
|
* @param string $domain |
1517
|
|
|
* @param string $locale |
1518
|
|
|
* |
1519
|
|
|
* @return string translated string |
1520
|
|
|
*/ |
1521
|
|
|
final protected function trans($id, array $parameters = [], $domain = null, $locale = null) |
1522
|
|
|
{ |
1523
|
|
|
$domain = $domain ?: $this->admin->getTranslationDomain(); |
1524
|
|
|
|
1525
|
|
|
return $this->get('translator')->trans($id, $parameters, $domain, $locale); |
1526
|
|
|
} |
1527
|
|
|
|
1528
|
|
|
private function checkParentChildAssociation(Request $request, $object): void |
1529
|
|
|
{ |
1530
|
|
|
if (!($parentAdmin = $this->admin->getParent())) { |
1531
|
|
|
return; |
1532
|
|
|
} |
1533
|
|
|
|
1534
|
|
|
// NEXT_MAJOR: remove this check |
1535
|
|
|
if (!$this->admin->getParentAssociationMapping()) { |
|
|
|
|
1536
|
|
|
return; |
1537
|
|
|
} |
1538
|
|
|
|
1539
|
|
|
$parentId = $request->get($parentAdmin->getIdParameter()); |
1540
|
|
|
|
1541
|
|
|
$propertyAccessor = PropertyAccess::createPropertyAccessor(); |
1542
|
|
|
$propertyPath = new PropertyPath($this->admin->getParentAssociationMapping()); |
|
|
|
|
1543
|
|
|
|
1544
|
|
|
if ($parentAdmin->getObject($parentId) !== $propertyAccessor->getValue($object, $propertyPath)) { |
1545
|
|
|
// NEXT_MAJOR: make this exception |
1546
|
|
|
@trigger_error("Accessing a child that isn't connected to a given parent is deprecated since 3.34" |
|
|
|
|
1547
|
|
|
." and won't be allowed in 4.0.", |
1548
|
|
|
E_USER_DEPRECATED |
1549
|
|
|
); |
1550
|
|
|
} |
1551
|
|
|
} |
1552
|
|
|
|
1553
|
|
|
/** |
1554
|
|
|
* Sets the admin form theme to form view. Used for compatibility between Symfony versions. |
1555
|
|
|
*/ |
1556
|
|
|
private function setFormTheme(FormView $formView, array $theme = null): void |
1557
|
|
|
{ |
1558
|
|
|
$twig = $this->get('twig'); |
1559
|
|
|
|
1560
|
|
|
// BC for Symfony < 3.2 where this runtime does not exists |
1561
|
|
|
if (!method_exists(AppVariable::class, 'getToken')) { |
1562
|
|
|
$twig->getExtension(FormExtension::class)->renderer->setTheme($formView, $theme); |
1563
|
|
|
|
1564
|
|
|
return; |
1565
|
|
|
} |
1566
|
|
|
|
1567
|
|
|
// BC for Symfony < 3.4 where runtime should be TwigRenderer |
1568
|
|
|
if (!method_exists(DebugCommand::class, 'getLoaderPaths')) { |
1569
|
|
|
$twig->getRuntime(TwigRenderer::class)->setTheme($formView, $theme); |
1570
|
|
|
|
1571
|
|
|
return; |
1572
|
|
|
} |
1573
|
|
|
|
1574
|
|
|
$twig->getRuntime(FormRenderer::class)->setTheme($formView, $theme); |
1575
|
|
|
} |
1576
|
|
|
} |
1577
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.