InstallableLibrary.php in Markdown 8.2
Namespace
Drupal\markdown\AnnotationFile
src/Annotation/InstallableLibrary.phpView source
<?php
namespace Drupal\markdown\Annotation;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Html;
use Drupal\Core\Cache\CacheableResponse;
use Drupal\Core\Link;
use Drupal\Core\Site\Settings;
use Drupal\Core\Utility\Error;
use Drupal\markdown\Traits\HttpClientTrait;
use Drupal\markdown\Util\Semver;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7\Request;
class InstallableLibrary extends AnnotationObject {
use HttpClientTrait;
use InstallablePluginTrait;
/**
* Optional. A customized human-readable label for the library.
*
* Note: this may be necessary if there is already a plugin or library using
* the same name and it needs to differentiate itself further. An example of
* this is checking a object requirement that is bundled as an extension
* inside the main library.
*
* @var string|null
*/
public $customLabel;
/**
* The version with extra metadata.
*
* @var string
*/
public $versionExtra;
/**
* All available versions, regardless of stability.
*
* @var string[]
*/
protected $availableVersions;
/**
* The latest version, based on currently available versions and stability.
*
* @var string[]
*/
protected $latestVersion = [];
/**
* An array of newer versions, based on currently set version and stability.
*
* @var array
*/
protected $newerVersions = [];
/**
* A specific version URL, if known.
*
* @var \Drupal\Core\Url[]
*/
protected $versionUrls = [];
/**
* The last exception thrown when attempting to initiate a request.
*
* @var \GuzzleHttp\Exception\GuzzleException
*/
protected $requestException;
/**
* {@inheritdoc}
*/
public function __construct($values = []) {
parent::__construct($values);
// Detect version of PHP extension.
if (!isset($this->version)) {
$version = $this
->detectVersion();
if (is_array($version) && count($version) === 2) {
list($raw, $extra) = $version;
$this->version = $raw;
$this->versionExtra = $extra;
}
else {
$this->version = $version;
}
}
}
public function createObjectRequirement(InstallablePlugin $definition = NULL) {
if ($this->object) {
$name = $this
->getLink($this->customLabel) ?: $this
->getId();
if (!$name && $definition) {
$name = $definition
->getLink($this->customLabel) ?: $definition
->getId();
}
return InstallableRequirement::create([
'value' => $this->object,
'constraints' => [
'Installed' => [
'name' => $name,
],
],
]);
}
}
/**
* Detects the version of the library installed.
*
* @return string|string[]|void
* The detected version, if any. If an array is returned, the first item
* should be the raw version provided by the library, the second item
* may contain additional meta information appended to the raw version
* to denote a more detailed version (i.e. bundled with another library).
*/
protected function detectVersion() {
}
/**
* Retrieves the available versions of the library.
*
* Note: this will likely make an HTTP request.
*
* @return string[]
* The available versions.
*/
public function getAvailableVersions() {
return [];
}
/**
* Retrieves the CLI command used to install the library, if any.
*
* @return string|void
* A install command to be used to install the library, if any.
*/
public function getInstallCommand() {
}
/**
* Retrieves the latest version based on available versions.
*
* Note: this will likely make an HTTP request.
*
* @param string $minimumStability
* Optional. The minimum stability to determine which latest version to
* retrieve.
*
* @return string|void
* The latest version, if successful.
*/
public function getLatestVersion($minimumStability = 'stable') {
if (!isset($this->latestVersion[$minimumStability])) {
$this->latestVersion[$minimumStability] = Semver::latestVersion($this
->getNewerVersions($minimumStability), "@{$minimumStability}");
}
return $this->latestVersion[$minimumStability];
}
/**
* Retrieves the newer versions of the library.
*
* Note: this will likely make an HTTP request.
*
* @param string $minimumStability
* Optional. The minimum stability to determine which latest version to
* retrieve.
*
* @return string[]
* The newer versions.
*/
public function getNewerVersions($minimumStability = 'stable') {
$version = ($this->version ?: '*') . "@{$minimumStability}";
if (!isset($this->newerVersions[$version])) {
$availableVersions = $this
->getAvailableVersions();
$this->newerVersions[$version] = Semver::sort($this->version ? Semver::satisfiedBy($availableVersions, ">{$version}") : $availableVersions);
}
return $this->newerVersions[$version];
}
/**
* Retrieves the current status of the library.
*
* @param bool $long
* Flag indicating whether to use longer explanations as indicated by
* the individual property values.
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
* The human readable status.
*/
public function getStatus($long = FALSE) {
// Immediately return if there are requirement violations (not installed).
if ($this->requirementViolations) {
return t('Not Installed');
}
// Determine the library status.
if ($this
->hasRequestFailure()) {
if ($long) {
return t('Request Failure. @explanation', [
'@explanation' => $this->requestException
->getMessage(),
]);
}
return t('Request Failure');
}
if ($this
->getNewerVersions()) {
if ($this->deprecated) {
return t('Deprecated');
}
if ($this->preferred) {
return t('Update Available');
}
return t('Upgrade Available');
}
if ($this
->isKnownVersion()) {
if ($this->deprecated && !$this->preferred) {
if ($long && $this->deprecated !== TRUE) {
return t('Upgrade Available. @explanation', [
'@explanation' => $this->deprecated,
]);
}
return t('Upgrade Available');
}
if ($this->deprecated && $this->preferred) {
if ($long && $this->deprecated !== TRUE) {
return t('Deprecated. @explanation', [
'@explanation' => $this->deprecated,
]);
}
return t('Deprecated');
}
if ($this->experimental) {
if ($long && $this->experimental !== TRUE) {
return t('Experimental. @explanation', [
'@explanation' => $this->experimental,
]);
}
return t('Experimental');
}
if ($this
->isPrerelease()) {
return t('Prerelease');
}
if ($this
->isDev()) {
return t('Development Release');
}
return t('Up to date');
}
return t('Unknown');
}
/**
* Retrieves the version as a link to a specific release.
*
* @param string $version
* A specific version to retrieve a URL for. If not specified, it will
* default to the currently installed version.
* @param string|\Drupal\Component\Render\MarkupInterface $label
* The label to use for the link. If not specified, it will default to
* the versionExtra or version value.
* @param array $options
* Optional. Options to pass to the creation of the URL object.
*
* @return \Drupal\Core\GeneratedLink|void
* The link to the version.
*/
public function getVersionLink($version = NULL, $label = NULL, array $options = []) {
if (!$version) {
$version = $this->version;
if (!isset($label)) {
$label = $this->versionExtra ?: $this->version;
}
}
if ($version && ($url = $this
->getVersionUrl($version, $options))) {
if (!isset($label)) {
$label = $version;
}
return Link::fromTextAndUrl($label, $url)
->toString();
}
}
/**
* Retrieves the version as a URL.
*
* @param string $version
* A specific version to retrieve a URL for. If not specified, it will
* default to the currently installed version.
* @param array $options
* Optional. Options to pass to the creation of the URL object.
*
* @return \Drupal\Core\Url|false
* A specific version URL, if set; FALSE otherwise.
*/
public function getVersionUrl($version = NULL, array $options = []) {
return FALSE;
}
/**
* Indicates whether there is an issue performing requests for the library.
*
* @return bool
* TRUE or FALSE
*/
public function hasRequestFailure() {
return !!$this->requestException;
}
/**
* Indicates whether this is a known version.
*
* @param string $version
* A specific version to test. If not specified, it will default to the
* currently installed version, if any.
*
* @return bool
* TRUE or FALSE
*/
public function isKnownVersion($version = NULL) {
if (!isset($version)) {
$version = $this->version;
}
return $version && in_array($version, $this
->getAvailableVersions(), TRUE);
}
/**
* Indicates whether this is an alpha version.
*
* @param string $version
* A specific version to test. If not specified, it will default to the
* currently installed version, if any.
*
* @return bool
* TRUE or FALSE
*/
public function isAlpha($version = NULL) {
if (!isset($version)) {
$version = $this->version;
}
return $version && Semver::satisfies($version, '@alpha') && !Semver::satisfies($version, '@beta') && !Semver::satisfies($version, '@RC') && !Semver::satisfies($version, '@stable');
}
/**
* Indicates whether this is an alpha version.
*
* @param string $version
* A specific version to test. If not specified, it will default to the
* currently installed version, if any.
*
* @return bool
* TRUE or FALSE
*/
public function isDev($version = NULL) {
if (!isset($version)) {
$version = $this->version;
}
return $version && Semver::satisfies($version, '@dev') && !Semver::satisfies($version, '@alpha') && !Semver::satisfies($version, '@beta') && !Semver::satisfies($version, '@RC') && !Semver::satisfies($version, '@stable');
}
/**
* Indicates whether this is a beta version.
*
* @param string $version
* A specific version to test. If not specified, it will default to the
* currently installed version, if any.
*
* @return bool
* TRUE or FALSE
*/
public function isBeta($version = NULL) {
if (!isset($version)) {
$version = $this->version;
}
return $version && !Semver::satisfies($version, '@alpha') && Semver::satisfies($version, '@beta') && !Semver::satisfies($version, '@RC') && !Semver::satisfies($version, '@stable');
}
/**
* Indicates whether this is a release candidate version.
*
* @param string $version
* A specific version to test. If not specified, it will default to the
* currently installed version, if any.
*
* @return bool
* TRUE or FALSE
*/
public function isRc($version = NULL) {
if (!isset($version)) {
$version = $this->version;
}
return $version && !Semver::satisfies($version, '@alpha') && !Semver::satisfies($version, '@beta') && Semver::satisfies($version, '@RC') && !Semver::satisfies($version, '@stable');
}
/**
* Indicates whether this is any prerelease version.
*
* @param string $version
* A specific version to test. If not specified, it will default to the
* currently installed version, if any.
*
* @return bool
* TRUE or FALSE
*/
public function isPrerelease($version = NULL) {
if (!isset($version)) {
$version = $this->version;
}
return $version && (Semver::satisfies($version, '@alpha') || Semver::satisfies($version, '@beta') || Semver::satisfies($version, '@RC')) && !Semver::satisfies($version, '@stable');
}
/**
* Indicates whether this is a stable version.
*
* @param string $version
* A specific version to test. If not specified, it will default to the
* currently installed version, if any.
*
* @return bool
* TRUE or FALSE
*/
public function isStable($version = NULL) {
if (!isset($version)) {
$version = $this->version;
}
return $version && Semver::satisfies($version, '@stable');
}
/**
* Requests a URL.
*
* @param string $url
* The URL being requested.
*
* @return \Drupal\Core\Cache\CacheableResponse
* A cacheable response.
*/
protected function request($url) {
$this->requestException = NULL;
// Clean the URL.
$extension = ltrim(pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION), '.');
$cleanUrl = Html::cleanCssIdentifier(preg_replace('/' . preg_quote($extension, '/') . '$/', '', $url)) . ($extension ? ".{$extension}" : '');
$cid = 'installable_library:' . $this->id . ':' . $cleanUrl;
$cache = \Drupal::cache('markdown');
// If there is a valid 24hr cached response in the database, use it.
if (($cached = $cache
->get($cid)) && isset($cached->data)) {
return $cached->data;
}
// Prepare the request.
$content = NULL;
$options = [];
$directory = 'public://installable_plugins/library';
file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
// If there's a cached file of the request, attempt to use it if its
// modified time is still valid and acknowledged by the responding server.
if (file_exists("{$directory}/{$cleanUrl}")) {
$content = file_get_contents("{$directory}/{$cleanUrl}");
$options['headers']['If-Modified-Since'] = date('r', filemtime("{$directory}/{$cleanUrl}"));
}
// Make the request.
try {
$response = static::httpClient()
->get($url, $options);
$statusCode = $response
->getStatusCode();
// New content.
if ($statusCode >= 200 && $statusCode < 300) {
$content = $response
->getBody()
->getContents();
file_put_contents("{$directory}/{$cleanUrl}", $content);
}
elseif ($statusCode >= 400) {
$request = new Request('GET', $url, isset($options['headers']) ? $options['headers'] : []);
throw new RequestException($response
->getBody()
->getContents(), $request, $response);
}
// Create a cacheable response.
$cacheableResponse = CacheableResponse::create($content, $statusCode, $response
->getHeaders());
// Cache response in the database. The TTL value defaults to one day,
// but allow it to be overrideable via settings.
$ttl = Settings::get('installable_library_request_ttl', 86400);
$cache
->set($cid, $cacheableResponse, REQUEST_TIME + $ttl);
} catch (GuzzleException $exception) {
\Drupal::logger('markdown')
->warning('%type: @message in %function (line %line of %file).<pre><code>@backtrace_string</code></pre>', Error::decodeException($exception));
$this->requestException = $exception;
$cacheableResponse = CacheableResponse::create($exception
->getMessage(), 500);
}
return $cacheableResponse;
}
/**
* Retrieves JSON from a URL.
*
* @param string $url
* The URL where to retrieve JSON from.
*
* @return array|void
* An array of JSON if successful, NULL otherwise.
*/
protected function requestJson($url) {
$response = $this
->request($url);
$statusCode = $response
->getStatusCode();
if ($statusCode >= 200 && $statusCode < 400 && ($contents = $response
->getContent()) && ($json = Json::decode($contents))) {
return $json;
}
}
/**
* Retrieves XML from a URL.
*
* @param string $url
* The URL where to retrieve JSON from.
* @param bool $array
* Flag indicating whether to return an array or a DOM object.
*
* @return \DOMDocument|array|void
* A DOMDocument or array (if $array is specified) of XML if successful,
* NULL otherwise.
*
* @noinspection PhpComposerExtensionStubsInspection
* @sse https://git.drupalcode.org/project/drupal/-/blob/5c4e2e76a1c7972c4b03496ab51846dad63aa762/core/modules/system/system.install#L197
*/
protected function requestXml($url, $array = FALSE) {
$response = $this
->request($url);
$statusCode = $response
->getStatusCode();
if ($statusCode >= 200 && $statusCode < 400 && ($contents = $response
->getContent())) {
if ($array) {
return (array) json_decode(json_encode(simplexml_load_string($contents)), TRUE);
}
elseif (($dom = new \DOMDocument()) && $dom
->loadXML($contents)) {
return $dom;
}
}
}
}
Classes
Name![]() |
Description |
---|---|
InstallableLibrary |