Issue #3031415 by markcarver: Reduce time it takes to populate CDN assets from cold cache request

parent ab31570e
......@@ -745,28 +745,19 @@ function bootstrap_element_smart_description(array &$element, array &$target = N
* $assets = bootstrap_get_cdn_assets($type, $provider, $theme);
*
* // After.
* use Drupal\bootstrap\Bootstrap;
* $theme = Bootstrap::getTheme($theme);
* $assets = [];
* if ($provider = $theme->getProvider($provider)) {
* $assets = $provider->getAssets($type);
* }
* use Drupal\bootstrap\Plugin\ProviderManager;
* $assets = ProviderManager::load($theme, $provider)->getAssets($type);
* @endcode
*
* @see \Drupal\bootstrap\Plugin\Provider\Custom::getAssets()
* @see \Drupal\bootstrap\Plugin\Provider\JsDelivr::getAssets()
* @see \Drupal\bootstrap\Plugin\Provider\ProviderBase::getAssets()
* @see \Drupal\bootstrap\Plugin\Provider\ProviderInterface::getAssets()
* @see \Drupal\bootstrap\Theme::getProvider()
* @see \Drupal\bootstrap\Bootstrap::getTheme()
* @see \Drupal\bootstrap\Plugin\ProviderManager::load()
*/
function bootstrap_get_cdn_assets($type = NULL, $provider = NULL, $theme = NULL) {
Bootstrap::deprecated();
$assets = [];
if ($provider = Bootstrap::getTheme($theme)->getProvider($provider)) {
$assets = $provider->getAssets($type);
}
return $assets;
return ProviderManager::load($theme, $provider)->getAssets($type);
}
/**
......
......@@ -25,6 +25,8 @@ class BootstrapProvider extends Plugin {
* An API URL used to retrieve data for the provider.
*
* @var string
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*/
protected $api = '';
......@@ -32,6 +34,8 @@ class BootstrapProvider extends Plugin {
* An array of CSS assets.
*
* @var array
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*/
protected $css = [];
......@@ -46,6 +50,8 @@ class BootstrapProvider extends Plugin {
* A flag determining whether or not the API request has failed.
*
* @var bool
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*/
protected $error = FALSE;
......@@ -53,6 +59,8 @@ class BootstrapProvider extends Plugin {
* A flag determining whether or not data has been manually imported.
*
* @var bool
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*/
protected $imported = FALSE;
......@@ -60,6 +68,8 @@ class BootstrapProvider extends Plugin {
* An array of JavaScript assets.
*
* @var array
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*/
protected $js = [];
......@@ -74,6 +84,8 @@ class BootstrapProvider extends Plugin {
* An associative array of minified CSS and JavaScript assets.
*
* @var array
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*/
protected $min = ['css' => [], 'js' => []];
......@@ -81,6 +93,8 @@ class BootstrapProvider extends Plugin {
* An array of themes supported by the provider.
*
* @var array
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*/
protected $themes = [];
......@@ -88,6 +102,8 @@ class BootstrapProvider extends Plugin {
* An array of versions supported by the provider.
*
* @var array
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*/
protected $versions = [];
......
......@@ -11,6 +11,7 @@ use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Markup;
/**
* The primary class for the Drupal Bootstrap base theme.
......@@ -415,15 +416,15 @@ class Bootstrap {
/**
* Logs and displays a warning about a deprecated function/method being used.
*
* @param bool $show_message
* Flag indicating whether to show a message to the user. If TRUE, it will
* force showing the message. If FALSE, it will only log the message. If
* not set, showing the message will be determined by whether the current
* theme has suppressed showing deprecated warnings.
*/
public static function deprecated() {
// Log backtrace.
public static function deprecated($show_message = NULL) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
\Drupal::logger('bootstrap')->warning('<pre><code>' . print_r($backtrace, TRUE) . '</code></pre>');
if (!self::getTheme()->getSetting('suppress_deprecated_warnings')) {
return;
}
// Extrapolate the caller.
$caller = $backtrace[1];
......@@ -432,11 +433,21 @@ class Bootstrap {
$parts = explode('\\', $caller['class']);
$class = array_pop($parts) . '::';
}
drupal_set_message(t('The following function(s) or method(s) have been deprecated, please check the logs for a more detailed backtrace on where these are being invoked. Click on the function or method link to search the documentation site for a possible replacement or solution.'), 'warning');
drupal_set_message(t('<a href=":url" target="_blank">@title</a>.', [
$message = t('The following function(s) or method(s) have been deprecated, please check the logs for a more detailed backtrace on where these are being invoked. Click on the function or method link to search the documentation site for a possible replacement or solution: <a href=":url" target="_blank">@title</a>', [
':url' => self::apiSearchUrl($class . $caller['function']),
'@title' => ($class ? $caller['class'] . $caller['type'] : '') . $caller['function'] . '()',
]), 'warning');
'@title' => ($class ? $caller['class'] . '::' : '') . $caller['function'] . '()',
]);
if ($show_message || (!isset($show_message) && !self::getTheme()->getSetting('suppress_deprecated_warnings', FALSE))) {
drupal_set_message($message, 'warning');
}
// Log message and accompanying backtrace.
\Drupal::logger('bootstrap')->warning('<div>@message</div><pre><code>@backtrace</code></pre>', [
'@message' => $message,
'@backtrace' => Markup::create(print_r($backtrace, TRUE)),
]);
}
/**
......
......@@ -2,9 +2,7 @@
namespace Drupal\bootstrap\Plugin\Alter;
use Drupal\bootstrap\Bootstrap;
use Drupal\bootstrap\Plugin\PluginBase;
use Drupal\Component\Utility\NestedArray;
/**
* Implements hook_library_info_alter().
......@@ -37,40 +35,9 @@ class LibraryInfo extends PluginBase implements AlterInterface {
unset($libraries['livereload']['js']['livereload.js']);
}
// Retrieve the theme's CDN provider and assets.
$provider = $this->theme->getProvider();
$assets = $provider ? $provider->getAssets() : [];
// Immediately return if there is no provider or assets.
if (!$provider || !$assets) {
return;
}
// Merge the assets into the library info.
$libraries['framework'] = NestedArray::mergeDeepArray([$assets, $libraries['framework']], TRUE);
// Add a specific version and theme CSS overrides file.
// @todo This should be retrieved by the Provider API.
$version = $this->theme->getSetting('cdn_' . $provider->getPluginId() . '_version') ?: Bootstrap::FRAMEWORK_VERSION;
$libraries['framework']['version'] = $version;
$provider_theme = $this->theme->getSetting('cdn_' . $provider->getPluginId() . '_theme') ?: 'bootstrap';
$provider_theme = $provider_theme === 'bootstrap' || $provider_theme === 'bootstrap_theme' ? '' : "-$provider_theme";
foreach ($this->theme->getAncestry(TRUE) as $ancestor) {
$overrides = $ancestor->getPath() . "/css/$version/overrides$provider_theme.min.css";
if (file_exists($overrides)) {
// Since this uses a relative path to the ancestor from DRUPAL_ROOT,
// we must prepend the entire path with forward slash (/) so it
// doesn't prepend the active theme's path.
$overrides = "/$overrides";
// The overrides file must also be stored in the "base" category so
// it isn't added after any potential sub-theme's "theme" category.
// There's no weight, so it will be added after the provider's assets.
// @see https://www.drupal.org/node/2770613
$libraries['framework']['css']['base'][$overrides] = [];
break;
}
// Alter the framework library based on currently set CDN provider.
if ($provider = $this->theme->getProvider()) {
$provider->alterFrameworkLibrary($libraries['framework']);
}
}
// Core replacements.
......
......@@ -119,6 +119,7 @@ class SystemThemeSettings extends FormBase implements FormInterface {
$cache_tags = [];
$save = FALSE;
$settings = $theme->settings();
$rebuild_cdn_assets = FALSE;
// Iterate over all setting plugins and manually save them since core's
// process is severely limiting and somewhat broken.
......@@ -147,6 +148,11 @@ class SystemThemeSettings extends FormBase implements FormInterface {
// Retrieve the cache tags for the setting.
$cache_tags = array_unique(array_merge($setting->getCacheTags()));
// A CDN setting has changed, flag that CDN assets should be rebuilt.
if (strpos($name, 'cdn') === 0) {
$rebuild_cdn_assets = TRUE;
}
// Flag the save.
$save = TRUE;
}
......@@ -157,6 +163,11 @@ class SystemThemeSettings extends FormBase implements FormInterface {
// Save the settings, if needed.
if ($save) {
// Remove any cached CDN assets so they can be rebuilt.
if ($rebuild_cdn_assets) {
$settings->clear('cdn_cache');
}
$settings->save();
// Invalidate necessary cache tags.
......
......@@ -23,10 +23,7 @@ class PluginBase extends CorePluginBase {
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
if (!isset($configuration['theme'])) {
$configuration['theme'] = Bootstrap::getTheme();
}
$this->theme = $configuration['theme'];
$this->theme = Bootstrap::getTheme(isset($configuration['theme']) ? $configuration['theme'] : NULL);
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
......
<?php
namespace Drupal\bootstrap\Plugin\Provider;
use Drupal\bootstrap\Plugin\PluginBase;
/**
* Broken CDN Provider instance.
*
* @ingroup plugins_provider
*
* @BootstrapProvider(
* id = "_broken",
* label = @Translation("Broken"),
* )
*/
class Broken extends PluginBase implements ProviderInterface {
/**
* {@inheritdoc}
*/
public function alterFrameworkLibrary(array &$framework, $min = NULL) {
// Intentionally left empty.
}
/**
* {@inheritdoc}
*/
public function getAssets($types = NULL) {
return [];
}
/**
* {@inheritdoc}
*/
public function getCdnAssets($version = NULL, $theme = NULL) {
return [];
}
/**
* {@inheritdoc}
*/
public function getCdnThemes($version = NULL) {
return [];
}
/**
* {@inheritdoc}
*/
public function getCdnVersions() {
return [];
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->t('Broken CDN Provider instance.');
}
/**
* {@inheritdoc}
*/
public function getLabel() {
return $this->t('Broken');
}
/**
* {@inheritdoc}
*/
public function getCdnTheme() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getThemes() {
return [];
}
/**
* {@inheritdoc}
*/
public function getCdnVersion() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getVersions() {
return [];
}
/**
* {@inheritdoc}
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*/
public function getApi() {
return NULL;
}
/**
* {@inheritdoc}
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*/
public function hasError() {
return FALSE;
}
/**
* {@inheritdoc}
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*/
public function isImported() {
return FALSE;
}
/**
* {@inheritdoc}
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*/
public function processDefinition(array &$definition, $plugin_id) {
// Intentionally left empty.
}
/**
* {@inheritdoc}
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*/
public function processApi(array $json, array &$definition) {
// Intentionally left empty.
}
}
......@@ -17,24 +17,24 @@ class Custom extends ProviderBase {
/**
* {@inheritdoc}
*/
public function getAssets($types = NULL) {
$this->assets = [];
// If no type is set, return all CSS and JS.
if (!isset($types)) {
$types = ['css', 'js'];
}
$types = is_array($types) ? $types : [$types];
foreach ($types as $type) {
protected function discoverCdnAssets($version, $theme) {
$assets = [];
foreach (['css', 'js'] as $type) {
if ($setting = $this->theme->getSetting('cdn_custom_' . $type)) {
$this->assets[$type][] = $setting;
$assets[$type][] = $setting;
}
if ($setting = $this->theme->getSetting('cdn_custom_' . $type . '_min')) {
$this->assets['min'][$type][] = $setting;
$assets['min'][$type][] = $setting;
}
}
return parent::getAssets($types);
return $assets;
}
/**
* {@inheritdoc}
*/
public function processDefinition(array &$definition, $plugin_id) {
// Intentionally left blank so it doesn't trigger a deprecation warning.
}
}
This diff is collapsed.
This diff is collapsed.
......@@ -13,26 +13,36 @@ use Drupal\Component\Plugin\PluginInspectionInterface;
interface ProviderInterface extends PluginInspectionInterface, DerivativeInspectionInterface {
/**
* Retrieves the API URL if set.
* Alters the framework library.
*
* @return string
* The API URL.
* @param array $framework
* The framework library, passed by reference.
* @param bool $min
* Optional. Flag determining whether to use minified resources. If not set,
* this will automatically be determined based on system configuration.
*/
public function getApi();
public function alterFrameworkLibrary(array &$framework, $min = NULL);
/**
* Retrieves Provider assets for the active provider, if any.
* Retrieves the assets from the CDN, if any.
*
* @param string|array $types
* The type of asset to retrieve: "css" or "js", defaults to an array
* array containing both if not set.
* @param string $version
* Optional. The version of assets to return. If not set, the setting
* stored in the active theme will be used.
* @param string $theme
* Optional. A specific set of themed assets to return, if any. If not set,
* the setting stored in the active theme will be used.
*
* @return array
* If $type is a string or an array with only one (1) item in it, the
* assets are returned as an indexed array of files. Otherwise, an
* associative array is returned keyed by the type.
* An associative array containing the following keys, if there were
* matching files found:
* - css
* - js
* - min:
* - css
* - js
*/
public function getAssets($types = NULL);
public function getCdnAssets($version = NULL, $theme = NULL);
/**
* Retrieves the provider description.
......@@ -50,13 +60,90 @@ interface ProviderInterface extends PluginInspectionInterface, DerivativeInspect
*/
public function getLabel();
/**
* Retrieves the currently set CDN provider theme.
*
* @return string
* The currently set CDN provider theme.
*/
public function getCdnTheme();
/**
* Retrieves the themes supported by the CDN provider.
*
* @param string $version
* Optional. A specific version of themes to retrieve. If not set, the
* currently set CDN version of the active theme will be used.
*
* @return array
* An array of themes. If the CDN provider does not support any it will
* just be an empty array.
*/
public function getCdnThemes($version = NULL);
/**
* Retrieves the currently set CDN provider version.
*
* @return string
* The currently set CDN provider version.
*/
public function getCdnVersion();
/**
* Retrieves the versions supported by the CDN provider.
*
* @return array
* An array of versions. If the CDN provider does not support any it will
* just be an empty array.
*/
public function getCdnVersions();
/****************************************************************************
*
* Deprecated methods
*
***************************************************************************/
/**
* Retrieves the API URL if set.
*
* @return string
* The API URL.
*
* @deprecated in 8.x-3.18, will be removed in a future release. There is no
* replacement for this functionality.
*/
public function getApi();
/**
* Retrieves Provider assets for the active provider, if any.
*
* @param string|array $types
* The type of asset to retrieve: "css" or "js", defaults to an array
* array containing both if not set.
*
* @return array
* If $type is a string or an array with only one (1) item in it, the
* assets are returned as an indexed array of files. Otherwise, an
* associative array is returned keyed by the type.
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*
* @see \Drupal\bootstrap\Plugin\Provider\ProviderInterface::getCdnAssets()
*/
public function getAssets($types = NULL);
/**
* Retrieves the themes supported by the CDN provider.
*
* @return array
* An array of themes. If the CDN provider does not support any it will
* just be an empty array.
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*
* @see \Drupal\bootstrap\Plugin\Provider\ProviderInterface::getCdnThemes()
*/
public function getThemes();
/**
......@@ -65,6 +152,10 @@ interface ProviderInterface extends PluginInspectionInterface, DerivativeInspect
* @return array
* An array of versions. If the CDN provider does not support any it will
* just be an empty array.
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*
* @see \Drupal\bootstrap\Plugin\Provider\ProviderInterface::getCdnVersions()
*/
public function getVersions();
......@@ -73,6 +164,9 @@ interface ProviderInterface extends PluginInspectionInterface, DerivativeInspect
*
* @return bool
* TRUE or FALSE
*
* @deprecated in 8.x-3.18, will be removed in a future release. There is no
* replacement for this functionality.
*/
public function hasError();
......@@ -81,6 +175,9 @@ interface ProviderInterface extends PluginInspectionInterface, DerivativeInspect
*
* @return bool
* TRUE or FALSE
*
* @deprecated in 8.x-3.18, will be removed in a future release. There is no
* replacement for this functionality.
*/
public function isImported();
......@@ -91,6 +188,9 @@ interface ProviderInterface extends PluginInspectionInterface, DerivativeInspect
* The provider plugin definition.
* @param string $plugin_id
* The plugin identifier.
*
* @deprecated in 8.x-3.18, will be removed in a future release. There is no
* replacement for this functionality.
*/
public function processDefinition(array &$definition, $plugin_id);
......@@ -101,6 +201,9 @@ interface ProviderInterface extends PluginInspectionInterface, DerivativeInspect
* The JSON data retrieved from the API request.
* @param array $definition
* The provider plugin definition.
*
* @deprecated in 8.x-3.18, will be removed in a future release. There is no
* replacement for this functionality.
*/
public function processApi(array $json, array &$definition);
......
......@@ -2,14 +2,16 @@
namespace Drupal\bootstrap\Plugin;
use Drupal\bootstrap\Bootstrap;
use Drupal\bootstrap\Theme;
use Drupal\Component\Plugin\FallbackPluginManagerInterface;
/**
* Manages discovery and instantiation of Bootstrap CDN providers.
*
* @ingroup plugins_provider
*/
class ProviderManager extends PluginManager {
class ProviderManager extends PluginManager implements FallbackPluginManagerInterface {
/**
* The base file system path for CDN providers.
*
......@@ -28,6 +30,29 @@ class ProviderManager extends PluginManager {
$this->setCacheBackend(\Drupal::cache('discovery'), 'theme:' . $theme->getName() . ':provider', $this->getCacheTags());
}
/**
* Retrieves a CDN Provider.
*
* @param string $provider
* Optional. The ID of the provider to load. If not set or an invalid
* provider was specified, the base provider will be returned.
* @param array $configuration
* Optional. An array of configuration relevant to the plugin instance.
*
* @return \Drupal\bootstrap\Plugin\Provider\ProviderInterface
* A CDN Provider instance.
*/
public function get($provider, array $configuration = []) {
return $this->createInstance($provider, $configuration + ['theme' => $this->theme]);
}
/**
* {@inheritdoc}
*/
public function getFallbackPluginId($plugin_id, array $configuration = []) {
return '_broken';
}
/**
* {@inheritdoc}
*/
......@@ -38,4 +63,24 @@ class ProviderManager extends PluginManager {
$provider->processDefinition($definition, $plugin_id);
}
/**
* Loads a CDN Provider.
*
* @param \Drupal\bootstrap\Theme|string $theme
* Optional. A theme to associate with the provider. If not set, the
* active theme will be used.
* @param string $provider
* Optional. The ID of the provider to load. If not set, the provider set
* on the supplied or active $theme will be used.
* @param array $configuration
* Optional. An array of configuration relevant to the plugin instance.
*
* @return \Drupal\bootstrap\Plugin\Provider\ProviderInterface
* A CDN Provider instance.
*/
public static function load($theme = NULL, $provider = NULL, array $configuration = []) {
$theme = Bootstrap::getTheme($theme);
return (new static($theme))->get($provider ?: $theme->getSetting('cdn_provider'), $configuration + ['theme' => $theme]);
}
}
......@@ -33,8 +33,8 @@ class CdnJsdelivrTheme extends CdnProvider {
*/
public function alterFormElement(Element $form, FormStateInterface $form_state, $form_id = NULL) {
$setting = $this->getSettingElement($form, $form_state);
$themes = $this->provider->getThemes();
$version = $form_state->getValue('cdn_jsdelivr_version', $this->theme->getSetting('cdn_jsdelivr_version'));
$themes = $this->provider->getCdnThemes($version);
$setting->setProperty('suffix', '<div id="bootstrap-theme-preview"></div>');
$setting->setProperty('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!', [
......@@ -43,11 +43,10 @@ class CdnJsdelivrTheme extends CdnProvider {
]));
$options = [];
if (isset($themes[$version])) {
foreach ($themes[$version] as $theme => $data) {
$options[$theme] = $data['title'];
}
foreach ($themes as $theme => $data) {
$options[$theme] = $data['title'];
}
$setting->setProperty('options', $options);
}
......
......@@ -38,8 +38,9 @@ class CdnJsdelivrVersion extends CdnProvider {
$plugin_id = Html::cleanCssIdentifier($this->provider->getPluginId());
$setting = $this->getSettingElement($form, $form_state);
$versions = $this->provider->getCdnVersions();
$setting->setProperty('options', $this->provider->getVersions());
$setting->setProperty('options', $versions);
$setting->setProperty('ajax', [
'callback' => [get_class($this), 'ajaxCallback'],
'wrapper' => 'cdn-provider-' . $plugin_id,
......
......@@ -56,9 +56,7 @@ class CdnProvider extends SettingBase {
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->providerManager = new ProviderManager($this->theme);
if (isset($plugin_definition['cdn_provider'])) {
$this->provider = $this->theme->getProvider($plugin_definition['cdn_provider']);
}
$this->provider = $this->providerManager