data)) { $providers = $cached->data; } if ($reset || !isset($providers)) { $providers = array( 'custom' => array( 'title' => t('Custom'), ), 'jsdelivr' => array( 'api' => 'http://api.jsdelivr.com/v1/bootstrap/libraries', 'title' => t('jsDelivr'), 'description' => t('
jsDelivr is a free multi-CDN infrastructure that uses MaxCDN, Cloudflare and many others to combine their powers for the good of the open source community... read more
', array( '!jsdelivr' => 'http://www.jsdelivr.com', '!jsdelivr_about' => 'http://www.jsdelivr.com/about', '!maxcdn' => 'http://www.maxcdn.com', '!cloudflare' => 'http://www.cloudflare.com', )), ), ); // @todo Use drupal_alter() once CDN is in bootstrap_core companion module. // drupal_alter('bootstrap_cdn_provider', $providers); // Defaults properties each provider must have. $defaults = array( 'api' => NULL, 'css' => array(), 'description' => NULL, 'error' => FALSE, 'js' => array(), 'imported' => FALSE, 'min' => array( 'css' => array(), 'js' => array(), ), 'title' => NULL, ); // Process the providers. foreach ($providers as $name => &$data) { $data += $defaults; $data['name'] = $name; if (empty($name)) { continue; } // Use manually imported API data, if it exists. $request = NULL; if (!empty($data['api']) && file_exists("$provider_path/$name.json") && ($imported_data = file_get_contents("$provider_path/$name.json"))) { $data['imported'] = TRUE; $request = new stdClass(); $request->code = '200'; $request->data = $imported_data; } // Otherwise, attempt to request API data if the provider has specified // an "api" URL to use. elseif (!empty($data['api'])) { $request = drupal_http_request($data['api']); } // Alter the specific provider. $function = 'bootstrap_bootstrap_cdn_provider_' . $name . '_alter'; if (function_exists($function)) { $function($data, $request); } // @todo Use drupal_alter() once CDN is in bootstrap_core companion module. // drupal_alter('bootstrap_cdn_provider_' . $name, $data, $request); } cache_set($cid, $providers); } } if (isset($original_provider)) { if (!isset($providers[$original_provider])) { return FALSE; } return $providers[$original_provider]; } return $providers; } /** * Implements hook_bootstrap_cdn_provider_PROVIDER_alter(). * * @param array $provider * The provider data array, passed by reference. * @param $request * The raw request object, if the provider specified an "api" URL to retrieve * data prior to this alter hook being called. It is up to whatever * implements these hooks to parse the requested data. * * @todo Finish documentation when hook is real. */ function bootstrap_bootstrap_cdn_provider_jsdelivr_alter(&$provider, $request = NULL) { $json = array(); $provider['versions'] = array(); $provider['themes'] = array(); if ($request->code === '200' && !empty($request->data)) { $json = drupal_json_decode($request->data); } // Expected library names from jsDelivr API v1. Must use "twitter-bootstrap" // instead of "bootstrap" (which is just a folder alias). // @see https://www.drupal.org/node/2504343 // @see https://github.com/jsdelivr/api/issues/94 $bootstrap = 'twitter-bootstrap'; $bootswatch = 'bootswatch'; // Extract the raw asset files from the JSON data for each framework. $libraries = array(); if ($json) { foreach ($json as $data) { if ($data['name'] === $bootstrap || $data['name'] === $bootswatch) { foreach ($data['assets'] as $asset) { if (preg_match('/^' . BOOTSTRAP_VERSION_MAJOR . '\.\d\.\d$/', $asset['version'])) { $libraries[$data['name']][$asset['version']] = $asset['files']; } } } } } // If the main bootstrap library could not be found, then provide defaults. if (!isset($libraries[$bootstrap])) { $provider['error'] = TRUE; $provider['versions'][BOOTSTRAP_VERSION] = BOOTSTRAP_VERSION; $provider['themes'][BOOTSTRAP_VERSION] = array( 'bootstrap' => array( 'title' => t('Bootstrap'), 'css' => array('//cdn.jsdelivr.net/bootstrap/' . BOOTSTRAP_VERSION . '/css/bootstrap.css'), 'js' => array('//cdn.jsdelivr.net/bootstrap/' . BOOTSTRAP_VERSION . '/js/bootstrap.js'), 'min' => array( 'css' => array('//cdn.jsdelivr.net/bootstrap/' . BOOTSTRAP_VERSION . '/css/bootstrap.min.css'), 'js' => array('//cdn.jsdelivr.net/bootstrap/' . BOOTSTRAP_VERSION . '/js/bootstrap.min.js'), ), ), ); return; } // Populate the provider array with the versions and themes available. foreach (array_keys($libraries[$bootstrap]) as $version) { $provider['versions'][$version] = $version; if (!isset($provider['themes'][$version])) { $provider['themes'][$version] = array(); } // Extract Bootstrap themes. $provider['themes'][$version] = drupal_array_merge_deep($provider['themes'][$version], _bootstrap_cdn_provider_jsdelivr_extract_themes($libraries[$bootstrap][$version], "//cdn.jsdelivr.net/bootstrap/$version")); // Extract Bootswatch themes. if (isset($libraries[$bootswatch][$version])) { $provider['themes'][$version] = drupal_array_merge_deep($provider['themes'][$version], _bootstrap_cdn_provider_jsdelivr_extract_themes($libraries[$bootswatch][$version], "//cdn.jsdelivr.net/bootswatch/$version")); } } // Post process the themes to fill in any missing assets. foreach (array_keys($provider['themes']) as $version) { foreach (array_keys($provider['themes'][$version]) as $theme) { // Some themes actually require Bootstrap framework assets to still // function properly. if ($theme !== 'bootstrap') { foreach (array('css', 'js') as $type) { // Bootswatch themes include the Bootstrap framework in their CSS. // Skip the CSS portions. if ($theme !== 'bootstrap_theme' && $type === 'css') { continue; } if (!isset($provider['themes'][$version][$theme][$type]) && !empty($provider['themes'][$version]['bootstrap'][$type])) { $provider['themes'][$version][$theme][$type] = array(); } $provider['themes'][$version][$theme][$type] = drupal_array_merge_deep($provider['themes'][$version]['bootstrap'][$type], $provider['themes'][$version][$theme][$type]); if (!isset($provider['themes'][$version][$theme]['min'][$type]) && !empty($provider['themes'][$version]['bootstrap']['min'][$type])) { $provider['themes'][$version][$theme]['min'][$type] = array(); } $provider['themes'][$version][$theme]['min'][$type] = drupal_array_merge_deep($provider['themes'][$version]['bootstrap']['min'][$type], $provider['themes'][$version][$theme]['min'][$type]); } } // Some themes do not have a non-minified version, clone them to the // "normal" css/js arrays to ensure that the theme still loads if // aggregation (minification) is disabled. foreach (array('css', 'js') as $type) { if (!isset($provider['themes'][$version][$theme][$type]) && isset($provider['themes'][$version][$theme]['min'][$type])) { $provider['themes'][$version][$theme][$type] = $provider['themes'][$version][$theme]['min'][$type]; } } } } } /** * Extracts theme information from files provided by the jsDelivr API. * * This will place the raw files into proper "css", "js" and "min" arrays * (if they exist) and prepends them with a base URL provided. * * @param array $files * An array of files to process. * @param string $base_url * The base URL each one of the $files are relative to, this usually * should also include the version path prefix as well. * * @return array * An associative array containing the following keys, if there were * matching files found: * - css * - js * - min: * - css * - js */ function _bootstrap_cdn_provider_jsdelivr_extract_themes($files, $base_url = '') { $themes = array(); foreach ($files as $file) { preg_match('`([^/]*)/bootstrap(-theme)?(\.min)?\.(js|css)$`', $file, $matches); if (!empty($matches[1]) && !empty($matches[4])) { $path = $matches[1]; $min = $matches[3]; $filetype = $matches[4]; // Determine the "theme" name. if ($path === 'css' || $path === 'js') { $theme = 'bootstrap'; $title = t('Bootstrap'); } else { $theme = $path; $title = ucfirst($path); } if ($matches[2]) { $theme = 'bootstrap_theme'; $title = t('Bootstrap Theme'); } $themes[$theme]['title'] = $title; if ($min) { $themes[$theme]['min'][$filetype][] = "$base_url/$path/bootstrap{$matches[2]}$min.$filetype"; } else { $themes[$theme][$filetype][] = "$base_url/$path/bootstrap{$matches[2]}$min.$filetype"; } } } return $themes; } /** * Implements hook_bootstrap_cdn_provider_PROVIDER_assets_alter(). * * @todo Create documentation when hook is real. */ function bootstrap_bootstrap_cdn_provider_custom_assets_alter(&$provider, $theme = NULL) { foreach (array('css', 'js') as $type) { if ($setting = bootstrap_setting('cdn_custom_' . $type, $theme)) { $provider[$type][] = $setting; } if ($setting = bootstrap_setting('cdn_custom_' . $type . '_min', $theme)) { $provider['min'][$type][] = $setting; } } } /** * Implements hook_bootstrap_cdn_provider_PROVIDER_assets_alter(). * * @todo Create documentation when hook is real. */ function bootstrap_bootstrap_cdn_provider_jsdelivr_assets_alter(&$provider, $theme = NULL) { $error = !empty($provider['error']); $version = $error ? BOOTSTRAP_VERSION : bootstrap_setting('cdn_jsdelivr_version', $theme); $theme = $error ? 'bootstrap' : bootstrap_setting('cdn_jsdelivr_theme', $theme); if (isset($provider['themes'][$version][$theme])) { $provider = drupal_array_merge_deep($provider, $provider['themes'][$version][$theme]); } } /** * Custom callback for CDN provider settings. * * @see bootstrap_form_system_theme_settings_alter() */ function bootstrap_cdn_provider_settings_form(&$form, &$form_state, $theme) { // Retrieve the provider from form values or the setting. $provider = isset($form_state['values']['bootstrap_cdn_provider']) ? $form_state['values']['bootstrap_cdn_provider'] : bootstrap_setting('cdn_provider', $theme); // Intercept possible manual import of API data via AJAX callback. if (isset($form_state['clicked_button']['#value']) && $form_state['clicked_button']['#value'] === t('Save provider data')) { $provider_path = BOOTSTRAP_CDN_PROVIDER_PATH; file_prepare_directory($provider_path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); $provider_data = isset($form_state['values']['bootstrap_cdn_provider_import_data']) ? $form_state['values']['bootstrap_cdn_provider_import_data'] : FALSE; $provider_file = "$provider_path/$provider.json"; if ($provider_data) { file_unmanaged_save_data($provider_data, $provider_file, FILE_EXISTS_REPLACE); } elseif ($provider_file && file_exists($provider_file)) { file_unmanaged_delete($provider_file); } bootstrap_cdn_provider(NULL, TRUE); } $form['advanced']['cdn'] = array( '#type' => 'fieldset', '#title' => t('CDN (Content Delivery Network)'), '#description' => ' ', '#collapsible' => TRUE, '#collapsed' => !$provider, ); $providers = bootstrap_cdn_provider(); $options = array(); foreach ($providers as $key => $data) { $options[$key] = $data['title']; } $form['advanced']['cdn']['bootstrap_cdn_provider'] = array( '#type' => 'select', '#title' => t('CDN Provider'), '#default_value' => $provider, '#empty_value' => '', '#options' => $options, ); // Render each provider. foreach ($providers as $name => $data) { $form['advanced']['cdn']['provider'][$name] = array( '#type' => 'container', '#prefix' => '