cdn.inc 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. <?php
  2. /**
  3. * @file
  4. * cdn.inc
  5. *
  6. * Provides necessary CDN integration.
  7. */
  8. define('BOOTSTRAP_CDN_PROVIDER_PATH', 'public://bootstrap/cdn_providers');
  9. /**
  10. * Retrieves JSON from a URI.
  11. *
  12. * @param string $url
  13. * The URI to retrieve JSON from.
  14. * @param array $options
  15. * The options to pass to the HTTP client.
  16. * @param \Exception|null $exception
  17. * The exception thrown if there was an error, passed by reference.
  18. *
  19. * @return array
  20. * The requested JSON array.
  21. */
  22. function _bootstrap_cdn_provider_request_json($url, array $options = array(), &$exception = NULL) {
  23. $json = array();
  24. $options += array(
  25. 'method' => 'GET',
  26. 'headers' => array(
  27. 'User-Agent' => 'Drupal Bootstrap 7.x-3.x (https://www.drupal.org/project/bootstrap)',
  28. ),
  29. );
  30. try {
  31. $response = drupal_http_request($url, $options);
  32. if (!empty($response->error)) {
  33. throw new \Exception($response->error, $response->code);
  34. }
  35. if ($response->code == 200) {
  36. $json = drupal_json_decode($response->data) ?: array();
  37. }
  38. }
  39. catch (\Exception $e) {
  40. $exception = $e;
  41. }
  42. return $json;
  43. }
  44. /**
  45. * Retrieves the Drupal Bootstrap Styles to use with CDN assets.
  46. *
  47. * @param string $version
  48. * A specific version to use. If not set, the currently set CDN Provider
  49. * version will be used.
  50. * @param string $theme
  51. * A specific theme to use. If not set, the currently set CDN Provider
  52. * theme will be used.
  53. *
  54. * @return string|false
  55. * The URL to the styles or FALSE if it doesn't exist.
  56. */
  57. function _bootstrap_cdn_get_drupal_bootstrap_styles_url($version = NULL, $theme = NULL) {
  58. global $theme_key;
  59. static $styles;
  60. if (!isset($provider)) {
  61. $provider = bootstrap_setting('cdn_provider', NULL, 'bootstrap', 'jsdelivr');
  62. }
  63. if (!isset($version)) {
  64. $version = bootstrap_setting('cdn_' . $provider . '_version') ?: BOOTSTRAP_VERSION;
  65. }
  66. if (!isset($theme)) {
  67. $theme = bootstrap_setting('cdn_' . $provider . '_theme') ?: 'bootstrap';
  68. }
  69. $cid = "theme_registry:$theme_key:drupal_bootstrap_styles";
  70. $min = variable_get('preprocess_css', FALSE) ? '.min' : '';
  71. $key = "$provider:$version:$theme$min";
  72. if (!isset($styles)) {
  73. // Allow sites to manually specify a custom path or URL.
  74. if ($data = variable_get('drupal_bootstrap_styles')) {
  75. $styles = array($key => $data);
  76. }
  77. // Otherwise, attempt to load cached database data.
  78. else {
  79. $styles = ($cache = cache_get($cid)) && !empty($cache->data) ? $cache->data : array();
  80. }
  81. }
  82. if (!isset($styles[$key])) {
  83. // Determine the latest @unicorn-fail/drupal-bootstrap-styles version.
  84. if (!isset($styles['versions'])) {
  85. $json = _bootstrap_cdn_provider_request_json('https://data.jsdelivr.com/v1/package/npm/@unicorn-fail/drupal-bootstrap-styles');
  86. $json += array('versions' => array());
  87. natsort($json['versions']);
  88. $styles['versions'] = $json['versions'];
  89. }
  90. // Latest version should be the last entry.
  91. $pkg_version = end($styles['versions']);
  92. // Retrieve the list of files from the actual distributed API JSON file in
  93. // @unicorn-fail/drupal-bootstrap-styles (note the sub-domain difference).
  94. if (!isset($styles["$pkg_version:files"])) {
  95. $json = _bootstrap_cdn_provider_request_json("https://cdn.jsdelivr.net/npm/@unicorn-fail/drupal-bootstrap-styles@$pkg_version/dist/api.json");
  96. $json += array('files' => array());
  97. $styles["$pkg_version:files"] = array_filter($json['files'], function ($file) {
  98. return !!preg_match('`^/?dist/(\d+\.\d+\.\d+)/7.x-3.x`', $file['name']);
  99. });
  100. }
  101. $styles[$key] = FALSE;
  102. $theme = $theme === 'bootstrap' || $theme === 'bootstrap_theme' ? '' : "-$theme";
  103. foreach ($styles["$pkg_version:files"] as $file) {
  104. if (!empty($file['name']) && $file['name'] === "/dist/$version/7.x-3.x/drupal-bootstrap$theme$min.css") {
  105. $filename = !empty($file['symlink']) ? $file['symlink'] : $file['name'];
  106. $styles[$key] = "https://cdn.jsdelivr.net/npm/@unicorn-fail/drupal-bootstrap-styles@$pkg_version$filename";
  107. break;
  108. }
  109. }
  110. // Now cache the styles.
  111. cache_set($cid, $styles);
  112. }
  113. return $styles[$key];
  114. }
  115. /**
  116. * Retrieves a list of available CDN providers for the Bootstrap framework.
  117. *
  118. * @param string $provider
  119. * A specific provider data to return.
  120. * @param bool $reset
  121. * Toggle determining whether or not to reset the database cache.
  122. *
  123. * @return array|false
  124. * An associative array of CDN providers, keyed by their machine name if
  125. * $provider is not set. If $provider is set and exists, its individual
  126. * data array will be returned. If $provider is set and the data does not
  127. * exist then FALSE will be returned.
  128. */
  129. function bootstrap_cdn_provider($provider = NULL, $reset = FALSE) {
  130. $original_provider = $provider;
  131. // Use the advanced drupal_static() pattern, since this is called very often.
  132. static $drupal_static_fast;
  133. if (!isset($drupal_static_fast)) {
  134. $drupal_static_fast['providers'] = &drupal_static(__FUNCTION__);
  135. }
  136. $providers = &$drupal_static_fast['providers'];
  137. if ($reset || !isset($providers)) {
  138. $provider_path = BOOTSTRAP_CDN_PROVIDER_PATH;
  139. file_prepare_directory($provider_path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
  140. $cid = 'theme_registry:bootstrap:cdn_providers';
  141. if (($cached = cache_get($cid)) && !empty($cached->data)) {
  142. $providers = $cached->data;
  143. }
  144. if ($reset || !isset($providers)) {
  145. $providers = array(
  146. 'custom' => array(
  147. 'title' => t('Custom'),
  148. ),
  149. 'jsdelivr' => array(
  150. 'title' => t('jsDelivr'),
  151. 'description' => t('<p><a href="!jsdelivr" target="_blank">jsDelivr</a> is a free multi-CDN infrastructure that uses <a href="!maxcdn" target="_blank">MaxCDN</a>, <a href="!cloudflare" target="_blank">Cloudflare</a> and many others to combine their powers for the good of the open source community... <a href="!jsdelivr_about" target="_blank">read more</a></p>', array(
  152. '!jsdelivr' => 'https://www.jsdelivr.com',
  153. '!jsdelivr_about' => 'https://www.jsdelivr.com/about',
  154. '!maxcdn' => 'https://www.maxcdn.com',
  155. '!cloudflare' => 'https://www.cloudflare.com',
  156. )),
  157. ),
  158. );
  159. // Defaults properties each provider must have.
  160. $defaults = array(
  161. 'api' => NULL,
  162. 'css' => array(),
  163. 'description' => NULL,
  164. 'error' => FALSE,
  165. 'js' => array(),
  166. 'imported' => FALSE,
  167. 'min' => array(
  168. 'css' => array(),
  169. 'js' => array(),
  170. ),
  171. 'title' => NULL,
  172. );
  173. // Process the providers.
  174. foreach ($providers as $name => &$data) {
  175. $data += $defaults;
  176. $data['name'] = $name;
  177. if (empty($name)) {
  178. continue;
  179. }
  180. // Use manually imported API data, if it exists.
  181. $request = NULL;
  182. if (!empty($data['api']) && file_exists("$provider_path/$name.json") && ($imported_data = file_get_contents("$provider_path/$name.json"))) {
  183. $data['imported'] = TRUE;
  184. $request = new stdClass();
  185. $request->code = '200';
  186. $request->data = $imported_data;
  187. }
  188. // Otherwise, attempt to request API data if the provider has specified
  189. // an "api" URL to use.
  190. elseif (!empty($data['api'])) {
  191. $request = drupal_http_request($data['api']);
  192. }
  193. // Skip alter (processing) if using backported CDN Provider logic.
  194. if ($instance = _bootstrap_backport_cdn_provider(NULL, $name)) {
  195. // Go ahead and use its description and label though.
  196. $data['description'] = $instance->getDescription();
  197. $data['title'] = $instance->getLabel();
  198. continue;
  199. }
  200. // Alter the specific provider.
  201. $function = 'bootstrap_bootstrap_cdn_provider_' . $name . '_alter';
  202. if (function_exists($function)) {
  203. $function($data, $request);
  204. }
  205. }
  206. cache_set($cid, $providers);
  207. }
  208. }
  209. if (isset($original_provider)) {
  210. if (!isset($providers[$original_provider])) {
  211. return FALSE;
  212. }
  213. return $providers[$original_provider];
  214. }
  215. return $providers;
  216. }
  217. /**
  218. * Callback for jsDelivr CDN.
  219. *
  220. * @param array $provider
  221. * The provider data array, passed by reference.
  222. * @param \stdClass $request
  223. * The raw request object, if the provider specified an "api" URL to retrieve
  224. * data prior to this alter hook being called. It is up to whatever
  225. * implements these hooks to parse the requested data.
  226. */
  227. function bootstrap_bootstrap_cdn_provider_jsdelivr_alter(array &$provider, \stdClass $request = NULL) {
  228. $provider['versions'] = array();
  229. $provider['themes'] = array();
  230. $json = array();
  231. foreach (array('bootstrap', 'bootswatch') as $package) {
  232. $data = array('name' => $package, 'assets' => array());
  233. $latest = '0.0.0';
  234. $versions = array();
  235. $packageJson = _bootstrap_cdn_provider_request_json("https://data.jsdelivr.com/v1/package/npm/$package") + array('versions' => array());
  236. foreach ($packageJson['versions'] as $key => $version) {
  237. // Skip irrelevant versions.
  238. if (!preg_match('/^' . substr(BOOTSTRAP_VERSION, 0, 1) . '\.\d+\.\d+$/', $version)) {
  239. continue;
  240. }
  241. $versionJson = _bootstrap_cdn_provider_request_json("https://data.jsdelivr.com/v1/package/npm/$package@$version/flat");
  242. // Skip empty files.
  243. if (empty($versionJson['files'])) {
  244. continue;
  245. }
  246. $versions[] = $version;
  247. if (version_compare($latest, $version) === -1) {
  248. $latest = $version;
  249. }
  250. $asset = array('files' => array(), 'version' => $version);
  251. foreach ($versionJson['files'] as $file) {
  252. // Skip old bootswatch file structure.
  253. if ($package === 'bootswatch' && preg_match('`^/2|/bower_components`', $file['name'], $matches)) {
  254. continue;
  255. }
  256. preg_match('`([^/]*)/bootstrap(-theme)?(\.min)?\.(js|css)$`', $file['name'], $matches);
  257. if (!empty($matches[1]) && !empty($matches[4])) {
  258. $asset['files'][] = $file['name'];
  259. }
  260. }
  261. $data['assets'][] = $asset;
  262. }
  263. $data['lastversion'] = $latest;
  264. $data['versions'] = $versions;
  265. $json[] = $data;
  266. }
  267. // Extract the raw asset files from the JSON data for each framework.
  268. $libraries = array();
  269. if ($json) {
  270. foreach ($json as $data) {
  271. if ($data['name'] === 'bootstrap' || $data['name'] === 'bootswatch') {
  272. foreach ($data['assets'] as $asset) {
  273. if (preg_match('/^' . BOOTSTRAP_VERSION_MAJOR . '\.\d\.\d$/', $asset['version'])) {
  274. $libraries[$data['name']][$asset['version']] = $asset['files'];
  275. }
  276. }
  277. }
  278. }
  279. }
  280. // If the main bootstrap library could not be found, then provide defaults.
  281. if (!isset($libraries['bootstrap'])) {
  282. $provider['error'] = TRUE;
  283. $provider['versions'][BOOTSTRAP_VERSION] = BOOTSTRAP_VERSION;
  284. $provider['themes'][BOOTSTRAP_VERSION] = array(
  285. 'bootstrap' => array(
  286. 'title' => t('Bootstrap'),
  287. 'css' => array('https://cdn.jsdelivr.net/npm/bootstrap@' . BOOTSTRAP_VERSION . '/dist/css/bootstrap.css'),
  288. 'js' => array('https://cdn.jsdelivr.net/npm/bootstrap@' . BOOTSTRAP_VERSION . '/dist/js/bootstrap.js'),
  289. 'min' => array(
  290. 'css' => array('https://cdn.jsdelivr.net/npm/bootstrap@' . BOOTSTRAP_VERSION . '/dist/css/bootstrap.min.css'),
  291. 'js' => array('https://cdn.jsdelivr.net/npm/bootstrap@' . BOOTSTRAP_VERSION . '/dist/js/bootstrap.min.js'),
  292. ),
  293. ),
  294. );
  295. return;
  296. }
  297. // Populate the provider array with the versions and themes available.
  298. foreach (array_keys($libraries['bootstrap']) as $version) {
  299. $provider['versions'][$version] = $version;
  300. if (!isset($provider['themes'][$version])) {
  301. $provider['themes'][$version] = array();
  302. }
  303. // Extract Bootstrap themes.
  304. $provider['themes'][$version] = drupal_array_merge_deep($provider['themes'][$version], _bootstrap_cdn_provider_jsdelivr_extract_themes($libraries['bootstrap'][$version], "https://cdn.jsdelivr.net/npm/bootstrap@$version"));
  305. // Extract Bootswatch themes.
  306. if (isset($libraries['bootswatch'][$version])) {
  307. $provider['themes'][$version] = drupal_array_merge_deep($provider['themes'][$version], _bootstrap_cdn_provider_jsdelivr_extract_themes($libraries['bootswatch'][$version], "https://cdn.jsdelivr.net/npm/bootswatch@$version"));
  308. }
  309. }
  310. // Post process the themes to fill in any missing assets.
  311. foreach (array_keys($provider['themes']) as $version) {
  312. foreach (array_keys($provider['themes'][$version]) as $theme) {
  313. // Some themes actually require Bootstrap framework assets to still
  314. // function properly.
  315. if ($theme !== 'bootstrap') {
  316. foreach (array('css', 'js') as $type) {
  317. // Bootswatch themes include the Bootstrap framework in their CSS.
  318. // Skip the CSS portions.
  319. if ($theme !== 'bootstrap_theme' && $type === 'css') {
  320. continue;
  321. }
  322. if (!isset($provider['themes'][$version][$theme][$type]) && !empty($provider['themes'][$version]['bootstrap'][$type])) {
  323. $provider['themes'][$version][$theme][$type] = array();
  324. }
  325. $provider['themes'][$version][$theme][$type] = drupal_array_merge_deep($provider['themes'][$version]['bootstrap'][$type], $provider['themes'][$version][$theme][$type]);
  326. if (!isset($provider['themes'][$version][$theme]['min'][$type]) && !empty($provider['themes'][$version]['bootstrap']['min'][$type])) {
  327. $provider['themes'][$version][$theme]['min'][$type] = array();
  328. }
  329. $provider['themes'][$version][$theme]['min'][$type] = drupal_array_merge_deep($provider['themes'][$version]['bootstrap']['min'][$type], $provider['themes'][$version][$theme]['min'][$type]);
  330. }
  331. }
  332. // Some themes do not have a non-minified version, clone them to the
  333. // "normal" css/js arrays to ensure that the theme still loads if
  334. // aggregation (minification) is disabled.
  335. foreach (array('css', 'js') as $type) {
  336. if (!isset($provider['themes'][$version][$theme][$type]) && isset($provider['themes'][$version][$theme]['min'][$type])) {
  337. $provider['themes'][$version][$theme][$type] = $provider['themes'][$version][$theme]['min'][$type];
  338. }
  339. }
  340. }
  341. }
  342. }
  343. /**
  344. * Extracts theme information from files provided by the jsDelivr API.
  345. *
  346. * This will place the raw files into proper "css", "js" and "min" arrays
  347. * (if they exist) and prepends them with a base URL provided.
  348. *
  349. * @param array $files
  350. * An array of files to process.
  351. * @param string $base_url
  352. * The base URL each one of the $files are relative to, this usually
  353. * should also include the version path prefix as well.
  354. *
  355. * @return array
  356. * An associative array containing the following keys, if there were
  357. * matching files found:
  358. * - css
  359. * - js
  360. * - min:
  361. * - css
  362. * - js
  363. */
  364. function _bootstrap_cdn_provider_jsdelivr_extract_themes(array $files, $base_url = '') {
  365. $themes = array();
  366. foreach ($files as $file) {
  367. preg_match('`([^/]*)/bootstrap(-theme)?(\.min)?\.(js|css)$`', $file, $matches);
  368. if (!empty($matches[1]) && !empty($matches[4])) {
  369. $path = $matches[1];
  370. $min = $matches[3];
  371. $filetype = $matches[4];
  372. // Determine the "theme" name.
  373. if ($path === 'css' || $path === 'js') {
  374. $theme = 'bootstrap';
  375. $title = t('Bootstrap');
  376. }
  377. else {
  378. $theme = $path;
  379. $title = ucfirst($path);
  380. }
  381. if ($matches[2]) {
  382. $theme = 'bootstrap_theme';
  383. $title = t('Bootstrap Theme');
  384. }
  385. $themes[$theme]['title'] = $title;
  386. if ($min) {
  387. $themes[$theme]['min'][$filetype][] = "$base_url/" . ltrim($file, '/');
  388. }
  389. else {
  390. $themes[$theme][$filetype][] = "$base_url/" . ltrim($file, '/');
  391. }
  392. }
  393. }
  394. return $themes;
  395. }
  396. /**
  397. * Callback for Custom CDN assets.
  398. */
  399. function bootstrap_bootstrap_cdn_provider_custom_assets_alter(&$provider, $theme = NULL) {
  400. foreach (array('css', 'js') as $type) {
  401. if ($setting = bootstrap_setting('cdn_custom_' . $type, $theme)) {
  402. $provider[$type][] = $setting;
  403. }
  404. if ($setting = bootstrap_setting('cdn_custom_' . $type . '_min', $theme)) {
  405. $provider['min'][$type][] = $setting;
  406. }
  407. }
  408. }
  409. /**
  410. * Callback for jsDelivr CDN assets.
  411. */
  412. function bootstrap_bootstrap_cdn_provider_jsdelivr_assets_alter(&$provider, $theme = NULL) {
  413. $error = !empty($provider['error']);
  414. $version = $error ? BOOTSTRAP_VERSION : bootstrap_setting('cdn_jsdelivr_version', $theme);
  415. $theme = $error ? 'bootstrap' : bootstrap_setting('cdn_jsdelivr_theme', $theme);
  416. if (isset($provider['themes'][$version][$theme])) {
  417. $provider = drupal_array_merge_deep($provider, $provider['themes'][$version][$theme]);
  418. }
  419. }
  420. /**
  421. * Custom callback for CDN provider settings.
  422. *
  423. * @see bootstrap_form_system_theme_settings_alter()
  424. */
  425. function bootstrap_cdn_provider_settings_form(&$form, &$form_state, $theme) {
  426. // Retrieve the provider from form values or the setting.
  427. $provider = isset($form_state['values']['bootstrap_cdn_provider']) ? $form_state['values']['bootstrap_cdn_provider'] : bootstrap_setting('cdn_provider', $theme);
  428. // Intercept possible manual import of API data via AJAX callback.
  429. if (isset($form_state['clicked_button']['#value']) && $form_state['clicked_button']['#value'] === t('Save provider data')) {
  430. $provider_path = BOOTSTRAP_CDN_PROVIDER_PATH;
  431. file_prepare_directory($provider_path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
  432. $provider_data = isset($form_state['values']['bootstrap_cdn_provider_import_data']) ? $form_state['values']['bootstrap_cdn_provider_import_data'] : FALSE;
  433. $provider_file = "$provider_path/$provider.json";
  434. if ($provider_data) {
  435. file_unmanaged_save_data($provider_data, $provider_file, FILE_EXISTS_REPLACE);
  436. }
  437. elseif ($provider_file && file_exists($provider_file)) {
  438. file_unmanaged_delete($provider_file);
  439. }
  440. bootstrap_cdn_provider(NULL, TRUE);
  441. }
  442. $description_label = t('NOTE');
  443. $description = t('Using one of the "CDN Provider" options below is the preferred method for loading Bootstrap CSS and JS on simpler sites that do not use a site-wide CDN. Using a "CDN Provider" for loading Bootstrap, however, does mean that it depends on a third-party service. There is no obligation or commitment by these third-parties that guarantees any up-time or service quality. If you need to customize Bootstrap and have chosen to compile the source code locally (served from this site), you must disable the "CDN Provider" option below by choosing "- None -" and alternatively enable a site-wide CDN implementation. All local (served from this site) versions of Bootstrap will be superseded by any enabled "CDN Provider" below. <strong>Do not do both</strong>.');
  444. $form['advanced']['cdn'] = array(
  445. '#type' => 'fieldset',
  446. '#title' => t('CDN (Content Delivery Network)'),
  447. '#description' => '<div class="alert alert-info messages warning"><strong>' . $description_label . ':</strong> ' . $description . '</div>',
  448. '#collapsible' => TRUE,
  449. '#collapsed' => !$provider,
  450. );
  451. $providers = bootstrap_cdn_provider();
  452. $options = array();
  453. foreach ($providers as $key => $data) {
  454. $options[$key] = $data['title'];
  455. }
  456. $form['advanced']['cdn']['bootstrap_cdn_provider'] = array(
  457. '#type' => 'select',
  458. '#title' => t('CDN Provider'),
  459. '#default_value' => $provider,
  460. '#empty_value' => '',
  461. '#options' => $options,
  462. );
  463. // Render each provider.
  464. foreach ($providers as $name => $data) {
  465. $form['advanced']['cdn']['provider'][$name] = array(
  466. '#type' => 'container',
  467. '#prefix' => '<div id="bootstrap-cdn-provider-' . $name . '">',
  468. '#suffix' => '</div>',
  469. '#states' => array(
  470. 'visible' => array(
  471. ':input[name="bootstrap_cdn_provider"]' => array('value' => $name),
  472. ),
  473. ),
  474. );
  475. $element = &$form['advanced']['cdn']['provider'][$name];
  476. $element['description']['#markup'] = '<div class="lead">' . $data['description'] . '</div>';
  477. // Indicate there was an error retrieving the provider's API data.
  478. if (!empty($data['error']) || !empty($data['imported'])) {
  479. if (!empty($data['error'])) {
  480. $description_label = t('ERROR');
  481. $description = t('Unable to reach or parse the data provided by the @title API. Ensure the server this website is hosted on is able to initiate HTTP requests via <a href="!drupal_http_request" target="_blank">drupal_http_request()</a>. If the request consistently fails, it is likely that there are certain PHP functions that have been disabled by the hosting provider for security reasons. It is possible to manually copy and paste the contents of the following URL into the "Imported @title data" section below.<br /><br /><a href="!provider_api" target="_blank">!provider_api</a>.', array(
  482. '@title' => $data['title'],
  483. '!provider_api' => $data['api'],
  484. '!drupal_http_request' => 'https://api.drupal.org/api/drupal/includes%21common.inc/function/drupal_http_request/7',
  485. ));
  486. $element['#prefix'] .= '<div class="alert alert-danger messages error"><strong>' . $description_label . ':</strong> ' . $description . '</div>';
  487. }
  488. $element['import'] = array(
  489. '#type' => 'fieldset',
  490. '#title' => t('Imported @title data', array(
  491. '@title' => $data['title'],
  492. )),
  493. '#description' => t('The provider will attempt to parse the data entered here each time it is saved. If no data has been entered, any saved files associated with this provider will be removed and the provider will again attempt to request the API data normally through the following URL: <a href="!provider_api" target="_blank">!provider_api</a>.', array(
  494. '!provider_api' => $data['api'],
  495. )),
  496. '#weight' => 10,
  497. '#collapsible' => TRUE,
  498. '#collapsed' => TRUE,
  499. );
  500. $element['import']['bootstrap_cdn_provider_import_data'] = array(
  501. '#type' => 'textarea',
  502. '#default_value' => file_exists(BOOTSTRAP_CDN_PROVIDER_PATH . '/' . $name . '.json') ? file_get_contents(BOOTSTRAP_CDN_PROVIDER_PATH . '/' . $name . '.json') : NULL,
  503. );
  504. $element['import']['submit'] = array(
  505. '#type' => 'submit',
  506. '#value' => t('Save provider data'),
  507. '#executes_submit_callback' => FALSE,
  508. '#ajax' => array(
  509. 'callback' => 'bootstrap_cdn_provider_settings_form_ajax_reload_provider',
  510. 'wrapper' => 'bootstrap-cdn-provider-' . $name,
  511. ),
  512. );
  513. }
  514. // Alter the specific provider.
  515. $function = 'bootstrap_bootstrap_cdn_provider_' . $name . '_settings_form_alter';
  516. if (function_exists($function)) {
  517. $function($element, $form_state, $data, $theme);
  518. }
  519. }
  520. }
  521. /**
  522. * AJAX callback for reloading CDN provider elements.
  523. */
  524. function bootstrap_cdn_provider_settings_form_ajax_reload_provider($form, $form_state) {
  525. return $form['advanced']['cdn']['provider'][$form_state['values']['bootstrap_cdn_provider']];
  526. }
  527. /**
  528. * Implements hook_bootstrap_cdn_provider_PROVIDER_settings_form_alter().
  529. */
  530. function bootstrap_bootstrap_cdn_provider_custom_settings_form_alter(&$element, &$form_state, $provider = array(), $theme = NULL) {
  531. foreach (array('css', 'js') as $type) {
  532. $setting = bootstrap_setting('cdn_custom_' . $type, $theme);
  533. $setting_min = bootstrap_setting('cdn_custom_' . $type . '_min', $theme);
  534. $element['bootstrap_cdn_custom_' . $type] = array(
  535. '#type' => 'textfield',
  536. '#title' => t('Bootstrap @type URL', array(
  537. '@type' => drupal_strtoupper($type),
  538. )),
  539. '#description' => t('It is best to use protocol relative URLs (i.e. without http: or https:) here as it will allow more flexibility if the need ever arises.'),
  540. '#default_value' => $setting,
  541. );
  542. $element['bootstrap_cdn_custom_' . $type . '_min'] = array(
  543. '#type' => 'textfield',
  544. '#title' => t('Minified Bootstrap @type URL', array(
  545. '@type' => drupal_strtoupper($type),
  546. )),
  547. '#description' => t('Additionally, you can provide the minimized version of the file. It will be used instead if site aggregation is enabled.'),
  548. '#default_value' => $setting_min,
  549. );
  550. }
  551. }
  552. /**
  553. * Implements hook_bootstrap_cdn_provider_PROVIDER_settings_form_alter().
  554. */
  555. function bootstrap_bootstrap_cdn_provider_jsdelivr_settings_form_alter(&$element, &$form_state, $provider = array(), $theme = NULL) {
  556. $version = isset($form_state['values']['bootstrap_cdn_jsdelivr_version']) ? $form_state['values']['bootstrap_cdn_jsdelivr_version'] : bootstrap_setting('cdn_jsdelivr_version', $theme);
  557. // Use backported CDN Provider logic from 8.x-3.x.
  558. if ($instance = _bootstrap_backport_cdn_provider($theme, $provider['name'])) {
  559. $versions = $instance->getCdnVersions();
  560. $themes = array();
  561. foreach ($instance->getCdnThemes($version) as $name => $data) {
  562. $themes[$name] = $data['title'];
  563. }
  564. }
  565. else {
  566. $versions = isset($provider['versions']) ? $provider['versions'] : array();
  567. $themes = array();
  568. if (isset($provider['themes'][$version])) {
  569. foreach ($provider['themes'][$version] as $_theme => $data) {
  570. $themes[$_theme] = $data['title'];
  571. }
  572. }
  573. }
  574. $element['bootstrap_cdn_jsdelivr_version'] = array(
  575. '#type' => 'select',
  576. '#title' => t('Version'),
  577. '#options' => $versions,
  578. '#default_value' => $version,
  579. '#ajax' => array(
  580. 'callback' => 'bootstrap_cdn_provider_settings_form_ajax_reload_provider',
  581. 'wrapper' => 'bootstrap-cdn-provider-jsdelivr',
  582. ),
  583. );
  584. if (empty($provider['error']) && empty($provider['imported'])) {
  585. $element['bootstrap_cdn_jsdelivr_version']['#description'] = t('These versions are automatically populated by the @provider API upon cache clear and newer versions may appear over time. It is highly recommended the version that the site was built with stays at that version. Until a newer version has been properly tested for updatability by the site maintainer, you should not arbitrarily "update" just because there is a newer version. This can cause many inconsistencies and undesired effects with an existing site.', array(
  586. '@provider' => $provider['title'],
  587. ));
  588. }
  589. // Themes.
  590. $element['bootstrap_cdn_jsdelivr_theme'] = array(
  591. '#type' => 'select',
  592. '#title' => t('Theme'),
  593. '#description' => t('Choose the example <a href="!bootstrap_theme" target="_blank">Bootstrap Theme</a> provided by Bootstrap or one of the many, many <a href="!bootswatch" target="_blank">Bootswatch</a> themes!', array(
  594. '!bootswatch' => 'https://bootswatch.com',
  595. '!bootstrap_theme' => 'https://getbootstrap.com/docs/3.3/examples/theme/',
  596. )),
  597. '#default_value' => bootstrap_setting('cdn_jsdelivr_theme', $theme),
  598. '#options' => $themes,
  599. '#empty_option' => t('Bootstrap (default)'),
  600. '#empty_value' => 'bootstrap',
  601. '#suffix' => '<div id="bootstrap-theme-preview"></div>',
  602. );
  603. }