file ProviderBase.php

  1. 8.x-3.x src/Plugin/Provider/ProviderBase.php
  2. 7.x-3.x includes/cdn/ProviderBase.php

Namespace

Drupal\bootstrap\Backport\Plugin\Provider
  1. <?php
  2. namespace Drupal\bootstrap\Backport\Plugin\Provider;
  3. /**
  4. * CDN provider base class.
  5. *
  6. * Note: this class is a backport from the 8.x-3.x code base.
  7. *
  8. * @see https://drupal-bootstrap.org/api/bootstrap/namespace/Drupal%21bootstrap%21Plugin%21Provider/8
  9. *
  10. * @ingroup plugins_provider
  11. */
  12. abstract class ProviderBase {
  13. /**
  14. * The plugin_id.
  15. *
  16. * @var string
  17. */
  18. protected $pluginId;
  19. /**
  20. * The currently set CDN assets.
  21. *
  22. * @var array
  23. */
  24. protected $cdnAssets;
  25. /**
  26. * The current theme name.
  27. *
  28. * @var string
  29. */
  30. protected $themeName;
  31. /**
  32. * The versions supplied by the CDN provider.
  33. *
  34. * @var array
  35. */
  36. protected $versions;
  37. /**
  38. * ProviderBase constructor.
  39. */
  40. public function __construct() {
  41. $this->themeName = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : '';
  42. }
  43. /**
  44. * {@inheritdoc}
  45. */
  46. public function alterFrameworkLibrary(array &$framework, $min = NULL) {
  47. // In Drupal 7, CSS and JS are separated into individual hooks and alters,
  48. // so this has the potential to be invoked at a minimum of 3 times.
  49. static $drupal_static_fast;
  50. if (!isset($drupal_static_fast)) {
  51. // Attempt to retrieve CDN assets from a sort of permanent cached in the
  52. // theme settings. This is primarily used to avoid unnecessary API requests
  53. // and speed up the process during a cache rebuild. Theme settings are used
  54. // as they persist through cache rebuilds. In order to prevent stale data,
  55. // a hash is used based on current CDN settings and this "permacache" is
  56. // reset at least once a week regardless.
  57. // @see https://www.drupal.org/project/bootstrap/issues/3031415
  58. $cdnCache = variable_get('bootstrap_cdn_cache') ?: array();
  59. // Reset cache if expired.
  60. if (isset($cdnCache['expire']) && (empty($cdnCache['expire']) || REQUEST_TIME > $cdnCache['expire'])) {
  61. $cdnCache = array();
  62. }
  63. // Set expiration date (1 week by default).
  64. if (!isset($cdnCache['expire'])) {
  65. $cdnCache['expire'] = REQUEST_TIME + variable_get('bootstrap_cdn_cache_expire', 604800);
  66. }
  67. $cdnVersion = $this->getCdnVersion();
  68. $cdnTheme = $this->getCdnTheme();
  69. // Cache not found.
  70. $cdnHash = drupal_hash_base64("{$this->pluginId}:$cdnTheme:$cdnVersion");
  71. if (!isset($cdnCache[$cdnHash])) {
  72. // Retrieve assets and reset cache (should only cache one at a time).
  73. $cdnCache = array(
  74. 'expire' => $cdnCache['expire'],
  75. $cdnHash => $this->getCdnAssets($cdnVersion, $cdnTheme),
  76. );
  77. variable_set('bootstrap_cdn_cache', $cdnCache);
  78. }
  79. // Immediately return if there are no theme CDN assets to use.
  80. if (empty($cdnCache[$cdnHash])) {
  81. return;
  82. }
  83. // Retrieve the system performance config.
  84. if (!isset($min)) {
  85. $min = array(
  86. 'css' => variable_get('preprocess_css', FALSE),
  87. 'js' => variable_get('preprocess_js', FALSE),
  88. );
  89. }
  90. else {
  91. $min = array('css' => !!$min, 'js' => !!$min);
  92. }
  93. // Iterate over each type.
  94. $assets = array();
  95. foreach (array('css', 'js') as $type) {
  96. $files = !empty($min[$type]) && isset($cdnCache[$cdnHash]['min'][$type]) ? $cdnCache[$cdnHash]['min'][$type] : (isset($cdnCache[$cdnHash][$type]) ? $cdnCache[$cdnHash][$type] : array());
  97. foreach ($files as $asset) {
  98. $assets[$type][$asset] = array('data' => $asset, 'type' => 'external');
  99. }
  100. }
  101. // Merge the assets into the library info.
  102. $drupal_static_fast = drupal_array_merge_deep_array(array($assets, $framework));
  103. // Override the framework version with the CDN version that is being used.
  104. $drupal_static_fast['version'] = $cdnVersion;
  105. }
  106. $framework = $drupal_static_fast;
  107. }
  108. /**
  109. * Retrieves a value from the CDN provider cache.
  110. *
  111. * @param string $key
  112. * The name of the item to retrieve. Note: this can be in the form of dot
  113. * notation if the value is nested in an array.
  114. * @param mixed $default
  115. * Optional. The default value to return if $key is not set.
  116. * @param callable $builder
  117. * Optional. If provided, a builder will be invoked when there is no cache
  118. * currently set.
  119. *
  120. * @return mixed
  121. * The cached value if it's set or the value supplied to $default if not.
  122. */
  123. protected function cacheGet($key, $default = NULL, $builder = NULL) {
  124. $cid = $this->getCacheId();
  125. $cache = cache_get($cid);
  126. $data = $cache && isset($cache->data) && is_array($cache->data) ? $cache->data : array();
  127. $parts = static::splitDelimiter($key);
  128. $value = drupal_array_get_nested_value($data, $parts, $key_exists);
  129. // Build the cache.
  130. if (!$key_exists && is_callable($builder)) {
  131. $value = $builder($default);
  132. if (!isset($value)) {
  133. $value = $default;
  134. }
  135. drupal_array_set_nested_value($data, $parts, $value);
  136. cache_set($cid, $data);
  137. return $value;
  138. }
  139. return $key_exists ? $value : $default;
  140. }
  141. /**
  142. * Sets a value in the CDN provider cache.
  143. *
  144. * @param string $key
  145. * The name of the item to set. Note: this can be in the form of dot
  146. * notation if the value is nested in an array.
  147. * @param mixed $value
  148. * Optional. The value to set.
  149. */
  150. protected function cacheSet($key, $value = NULL) {
  151. $cid = $this->getCacheId();
  152. $cache = cache_get($cid);
  153. $data = $cache && isset($cache->data) && is_array($cache->data) ? $cache->data : array();
  154. $parts = static::splitDelimiter($key);
  155. drupal_array_set_nested_value($data, $parts, $value);
  156. cache_set($cid, $data);
  157. }
  158. /**
  159. * {@inheritdoc}
  160. */
  161. protected function discoverCdnAssets($version, $theme) {
  162. return array();
  163. }
  164. /**
  165. * Retrieves the unique cache identifier for the CDN provider.
  166. *
  167. * @return string
  168. * The CDN provider cache identifier.
  169. */
  170. protected function getCacheId() {
  171. return "theme_registry:{$this->themeName}:provider:{$this->pluginId}";
  172. }
  173. /**
  174. * {@inheritdoc}
  175. */
  176. public function getCdnAssets($version = NULL, $theme = NULL) {
  177. if (!isset($version)) {
  178. $version = $this->getCdnVersion();
  179. }
  180. if (!isset($theme)) {
  181. $theme = $this->getCdnTheme();
  182. }
  183. if (!isset($this->cdnAssets)) {
  184. $this->cdnAssets = $this->cacheGet('cdn.assets', array());
  185. }
  186. if (!isset($this->cdnAssets[$version][$theme])) {
  187. $escapedVersion = static::escapeDelimiter($version);
  188. $instance = $this;
  189. $this->cdnAssets[$version][$theme] = $this->cacheGet("cdn.assets.$escapedVersion.$theme", array(), function () use ($version, $theme, $instance) {
  190. return $instance->discoverCdnAssets($version, $theme);
  191. });
  192. }
  193. return $this->cdnAssets[$version][$theme];
  194. }
  195. /**
  196. * {@inheritdoc}
  197. */
  198. public function getCdnTheme() {
  199. return bootstrap_setting("cdn_{$this->pluginId}_theme") ?: 'bootstrap';
  200. }
  201. /**
  202. * {@inheritdoc}
  203. */
  204. public function getCdnThemes($version = NULL) {
  205. return array();
  206. }
  207. /**
  208. * {@inheritdoc}
  209. */
  210. public function getCdnVersion() {
  211. return bootstrap_setting("cdn_{$this->pluginId}_version") ?: BOOTSTRAP_VERSION;
  212. }
  213. /**
  214. * {@inheritdoc}
  215. */
  216. public function getCdnVersions() {
  217. return array();
  218. }
  219. /**
  220. * {@inheritdoc}
  221. */
  222. public function getDescription() {
  223. return '';
  224. }
  225. /**
  226. * {@inheritdoc}
  227. */
  228. public function getLabel() {
  229. return t(ucfirst($this->pluginId));
  230. }
  231. /**
  232. * Allows providers a way to map a version to a different version.
  233. *
  234. * @param string $version
  235. * The version to map.
  236. *
  237. * @return string
  238. * The mapped version.
  239. */
  240. protected function mapVersion($version) {
  241. return $version;
  242. }
  243. /**
  244. * Retrieves JSON from a URI.
  245. *
  246. * @param string $uri
  247. * The URI to retrieve JSON from.
  248. * @param array $options
  249. * The options to pass to the HTTP client.
  250. * @param \Exception|null $exception
  251. * The exception thrown if there was an error, passed by reference.
  252. *
  253. * @return array
  254. * The requested JSON array.
  255. */
  256. protected function requestJson($uri, array $options = array(), &$exception = NULL) {
  257. $json = array();
  258. $options += array(
  259. 'method' => 'GET',
  260. 'headers' => array(
  261. 'User-Agent' => 'Drupal Bootstrap 7.x-3.x (https://www.drupal.org/project/bootstrap)',
  262. ),
  263. );
  264. try {
  265. $response = drupal_http_request($uri, $options);
  266. if (!empty($response->error)) {
  267. throw new \Exception("$uri: {$response->error}", $response->code);
  268. }
  269. if ($response->code >= 200 && $response->code < 400) {
  270. $json = drupal_json_decode($response->data) ?: array();
  271. }
  272. else {
  273. throw new \Exception("$uri: Invalid response", $response->code);
  274. }
  275. }
  276. catch (\Exception $e) {
  277. $exception = $e;
  278. }
  279. return $json;
  280. }
  281. /**
  282. * Escapes a delimiter in a string.
  283. *
  284. * Note: this is primarily useful in situations where dot notation is used
  285. * where the values also contain dots, like in a semantic version string.
  286. *
  287. * @param string $string
  288. * The string to search in.
  289. * @param string $delimiter
  290. * The delimiter to escape.
  291. *
  292. * @return string
  293. * The escaped string.
  294. *
  295. * @see \Drupal\bootstrap\Plugin\Provider\ProviderBase::splitDelimiter()
  296. */
  297. public static function escapeDelimiter($string, $delimiter = '.') {
  298. return str_replace($delimiter, "\\$delimiter", $string);
  299. }
  300. /**
  301. * Splits a string by a specified delimiter, allowing them to be escaped.
  302. *
  303. * Note: this is primarily useful in situations where dot notation is used
  304. * where the values also contain dots, like in a semantic version string.
  305. *
  306. * @param string $string
  307. * The string to split into parts.
  308. * @param string $delimiter
  309. * The delimiter used to split the string.
  310. * @param bool $escapable
  311. * Flag indicating whether the $delimiter can be escaped using a backward
  312. * slash (\).
  313. *
  314. * @return array
  315. * An array of strings, split where the specified $delimiter was present.
  316. *
  317. * @see \Drupal\bootstrap\Plugin\Provider\ProviderBase::escapeDelimiter()
  318. * @see https://stackoverflow.com/a/6243797
  319. */
  320. public static function splitDelimiter($string, $delimiter = '.', $escapable = TRUE) {
  321. if (!$escapable) {
  322. return explode($delimiter, $string);
  323. }
  324. // Split based on delimiter.
  325. $parts = preg_split('~\\\\' . preg_quote($delimiter, '~') . '(*SKIP)(*FAIL)|\.~s', $string);
  326. // Iterate over the parts and remove backslashes from delimiters.
  327. return array_map(function ($string) use ($delimiter) {
  328. return str_replace("\\$delimiter", $delimiter, $string);
  329. }, $parts);
  330. }
  331. }

Classes

Name Description
ProviderBase CDN provider base class.