From 9713a8251ce625ec9601dfc90fb3a48dee095a41 Mon Sep 17 00:00:00 2001
From: tatarbj <tatarbj@649590.no-reply.drupal.org>
Date: Mon, 1 Jul 2019 13:18:25 -0600
Subject: [PATCH] Issue #3063131 by heddn, tatarbj: Backport MissingProjectInfo

---
 ReadinessCheckers/MissingProjectInfo.php      | 99 +++++++++++++++++++
 ReadinessCheckers/ReadinessCheckerManager.php |  1 +
 automatic_updates.admin.inc                   |  2 +-
 automatic_updates.info                        |  1 +
 automatic_updates.install                     |  7 +-
 tests/automatic_updates.test                  | 10 +-
 6 files changed, 112 insertions(+), 8 deletions(-)
 create mode 100644 ReadinessCheckers/MissingProjectInfo.php

diff --git a/ReadinessCheckers/MissingProjectInfo.php b/ReadinessCheckers/MissingProjectInfo.php
new file mode 100644
index 0000000000..aa64c802de
--- /dev/null
+++ b/ReadinessCheckers/MissingProjectInfo.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * Missing project info checker.
+ */
+class MissingProjectInfo implements ReadinessCheckerInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function run() {
+    return static::missingProjectInfoCheck();
+  }
+
+  /**
+   * Check for projects missing project info.
+   *
+   * @return array
+   *   An array of translatable strings if any checks fail.
+   */
+  protected static function missingProjectInfoCheck() {
+    $messages = [];
+    foreach (static::getInfos() as $extension_name => $info) {
+      if (static::isIgnoredPath($info['extension_uri'])) {
+        continue;
+      }
+      if (!static::getExtensionVersion($extension_name, $info)) {
+        $messages[] = t('The project "@extension" can not be updated because its version is either undefined or a dev release.', ['@extension' => $extension_name]);
+      }
+    }
+    return $messages;
+  }
+
+  /**
+   * Get the extension types.
+   *
+   * @return array
+   *   The extension types.
+   */
+  protected static function getExtensionsTypes() {
+    return ['modules', 'profiles', 'themes'];
+  }
+
+  /**
+   * Returns an array of info files information of available extensions.
+   *
+   * @return array
+   *   An associative array of extension information arrays, keyed by extension
+   *   name.
+   */
+  protected static function getInfos() {
+    $infos = [];
+    // Find extensions.
+    $extensions = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info$/', 'modules', $key = 'name', $min_depth = 1);
+    $extensions = array_merge($extensions, drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info$/', 'themes', $key = 'name', $min_depth = 1));
+    foreach ($extensions as $extension) {
+      if (file_exists($info_file = dirname($extension->uri) . '/' . $extension->name . '.info')) {
+        // Get the .info file for the module or theme this file belongs to.
+        $infos[$extension->name] = drupal_parse_info_file($info_file);
+        $infos[$extension->name]['extension_uri'] = $extension->uri;
+      }
+    }
+
+    return $infos;
+  }
+
+  /**
+   * Get the extension version.
+   *
+   * @param string $extension_name
+   *   The extension name.
+   * @param array $info
+   *   The extension's info.
+   *
+   * @return string|null
+   *   The version or NULL if undefined.
+   */
+  protected static function getExtensionVersion($extension_name, array $info) {
+    if (isset($info['version']) && strpos($info['version'], '-dev') === FALSE) {
+      return $info['version'];
+    }
+  }
+
+  /**
+   * Check if the file path is ignored.
+   *
+   * @param string $file_path
+   *   The file path.
+   *
+   * @return bool
+   *   TRUE if file path is ignored, else FALSE.
+   */
+  protected static function isIgnoredPath($file_path) {
+    $paths = variable_get('automatic_updates_ignored_paths', "sites/all/modules/custom/*\nsites/all/themes/custom/*");
+    if (drupal_match_path($file_path, $paths)) {
+      return TRUE;
+    }
+  }
+}
diff --git a/ReadinessCheckers/ReadinessCheckerManager.php b/ReadinessCheckers/ReadinessCheckerManager.php
index 57c0b93ee5..77213726e5 100644
--- a/ReadinessCheckers/ReadinessCheckerManager.php
+++ b/ReadinessCheckers/ReadinessCheckerManager.php
@@ -23,6 +23,7 @@ class ReadinessCheckerManager {
    */
   protected static function getCheckers() {
     static::$checkers['warning'][0][] = 'FileOwnership';
+    static::$checkers['warning'][0][] = 'MissingProjectInfo';
     static::$checkers['warning'][0][] = 'ModifiedFiles';
     static::$checkers['warning'][0][] = 'PhpSapi';
     static::$checkers['error'][0][] = 'PhpSapi';
diff --git a/automatic_updates.admin.inc b/automatic_updates.admin.inc
index 20aa1b4ab0..acbffcffde 100644
--- a/automatic_updates.admin.inc
+++ b/automatic_updates.admin.inc
@@ -33,7 +33,7 @@ function automatic_updates_admin_form() {
     '#type' => 'textarea',
     '#title' => t('Paths to ignore for readiness checks'),
     '#description' => t('Paths relative to %drupal_root. One path per line.', ['%drupal_root' => DRUPAL_ROOT]),
-    '#default_value' => variable_get('automatic_updates_ignored_paths', "modules/custom/*\nthemes/custom/*\nprofiles/custom/*"),
+    '#default_value' => variable_get('automatic_updates_ignored_paths', "sites/all/modules/custom/*\nsites/all/themes/custom/*"),
     '#states' => [
       'visible' => [
         ':input[name="automatic_updates_enable_readiness_checks"]' => ['checked' => TRUE],
diff --git a/automatic_updates.info b/automatic_updates.info
index db8c32ee27..9969fe4de1 100644
--- a/automatic_updates.info
+++ b/automatic_updates.info
@@ -10,6 +10,7 @@ files[] = ModifiedFilesService.php
 files[] = ReadinessCheckers/ReadinessCheckerManager.php
 files[] = ReadinessCheckers/ReadinessCheckerInterface.php
 files[] = ReadinessCheckers/FileOwnership.php
+files[] = ReadinessCheckers/MissingProjectInfo.php
 files[] = ReadinessCheckers/ModifiedFiles.php
 files[] = ReadinessCheckers/PhpSapi.php
 files[] = ReadinessCheckers/ReadOnlyFilesystem.php
diff --git a/automatic_updates.install b/automatic_updates.install
index ee53684185..f5c847e88a 100644
--- a/automatic_updates.install
+++ b/automatic_updates.install
@@ -55,10 +55,9 @@ function _automatic_updates_checker_requirements(array &$requirements) {
   if (!empty($checker_results)) {
     $requirements['automatic_updates_readiness']['severity'] = $error_results ? REQUIREMENT_ERROR : REQUIREMENT_WARNING;
     $requirements['automatic_updates_readiness']['value'] = format_plural(count($checker_results), '@count check failed:', '@count checks failed:');
-    $requirements['automatic_updates_readiness']['description'] = [
-      '#theme' => 'item_list',
-      '#items' => $checker_results,
-    ];
+    $requirements['automatic_updates_readiness']['description'] = theme('item_list', array(
+      'items' => $checker_results,
+    ));
   }
   if (REQUEST_TIME > $last_check_timestamp + 3600 * 24) {
     $requirements['automatic_updates_readiness']['severity'] = REQUIREMENT_ERROR;
diff --git a/tests/automatic_updates.test b/tests/automatic_updates.test
index 082d31f34c..c50fe822ea 100644
--- a/tests/automatic_updates.test
+++ b/tests/automatic_updates.test
@@ -78,11 +78,15 @@ class AutomaticUpdatesTestCase extends DrupalWebTestCase {
   public function testReadinessChecks() {
     // Fabricate a readiness issue.
     $this->drupalGet($this->getAbsoluteUrl('admin/config/system/automatic_updates'));
-    variable_set('automatic_updates.php_sapi', 'foo');
     $this->clickLink('run the readiness checks');
     $this->assertText('Your site does not pass some readiness checks for automatic updates. Depending on the nature of the failures, it might effect the eligibility for automatic updates.');
-    $this->assertText('PHP changed from running as');
-    $this->assertText('This can lead to inconsistent and misleading results.');
+    $this->assertText('The project "drupal_autoload_test" can not be updated because its version is either undefined or a dev release');
+
+    // Ignore certain folders for readiness checks.
+    variable_set('automatic_updates_ignored_paths', "sites/all/modules/custom/*\nsites/all/themes/custom/*");
+    $this->drupalGet($this->getAbsoluteUrl('admin/config/system/automatic_updates'));
+    $this->clickLink('run the readiness checks');
+    $this->assertText('No issues found. Your site is completely ready for automatic updates.');
   }
 
 }
-- 
GitLab