PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/Twig/Extension/SonataAdminExtension.php

http://github.com/sonata-project/SonataAdminBundle
PHP | 461 lines | 291 code | 47 blank | 123 comment | 17 complexity | eaf078ecb7ea90f1f41d82aa108bf966 MD5 | raw file
Possible License(s): JSON, Apache-2.0, MIT
  1. <?php
  2. /*
  3. * This file is part of the Sonata Project package.
  4. *
  5. * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Sonata\AdminBundle\Twig\Extension;
  11. use Doctrine\Common\Util\ClassUtils;
  12. use Psr\Log\LoggerInterface;
  13. use Sonata\AdminBundle\Admin\AdminInterface;
  14. use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
  15. use Sonata\AdminBundle\Admin\Pool;
  16. use Sonata\AdminBundle\Exception\NoValueException;
  17. /**
  18. * Class SonataAdminExtension.
  19. *
  20. * @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
  21. */
  22. class SonataAdminExtension extends \Twig_Extension
  23. {
  24. /**
  25. * @var Pool
  26. */
  27. protected $pool;
  28. /**
  29. * @var LoggerInterface
  30. */
  31. protected $logger;
  32. /**
  33. * @var string[]
  34. */
  35. private $xEditableTypeMapping = array();
  36. /**
  37. * @param Pool $pool
  38. * @param LoggerInterface $logger
  39. */
  40. public function __construct(Pool $pool, LoggerInterface $logger = null)
  41. {
  42. $this->pool = $pool;
  43. $this->logger = $logger;
  44. }
  45. /**
  46. * {@inheritdoc}
  47. */
  48. public function getFilters()
  49. {
  50. return array(
  51. new \Twig_SimpleFilter(
  52. 'render_list_element',
  53. array($this, 'renderListElement'),
  54. array(
  55. 'is_safe' => array('html'),
  56. 'needs_environment' => true,
  57. )
  58. ),
  59. new \Twig_SimpleFilter(
  60. 'render_view_element',
  61. array($this, 'renderViewElement'),
  62. array(
  63. 'is_safe' => array('html'),
  64. 'needs_environment' => true,
  65. )
  66. ),
  67. new \Twig_SimpleFilter(
  68. 'render_view_element_compare',
  69. array($this, 'renderViewElementCompare'),
  70. array(
  71. 'is_safe' => array('html'),
  72. 'needs_environment' => true,
  73. )
  74. ),
  75. new \Twig_SimpleFilter(
  76. 'render_relation_element',
  77. array($this, 'renderRelationElement')
  78. ),
  79. new \Twig_SimpleFilter(
  80. 'sonata_urlsafeid',
  81. array($this, 'getUrlsafeIdentifier')
  82. ),
  83. new \Twig_SimpleFilter(
  84. 'sonata_xeditable_type',
  85. array($this, 'getXEditableType')
  86. ),
  87. new \Twig_SimpleFilter(
  88. 'sonata_xeditable_choices',
  89. array($this, 'getXEditableChoices')
  90. ),
  91. );
  92. }
  93. /**
  94. * {@inheritdoc}
  95. */
  96. public function getName()
  97. {
  98. return 'sonata_admin';
  99. }
  100. /**
  101. * render a list element from the FieldDescription.
  102. *
  103. * @param mixed $object
  104. * @param FieldDescriptionInterface $fieldDescription
  105. * @param array $params
  106. *
  107. * @return string
  108. */
  109. public function renderListElement(
  110. \Twig_Environment $environment,
  111. $object,
  112. FieldDescriptionInterface $fieldDescription,
  113. $params = array()
  114. ) {
  115. $template = $this->getTemplate(
  116. $fieldDescription,
  117. $fieldDescription->getAdmin()->getTemplate('base_list_field'),
  118. $environment
  119. );
  120. return $this->output($fieldDescription, $template, array_merge($params, array(
  121. 'admin' => $fieldDescription->getAdmin(),
  122. 'object' => $object,
  123. 'value' => $this->getValueFromFieldDescription($object, $fieldDescription),
  124. 'field_description' => $fieldDescription,
  125. )), $environment);
  126. }
  127. /**
  128. * @param FieldDescriptionInterface $fieldDescription
  129. * @param \Twig_Template $template
  130. * @param array $parameters
  131. *
  132. * @return string
  133. */
  134. public function output(
  135. FieldDescriptionInterface $fieldDescription,
  136. \Twig_Template $template,
  137. array $parameters,
  138. \Twig_Environment $environment
  139. ) {
  140. $content = $template->render($parameters);
  141. if ($environment->isDebug()) {
  142. $commentTemplate = <<<'EOT'
  143. <!-- START
  144. fieldName: %s
  145. template: %s
  146. compiled template: %s
  147. -->
  148. %s
  149. <!-- END - fieldName: %s -->
  150. EOT;
  151. return sprintf(
  152. $commentTemplate,
  153. $fieldDescription->getFieldName(),
  154. $fieldDescription->getTemplate(),
  155. $template->getTemplateName(),
  156. $content,
  157. $fieldDescription->getFieldName()
  158. );
  159. }
  160. return $content;
  161. }
  162. /**
  163. * return the value related to FieldDescription, if the associated object does no
  164. * exists => a temporary one is created.
  165. *
  166. * @param object $object
  167. * @param FieldDescriptionInterface $fieldDescription
  168. * @param array $params
  169. *
  170. * @throws \RuntimeException
  171. *
  172. * @return mixed
  173. */
  174. public function getValueFromFieldDescription(
  175. $object,
  176. FieldDescriptionInterface $fieldDescription,
  177. array $params = array()
  178. ) {
  179. if (isset($params['loop']) && $object instanceof \ArrayAccess) {
  180. throw new \RuntimeException('remove the loop requirement');
  181. }
  182. $value = null;
  183. try {
  184. $value = $fieldDescription->getValue($object);
  185. } catch (NoValueException $e) {
  186. if ($fieldDescription->getAssociationAdmin()) {
  187. $value = $fieldDescription->getAssociationAdmin()->getNewInstance();
  188. }
  189. }
  190. return $value;
  191. }
  192. /**
  193. * render a view element.
  194. *
  195. * @param FieldDescriptionInterface $fieldDescription
  196. * @param mixed $object
  197. *
  198. * @return string
  199. */
  200. public function renderViewElement(
  201. \Twig_Environment $environment,
  202. FieldDescriptionInterface $fieldDescription,
  203. $object
  204. ) {
  205. $template = $this->getTemplate(
  206. $fieldDescription,
  207. 'SonataAdminBundle:CRUD:base_show_field.html.twig',
  208. $environment
  209. );
  210. try {
  211. $value = $fieldDescription->getValue($object);
  212. } catch (NoValueException $e) {
  213. $value = null;
  214. }
  215. return $this->output($fieldDescription, $template, array(
  216. 'field_description' => $fieldDescription,
  217. 'object' => $object,
  218. 'value' => $value,
  219. 'admin' => $fieldDescription->getAdmin(),
  220. ), $environment);
  221. }
  222. /**
  223. * render a compared view element.
  224. *
  225. * @param FieldDescriptionInterface $fieldDescription
  226. * @param mixed $baseObject
  227. * @param mixed $compareObject
  228. *
  229. * @return string
  230. */
  231. public function renderViewElementCompare(
  232. \Twig_Environment $environment,
  233. FieldDescriptionInterface $fieldDescription,
  234. $baseObject,
  235. $compareObject
  236. ) {
  237. $template = $this->getTemplate(
  238. $fieldDescription,
  239. 'SonataAdminBundle:CRUD:base_show_field.html.twig',
  240. $environment
  241. );
  242. try {
  243. $baseValue = $fieldDescription->getValue($baseObject);
  244. } catch (NoValueException $e) {
  245. $baseValue = null;
  246. }
  247. try {
  248. $compareValue = $fieldDescription->getValue($compareObject);
  249. } catch (NoValueException $e) {
  250. $compareValue = null;
  251. }
  252. $baseValueOutput = $template->render(array(
  253. 'admin' => $fieldDescription->getAdmin(),
  254. 'field_description' => $fieldDescription,
  255. 'value' => $baseValue,
  256. ));
  257. $compareValueOutput = $template->render(array(
  258. 'field_description' => $fieldDescription,
  259. 'admin' => $fieldDescription->getAdmin(),
  260. 'value' => $compareValue,
  261. ));
  262. // Compare the rendered output of both objects by using the (possibly) overridden field block
  263. $isDiff = $baseValueOutput !== $compareValueOutput;
  264. return $this->output($fieldDescription, $template, array(
  265. 'field_description' => $fieldDescription,
  266. 'value' => $baseValue,
  267. 'value_compare' => $compareValue,
  268. 'is_diff' => $isDiff,
  269. 'admin' => $fieldDescription->getAdmin(),
  270. ), $environment);
  271. }
  272. /**
  273. * @throws \RuntimeException
  274. *
  275. * @param mixed $element
  276. * @param FieldDescriptionInterface $fieldDescription
  277. *
  278. * @return mixed
  279. */
  280. public function renderRelationElement($element, FieldDescriptionInterface $fieldDescription)
  281. {
  282. if (!is_object($element)) {
  283. return $element;
  284. }
  285. $propertyPath = $fieldDescription->getOption('associated_property');
  286. if (null === $propertyPath) {
  287. // For BC kept associated_tostring option behavior
  288. $method = $fieldDescription->getOption('associated_tostring');
  289. if ($method) {
  290. @trigger_error(
  291. 'Option "associated_tostring" is deprecated since version 2.3. Use "associated_property" instead.',
  292. E_USER_DEPRECATED
  293. );
  294. } else {
  295. $method = '__toString';
  296. }
  297. if (!method_exists($element, $method)) {
  298. throw new \RuntimeException(sprintf(
  299. 'You must define an `associated_property` option or '.
  300. 'create a `%s::__toString` method to the field option %s from service %s is ',
  301. get_class($element),
  302. $fieldDescription->getName(),
  303. $fieldDescription->getAdmin()->getCode()
  304. ));
  305. }
  306. return call_user_func(array($element, $method));
  307. }
  308. if (is_callable($propertyPath)) {
  309. return $propertyPath($element);
  310. }
  311. return $this->pool->getPropertyAccessor()->getValue($element, $propertyPath);
  312. }
  313. /**
  314. * Get the identifiers as a string that is save to use in an url.
  315. *
  316. * @param object $model
  317. * @param AdminInterface $admin
  318. *
  319. * @return string string representation of the id that is save to use in an url
  320. */
  321. public function getUrlsafeIdentifier($model, AdminInterface $admin = null)
  322. {
  323. if (is_null($admin)) {
  324. $admin = $this->pool->getAdminByClass(ClassUtils::getClass($model));
  325. }
  326. return $admin->getUrlsafeIdentifier($model);
  327. }
  328. /**
  329. * @param string[] $xEditableTypeMapping
  330. */
  331. public function setXEditableTypeMapping($xEditableTypeMapping)
  332. {
  333. $this->xEditableTypeMapping = $xEditableTypeMapping;
  334. }
  335. /**
  336. * @param $type
  337. *
  338. * @return string|bool
  339. */
  340. public function getXEditableType($type)
  341. {
  342. return isset($this->xEditableTypeMapping[$type]) ? $this->xEditableTypeMapping[$type] : false;
  343. }
  344. /**
  345. * Return xEditable choices based on the field description choices options & catalogue options.
  346. * With the following choice options:
  347. * ['Status1' => 'Alias1', 'Status2' => 'Alias2']
  348. * The method will return:
  349. * [['value' => 'Status1', 'text' => 'Alias1'], ['value' => 'Status2', 'text' => 'Alias2']].
  350. *
  351. * @param FieldDescriptionInterface $fieldDescription
  352. *
  353. * @return array
  354. */
  355. public function getXEditableChoices(FieldDescriptionInterface $fieldDescription)
  356. {
  357. $choices = $fieldDescription->getOption('choices', array());
  358. $catalogue = $fieldDescription->getOption('catalogue');
  359. $xEditableChoices = array();
  360. if (!empty($choices)) {
  361. reset($choices);
  362. $first = current($choices);
  363. // the choices are already in the right format
  364. if (is_array($first) && array_key_exists('value', $first) && array_key_exists('text', $first)) {
  365. $xEditableChoices = $choices;
  366. } else {
  367. foreach ($choices as $value => $text) {
  368. $text = $catalogue ? $fieldDescription->getAdmin()->trans($text, array(), $catalogue) : $text;
  369. $xEditableChoices[] = array(
  370. 'value' => $value,
  371. 'text' => $text,
  372. );
  373. }
  374. }
  375. }
  376. return $xEditableChoices;
  377. }
  378. /**
  379. * Get template.
  380. *
  381. * @param FieldDescriptionInterface $fieldDescription
  382. * @param string $defaultTemplate
  383. *
  384. * @return \Twig_Template
  385. */
  386. protected function getTemplate(
  387. FieldDescriptionInterface $fieldDescription,
  388. $defaultTemplate,
  389. \Twig_Environment $environment
  390. ) {
  391. $templateName = $fieldDescription->getTemplate() ?: $defaultTemplate;
  392. try {
  393. $template = $environment->loadTemplate($templateName);
  394. } catch (\Twig_Error_Loader $e) {
  395. @trigger_error(
  396. 'Relying on default template loading on field template loading exception '.
  397. 'is deprecated since 3.1 and will be removed in 4.0. '.
  398. 'A \Twig_Error_Loader exception will be thrown instead',
  399. E_USER_DEPRECATED
  400. );
  401. $template = $environment->loadTemplate($defaultTemplate);
  402. if (null !== $this->logger) {
  403. $this->logger->warning(sprintf(
  404. 'An error occured trying to load the template "%s" for the field "%s", '.
  405. 'the default template "%s" was used instead.',
  406. $templateName,
  407. $fieldDescription->getFieldName(),
  408. $defaultTemplate
  409. ), array('exception' => $e));
  410. }
  411. }
  412. return $template;
  413. }
  414. }