file ApiProviderBase.php

Namespace

Drupal\bootstrap\Plugin\Provider
  1. <?php
  2. namespace Drupal\bootstrap\Plugin\Provider;
  3. use Drupal\bootstrap\Bootstrap;
  4. use Drupal\Component\Render\FormattableMarkup;
  5. use Drupal\Component\Utility\UrlHelper;
  6. use Drupal\Core\Render\Markup;
  7. /**
  8. * CDN Provider base that uses an API to populate its assets.
  9. *
  10. * @ingroup plugins_provider
  11. */
  12. abstract class ApiProviderBase extends ProviderBase {
  13. /**
  14. * {@inheritdoc}
  15. */
  16. protected function discoverCdnAssets($version, $theme = NULL) {
  17. if ($this->supportsThemes()) {
  18. $themes = $this->getCdnThemes($version);
  19. if (isset($themes[$theme])) {
  20. return $themes[$theme];
  21. }
  22. // Fall back to the first available theme if possible (likely Bootstrap).
  23. return reset($themes) ?: new CdnAssets();
  24. }
  25. return $this->requestApiAssets('bootstrap', $version)->getTheme('bootstrap');
  26. }
  27. /**
  28. * {@inheritdoc}
  29. */
  30. protected function discoverCdnThemes($version) {
  31. $assets = new CdnAssets();
  32. foreach (['bootstrap', 'bootswatch'] as $library) {
  33. $assets = $this->requestApiAssets($library, $version, $assets);
  34. }
  35. return $assets->getThemes();
  36. }
  37. /**
  38. * {@inheritdoc}
  39. */
  40. protected function discoverCdnVersions() {
  41. return $this->requestApiVersions('bootstrap');
  42. }
  43. /**
  44. * Retrieves the URL to use for determining available versions from the API.
  45. *
  46. * @param string $library
  47. * The library to request.
  48. * @param string $version
  49. * The version to request.
  50. *
  51. * @return string
  52. * The API URL to use.
  53. */
  54. protected function getApiAssetsUrl($library, $version) {
  55. return (string) new FormattableMarkup($this->getApiAssetsUrlTemplate(), [
  56. '@library' => Markup::create($this->mapLibrary($library)),
  57. '@version' => Markup::create($this->mapVersion($version, $library)),
  58. ]);
  59. }
  60. /**
  61. * Retrieves the API URL template to use when requesting a specific asset.
  62. *
  63. * Available placeholders (must be prepended with an at symbol, @):
  64. * - library - The library to request.
  65. * - version - The version to request.
  66. *
  67. * @return string
  68. * The CDN URL template.
  69. */
  70. abstract protected function getApiAssetsUrlTemplate();
  71. /**
  72. * Retrieves the URL to use for determining available versions from the API.
  73. *
  74. * @param string $library
  75. * The library to request.
  76. *
  77. * @return string
  78. * The API URL to use.
  79. */
  80. protected function getApiVersionsUrl($library) {
  81. return (string) new FormattableMarkup($this->getApiVersionsUrlTemplate(), [
  82. '@library' => Markup::create($this->mapLibrary($library)),
  83. ]);
  84. }
  85. /**
  86. * Retrieves the API URL template to use for determining available versions.
  87. *
  88. * Available placeholders (must be prepended with an at symbol, @):
  89. * - library - The specific library being requested.
  90. *
  91. * @return string
  92. * The CDN URL template.
  93. */
  94. abstract protected function getApiVersionsUrlTemplate();
  95. /**
  96. * Retrieves a CDN URL based on provided variables.
  97. *
  98. * @param string $library
  99. * The library to request.
  100. * @param string $version
  101. * The version to request.
  102. * @param string $file
  103. * The file to request.
  104. * @param array $info
  105. * Additional information about the file, if any.
  106. *
  107. * @return \Drupal\bootstrap\Plugin\Provider\CdnAsset
  108. * A CDN Asset object, for a given URL.
  109. */
  110. protected function getCdnUrl($library, $version, $file, array $info = []) {
  111. $library = $this->mapLibrary($library);
  112. $version = $this->mapVersion($version, $library);
  113. // Check if the "file" is really a fully qualified URL.
  114. if (UrlHelper::isExternal($file)) {
  115. $url = $file;
  116. }
  117. // Otherwise, use the template.
  118. else {
  119. $url = (string) new FormattableMarkup($this->getCdnUrlTemplate(), [
  120. '@library' => Markup::create($library),
  121. '@version' => Markup::create($version),
  122. '@file' => Markup::create(ltrim($file, '/')),
  123. ]);
  124. }
  125. return new CdnAsset($url, $library, $version, $info);
  126. }
  127. /**
  128. * Retrieves the CDN URL template to use.
  129. *
  130. * Available placeholders (must be prepended with an at symbol, @):
  131. * - library - The library to request.
  132. * - version - The version to request.
  133. * - file - The file to request.
  134. * - theme - The theme to request.
  135. *
  136. * @return string
  137. * The CDN URL template.
  138. */
  139. abstract protected function getCdnUrlTemplate();
  140. /**
  141. * Checks whether a version is valid.
  142. *
  143. * @param string $version
  144. * The version to check.
  145. *
  146. * @return bool
  147. * TRUE or FALSE
  148. *
  149. * @todo Move regular expression to a constant once PHP 5.5 is no longer
  150. * supported.
  151. */
  152. public static function isValidVersion($version) {
  153. return !!is_string($version) && preg_match('/^' . substr(Bootstrap::FRAMEWORK_VERSION, 0, 1) . '\.\d+\.\d+$/', $version);
  154. }
  155. /**
  156. * Allows providers a way to map a library to a different library.
  157. *
  158. * @param string $library
  159. * The library to map.
  160. *
  161. * @return string
  162. * The mapped library.
  163. */
  164. protected function mapLibrary($library) {
  165. return $library;
  166. }
  167. /**
  168. * {@inheritdoc}
  169. */
  170. protected function mapVersion($version, $library = NULL) {
  171. $mapped = [];
  172. // While the Bootswatch project attempts to maintain version parity with
  173. // Bootstrap, it doesn't always happen. This causes issues when the system
  174. // expects a 1:1 version match between Bootstrap and Bootswatch.
  175. // @see https://github.com/thomaspark/bootswatch/issues/892#ref-issue-410070082
  176. if ($library === 'bootswatch') {
  177. // This version is "broken" because of jsDelivr's API limit.
  178. $mapped['3.4.1'] = '3.4.0';
  179. // This version doesn't exist.
  180. $mapped['3.1.1'] = '3.2.0';
  181. }
  182. return isset($mapped[$version]) ? $mapped[$version] : $version;
  183. }
  184. /**
  185. * Parses assets provided by the API data.
  186. *
  187. * @param array $data
  188. * The data to parse.
  189. * @param string $library
  190. * The base URL each one of the $files are relative to, this usually
  191. * should also include the version path prefix as well.
  192. * @param string $version
  193. * A specific version to use.
  194. * @param \Drupal\bootstrap\Plugin\Provider\CdnAssets $assets
  195. * An existing CdnAssets object, if chaining multiple requests together.
  196. *
  197. * @return \Drupal\bootstrap\Plugin\Provider\CdnAssets
  198. * A CdnAssets object containing the necessary assets.
  199. */
  200. protected function parseAssets(array $data, $library, $version, CdnAssets $assets = NULL) {
  201. if (!isset($assets)) {
  202. $assets = new CdnAssets();
  203. }
  204. $files = [];
  205. // Support APIs that have a dedicated "files" property.
  206. if (isset($data['files'])) {
  207. $files = $data['files'];
  208. }
  209. elseif (isset($data['assets'])) {
  210. foreach ($data['assets'] as $asset) {
  211. // Support APIs that clump all the assets together, regardless of their
  212. // versions. Skip assets that don't match this version.
  213. if (isset($asset['version']) && $asset['version'] !== $version) {
  214. continue;
  215. }
  216. // Found the necessary files for the specified version.
  217. if (!empty($asset['files'])) {
  218. $files = $asset['files'];
  219. break;
  220. }
  221. }
  222. }
  223. foreach ($files as $file) {
  224. // Support APIs that simply use simple strings as files.
  225. if (is_string($file) && CdnAsset::isFileValid($file)) {
  226. $assets->append($this->getCdnUrl($library, $version, $file));
  227. }
  228. // Support APIs that put each file into its own array (metadata).
  229. elseif (is_array($file)) {
  230. // Support APIs that clump all the files together, regardless of their
  231. // versions. Skip assets that don't match this version.
  232. if (isset($file['version']) && $file['version'] !== $version) {
  233. continue;
  234. }
  235. // Support multiple keys for the "file".
  236. foreach (['filename', 'name', 'url', 'uri', 'path'] as $key) {
  237. if (!empty($file[$key]) && CdnAsset::isFileValid($file[$key])) {
  238. $assets->append($this->getCdnUrl($library, $version, $file[$key], $file));
  239. break;
  240. }
  241. }
  242. }
  243. }
  244. return $assets;
  245. }
  246. /**
  247. * Parses available versions provided by the API data.
  248. *
  249. * @param array $data
  250. * The data to parse.
  251. *
  252. * @return array
  253. * An associative array of versions, keyed by version.
  254. */
  255. protected function parseVersions(array $data = []) {
  256. $versions = [];
  257. // Support APIs that have a dedicated "versions" property.
  258. if (!empty($data['versions'])) {
  259. foreach ($data['versions'] as $version) {
  260. // Only extract valid versions.
  261. if ($this->isValidVersion($version)) {
  262. $versions[$version] = $version;
  263. }
  264. }
  265. }
  266. // Support APIs that have the version nested under individual assets.
  267. elseif (!empty($data['assets'])) {
  268. foreach ($data['assets'] as $asset) {
  269. if (isset($asset['version']) && $this->isValidVersion($asset['version'])) {
  270. $versions[$asset['version']] = $asset['version'];
  271. }
  272. }
  273. }
  274. return $versions;
  275. }
  276. /**
  277. * Requests available assets from the CDN Provider API.
  278. *
  279. * @param string $library
  280. * The library to request.
  281. * @param string $version
  282. * The version to request.
  283. * @param \Drupal\bootstrap\Plugin\Provider\CdnAssets $assets
  284. * An existing CdnAssets object, if chaining multiple requests together.
  285. *
  286. * @return \Drupal\bootstrap\Plugin\Provider\CdnAssets
  287. * The CdnAssets provided by the API.
  288. */
  289. protected function requestApiAssets($library, $version, CdnAssets $assets = NULL) {
  290. $url = $this->getApiAssetsUrl($library, $version);
  291. $options = ['ttl' => $this->getCacheTtl(static::CACHE_ASSETS)];
  292. $data = $this->request($url, $options)->getData();
  293. // If bootstrap data could not be returned, provide defaults.
  294. if (!$data && $this->cdnExceptions && $library === 'bootstrap') {
  295. $data = [
  296. 'files' => [
  297. '/dist/css/bootstrap.css',
  298. '/dist/js/bootstrap.js',
  299. '/dist/css/bootstrap.min.css',
  300. '/dist/js/bootstrap.min.js',
  301. ],
  302. ];
  303. }
  304. // Parse the files from data.
  305. return $this->parseAssets($data, $library, $version, $assets);
  306. }
  307. /**
  308. * Requests available versions from the CDN Provider API.
  309. *
  310. * @param string $library
  311. * The library to request versions for.
  312. *
  313. * @return array
  314. * An associative array of versions, keyed by version.
  315. */
  316. public function requestApiVersions($library) {
  317. $url = $this->getApiVersionsUrl($library);
  318. $options = ['ttl' => $this->getCacheTtl(static::CACHE_VERSIONS)];
  319. $data = $this->request($url, $options)->getData();
  320. // If bootstrap data could not be returned, provide defaults.
  321. if (!$data && $this->cdnExceptions && $library === 'bootstrap') {
  322. $data = ['versions' => [Bootstrap::FRAMEWORK_VERSION]];
  323. }
  324. return $this->parseVersions($data);
  325. }
  326. /**
  327. * {@inheritdoc}
  328. *
  329. * @deprecated in 8.x-3.18, will be removed in a future release.
  330. */
  331. public function processDefinition(array &$definition, $plugin_id) {
  332. // Intentionally left blank so it doesn't trigger a deprecation warning.
  333. }
  334. }

Classes

Name Description
ApiProviderBase CDN Provider base that uses an API to populate its assets.