file CdnAssets.php

Namespace

Drupal\bootstrap\Plugin\Provider
  1. <?php
  2. namespace Drupal\bootstrap\Plugin\Provider;
  3. use Drupal\bootstrap\Utility\Crypt;
  4. use Drupal\Component\Render\MarkupInterface;
  5. /**
  6. * Class CdnAssets.
  7. */
  8. class CdnAssets {
  9. /**
  10. * An array of CdnAsset objects.
  11. *
  12. * @var \Drupal\bootstrap\Plugin\Provider\CdnAsset[]
  13. */
  14. protected $assets = [];
  15. /**
  16. * The human readable label for these assets.
  17. *
  18. * @var \Drupal\Component\Render\MarkupInterface
  19. */
  20. protected $label;
  21. /**
  22. * The library associated with these assets.
  23. *
  24. * @var string
  25. */
  26. protected $library;
  27. /**
  28. * CdnAssets constructor.
  29. *
  30. * @param \Drupal\bootstrap\Plugin\Provider\CdnAsset[] $assets
  31. * Optional. An array of CdnAsset objects to set.
  32. */
  33. public function __construct(array $assets = []) {
  34. $this->appendAssets($assets);
  35. }
  36. /**
  37. * Retrieves all assets.
  38. *
  39. * @param bool|bool[] $minified
  40. * Flag indicating whether only the minified asset should be retrieved.
  41. * This can be an associative array where the key is the asset type and
  42. * the value is a boolean indicating whether to use minified assets for
  43. * that specific type. If not set, all assets are retrieved regardless
  44. * if they are minified or not.
  45. *
  46. * @return \Drupal\bootstrap\Plugin\Provider\CdnAsset[]
  47. * An array of CdnAsset objects.
  48. */
  49. public function all($minified = NULL) {
  50. $assets = [];
  51. if (isset($minified) && !is_array($minified)) {
  52. $minified = ['css' => !!$minified, 'js' => !!$minified];
  53. }
  54. foreach (['css', 'js'] as $type) {
  55. $assets = array_merge($assets, $this->get($type, isset($minified[$type]) ? $minified[$type] : NULL));
  56. }
  57. return $assets;
  58. }
  59. /**
  60. * Appends a CdnAsset object to the list.
  61. *
  62. * @param \Drupal\bootstrap\Plugin\Provider\CdnAsset $asset
  63. * A CdnAsset object.
  64. */
  65. public function append(CdnAsset $asset) {
  66. if (isset($this->assets[$asset->getId()])) {
  67. $this->assets[$asset->getId()] = $asset;
  68. }
  69. else {
  70. $this->assets = array_merge($this->assets, [$asset->getId() => $asset]);
  71. }
  72. }
  73. /**
  74. * Appends an array of CdnAsset objects to the list.
  75. *
  76. * @param \Drupal\bootstrap\Plugin\Provider\CdnAsset[] $assets
  77. * An array of CdnAsset objects.
  78. */
  79. public function appendAssets(array $assets) {
  80. foreach ($assets as $asset) {
  81. $this->append($asset);
  82. }
  83. }
  84. /**
  85. * Retrieves specific types of assets.
  86. *
  87. * @param string $type
  88. * The type of assets to retrieve (e.g. css or js).
  89. * @param bool|bool[] $minified
  90. * Flag indicating whether only the minified asset should be retrieved.
  91. * This can be an associative array where the key is the asset type and
  92. * the value is a boolean indicating whether to use minified assets for
  93. * that specific type. If not set, all assets are retrieved regardless
  94. * if they are minified or not.
  95. *
  96. * @return \Drupal\bootstrap\Plugin\Provider\CdnAsset[]
  97. * An array of CdnAsset objects.
  98. */
  99. public function get($type, $minified = NULL) {
  100. // Filter by type.
  101. $assets = array_filter($this->assets, function (CdnAsset $asset) use ($type) {
  102. return $asset->getType() === $type;
  103. });
  104. // Filter assets by matching minification value.
  105. if (isset($minified)) {
  106. $assets = array_filter($assets, function (CdnAsset $asset) use ($minified) {
  107. return $asset->isMinified() === $minified;
  108. });
  109. }
  110. return $assets;
  111. }
  112. /**
  113. * Retrieves the human readable label.
  114. *
  115. * Note: if the label isn't yet set, it will attempt to retrieve the label
  116. * from the first available asset.
  117. *
  118. * @return \Drupal\Component\Render\MarkupInterface
  119. * The label.
  120. */
  121. public function getLabel() {
  122. if (!isset($this->label)) {
  123. $asset = reset($this->assets);
  124. $this->label = $asset ? $asset->getLabel() : NULL;
  125. }
  126. return $this->label;
  127. }
  128. /**
  129. * Retrieves the library associated with these assets.
  130. *
  131. * Note: if the library isn't yet set, it will attempt to retrieve the library
  132. * from the first available asset.
  133. *
  134. * @return \Drupal\Component\Render\MarkupInterface
  135. * The library.
  136. */
  137. public function getLibrary() {
  138. if (!isset($this->library)) {
  139. $asset = reset($this->assets);
  140. $this->library = $asset ? $asset->getLibrary() : NULL;
  141. }
  142. return $this->library;
  143. }
  144. /**
  145. * Retrieves a specific theme.
  146. *
  147. * @param string $theme
  148. * The theme to return. If not specified, the first available theme will
  149. * be returned.
  150. *
  151. * @return static
  152. */
  153. public function getTheme($theme = NULL) {
  154. $themes = $this->getThemes();
  155. if (!$theme) {
  156. return reset($themes) ?: new static();
  157. }
  158. if (isset($themes[$theme])) {
  159. return $themes[$theme];
  160. }
  161. return new static();
  162. }
  163. /**
  164. * Groups available assets by theme.
  165. *
  166. * @return \Drupal\bootstrap\Plugin\Provider\CdnAssets[]
  167. * A collection of newly created CdnAssets objects, keyed by theme name.
  168. */
  169. public function getThemes() {
  170. /** @var \Drupal\bootstrap\Plugin\Provider\CdnAssets[] $themes */
  171. $themes = [];
  172. foreach ($this->assets as $asset) {
  173. $theme = $asset->getTheme();
  174. if (!isset($themes[$theme])) {
  175. $themes[$theme] = (new static())
  176. ->setLabel($asset->getLabel())
  177. ->setLibrary($asset->getLibrary());
  178. }
  179. $themes[$theme]->append($asset);
  180. }
  181. // Sort the themes.
  182. uksort($themes, [$this, 'sortThemes']);
  183. // Post process the themes to fill in any missing assets.
  184. $bootstrap = isset($themes['bootstrap']) ? $themes['bootstrap'] : new static();
  185. foreach (array_keys($themes) as $theme) {
  186. // The example Bootstrap theme are just overrides, it requires the main
  187. // bootstrap library CSS to be loaded first.
  188. if ($theme === 'bootstrap_theme') {
  189. if ($css = $bootstrap->get('css', TRUE)) {
  190. $themes['bootstrap_theme']->prependAssets($css);
  191. }
  192. if ($css = $bootstrap->get('css', FALSE)) {
  193. $themes['bootstrap_theme']->prependAssets($css);
  194. }
  195. }
  196. // Populate missing JavaScript.
  197. if (!$themes[$theme]->get('js', TRUE)) {
  198. if ($js = $bootstrap->get('js', FALSE)) {
  199. $themes[$theme]->appendAssets($js);
  200. }
  201. if ($js = $bootstrap->get('js', TRUE)) {
  202. $themes[$theme]->appendAssets($js);
  203. }
  204. }
  205. }
  206. return $themes;
  207. }
  208. /**
  209. * Merges another CdnAssets object onto this one.
  210. *
  211. * @param \Drupal\bootstrap\Plugin\Provider\CdnAssets $assets
  212. * A CdnAssets object.
  213. *
  214. * @return static
  215. */
  216. public function merge(CdnAssets $assets) {
  217. $this->appendAssets($assets->toArray());
  218. return $this;
  219. }
  220. /**
  221. * Prepends a CdnAsset object to the list.
  222. *
  223. * @param \Drupal\bootstrap\Plugin\Provider\CdnAsset $asset
  224. * A CdnAsset object.
  225. */
  226. public function prepend(CdnAsset $asset) {
  227. if (isset($this->assets[$asset->getId()])) {
  228. $this->assets[$asset->getId()] = $asset;
  229. }
  230. else {
  231. $this->assets = array_merge([$asset->getId() => $asset], $this->assets);
  232. }
  233. }
  234. /**
  235. * Prepends an array of CdnAsset objects to the list.
  236. *
  237. * @param \Drupal\bootstrap\Plugin\Provider\CdnAsset[] $assets
  238. * An array of CdnAsset objects.
  239. */
  240. public function prependAssets(array $assets) {
  241. foreach (array_reverse($assets) as $asset) {
  242. $this->prepend($asset);
  243. }
  244. }
  245. /**
  246. * Retrieves all the set CDN Asset objects, as an array.
  247. *
  248. * @return \Drupal\bootstrap\Plugin\Provider\CdnAsset[]
  249. * The CDN Asset objects.
  250. */
  251. public function toArray() {
  252. return $this->assets;
  253. }
  254. /**
  255. * Converts the CDN Assets into an array suitable for a Drupal library array.
  256. *
  257. * @param bool $minified
  258. * Flag indicating whether to use minified assets.
  259. *
  260. * @return array
  261. * An array structured for use in a Drupal library.
  262. */
  263. public function toLibraryArray($minified = NULL) {
  264. $assets = $this->all($minified);
  265. $library = [];
  266. // Iterate over each type.
  267. foreach ($assets as $asset) {
  268. $url = (string) $asset;
  269. $type = $asset->getType();
  270. $data = ['data' => $url, 'type' => 'external'];
  271. // Attempt to add a corresponding SRI attribute for the URL.
  272. // @see https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
  273. foreach (['sha512', 'sha384', 'sha256', 'sha', 'hash', 'sri', 'integrity'] as $key) {
  274. if ($integrity = $asset->getInfo($key)) {
  275. // Parse the SRI integrity value to extract both the algorithm and
  276. // hash. Note: this is needed as some APIs do not prepend the hash
  277. // with the actual algorithm used. This is likely because the field,
  278. // while a valid base64 encoded hash, isn't specifically intended for
  279. // use as an SRI integrity attribute value.
  280. list($algorithm, $hash) = Crypt::parseSriIntegrity($integrity);
  281. // Ensure the algorithm and hash are valid.
  282. if (Crypt::checkBase64HashAlgorithm($algorithm, $hash, TRUE)) {
  283. $data['attributes'] = [
  284. 'integrity' => "$algorithm-$hash",
  285. 'crossorigin' => $asset->getInfo('crossorigin', 'anonymous'),
  286. ];
  287. }
  288. break;
  289. }
  290. }
  291. // CSS library assets use "SMACSS" categorization, assign to "base".
  292. if ($type === 'css') {
  293. $library[$type]['base'][$url] = $data;
  294. }
  295. else {
  296. $library[$type][$url] = $data;
  297. }
  298. }
  299. return $library;
  300. }
  301. /**
  302. * Sets the label.
  303. *
  304. * @param \Drupal\Component\Render\MarkupInterface $label
  305. * The label to set.
  306. *
  307. * @return static
  308. */
  309. public function setLabel(MarkupInterface $label) {
  310. $this->label = $label;
  311. return $this;
  312. }
  313. /**
  314. * Sets the library associated with these assets.
  315. *
  316. * @param string $library
  317. * The library to set.
  318. *
  319. * @return static
  320. */
  321. public function setLibrary($library) {
  322. $this->library = $library;
  323. return $this;
  324. }
  325. /**
  326. * Sorts themes.
  327. *
  328. * @param string $a
  329. * First theme to compare.
  330. * @param string $b
  331. * Second theme to compare.
  332. *
  333. * @return false|int|string
  334. * The comparision value, similar to other comparison functions.
  335. */
  336. protected function sortThemes($a, $b) {
  337. $order = ['bootstrap', 'bootstrap_theme'];
  338. $aIndex = array_search($a, $order);
  339. if ($aIndex === FALSE) {
  340. $aIndex = 2;
  341. }
  342. $bIndex = array_search($b, $order);
  343. if ($bIndex === FALSE) {
  344. $bIndex = 2;
  345. }
  346. if ($aIndex !== $bIndex) {
  347. return $aIndex - $bIndex;
  348. }
  349. return strnatcasecmp($a, $b);
  350. }
  351. }

Classes

Name Description
CdnAssets Class CdnAssets.