AddDependencyCallsCompilerPass.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  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\DependencyInjection\Compiler;
  11. use Doctrine\Common\Inflector\Inflector;
  12. use Sonata\AdminBundle\Datagrid\Pager;
  13. use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
  14. use Symfony\Component\DependencyInjection\ContainerBuilder;
  15. use Symfony\Component\DependencyInjection\ContainerInterface;
  16. use Symfony\Component\DependencyInjection\Definition;
  17. use Symfony\Component\DependencyInjection\DefinitionDecorator;
  18. use Symfony\Component\DependencyInjection\Reference;
  19. /**
  20. * Add all dependencies to the Admin class, this avoid to write too many lines
  21. * in the configuration files.
  22. *
  23. * @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
  24. */
  25. class AddDependencyCallsCompilerPass implements CompilerPassInterface
  26. {
  27. /**
  28. * {@inheritdoc}
  29. */
  30. public function process(ContainerBuilder $container)
  31. {
  32. // check if translator service exist
  33. if (!$container->has('translator')) {
  34. throw new \RuntimeException('The "translator" service is not yet enabled.
  35. It\'s required by SonataAdmin to display all labels properly.
  36. To learn how to enable the translator service please visit:
  37. http://symfony.com/doc/current/translation.html#configuration
  38. ');
  39. }
  40. $parameterBag = $container->getParameterBag();
  41. $groupDefaults = $admins = $classes = array();
  42. $pool = $container->getDefinition('sonata.admin.pool');
  43. foreach ($container->findTaggedServiceIds('sonata.admin') as $id => $tags) {
  44. foreach ($tags as $attributes) {
  45. $definition = $container->getDefinition($id);
  46. $parentDefinition = $definition instanceof DefinitionDecorator ?
  47. $container->getDefinition($definition->getParent()) :
  48. null;
  49. $this->replaceDefaultArguments(array(
  50. 0 => $id,
  51. 2 => 'SonataAdminBundle:CRUD',
  52. ), $definition, $parentDefinition);
  53. $this->applyConfigurationFromAttribute($definition, $attributes);
  54. $this->applyDefaults($container, $id, $attributes);
  55. $arguments = $parentDefinition ?
  56. array_merge($parentDefinition->getArguments(), $definition->getArguments()) :
  57. $definition->getArguments();
  58. $admins[] = $id;
  59. if (!isset($classes[$arguments[1]])) {
  60. $classes[$arguments[1]] = array();
  61. }
  62. $classes[$arguments[1]][] = $id;
  63. $showInDashboard = (bool) (isset($attributes['show_in_dashboard']) ? $parameterBag->resolveValue($attributes['show_in_dashboard']) : true);
  64. if (!$showInDashboard) {
  65. continue;
  66. }
  67. $resolvedGroupName = isset($attributes['group']) ? $parameterBag->resolveValue($attributes['group']) : 'default';
  68. $labelCatalogue = isset($attributes['label_catalogue']) ? $attributes['label_catalogue'] : 'SonataAdminBundle';
  69. $icon = isset($attributes['icon']) ? $attributes['icon'] : '<i class="fa fa-folder"></i>';
  70. $onTop = isset($attributes['on_top']) ? $attributes['on_top'] : false;
  71. $keepOpen = isset($attributes['keep_open']) ? $attributes['keep_open'] : false;
  72. if (!isset($groupDefaults[$resolvedGroupName])) {
  73. $groupDefaults[$resolvedGroupName] = array(
  74. 'label' => $resolvedGroupName,
  75. 'label_catalogue' => $labelCatalogue,
  76. 'icon' => $icon,
  77. 'roles' => array(),
  78. 'on_top' => false,
  79. 'keep_open' => false,
  80. );
  81. }
  82. $groupDefaults[$resolvedGroupName]['items'][] = array(
  83. 'admin' => $id,
  84. 'label' => !empty($attributes['label']) ? $attributes['label'] : '',
  85. 'route' => '',
  86. 'route_params' => array(),
  87. 'route_absolute' => true,
  88. );
  89. if (isset($groupDefaults[$resolvedGroupName]['on_top']) && $groupDefaults[$resolvedGroupName]['on_top']
  90. || $onTop && (count($groupDefaults[$resolvedGroupName]['items']) > 1)) {
  91. throw new \RuntimeException('You can\'t use "on_top" option with multiple same name groups.');
  92. }
  93. $groupDefaults[$resolvedGroupName]['on_top'] = $onTop;
  94. $groupDefaults[$resolvedGroupName]['keep_open'] = $keepOpen;
  95. }
  96. }
  97. $dashboardGroupsSettings = $container->getParameter('sonata.admin.configuration.dashboard_groups');
  98. if (!empty($dashboardGroupsSettings)) {
  99. $groups = $dashboardGroupsSettings;
  100. foreach ($dashboardGroupsSettings as $groupName => $group) {
  101. $resolvedGroupName = $parameterBag->resolveValue($groupName);
  102. if (!isset($groupDefaults[$resolvedGroupName])) {
  103. $groupDefaults[$resolvedGroupName] = array(
  104. 'items' => array(),
  105. 'label' => $resolvedGroupName,
  106. 'roles' => array(),
  107. 'on_top' => false,
  108. 'keep_open' => false,
  109. );
  110. }
  111. if (empty($group['items'])) {
  112. $groups[$resolvedGroupName]['items'] = $groupDefaults[$resolvedGroupName]['items'];
  113. }
  114. if (empty($group['label'])) {
  115. $groups[$resolvedGroupName]['label'] = $groupDefaults[$resolvedGroupName]['label'];
  116. }
  117. if (empty($group['label_catalogue'])) {
  118. $groups[$resolvedGroupName]['label_catalogue'] = 'SonataAdminBundle';
  119. }
  120. if (empty($group['icon'])) {
  121. $groups[$resolvedGroupName]['icon'] = $groupDefaults[$resolvedGroupName]['icon'];
  122. }
  123. if (!empty($group['item_adds'])) {
  124. $groups[$resolvedGroupName]['items'] = array_merge($groups[$resolvedGroupName]['items'], $group['item_adds']);
  125. }
  126. if (empty($group['roles'])) {
  127. $groups[$resolvedGroupName]['roles'] = $groupDefaults[$resolvedGroupName]['roles'];
  128. }
  129. if (isset($groups[$resolvedGroupName]['on_top']) && !empty($group['on_top']) && $group['on_top']
  130. && (count($groups[$resolvedGroupName]['items']) > 1)) {
  131. throw new \RuntimeException('You can\'t use "on_top" option with multiple same name groups.');
  132. }
  133. if (empty($group['on_top'])) {
  134. $groups[$resolvedGroupName]['on_top'] = $groupDefaults[$resolvedGroupName]['on_top'];
  135. }
  136. if (empty($group['keep_open'])) {
  137. $groups[$resolvedGroupName]['keep_open'] = $groupDefaults[$resolvedGroupName]['keep_open'];
  138. }
  139. }
  140. } elseif ($container->getParameter('sonata.admin.configuration.sort_admins')) {
  141. $groups = $groupDefaults;
  142. $elementSort = function (&$element) {
  143. usort(
  144. $element['items'],
  145. function ($a, $b) {
  146. $a = !empty($a['label']) ? $a['label'] : $a['admin'];
  147. $b = !empty($b['label']) ? $b['label'] : $b['admin'];
  148. if ($a === $b) {
  149. return 0;
  150. }
  151. return $a < $b ? -1 : 1;
  152. }
  153. );
  154. };
  155. /*
  156. * 1) sort the groups by their index
  157. * 2) sort the elements within each group by label/admin
  158. */
  159. ksort($groups);
  160. array_walk($groups, $elementSort);
  161. } else {
  162. $groups = $groupDefaults;
  163. }
  164. $pool->addMethodCall('setAdminServiceIds', array($admins));
  165. $pool->addMethodCall('setAdminGroups', array($groups));
  166. $pool->addMethodCall('setAdminClasses', array($classes));
  167. $routeLoader = $container->getDefinition('sonata.admin.route_loader');
  168. $routeLoader->replaceArgument(1, $admins);
  169. }
  170. /**
  171. * This method read the attribute keys and configure admin class to use the related dependency.
  172. *
  173. * @param Definition $definition
  174. * @param array $attributes
  175. */
  176. public function applyConfigurationFromAttribute(Definition $definition, array $attributes)
  177. {
  178. $keys = array(
  179. 'model_manager',
  180. 'form_contractor',
  181. 'show_builder',
  182. 'list_builder',
  183. 'datagrid_builder',
  184. 'translator',
  185. 'configuration_pool',
  186. 'router',
  187. 'validator',
  188. 'security_handler',
  189. 'menu_factory',
  190. 'route_builder',
  191. 'label_translator_strategy',
  192. );
  193. foreach ($keys as $key) {
  194. $method = 'set'.Inflector::classify($key);
  195. if (!isset($attributes[$key]) || $definition->hasMethodCall($method)) {
  196. continue;
  197. }
  198. $definition->addMethodCall($method, array(new Reference($attributes[$key])));
  199. }
  200. }
  201. /**
  202. * Apply the default values required by the AdminInterface to the Admin service definition.
  203. *
  204. * @param ContainerBuilder $container
  205. * @param string $serviceId
  206. * @param array $attributes
  207. *
  208. * @return Definition
  209. */
  210. public function applyDefaults(ContainerBuilder $container, $serviceId, array $attributes = array())
  211. {
  212. $definition = $container->getDefinition($serviceId);
  213. $settings = $container->getParameter('sonata.admin.configuration.admin_services');
  214. if (method_exists($definition, 'setShared')) { // Symfony 2.8+
  215. $definition->setShared(false);
  216. } else { // For Symfony <2.8 compatibility
  217. $definition->setScope(ContainerInterface::SCOPE_PROTOTYPE);
  218. }
  219. $manager_type = $attributes['manager_type'];
  220. $overwriteAdminConfiguration = isset($settings[$serviceId]) ? $settings[$serviceId] : array();
  221. $defaultAddServices = array(
  222. 'model_manager' => sprintf('sonata.admin.manager.%s', $manager_type),
  223. 'form_contractor' => sprintf('sonata.admin.builder.%s_form', $manager_type),
  224. 'show_builder' => sprintf('sonata.admin.builder.%s_show', $manager_type),
  225. 'list_builder' => sprintf('sonata.admin.builder.%s_list', $manager_type),
  226. 'datagrid_builder' => sprintf('sonata.admin.builder.%s_datagrid', $manager_type),
  227. 'translator' => 'translator',
  228. 'configuration_pool' => 'sonata.admin.pool',
  229. 'route_generator' => 'sonata.admin.route.default_generator',
  230. 'validator' => 'validator',
  231. 'security_handler' => 'sonata.admin.security.handler',
  232. 'menu_factory' => 'knp_menu.factory',
  233. 'route_builder' => 'sonata.admin.route.path_info'.
  234. (($manager_type == 'doctrine_phpcr') ? '_slashes' : ''),
  235. 'label_translator_strategy' => 'sonata.admin.label.strategy.native',
  236. );
  237. $definition->addMethodCall('setManagerType', array($manager_type));
  238. foreach ($defaultAddServices as $attr => $addServiceId) {
  239. $method = 'set'.Inflector::classify($attr);
  240. if (isset($overwriteAdminConfiguration[$attr]) || !$definition->hasMethodCall($method)) {
  241. $args = array(new Reference(isset($overwriteAdminConfiguration[$attr]) ? $overwriteAdminConfiguration[$attr] : $addServiceId));
  242. if ('translator' === $attr) {
  243. $args[] = false;
  244. }
  245. $definition->addMethodCall($method, $args);
  246. }
  247. }
  248. if (isset($overwriteAdminConfiguration['pager_type'])) {
  249. $pagerType = $overwriteAdminConfiguration['pager_type'];
  250. } elseif (isset($attributes['pager_type'])) {
  251. $pagerType = $attributes['pager_type'];
  252. } else {
  253. $pagerType = Pager::TYPE_DEFAULT;
  254. }
  255. $definition->addMethodCall('setPagerType', array($pagerType));
  256. if (isset($overwriteAdminConfiguration['label'])) {
  257. $label = $overwriteAdminConfiguration['label'];
  258. } elseif (isset($attributes['label'])) {
  259. $label = $attributes['label'];
  260. } else {
  261. $label = '-';
  262. }
  263. $definition->addMethodCall('setLabel', array($label));
  264. if (isset($attributes['persist_filters'])) {
  265. $persistFilters = (bool) $attributes['persist_filters'];
  266. } else {
  267. $persistFilters = (bool) $container->getParameter('sonata.admin.configuration.filters.persist');
  268. }
  269. $definition->addMethodCall('setPersistFilters', array($persistFilters));
  270. if (isset($overwriteAdminConfiguration['show_mosaic_button'])) {
  271. $showMosaicButton = $overwriteAdminConfiguration['show_mosaic_button'];
  272. } elseif (isset($attributes['show_mosaic_button'])) {
  273. $showMosaicButton = $attributes['show_mosaic_button'];
  274. } else {
  275. $showMosaicButton = $container->getParameter('sonata.admin.configuration.show.mosaic.button');
  276. }
  277. $definition->addMethodCall('showMosaicButton', array($showMosaicButton));
  278. $this->fixTemplates($container, $definition, isset($overwriteAdminConfiguration['templates']) ? $overwriteAdminConfiguration['templates'] : array('view' => array()));
  279. if ($container->hasParameter('sonata.admin.configuration.security.information') && !$definition->hasMethodCall('setSecurityInformation')) {
  280. $definition->addMethodCall('setSecurityInformation', array('%sonata.admin.configuration.security.information%'));
  281. }
  282. $definition->addMethodCall('initialize');
  283. return $definition;
  284. }
  285. /**
  286. * @param ContainerBuilder $container
  287. * @param Definition $definition
  288. * @param array $overwrittenTemplates
  289. */
  290. public function fixTemplates(ContainerBuilder $container, Definition $definition, array $overwrittenTemplates = array())
  291. {
  292. $definedTemplates = $container->getParameter('sonata.admin.configuration.templates');
  293. $methods = array();
  294. $pos = 0;
  295. foreach ($definition->getMethodCalls() as $method) {
  296. if ($method[0] == 'setTemplates') {
  297. $definedTemplates = array_merge($definedTemplates, $method[1][0]);
  298. continue;
  299. }
  300. if ($method[0] == 'setTemplate') {
  301. $definedTemplates[$method[1][0]] = $method[1][1];
  302. continue;
  303. }
  304. // set template for simple pager if it is not already overwritten
  305. if ($method[0] === 'setPagerType'
  306. && $method[1][0] === Pager::TYPE_SIMPLE
  307. && (
  308. !isset($definedTemplates['pager_results'])
  309. || $definedTemplates['pager_results'] === 'SonataAdminBundle:Pager:results.html.twig'
  310. )
  311. ) {
  312. $definedTemplates['pager_results'] = 'SonataAdminBundle:Pager:simple_pager_results.html.twig';
  313. }
  314. $methods[$pos] = $method;
  315. ++$pos;
  316. }
  317. $definition->setMethodCalls($methods);
  318. // make sure the default templates are defined
  319. $definedTemplates = array_merge(array(
  320. 'user_block' => 'SonataAdminBundle:Core:user_block.html.twig',
  321. 'add_block' => 'SonataAdminBundle:Core:add_block.html.twig',
  322. 'layout' => 'SonataAdminBundle::standard_layout.html.twig',
  323. 'ajax' => 'SonataAdminBundle::ajax_layout.html.twig',
  324. 'dashboard' => 'SonataAdminBundle:Core:dashboard.html.twig',
  325. 'list' => 'SonataAdminBundle:CRUD:list.html.twig',
  326. 'filter' => 'SonataAdminBundle:Form:filter_admin_fields.html.twig',
  327. 'show' => 'SonataAdminBundle:CRUD:show.html.twig',
  328. 'show_compare' => 'SonataAdminBundle:CRUD:show_compare.html.twig',
  329. 'edit' => 'SonataAdminBundle:CRUD:edit.html.twig',
  330. 'history' => 'SonataAdminBundle:CRUD:history.html.twig',
  331. 'history_revision_timestamp' => 'SonataAdminBundle:CRUD:history_revision_timestamp.html.twig',
  332. 'acl' => 'SonataAdminBundle:CRUD:acl.html.twig',
  333. 'action' => 'SonataAdminBundle:CRUD:action.html.twig',
  334. 'short_object_description' => 'SonataAdminBundle:Helper:short-object-description.html.twig',
  335. 'preview' => 'SonataAdminBundle:CRUD:preview.html.twig',
  336. 'list_block' => 'SonataAdminBundle:Block:block_admin_list.html.twig',
  337. 'delete' => 'SonataAdminBundle:CRUD:delete.html.twig',
  338. 'batch' => 'SonataAdminBundle:CRUD:list__batch.html.twig',
  339. 'select' => 'SonataAdminBundle:CRUD:list__select.html.twig',
  340. 'batch_confirmation' => 'SonataAdminBundle:CRUD:batch_confirmation.html.twig',
  341. 'inner_list_row' => 'SonataAdminBundle:CRUD:list_inner_row.html.twig',
  342. 'base_list_field' => 'SonataAdminBundle:CRUD:base_list_field.html.twig',
  343. 'pager_links' => 'SonataAdminBundle:Pager:links.html.twig',
  344. 'pager_results' => 'SonataAdminBundle:Pager:results.html.twig',
  345. 'tab_menu_template' => 'SonataAdminBundle:Core:tab_menu_template.html.twig',
  346. 'knp_menu_template' => 'SonataAdminBundle:Menu:sonata_menu.html.twig',
  347. 'outer_list_rows_mosaic' => 'SonataAdminBundle:CRUD:list_outer_rows_mosaic.html.twig',
  348. 'outer_list_rows_list' => 'SonataAdminBundle:CRUD:list_outer_rows_list.html.twig',
  349. 'outer_list_rows_tree' => 'SonataAdminBundle:CRUD:list_outer_rows_tree.html.twig',
  350. ), $definedTemplates, $overwrittenTemplates['view']);
  351. $definition->addMethodCall('setTemplates', array($definedTemplates));
  352. }
  353. /**
  354. * Replace the empty arguments required by the Admin service definition.
  355. *
  356. * @param array $defaultArguments
  357. * @param Definition $definition
  358. * @param Definition|null $parentDefinition
  359. */
  360. private function replaceDefaultArguments(array $defaultArguments, Definition $definition, Definition $parentDefinition = null)
  361. {
  362. $arguments = $definition->getArguments();
  363. $parentArguments = $parentDefinition ? $parentDefinition->getArguments() : array();
  364. foreach ($defaultArguments as $index => $value) {
  365. $declaredInParent = $parentDefinition && array_key_exists($index, $parentArguments);
  366. if (strlen($declaredInParent ? $parentArguments[$index] : $arguments[$index]) == 0) {
  367. $arguments[$declaredInParent ? sprintf('index_%s', $index) : $index] = $value;
  368. }
  369. }
  370. $definition->setArguments($arguments);
  371. }
  372. }