LayoutPluginManager.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. <?php
  2. namespace Drupal\Core\Layout;
  3. use Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator;
  4. use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
  5. use Drupal\Core\Cache\CacheBackendInterface;
  6. use Drupal\Core\Extension\ModuleHandlerInterface;
  7. use Drupal\Core\Extension\ThemeHandlerInterface;
  8. use Drupal\Core\Plugin\DefaultPluginManager;
  9. use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
  10. use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
  11. use Drupal\Core\Plugin\Discovery\YamlDiscoveryDecorator;
  12. use Drupal\Core\Layout\Annotation\Layout;
  13. /**
  14. * Provides a plugin manager for layouts.
  15. */
  16. class LayoutPluginManager extends DefaultPluginManager implements LayoutPluginManagerInterface {
  17. /**
  18. * The theme handler.
  19. *
  20. * @var \Drupal\Core\Extension\ThemeHandlerInterface
  21. */
  22. protected $themeHandler;
  23. /**
  24. * LayoutPluginManager constructor.
  25. *
  26. * @param \Traversable $namespaces
  27. * An object that implements \Traversable which contains the root paths
  28. * keyed by the corresponding namespace to look for plugin implementations.
  29. * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
  30. * Cache backend instance to use.
  31. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  32. * The module handler to invoke the alter hook with.
  33. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
  34. * The theme handler to invoke the alter hook with.
  35. */
  36. public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) {
  37. parent::__construct('Plugin/Layout', $namespaces, $module_handler, LayoutInterface::class, Layout::class);
  38. $this->themeHandler = $theme_handler;
  39. $this->setCacheBackend($cache_backend, 'layout');
  40. $this->alterInfo('layout');
  41. }
  42. /**
  43. * {@inheritdoc}
  44. */
  45. protected function providerExists($provider) {
  46. return $this->moduleHandler->moduleExists($provider) || $this->themeHandler->themeExists($provider);
  47. }
  48. /**
  49. * {@inheritdoc}
  50. */
  51. protected function getDiscovery() {
  52. if (!$this->discovery) {
  53. $discovery = new AnnotatedClassDiscovery($this->subdir, $this->namespaces, $this->pluginDefinitionAnnotationName, $this->additionalAnnotationNamespaces);
  54. $discovery = new YamlDiscoveryDecorator($discovery, 'layouts', $this->moduleHandler->getModuleDirectories() + $this->themeHandler->getThemeDirectories());
  55. $discovery = new AnnotationBridgeDecorator($discovery, $this->pluginDefinitionAnnotationName);
  56. $discovery = new ContainerDerivativeDiscoveryDecorator($discovery);
  57. $this->discovery = $discovery;
  58. }
  59. return $this->discovery;
  60. }
  61. /**
  62. * {@inheritdoc}
  63. */
  64. public function processDefinition(&$definition, $plugin_id) {
  65. parent::processDefinition($definition, $plugin_id);
  66. if (!$definition instanceof LayoutDefinition) {
  67. throw new InvalidPluginDefinitionException($plugin_id, sprintf('The "%s" layout definition must extend %s', $plugin_id, LayoutDefinition::class));
  68. }
  69. // Add the module or theme path to the 'path'.
  70. $provider = $definition->getProvider();
  71. if ($this->moduleHandler->moduleExists($provider)) {
  72. $base_path = $this->moduleHandler->getModule($provider)->getPath();
  73. }
  74. elseif ($this->themeHandler->themeExists($provider)) {
  75. $base_path = $this->themeHandler->getTheme($provider)->getPath();
  76. }
  77. else {
  78. $base_path = '';
  79. }
  80. $path = $definition->getPath();
  81. $path = !empty($path) ? $base_path . '/' . $path : $base_path;
  82. $definition->setPath($path);
  83. // Add the base path to the icon path.
  84. if ($icon_path = $definition->getIconPath()) {
  85. $definition->setIconPath($path . '/' . $icon_path);
  86. }
  87. // Add a dependency on the provider of the library.
  88. if ($library = $definition->getLibrary()) {
  89. $config_dependencies = $definition->getConfigDependencies();
  90. list($library_provider) = explode('/', $library, 2);
  91. if ($this->moduleHandler->moduleExists($library_provider)) {
  92. $config_dependencies['module'][] = $library_provider;
  93. }
  94. elseif ($this->themeHandler->themeExists($library_provider)) {
  95. $config_dependencies['theme'][] = $library_provider;
  96. }
  97. $definition->setConfigDependencies($config_dependencies);
  98. }
  99. // If 'template' is set, then we'll derive 'template_path' and 'theme_hook'.
  100. $template = $definition->getTemplate();
  101. if (!empty($template)) {
  102. $template_parts = explode('/', $template);
  103. $template = array_pop($template_parts);
  104. $template_path = $path;
  105. if (count($template_parts) > 0) {
  106. $template_path .= '/' . implode('/', $template_parts);
  107. }
  108. $definition->setTemplate($template);
  109. $definition->setThemeHook(strtr($template, '-', '_'));
  110. $definition->setTemplatePath($template_path);
  111. }
  112. if (!$definition->getDefaultRegion()) {
  113. $definition->setDefaultRegion(key($definition->getRegions()));
  114. }
  115. }
  116. /**
  117. * {@inheritdoc}
  118. */
  119. public function getThemeImplementations() {
  120. $hooks = [];
  121. $hooks['layout'] = [
  122. 'render element' => 'content',
  123. ];
  124. /** @var \Drupal\Core\Layout\LayoutDefinition[] $definitions */
  125. $definitions = $this->getDefinitions();
  126. foreach ($definitions as $definition) {
  127. if ($template = $definition->getTemplate()) {
  128. $hooks[$definition->getThemeHook()] = [
  129. 'render element' => 'content',
  130. 'base hook' => 'layout',
  131. 'template' => $template,
  132. 'path' => $definition->getTemplatePath(),
  133. ];
  134. }
  135. }
  136. return $hooks;
  137. }
  138. /**
  139. * {@inheritdoc}
  140. */
  141. public function getCategories() {
  142. // Fetch all categories from definitions and remove duplicates.
  143. $categories = array_unique(array_values(array_map(function (LayoutDefinition $definition) {
  144. return $definition->getCategory();
  145. }, $this->getDefinitions())));
  146. natcasesort($categories);
  147. return $categories;
  148. }
  149. /**
  150. * {@inheritdoc}
  151. *
  152. * @return \Drupal\Core\Layout\LayoutDefinition[]
  153. */
  154. public function getSortedDefinitions(array $definitions = NULL, $label_key = 'label') {
  155. // Sort the plugins first by category, then by label.
  156. $definitions = isset($definitions) ? $definitions : $this->getDefinitions();
  157. // Suppress errors because PHPUnit will indirectly modify the contents,
  158. // triggering https://bugs.php.net/bug.php?id=50688.
  159. @uasort($definitions, function (LayoutDefinition $a, LayoutDefinition $b) {
  160. if ($a->getCategory() != $b->getCategory()) {
  161. return strnatcasecmp($a->getCategory(), $b->getCategory());
  162. }
  163. return strnatcasecmp($a->getLabel(), $b->getLabel());
  164. });
  165. return $definitions;
  166. }
  167. /**
  168. * {@inheritdoc}
  169. *
  170. * @return \Drupal\Core\Layout\LayoutDefinition[][]
  171. */
  172. public function getGroupedDefinitions(array $definitions = NULL, $label_key = 'label') {
  173. $definitions = $this->getSortedDefinitions(isset($definitions) ? $definitions : $this->getDefinitions(), $label_key);
  174. $grouped_definitions = [];
  175. foreach ($definitions as $id => $definition) {
  176. $grouped_definitions[(string) $definition->getCategory()][$id] = $definition;
  177. }
  178. return $grouped_definitions;
  179. }
  180. /**
  181. * {@inheritdoc}
  182. */
  183. public function getLayoutOptions() {
  184. $layout_options = [];
  185. foreach ($this->getGroupedDefinitions() as $category => $layout_definitions) {
  186. foreach ($layout_definitions as $name => $layout_definition) {
  187. $layout_options[$category][$name] = $layout_definition->getLabel();
  188. }
  189. }
  190. return $layout_options;
  191. }
  192. }