SonataAdminExtension.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. <?php
  2. /*
  3. * This file is part of the Sonata 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. use Symfony\Component\PropertyAccess\PropertyAccess;
  18. /**
  19. * Class SonataAdminExtension
  20. *
  21. * @package Sonata\AdminBundle\Twig\Extension
  22. * @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
  23. */
  24. class SonataAdminExtension extends \Twig_Extension
  25. {
  26. /**
  27. * @var \Twig_Environment
  28. */
  29. protected $environment;
  30. /**
  31. * @var Pool
  32. */
  33. protected $pool;
  34. /**
  35. * @var LoggerInterface
  36. */
  37. protected $logger;
  38. /**
  39. * @param Pool $pool
  40. * @param LoggerInterface $logger
  41. */
  42. public function __construct(Pool $pool, LoggerInterface $logger = null)
  43. {
  44. $this->pool = $pool;
  45. $this->logger = $logger;
  46. }
  47. /**
  48. * {@inheritdoc}
  49. */
  50. public function initRuntime(\Twig_Environment $environment)
  51. {
  52. $this->environment = $environment;
  53. }
  54. /**
  55. * {@inheritDoc}
  56. */
  57. public function getFilters()
  58. {
  59. return array(
  60. 'render_list_element' => new \Twig_Filter_Method($this, 'renderListElement', array('is_safe' => array('html'))),
  61. 'render_view_element' => new \Twig_Filter_Method($this, 'renderViewElement', array('is_safe' => array('html'))),
  62. 'render_view_element_compare' => new \Twig_Filter_Method($this, 'renderViewElementCompare', array('is_safe' => array('html'))),
  63. 'render_relation_element' => new \Twig_Filter_Method($this, 'renderRelationElement'),
  64. 'sonata_urlsafeid' => new \Twig_Filter_Method($this, 'getUrlsafeIdentifier'),
  65. 'sonata_xeditable_type' => new \Twig_Filter_Method($this, 'getXEditableType'),
  66. );
  67. }
  68. /**
  69. * {@inheritDoc}
  70. */
  71. public function getFunctions()
  72. {
  73. return array();
  74. }
  75. /**
  76. * {@inheritDoc}
  77. */
  78. public function getTokenParsers()
  79. {
  80. return array();
  81. }
  82. /**
  83. * {@inheritDoc}
  84. */
  85. public function getName()
  86. {
  87. return 'sonata_admin';
  88. }
  89. /**
  90. * Get template
  91. *
  92. * @param FieldDescriptionInterface $fieldDescription
  93. * @param string $defaultTemplate
  94. *
  95. * @return \Twig_TemplateInterface
  96. */
  97. protected function getTemplate(FieldDescriptionInterface $fieldDescription, $defaultTemplate)
  98. {
  99. $templateName = $fieldDescription->getTemplate() ?: $defaultTemplate;
  100. try {
  101. $template = $this->environment->loadTemplate($templateName);
  102. } catch (\Twig_Error_Loader $e) {
  103. $template = $this->environment->loadTemplate($defaultTemplate);
  104. if (null !== $this->logger) {
  105. $this->logger->warning(sprintf('An error occured trying to load the template "%s" for the field "%s", the default template "%s" was used instead: "%s". ', $templateName, $fieldDescription->getFieldName(), $defaultTemplate, $e->getMessage()));
  106. }
  107. }
  108. return $template;
  109. }
  110. /**
  111. * render a list element from the FieldDescription
  112. *
  113. * @param mixed $object
  114. * @param FieldDescriptionInterface $fieldDescription
  115. * @param array $params
  116. *
  117. * @return string
  118. */
  119. public function renderListElement($object, FieldDescriptionInterface $fieldDescription, $params = array())
  120. {
  121. $template = $this->getTemplate($fieldDescription, $fieldDescription->getAdmin()->getTemplate('base_list_field'));
  122. return $this->output($fieldDescription, $template, array_merge($params, array(
  123. 'admin' => $fieldDescription->getAdmin(),
  124. 'object' => $object,
  125. 'value' => $this->getValueFromFieldDescription($object, $fieldDescription),
  126. 'field_description' => $fieldDescription,
  127. )));
  128. }
  129. /**
  130. * @param FieldDescriptionInterface $fieldDescription
  131. * @param \Twig_TemplateInterface $template
  132. * @param array $parameters
  133. *
  134. * @return string
  135. */
  136. public function output(FieldDescriptionInterface $fieldDescription, \Twig_TemplateInterface $template, array $parameters = array())
  137. {
  138. $content = $template->render($parameters);
  139. if ($this->environment->isDebug()) {
  140. return sprintf("\n<!-- START \n fieldName: %s\n template: %s\n compiled template: %s\n -->\n%s\n<!-- END - fieldName: %s -->",
  141. $fieldDescription->getFieldName(),
  142. $fieldDescription->getTemplate(),
  143. $template->getTemplateName(),
  144. $content,
  145. $fieldDescription->getFieldName()
  146. );
  147. }
  148. return $content;
  149. }
  150. /**
  151. * return the value related to FieldDescription, if the associated object does no
  152. * exists => a temporary one is created
  153. *
  154. * @param object $object
  155. * @param FieldDescriptionInterface $fieldDescription
  156. * @param array $params
  157. *
  158. * @throws \RuntimeException
  159. *
  160. * @return mixed
  161. */
  162. public function getValueFromFieldDescription($object, FieldDescriptionInterface $fieldDescription, array $params = array())
  163. {
  164. if (isset($params['loop']) && $object instanceof \ArrayAccess) {
  165. throw new \RuntimeException('remove the loop requirement');
  166. }
  167. $value = null;
  168. try {
  169. $value = $fieldDescription->getValue($object);
  170. } catch (NoValueException $e) {
  171. if ($fieldDescription->getAssociationAdmin()) {
  172. $value = $fieldDescription->getAssociationAdmin()->getNewInstance();
  173. }
  174. }
  175. return $value;
  176. }
  177. /**
  178. * render a view element
  179. *
  180. * @param FieldDescriptionInterface $fieldDescription
  181. * @param mixed $object
  182. *
  183. * @return string
  184. */
  185. public function renderViewElement(FieldDescriptionInterface $fieldDescription, $object)
  186. {
  187. $template = $this->getTemplate($fieldDescription, 'SonataAdminBundle:CRUD:base_show_field.html.twig');
  188. try {
  189. $value = $fieldDescription->getValue($object);
  190. } catch (NoValueException $e) {
  191. $value = null;
  192. }
  193. return $this->output($fieldDescription, $template, array(
  194. 'field_description' => $fieldDescription,
  195. 'object' => $object,
  196. 'value' => $value,
  197. 'admin' => $fieldDescription->getAdmin(),
  198. ));
  199. }
  200. /**
  201. * render a compared view element
  202. *
  203. * @param FieldDescriptionInterface $fieldDescription
  204. * @param mixed $baseObject
  205. * @param mixed $compareObject
  206. *
  207. * @return string
  208. */
  209. public function renderViewElementCompare(FieldDescriptionInterface $fieldDescription, $baseObject, $compareObject)
  210. {
  211. $template = $this->getTemplate($fieldDescription, 'SonataAdminBundle:CRUD:base_show_field.html.twig');
  212. try {
  213. $baseValue = $fieldDescription->getValue($baseObject);
  214. } catch (NoValueException $e) {
  215. $baseValue = null;
  216. }
  217. try {
  218. $compareValue = $fieldDescription->getValue($compareObject);
  219. } catch (NoValueException $e) {
  220. $compareValue = null;
  221. }
  222. $baseValueOutput = $template->render(array(
  223. 'admin' => $fieldDescription->getAdmin(),
  224. 'field_description' => $fieldDescription,
  225. 'value' => $baseValue,
  226. ));
  227. $compareValueOutput = $template->render(array(
  228. 'field_description' => $fieldDescription,
  229. 'admin' => $fieldDescription->getAdmin(),
  230. 'value' => $compareValue,
  231. ));
  232. // Compare the rendered output of both objects by using the (possibly) overridden field block
  233. $isDiff = $baseValueOutput !== $compareValueOutput;
  234. return $this->output($fieldDescription, $template, array(
  235. 'field_description' => $fieldDescription,
  236. 'value' => $baseValue,
  237. 'value_compare' => $compareValue,
  238. 'is_diff' => $isDiff,
  239. 'admin' => $fieldDescription->getAdmin(),
  240. ));
  241. }
  242. /**
  243. * @throws \RunTimeException
  244. *
  245. * @param mixed $element
  246. * @param FieldDescriptionInterface $fieldDescription
  247. *
  248. * @return mixed
  249. */
  250. public function renderRelationElement($element, FieldDescriptionInterface $fieldDescription)
  251. {
  252. if (!is_object($element)) {
  253. return $element;
  254. }
  255. $propertyPath = $fieldDescription->getOption('associated_property');
  256. if (null === $propertyPath) {
  257. // For BC kept associated_tostring option behavior
  258. $method = $fieldDescription->getOption('associated_tostring', '__toString');
  259. if (!method_exists($element, $method)) {
  260. throw new \RuntimeException(sprintf(
  261. 'You must define an `associated_property` option or create a `%s::__toString` method to the field option %s from service %s is ',
  262. get_class($element),
  263. $fieldDescription->getName(),
  264. $fieldDescription->getAdmin()->getCode()
  265. ));
  266. }
  267. return call_user_func(array($element, $method));
  268. }
  269. if (is_callable($propertyPath)) {
  270. return $propertyPath($element);
  271. }
  272. return PropertyAccess::createPropertyAccessor()->getValue($element, $propertyPath);
  273. }
  274. /**
  275. * Get the identifiers as a string that is save to use in an url.
  276. *
  277. * @param object $model
  278. * @param AdminInterface $admin
  279. *
  280. * @return string string representation of the id that is save to use in an url
  281. */
  282. public function getUrlsafeIdentifier($model, AdminInterface $admin = null)
  283. {
  284. if (is_null($admin)) {
  285. $admin = $this->pool->getAdminByClass(
  286. ClassUtils::getClass($model)
  287. );
  288. }
  289. return $admin->getUrlsafeIdentifier($model);
  290. }
  291. /**
  292. * @param $type
  293. *
  294. * @return string|bool
  295. */
  296. public function getXEditableType($type)
  297. {
  298. $mapping = array(
  299. 'boolean' => 'select',
  300. 'text' => 'text',
  301. 'textarea' => 'textarea',
  302. 'html' => 'textarea',
  303. 'email' => 'email',
  304. 'string' => 'text',
  305. 'smallint' => 'text',
  306. 'bigint' => 'text',
  307. 'integer' => 'number',
  308. 'decimal' => 'number',
  309. 'currency' => 'number',
  310. 'percent' => 'number',
  311. 'url' => 'url',
  312. );
  313. return isset($mapping[$type]) ? $mapping[$type] : false;
  314. }
  315. }