ProjectInfo.php
Same filename in this branch
Same filename in other branches
Namespace
Drupal\package_managerFile
-
core/
modules/ package_manager/ src/ ProjectInfo.php
View source
<?php
declare (strict_types=1);
namespace Drupal\package_manager;
use Composer\Semver\Comparator;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\update\ProjectRelease;
use Drupal\Core\Extension\ExtensionVersion;
use Drupal\Core\Utility\Error;
use Drupal\update\UpdateManagerInterface;
/**
* Defines a class for retrieving project information from Update module.
*
* @internal
* This is an internal part of Automatic Updates and may be changed or removed
* at any time without warning. External code should use the Update API
* directly.
*/
final class ProjectInfo {
public function __construct(string $name) {
}
/**
* Determines if a release can be installed.
*
* @param \Drupal\update\ProjectRelease $release
* The project release.
* @param string[] $support_branches
* The supported branches.
*
* @return bool
* TRUE if the release is installable, otherwise FALSE. A release will be
* considered installable if it is secure, published, supported, and in
* a supported branch.
*/
private function isInstallable(ProjectRelease $release, array $support_branches) : bool {
if ($release->isInsecure() || !$release->isPublished() || $release->isUnsupported()) {
return FALSE;
}
$version = ExtensionVersion::createFromVersionString($release->getVersion());
if ($version->getVersionExtra() === 'dev') {
return FALSE;
}
foreach ($support_branches as $support_branch) {
$support_branch_version = ExtensionVersion::createFromSupportBranch($support_branch);
if ($support_branch_version->getMajorVersion() === $version->getMajorVersion() && $support_branch_version->getMinorVersion() === $version->getMinorVersion()) {
return TRUE;
}
}
return FALSE;
}
/**
* Returns up-to-date project information.
*
* @return mixed[]|null
* The retrieved project information.
*
* @throws \RuntimeException
* If data about available updates cannot be retrieved.
*/
public function getProjectInfo() : ?array {
$available_updates = $this->getAvailableProjects();
$project_data = update_calculate_project_data($available_updates);
if (!isset($project_data[$this->name])) {
return $available_updates[$this->name] ?? NULL;
}
return $project_data[$this->name];
}
/**
* Gets all project releases to which the site can update.
*
* @return \Drupal\update\ProjectRelease[]|null
* If the project information is available, an array of releases that can be
* installed, keyed by version number; otherwise NULL. The releases are in
* descending order by version number (i.e., higher versions are listed
* first). The currently installed version of the project, and any older
* versions, are not considered installable releases.
*
* @throws \RuntimeException
* Thrown if there are no available releases.
*
* @todo Remove or simplify this function in https://www.drupal.org/i/3252190.
*/
public function getInstallableReleases() : ?array {
$project = $this->getProjectInfo();
if (!$project) {
return NULL;
}
$available_updates = $this->getAvailableProjects()[$this->name];
if ($available_updates['project_status'] !== 'published') {
throw new \RuntimeException("The project '{$this->name}' can not be updated because its status is " . $available_updates['project_status']);
}
$installed_version = $this->getInstalledVersion();
if ($installed_version && empty($available_updates['releases'])) {
// If project is installed but not current we should always have at
// least one release.
throw new \RuntimeException('There was a problem getting update information. Try again later.');
}
$support_branches = explode(',', $available_updates['supported_branches']);
$installable_releases = [];
foreach ($available_updates['releases'] as $release_info) {
try {
$release = ProjectRelease::createFromArray($release_info);
} catch (\UnexpectedValueException $exception) {
// Ignore releases that are in an invalid format. Although this is
// unlikely we should still only process releases in the correct format.
\Drupal::logger('package_manager')->error(sprintf('Invalid project format: %s', print_r($release_info, TRUE)), Error::decodeException($exception));
continue;
}
$version = $release->getVersion();
if ($installed_version) {
$semantic_version = LegacyVersionUtility::convertToSemanticVersion($version);
$semantic_installed_version = LegacyVersionUtility::convertToSemanticVersion($installed_version);
if (Comparator::lessThanOrEqualTo($semantic_version, $semantic_installed_version)) {
// If the project is installed stop searching for releases as soon as
// we find the installed version.
break;
}
}
if ($this->isInstallable($release, $support_branches)) {
$installable_releases[$version] = $release;
}
}
return $installable_releases;
}
/**
* Returns the installed project version, according to the Update module.
*
* @return string|null
* The installed project version as known to the Update module or NULL if
* the project information is not available.
*/
public function getInstalledVersion() : ?string {
$project_data = $this->getProjectInfo();
if ($project_data && array_key_exists('existing_version', $project_data)) {
$existing_version = $project_data['existing_version'];
// Treat an unknown version the same as a project whose project
// information is not available, so return NULL.
// @see \update_process_project_info()
if ($existing_version instanceof TranslatableMarkup && $existing_version->getUntranslatedString() === 'Unknown') {
return NULL;
}
// TRICKY: Since this is relying on data coming from
// \Drupal\update\UpdateManager::getProjects(), we cannot be certain that
// we are actually receiving strings.
// @see \Drupal\update\UpdateManager::getProjects()
if (!is_string($existing_version)) {
return NULL;
}
return $existing_version;
}
return NULL;
}
/**
* Gets the available projects.
*
* @return array
* The available projects keyed by project machine name in the format
* returned by update_get_available(). If the project specified in ::name is
* not returned from update_get_available() this project will be explicitly
* fetched and added the return value of this function.
*
* @see \update_get_available()
*/
private function getAvailableProjects() : array {
$available_projects = update_get_available(TRUE);
// update_get_available() will only returns projects that are in the active
// codebase. If the project specified by ::name is not returned in
// $available_projects, it means it is not in the active codebase, therefore
// we will retrieve the project information from Package Manager's own
// update processor service.
if (!isset($available_projects[$this->name])) {
/** @var \Drupal\package_manager\PackageManagerUpdateProcessor $update_processor */
$update_processor = \Drupal::service(PackageManagerUpdateProcessor::class);
if ($project_data = $update_processor->getProjectData($this->name)) {
$available_projects[$this->name] = $project_data;
}
}
return $available_projects;
}
/**
* Checks if the installed version of this project is safe to use.
*
* @return bool
* TRUE if the installed version of this project is secure, supported, and
* published. Otherwise, or if the project information could not be
* retrieved, returns FALSE.
*/
public function isInstalledVersionSafe() : bool {
$project_data = $this->getProjectInfo();
if ($project_data) {
$unsafe = [
UpdateManagerInterface::NOT_SECURE,
UpdateManagerInterface::NOT_SUPPORTED,
UpdateManagerInterface::REVOKED,
];
return !in_array($project_data['status'], $unsafe, TRUE);
}
// If we couldn't get project data, assume the installed version is unsafe.
return FALSE;
}
/**
* Gets the supported branches of the project.
*
* @return string[]
* The supported branches.
*/
public function getSupportedBranches() : array {
$available_updates = $this->getAvailableProjects()[$this->name];
return isset($available_updates['supported_branches']) ? explode(',', $available_updates['supported_branches']) : [];
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
ProjectInfo | Defines a class for retrieving project information from Update module. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.