BreakpointManager.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. <?php
  2. namespace Drupal\breakpoint;
  3. use Drupal\Core\Cache\Cache;
  4. use Drupal\Core\Cache\CacheBackendInterface;
  5. use Drupal\Core\Extension\ModuleHandlerInterface;
  6. use Drupal\Core\Extension\ThemeHandlerInterface;
  7. use Drupal\Core\Plugin\DefaultPluginManager;
  8. use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
  9. use Drupal\Core\Plugin\Discovery\YamlDiscovery;
  10. use Drupal\Core\Plugin\Factory\ContainerFactory;
  11. use Drupal\Core\StringTranslation\StringTranslationTrait;
  12. use Drupal\Core\StringTranslation\TranslationInterface;
  13. /**
  14. * Defines a breakpoint plugin manager to deal with breakpoints.
  15. *
  16. * Extension can define breakpoints in a EXTENSION_NAME.breakpoints.yml file
  17. * contained in the extension's base directory. Each breakpoint has the
  18. * following structure:
  19. * @code
  20. * MACHINE_NAME:
  21. * label: STRING
  22. * mediaQuery: STRING
  23. * weight: INTEGER
  24. * multipliers:
  25. * - STRING
  26. * @endcode
  27. * For example:
  28. * @code
  29. * bartik.mobile:
  30. * label: mobile
  31. * mediaQuery: '(min-width: 0px)'
  32. * weight: 0
  33. * multipliers:
  34. * - 1x
  35. * - 2x
  36. * @endcode
  37. * Optionally a breakpoint can provide a group key. By default an extensions
  38. * breakpoints will be placed in a group labelled with the extension name.
  39. *
  40. * @see \Drupal\breakpoint\Breakpoint
  41. * @see \Drupal\breakpoint\BreakpointInterface
  42. * @see plugin_api
  43. */
  44. class BreakpointManager extends DefaultPluginManager implements BreakpointManagerInterface {
  45. use StringTranslationTrait;
  46. /**
  47. * {@inheritdoc}
  48. */
  49. protected $defaults = [
  50. // Human readable label for breakpoint.
  51. 'label' => '',
  52. // The media query for the breakpoint.
  53. 'mediaQuery' => '',
  54. // Weight used for ordering breakpoints.
  55. 'weight' => 0,
  56. // Breakpoint multipliers.
  57. 'multipliers' => [],
  58. // The breakpoint group.
  59. 'group' => '',
  60. // Default class for breakpoint implementations.
  61. 'class' => 'Drupal\breakpoint\Breakpoint',
  62. // The plugin id. Set by the plugin system based on the top-level YAML key.
  63. 'id' => '',
  64. ];
  65. /**
  66. * The theme handler.
  67. *
  68. * @var \Drupal\Core\Extension\ThemeHandlerInterface
  69. */
  70. protected $themeHandler;
  71. /**
  72. * Static cache of breakpoints keyed by group.
  73. *
  74. * @var array
  75. */
  76. protected $breakpointsByGroup;
  77. /**
  78. * The plugin instances.
  79. *
  80. * @var array
  81. */
  82. protected $instances = [];
  83. /**
  84. * Constructs a new BreakpointManager instance.
  85. *
  86. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  87. * The module handler.
  88. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
  89. * The theme handler.
  90. * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
  91. * The cache backend.
  92. * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
  93. * The string translation service.
  94. */
  95. public function __construct(ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, CacheBackendInterface $cache_backend, TranslationInterface $string_translation) {
  96. $this->factory = new ContainerFactory($this);
  97. $this->moduleHandler = $module_handler;
  98. $this->themeHandler = $theme_handler;
  99. $this->setStringTranslation($string_translation);
  100. $this->alterInfo('breakpoints');
  101. $this->setCacheBackend($cache_backend, 'breakpoints', ['breakpoints']);
  102. }
  103. /**
  104. * {@inheritdoc}
  105. */
  106. protected function getDiscovery() {
  107. if (!isset($this->discovery)) {
  108. $this->discovery = new YamlDiscovery('breakpoints', $this->moduleHandler->getModuleDirectories() + $this->themeHandler->getThemeDirectories());
  109. $this->discovery = new ContainerDerivativeDiscoveryDecorator($this->discovery);
  110. }
  111. return $this->discovery;
  112. }
  113. /**
  114. * {@inheritdoc}
  115. */
  116. public function processDefinition(&$definition, $plugin_id) {
  117. parent::processDefinition($definition, $plugin_id);
  118. // Allow custom groups and therefore more than one group per extension.
  119. if (empty($definition['group'])) {
  120. $definition['group'] = $definition['provider'];
  121. }
  122. // Ensure a 1x multiplier exists.
  123. if (!in_array('1x', $definition['multipliers'])) {
  124. $definition['multipliers'][] = '1x';
  125. }
  126. // Ensure that multipliers are sorted correctly.
  127. sort($definition['multipliers']);
  128. }
  129. /**
  130. * {@inheritdoc}
  131. */
  132. protected function providerExists($provider) {
  133. return $this->moduleHandler->moduleExists($provider) || $this->themeHandler->themeExists($provider);
  134. }
  135. /**
  136. * {@inheritdoc}
  137. */
  138. public function getBreakpointsByGroup($group) {
  139. if (!isset($this->breakpointsByGroup[$group])) {
  140. if ($cache = $this->cacheBackend->get($this->cacheKey . ':' . $group)) {
  141. $this->breakpointsByGroup[$group] = $cache->data;
  142. }
  143. else {
  144. $breakpoints = [];
  145. foreach ($this->getDefinitions() as $plugin_id => $plugin_definition) {
  146. if ($plugin_definition['group'] == $group) {
  147. $breakpoints[$plugin_id] = $plugin_definition;
  148. }
  149. }
  150. uasort($breakpoints, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']);
  151. $this->cacheBackend->set($this->cacheKey . ':' . $group, $breakpoints, Cache::PERMANENT, ['breakpoints']);
  152. $this->breakpointsByGroup[$group] = $breakpoints;
  153. }
  154. }
  155. $instances = [];
  156. foreach ($this->breakpointsByGroup[$group] as $plugin_id => $definition) {
  157. if (!isset($this->instances[$plugin_id])) {
  158. $this->instances[$plugin_id] = $this->createInstance($plugin_id);
  159. }
  160. $instances[$plugin_id] = $this->instances[$plugin_id];
  161. }
  162. return $instances;
  163. }
  164. /**
  165. * {@inheritdoc}
  166. */
  167. public function getGroups() {
  168. // Use a double colon so as to not clash with the cache for each group.
  169. if ($cache = $this->cacheBackend->get($this->cacheKey . '::groups')) {
  170. $groups = $cache->data;
  171. }
  172. else {
  173. $groups = [];
  174. foreach ($this->getDefinitions() as $plugin_definition) {
  175. if (!isset($groups[$plugin_definition['group']])) {
  176. $groups[$plugin_definition['group']] = $plugin_definition['group'];
  177. }
  178. }
  179. $this->cacheBackend->set($this->cacheKey . '::groups', $groups, Cache::PERMANENT, ['breakpoints']);
  180. }
  181. // Get the labels. This is not cacheable due to translation.
  182. $group_labels = [];
  183. foreach ($groups as $group) {
  184. $group_labels[$group] = $this->getGroupLabel($group);
  185. }
  186. asort($group_labels);
  187. return $group_labels;
  188. }
  189. /**
  190. * {@inheritdoc}
  191. */
  192. public function getGroupProviders($group) {
  193. $providers = [];
  194. $breakpoints = $this->getBreakpointsByGroup($group);
  195. foreach ($breakpoints as $breakpoint) {
  196. $provider = $breakpoint->getProvider();
  197. $extension = FALSE;
  198. if ($this->moduleHandler->moduleExists($provider)) {
  199. $extension = $this->moduleHandler->getModule($provider);
  200. }
  201. elseif ($this->themeHandler->themeExists($provider)) {
  202. $extension = $this->themeHandler->getTheme($provider);
  203. }
  204. if ($extension) {
  205. $providers[$extension->getName()] = $extension->getType();
  206. }
  207. }
  208. return $providers;
  209. }
  210. /**
  211. * {@inheritdoc}
  212. */
  213. public function clearCachedDefinitions() {
  214. parent::clearCachedDefinitions();
  215. $this->breakpointsByGroup = NULL;
  216. $this->instances = [];
  217. }
  218. /**
  219. * Gets the label for a breakpoint group.
  220. *
  221. * @param string $group
  222. * The breakpoint group.
  223. *
  224. * @return string
  225. * The label.
  226. */
  227. protected function getGroupLabel($group) {
  228. // Extension names are not translatable.
  229. if ($this->moduleHandler->moduleExists($group)) {
  230. $label = $this->moduleHandler->getName($group);
  231. }
  232. elseif ($this->themeHandler->themeExists($group)) {
  233. $label = $this->themeHandler->getName($group);
  234. }
  235. else {
  236. // Custom group label that should be translatable.
  237. $label = $this->t($group, [], ['context' => 'breakpoint']);
  238. }
  239. return $label;
  240. }
  241. }