Advertisement
Guest User

Untitled

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