123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- <?php
- namespace Drupal\update;
- use Drupal\Core\StringTranslation\StringTranslationTrait;
- use Drupal\Core\Url;
- /**
- * Class for generating a project's security requirement.
- *
- * @see update_requirements()
- *
- * @internal
- * This class implements logic to determine security coverage for Drupal core
- * according to Drupal core security policy. It should not be called directly.
- */
- final class ProjectSecurityRequirement {
- use StringTranslationTrait;
- /**
- * The project title.
- *
- * @var string|null
- */
- protected $projectTitle;
- /**
- * Security coverage information for the project.
- *
- * @var array
- *
- * @see \Drupal\update\ProjectSecurityData::getCoverageInfo()
- */
- private $securityCoverageInfo;
- /**
- * The next version after the installed version in the format [MAJOR].[MINOR].
- *
- * @var string|null
- */
- private $nextMajorMinorVersion;
- /**
- * The existing (currently installed) version in the format [MAJOR].[MINOR].
- *
- * @var string|null
- */
- private $existingMajorMinorVersion;
- /**
- * Constructs a ProjectSecurityRequirement object.
- *
- * @param string|null $project_title
- * The project title.
- * @param array $security_coverage_info
- * Security coverage information as set by
- * \Drupal\update\ProjectSecurityData::getCoverageInfo().
- * @param string|null $existing_major_minor_version
- * The existing (currently installed) version in the format [MAJOR].[MINOR].
- * @param string|null $next_major_minor_version
- * The next version after the installed version in the format
- * [MAJOR].[MINOR].
- */
- private function __construct($project_title = NULL, array $security_coverage_info = [], $existing_major_minor_version = NULL, $next_major_minor_version = NULL) {
- $this->projectTitle = $project_title;
- $this->securityCoverageInfo = $security_coverage_info;
- $this->existingMajorMinorVersion = $existing_major_minor_version;
- $this->nextMajorMinorVersion = $next_major_minor_version;
- }
- /**
- * Creates a ProjectSecurityRequirement object from project data.
- *
- * @param array $project_data
- * Project data from Drupal\update\UpdateManagerInterface::getProjects().
- * The 'security_coverage_info' key should be set by
- * calling \Drupal\update\ProjectSecurityData::getCoverageInfo() before
- * calling this method. The following keys are used in this method:
- * - existing_version (string): The version of the project that is installed
- * on the site.
- * - project_type (string): The type of project.
- * - name (string): The project machine name.
- * - title (string): The project title.
- * @param array $security_coverage_info
- * The security coverage information as returned by
- * \Drupal\update\ProjectSecurityData::getCoverageInfo().
- *
- * @return static
- *
- * @see \Drupal\update\UpdateManagerInterface::getProjects()
- * @see \Drupal\update\ProjectSecurityData::getCoverageInfo()
- * @see update_process_project_info()
- */
- public static function createFromProjectDataAndSecurityCoverageInfo(array $project_data, array $security_coverage_info) {
- if ($project_data['project_type'] !== 'core' || $project_data['name'] !== 'drupal' || empty($security_coverage_info)) {
- return new static();
- }
- if (isset($project_data['existing_version'])) {
- list($major, $minor) = explode('.', $project_data['existing_version']);
- $existing_version = "$major.$minor";
- $next_version = "$major." . ((int) $minor + 1);
- return new static($project_data['title'], $security_coverage_info, $existing_version, $next_version);
- }
- return new static($project_data['title'], $security_coverage_info);
- }
- /**
- * Gets the security coverage requirement, if any.
- *
- * @return array
- * Requirements array as specified by hook_requirements(), or an empty array
- * if no requirements can be determined.
- */
- public function getRequirement() {
- if (isset($this->securityCoverageInfo['security_coverage_end_version'])) {
- $requirement = $this->getVersionEndRequirement();
- }
- elseif (isset($this->securityCoverageInfo['security_coverage_end_date'])) {
- $requirement = $this->getDateEndRequirement();
- }
- else {
- return [];
- }
- $requirement['title'] = $this->t('Drupal core security coverage');
- return $requirement;
- }
- /**
- * Gets the requirements based on security coverage until a specific version.
- *
- * @return array
- * Requirements array as specified by hook_requirements().
- */
- private function getVersionEndRequirement() {
- $requirement = [];
- if ($security_coverage_message = $this->getVersionEndCoverageMessage()) {
- $requirement['description'] = $security_coverage_message;
- if ($this->securityCoverageInfo['additional_minors_coverage'] > 0) {
- $requirement['value'] = $this->t(
- 'Covered until @end_version',
- ['@end_version' => $this->securityCoverageInfo['security_coverage_end_version']]
- );
- $requirement['severity'] = $this->securityCoverageInfo['additional_minors_coverage'] > 1 ? REQUIREMENT_INFO : REQUIREMENT_WARNING;
- }
- else {
- $requirement['value'] = $this->t('Coverage has ended');
- $requirement['severity'] = REQUIREMENT_ERROR;
- }
- }
- return $requirement;
- }
- /**
- * Gets the message for additional minor version security coverage.
- *
- * @return array[]
- * A render array containing security coverage message.
- *
- * @see \Drupal\update\ProjectSecurityData::getCoverageInfo()
- */
- private function getVersionEndCoverageMessage() {
- if ($this->securityCoverageInfo['additional_minors_coverage'] > 0) {
- // If the installed minor version will receive security coverage until
- // newer minor versions are released, inform the user.
- if ($this->securityCoverageInfo['additional_minors_coverage'] === 1) {
- // If the installed minor version will only receive security coverage
- // for 1 newer minor core version, encourage the site owner to update
- // soon.
- $message['coverage_message'] = [
- '#markup' => $this->t(
- '<a href=":update_status_report">Update to @next_minor or higher</a> soon to continue receiving security updates.',
- [
- ':update_status_report' => Url::fromRoute('update.status')->toString(),
- '@next_minor' => $this->nextMajorMinorVersion,
- ]
- ),
- '#suffix' => ' ',
- ];
- }
- }
- else {
- // Because the current minor version no longer has security coverage,
- // advise the site owner to update.
- $message['coverage_message'] = [
- '#markup' => $this->getVersionNoSecurityCoverageMessage(),
- '#suffix' => ' ',
- ];
- }
- $message['release_cycle_link'] = [
- '#markup' => $this->getReleaseCycleLink(),
- ];
- return $message;
- }
- /**
- * Gets the security coverage requirement based on an end date.
- *
- * @return array
- * Requirements array as specified by hook_requirements().
- */
- private function getDateEndRequirement() {
- $requirement = [];
- /** @var \Drupal\Component\Datetime\Time $time */
- $time = \Drupal::service('datetime.time');
- /** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
- $date_formatter = \Drupal::service('date.formatter');
- // 'security_coverage_end_date' will either be in format 'Y-m-d' or 'Y-m'.
- if (substr_count($this->securityCoverageInfo['security_coverage_end_date'], '-') === 2) {
- $date_format = 'Y-m-d';
- $full_security_coverage_end_date = $this->securityCoverageInfo['security_coverage_end_date'];
- }
- else {
- $date_format = 'Y-m';
- // If the date does not include a day, use '15'. When calling
- // \DateTime::createFromFormat() the current day will be used if one is
- // not provided. This may cause the month to be wrong at the beginning or
- // end of the month. '15' will never be displayed because we are using the
- // 'Y-m' format.
- $full_security_coverage_end_date = $this->securityCoverageInfo['security_coverage_end_date'] . '-15';
- }
- $comparable_request_date = $date_formatter->format($time->getRequestTime(), 'custom', $date_format);
- if ($this->securityCoverageInfo['security_coverage_end_date'] <= $comparable_request_date) {
- // Security coverage is over.
- $requirement['value'] = $this->t('Coverage has ended');
- $requirement['severity'] = REQUIREMENT_ERROR;
- $requirement['description']['coverage_message'] = [
- '#markup' => $this->getVersionNoSecurityCoverageMessage(),
- '#suffix' => ' ',
- ];
- }
- else {
- $security_coverage_end_timestamp = \DateTime::createFromFormat('Y-m-d', $full_security_coverage_end_date)->getTimestamp();
- $output_date_format = $date_format === 'Y-m-d' ? 'Y-M-d' : 'Y-M';
- $formatted_end_date = $date_formatter
- ->format($security_coverage_end_timestamp, 'custom', $output_date_format);
- $translation_arguments = ['@date' => $formatted_end_date];
- $requirement['value'] = $this->t('Covered until @date', $translation_arguments);
- $requirement['severity'] = REQUIREMENT_INFO;
- // 'security_coverage_ending_warn_date' will always be in the format
- // 'Y-m-d'.
- $request_date = $date_formatter->format($time->getRequestTime(), 'custom', 'Y-m-d');
- if (!empty($this->securityCoverageInfo['security_coverage_ending_warn_date']) && $this->securityCoverageInfo['security_coverage_ending_warn_date'] <= $request_date) {
- $requirement['description']['coverage_message'] = [
- '#markup' => $this->t('Update to a supported minor version soon to continue receiving security updates.'),
- '#suffix' => ' ',
- ];
- $requirement['severity'] = REQUIREMENT_WARNING;
- }
- }
- $requirement['description']['release_cycle_link'] = ['#markup' => $this->getReleaseCycleLink()];
- return $requirement;
- }
- /**
- * Gets the formatted message for a project with no security coverage.
- *
- * @return string
- * The message for a version with no security coverage.
- */
- private function getVersionNoSecurityCoverageMessage() {
- return $this->t(
- '<a href=":update_status_report">Update to a supported minor</a> as soon as possible to continue receiving security updates.',
- [':update_status_report' => Url::fromRoute('update.status')->toString()]
- );
- }
- /**
- * Gets a link the release cycle page on drupal.org.
- *
- * @return string
- * A link to the release cycle page on drupal.org.
- */
- private function getReleaseCycleLink() {
- return $this->t(
- 'Visit the <a href=":url">release cycle overview</a> for more information on supported releases.',
- [':url' => 'https://www.drupal.org/core/release-cycle-overview']
- );
- }
- }
|