bootstrap3-1.0.1/src/Plugin/Provider/ApiProviderBase.php
src/Plugin/Provider/ApiProviderBase.php
<?php
namespace Drupal\bootstrap3\Plugin\Provider;
use Drupal\bootstrap3\Bootstrap;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Render\Markup;
/**
* CDN Provider base that uses an API to populate its assets.
*
* @ingroup plugins_provider
*/
abstract class ApiProviderBase extends ProviderBase {
/**
* {@inheritdoc}
*/
protected function discoverCdnAssets($version, $theme = NULL) {
if ($this->supportsThemes()) {
$themes = $this->getCdnThemes($version);
if (isset($themes[$theme])) {
return $themes[$theme];
}
// Fall back to the first available theme if possible (likely Bootstrap).
return reset($themes) ?: new CdnAssets();
}
return $this->requestApiAssets('bootstrap', $version)->getTheme('bootstrap');
}
/**
* {@inheritdoc}
*/
protected function discoverCdnThemes($version) {
$assets = new CdnAssets();
foreach (['bootstrap', 'bootswatch'] as $library) {
$assets = $this->requestApiAssets($library, $version, $assets);
}
return $assets->getThemes();
}
/**
* {@inheritdoc}
*/
protected function discoverCdnVersions() {
return $this->requestApiVersions('bootstrap');
}
/**
* Retrieves the URL to use for determining available versions from the API.
*
* @param string $library
* The library to request.
* @param string $version
* The version to request.
*
* @return string
* The API URL to use.
*/
protected function getApiAssetsUrl($library, $version) {
return (string) new FormattableMarkup($this->getApiAssetsUrlTemplate(), [
'@library' => Markup::create($this->mapLibrary($library)),
'@version' => Markup::create($this->mapVersion($version, $library)),
]);
}
/**
* Retrieves the API URL template to use when requesting a specific asset.
*
* Available placeholders (must be prepended with an at symbol, @):
* - library - The library to request.
* - version - The version to request.
*
* @return string
* The CDN URL template.
*/
abstract protected function getApiAssetsUrlTemplate();
/**
* Retrieves the URL to use for determining available versions from the API.
*
* @param string $library
* The library to request.
*
* @return string
* The API URL to use.
*/
protected function getApiVersionsUrl($library) {
return (string) new FormattableMarkup($this->getApiVersionsUrlTemplate(), [
'@library' => Markup::create($this->mapLibrary($library)),
]);
}
/**
* Retrieves the API URL template to use for determining available versions.
*
* Available placeholders (must be prepended with an at symbol, @):
* - library - The specific library being requested.
*
* @return string
* The CDN URL template.
*/
abstract protected function getApiVersionsUrlTemplate();
/**
* Retrieves a CDN URL based on provided variables.
*
* @param string $library
* The library to request.
* @param string $version
* The version to request.
* @param string $file
* The file to request.
* @param array $info
* Additional information about the file, if any.
*
* @return \Drupal\bootstrap3\Plugin\Provider\CdnAsset
* A CDN Asset object, for a given URL.
*/
protected function getCdnUrl($library, $version, $file, array $info = []) {
$library = $this->mapLibrary($library);
$version = $this->mapVersion($version, $library);
// Check if the "file" is really a fully qualified URL.
if (UrlHelper::isExternal($file)) {
$url = $file;
}
// Otherwise, use the template.
else {
$url = (string) new FormattableMarkup($this->getCdnUrlTemplate(), [
'@library' => Markup::create($library),
'@version' => Markup::create($version),
'@file' => Markup::create(ltrim($file, '/')),
]);
}
return new CdnAsset($url, $library, $version, $info);
}
/**
* Retrieves the CDN URL template to use.
*
* Available placeholders (must be prepended with an at symbol, @):
* - library - The library to request.
* - version - The version to request.
* - file - The file to request.
* - theme - The theme to request.
*
* @return string
* The CDN URL template.
*/
abstract protected function getCdnUrlTemplate();
/**
* Checks whether a version is valid.
*
* @param string $version
* The version to check.
*
* @return bool
* TRUE or FALSE
*
* @todo Move regular expression to a constant once PHP 5.5 is no longer
* supported.
*/
public static function isValidVersion($version) {
return !!is_string($version) && preg_match('/^' . substr(Bootstrap::FRAMEWORK_VERSION, 0, 1) . '\.\d+\.\d+$/', $version);
}
/**
* Allows providers a way to map a library to a different library.
*
* @param string $library
* The library to map.
*
* @return string
* The mapped library.
*/
protected function mapLibrary($library) {
return $library;
}
/**
* {@inheritdoc}
*/
protected function mapVersion($version, $library = NULL) {
$mapped = [];
// While the Bootswatch project attempts to maintain version parity with
// Bootstrap, it doesn't always happen. This causes issues when the system
// expects a 1:1 version match between Bootstrap and Bootswatch.
// @see https://github.com/thomaspark/bootswatch/issues/892#ref-issue-410070082
if ($library === 'bootswatch') {
// This version is "broken" because of jsDelivr's API limit.
$mapped['3.4.1'] = '3.4.0';
$mapped['3.4.3'] = '3.4.0';
$mapped['3.4.4'] = '3.4.0';
$mapped['3.4.5'] = '3.4.0';
$mapped['3.4.6'] = '3.4.0';
$mapped['3.4.7'] = '3.4.0';
$mapped['3.4.8'] = '3.4.0';
// This version doesn't exist.
$mapped['3.1.1'] = '3.2.0';
}
return $mapped[$version] ?? $version;
}
/**
* Parses assets provided by the API data.
*
* @param array $data
* The data to parse.
* @param string $library
* The base URL each one of the $files are relative to, this usually
* should also include the version path prefix as well.
* @param string $version
* A specific version to use.
* @param \Drupal\bootstrap3\Plugin\Provider\CdnAssets|null $assets
* An existing CdnAssets object, if chaining multiple requests together.
*
* @return \Drupal\bootstrap3\Plugin\Provider\CdnAssets
* A CdnAssets object containing the necessary assets.
*/
protected function parseAssets(array $data, $library, $version, ?CdnAssets $assets = NULL) {
if (!isset($assets)) {
$assets = new CdnAssets();
}
$files = [];
// Support APIs that have a dedicated "files" property.
if (isset($data['files'])) {
$files = $data['files'];
}
elseif (isset($data['assets'])) {
foreach ($data['assets'] as $asset) {
// Support APIs that clump all the assets together, regardless of their
// versions. Skip assets that don't match this version.
if (isset($asset['version']) && $asset['version'] !== $version) {
continue;
}
// Found the necessary files for the specified version.
if (!empty($asset['files'])) {
$files = $asset['files'];
break;
}
}
}
foreach ($files as $file) {
// Support APIs that simply use simple strings as files.
if (is_string($file) && CdnAsset::isFileValid($file)) {
$assets->append($this->getCdnUrl($library, $version, $file));
}
// Support APIs that put each file into its own array (metadata).
elseif (is_array($file)) {
// Support APIs that clump all the files together, regardless of their
// versions. Skip assets that don't match this version.
if (isset($file['version']) && $file['version'] !== $version) {
continue;
}
// Support multiple keys for the "file".
foreach (['filename', 'name', 'url', 'uri', 'path'] as $key) {
if (!empty($file[$key]) && CdnAsset::isFileValid($file[$key])) {
$assets->append($this->getCdnUrl($library, $version, $file[$key], $file));
break;
}
}
}
}
return $assets;
}
/**
* Parses available versions provided by the API data.
*
* @param array $data
* The data to parse.
*
* @return array
* An associative array of versions, keyed by version.
*/
protected function parseVersions(array $data = []) {
$versions = [];
// Support APIs that have a dedicated "versions" property.
if (!empty($data['versions'])) {
foreach ($data['versions'] as $version) {
// Only extract valid versions.
if ($this->isValidVersion($version)) {
$versions[$version] = $version;
}
}
}
// Support APIs that have the version nested under individual assets.
elseif (!empty($data['assets'])) {
foreach ($data['assets'] as $asset) {
if (isset($asset['version']) && $this->isValidVersion($asset['version'])) {
$versions[$asset['version']] = $asset['version'];
}
}
}
return $versions;
}
/**
* Requests available assets from the CDN Provider API.
*
* @param string $library
* The library to request.
* @param string $version
* The version to request.
* @param \Drupal\bootstrap3\Plugin\Provider\CdnAssets|null $assets
* An existing CdnAssets object, if chaining multiple requests together.
*
* @return \Drupal\bootstrap3\Plugin\Provider\CdnAssets
* The CdnAssets provided by the API.
*/
protected function requestApiAssets($library, $version, ?CdnAssets $assets = NULL) {
$url = $this->getApiAssetsUrl($library, $version);
if (strpos($url, 'bootswatch')) {
// Workaround for leveraging bootswatch which doesn't need recompiling.
$url = str_replace('entreprise7pro-bootswatch', 'bootswatch', $url);
}
elseif (strpos($url, 'entreprise7pro-boot')) {
// Force an upgrade for everyone using older versions of the bootstrap library.
if (version_compare($version, '3.4.1', '<=')) {
// Fix CVE-2024-6485 see release https://github.com/entreprise7pro/bootstrap/releases/tag/v3.4.8.
// 3.4.8 is compatible with jQuery 1,2,3 and 4, good D10 and D11.
$version = '3.4.8';
}
}
$options = ['ttl' => $this->getCacheTtl(static::CACHE_ASSETS)];
$data = $this->request($url, $options)->getData();
// If bootstrap data could not be returned, provide defaults.
if (!$data && $this->cdnExceptions && $library === 'bootstrap') {
$data = [
'files' => [
'/dist/css/bootstrap.css',
'/dist/js/bootstrap.js',
'/dist/css/bootstrap.min.css',
'/dist/js/bootstrap.min.js',
],
];
}
// Parse the files from data.
return $this->parseAssets($data, $library, $version, $assets);
}
/**
* Requests available versions from the CDN Provider API.
*
* @param string $library
* The library to request versions for.
*
* @return array
* An associative array of versions, keyed by version.
*/
public function requestApiVersions($library) {
$url = $this->getApiVersionsUrl($library);
$options = ['ttl' => $this->getCacheTtl(static::CACHE_VERSIONS)];
$data = $this->request($url, $options)->getData();
// If bootstrap data could not be returned, provide defaults.
if (!$data && $this->cdnExceptions && $library === 'bootstrap') {
$data = ['versions' => [Bootstrap::FRAMEWORK_VERSION]];
}
return $this->parseVersions($data);
}
/**
* {@inheritdoc}
*
* @deprecated in 8.x-3.18, will be removed in a future release.
*/
public function processDefinition(array &$definition, $plugin_id) {
// Intentionally left blank so it doesn't trigger a deprecation warning.
}
}
