SonataAdminExtension.php 11 KB

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