SonataAdminExtension.php 11 KB

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