SonataAdminExtension.php 11 KB

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