From e375b0fe105b82b8166ed7e4decf09bc043f90b9 Mon Sep 17 00:00:00 2001
From: lucashedding <lucashedding@1463982.no-reply.drupal.org>
Date: Mon, 3 Jun 2019 15:46:27 -0500
Subject: [PATCH] Issue #3054006 by heddn, eiriksm, catch: Add method to ignore
 certain paths (custom modules, themes, etc) for the modified code checker

---
 config/install/automatic_updates.settings.yml |  1 +
 config/schema/automatic_updates.schema.yml    |  3 ++
 src/Form/SettingsForm.php                     | 21 ++++++++
 src/IgnoredPathsTrait.php                     | 52 +++++++++++++++++++
 src/ReadinessChecker/MissingProjectInfo.php   |  5 ++
 src/Services/ModifiedFiles.php                |  5 ++
 tests/src/Functional/AutomaticUpdatesTest.php | 15 ++++--
 .../MissingProjectInfoTest.php                |  8 +++
 8 files changed, 105 insertions(+), 5 deletions(-)
 create mode 100644 src/IgnoredPathsTrait.php

diff --git a/config/install/automatic_updates.settings.yml b/config/install/automatic_updates.settings.yml
index 799251acd2..e7c695ec29 100644
--- a/config/install/automatic_updates.settings.yml
+++ b/config/install/automatic_updates.settings.yml
@@ -7,3 +7,4 @@ notify: true
 check_frequency: 43200
 enable_readiness_checks: true
 download_uri: 'https://ftp.drupal.org/files/projects'
+ignored_paths: "modules/custom/*\nthemes/custom/*\nprofiles/custom/*"
diff --git a/config/schema/automatic_updates.schema.yml b/config/schema/automatic_updates.schema.yml
index 35a15326ad..7d378de5d6 100644
--- a/config/schema/automatic_updates.schema.yml
+++ b/config/schema/automatic_updates.schema.yml
@@ -20,3 +20,6 @@ automatic_updates.settings:
     download_uri:
       type: string
       label: 'Endpoint URI for file hashes and quasi patch files'
+    ignored_paths:
+      type: string
+      label: 'List of files paths to ignore when running readiness checks'
diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php
index b3806509d8..9f38e66a95 100644
--- a/src/Form/SettingsForm.php
+++ b/src/Form/SettingsForm.php
@@ -25,6 +25,13 @@ class SettingsForm extends ConfigFormBase {
    */
   protected $dateFormatter;
 
+  /**
+   * Drupal root path.
+   *
+   * @var string
+   */
+  protected $drupalRoot;
+
   /**
    * {@inheritdoc}
    */
@@ -32,6 +39,9 @@ class SettingsForm extends ConfigFormBase {
     $instance = parent::create($container);
     $instance->checker = $container->get('automatic_updates.readiness_checker');
     $instance->dateFormatter = $container->get('date.formatter');
+    $drupal_finder = $container->get('automatic_updates.drupal_finder');
+    $drupal_finder->locateRoot(getcwd());
+    $instance->drupalRoot = $drupal_finder->getDrupalRoot();
     return $instance;
   }
 
@@ -82,6 +92,17 @@ class SettingsForm extends ConfigFormBase {
         '@link' => Url::fromRoute('automatic_updates.update_readiness')->toString(),
       ]);
     }
+    $form['ignored_paths'] = [
+      '#type' => 'textarea',
+      '#title' => $this->t('Paths to ignore for readiness checks'),
+      '#description' => $this->t('Paths relative to %drupal_root. One path per line.', ['%drupal_root' => $this->drupalRoot]),
+      '#default_value' => $config->get('ignored_paths'),
+      '#states' => [
+        'visible' => [
+          ':input[name="enable_readiness_checks"]' => ['checked' => TRUE],
+        ],
+      ],
+    ];
     return parent::buildForm($form, $form_state);
   }
 
diff --git a/src/IgnoredPathsTrait.php b/src/IgnoredPathsTrait.php
new file mode 100644
index 0000000000..31ae1a288f
--- /dev/null
+++ b/src/IgnoredPathsTrait.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Drupal\automatic_updates;
+
+/**
+ * Provide a helper to check if file paths are ignored.
+ */
+trait IgnoredPathsTrait {
+
+  /**
+   * 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 function isIgnoredPath($file_path) {
+    $paths = $this->getConfigFactory()->get('automatic_updates.settings')->get('ignored_paths');
+    if ($this->getPathMatcher()->matchPath($file_path, $paths)) {
+      return TRUE;
+    }
+  }
+
+  /**
+   * Gets the config factory.
+   *
+   * @return \Drupal\Core\Config\ConfigFactoryInterface
+   *   The config factory.
+   */
+  protected function getConfigFactory() {
+    if (isset($this->configFactory)) {
+      return $this->configFactory;
+    }
+    return \Drupal::configFactory();
+  }
+
+  /**
+   * Get the path matcher service.
+   *
+   * @return \Drupal\Core\Path\PathMatcherInterface
+   *   The path matcher.
+   */
+  protected function getPathMatcher() {
+    if (isset($this->pathMatcher)) {
+      return $this->pathMatcher;
+    }
+    return \Drupal::service('path.matcher');
+  }
+
+}
diff --git a/src/ReadinessChecker/MissingProjectInfo.php b/src/ReadinessChecker/MissingProjectInfo.php
index 9b202732ba..e36d7df90c 100644
--- a/src/ReadinessChecker/MissingProjectInfo.php
+++ b/src/ReadinessChecker/MissingProjectInfo.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\automatic_updates\ReadinessChecker;
 
+use Drupal\automatic_updates\IgnoredPathsTrait;
 use Drupal\Core\Extension\ExtensionList;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use DrupalFinder\DrupalFinder;
@@ -10,6 +11,7 @@ use DrupalFinder\DrupalFinder;
  * Missing project info checker.
  */
 class MissingProjectInfo extends Filesystem {
+  use IgnoredPathsTrait;
   use StringTranslationTrait;
 
   /**
@@ -69,6 +71,9 @@ class MissingProjectInfo extends Filesystem {
     $messages = [];
     foreach ($this->getExtensionsTypes() as $extension_type) {
       foreach ($this->getInfos($extension_type) as $extension_name => $info) {
+        if ($this->isIgnoredPath(drupal_get_path($info['type'], $extension_name))) {
+          continue;
+        }
         if (empty($info['version'])) {
           $messages[] = $this->t('The project "@extension" will not be updated because it is missing the "version" key in the @extension.info.yml file.', ['@extension' => $extension_name]);
         }
diff --git a/src/Services/ModifiedFiles.php b/src/Services/ModifiedFiles.php
index c0e250a578..7d2d13ba7f 100644
--- a/src/Services/ModifiedFiles.php
+++ b/src/Services/ModifiedFiles.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\automatic_updates\Services;
 
+use Drupal\automatic_updates\IgnoredPathsTrait;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Url;
 use DrupalFinder\DrupalFinder;
@@ -14,6 +15,7 @@ use Psr\Log\LoggerInterface;
  * Modified files service.
  */
 class ModifiedFiles implements ModifiedFilesInterface {
+  use IgnoredPathsTrait;
 
   /**
    * The logger.
@@ -100,6 +102,9 @@ class ModifiedFiles implements ModifiedFilesInterface {
         $this->logger->error('@hash or @file is empty; the hash file is malformed for this line.', ['@hash' => $hash, '@file' => $file]);
         continue;
       }
+      if ($this->isIgnoredPath($file)) {
+        continue;
+      }
       $file_path = $this->drupalFinder->getDrupalRoot() . DIRECTORY_SEPARATOR . $file;
       if (!file_exists($file_path) || hash_file('sha512', $file_path) !== $hash) {
         $modified_files[] = $file_path;
diff --git a/tests/src/Functional/AutomaticUpdatesTest.php b/tests/src/Functional/AutomaticUpdatesTest.php
index 7f5c337b63..cbd4cb924b 100644
--- a/tests/src/Functional/AutomaticUpdatesTest.php
+++ b/tests/src/Functional/AutomaticUpdatesTest.php
@@ -62,7 +62,7 @@ class AutomaticUpdatesTest extends BrowserTestBase {
     $this->assertSession()->pageTextContains('3 urgent announcements require your attention:');
 
     // Test cache.
-    $end_point = 'http://localhost/automatic_updates/test-json-denied';
+    $end_point = $this->buildUrl(Url::fromRoute('test_automatic_updates.json_test_denied_controller'));
     $this->config('automatic_updates.settings')
       ->set('psa_endpoint', $end_point)
       ->save();
@@ -72,7 +72,7 @@ class AutomaticUpdatesTest extends BrowserTestBase {
     // Test transmit errors with JSON endpoint.
     drupal_flush_all_caches();
     $this->drupalGet(Url::fromRoute('system.admin'));
-    $this->assertSession()->pageTextContains('Drupal PSA endpoint http://localhost/automatic_updates/test-json-denied is unreachable.');
+    $this->assertSession()->pageTextContains("Drupal PSA endpoint $end_point is unreachable.");
 
     // Test disabling PSAs.
     $end_point = $this->buildUrl(Url::fromRoute('test_automatic_updates.json_test_controller'));
@@ -91,12 +91,17 @@ class AutomaticUpdatesTest extends BrowserTestBase {
    * Tests manually running readiness checks.
    */
   public function testReadinessChecks() {
-    // Test manually running readiness checks.
-    $url = $this->buildUrl('<front>') . '/automatic_updates';
-    $this->config('automatic_updates.settings')->set('download_uri', $url);
+    // Test manually running readiness checks. A few warnings will occur.
     $this->drupalGet(Url::fromRoute('automatic_updates.settings'));
     $this->clickLink('run the readiness checks');
     $this->assertSession()->pageTextContains('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.');
+
+    // Ignore specific file paths to see no readiness issues.
+    $this->config('automatic_updates.settings')->set('ignored_paths', "core/*\nmodules/*\nthemes/*\nprofiles/*")
+      ->save();
+    $this->drupalGet(Url::fromRoute('automatic_updates.settings'));
+    $this->clickLink('run the readiness checks');
+    $this->assertSession()->pageTextContains('No issues found. Your site is completely ready for automatic updates.');
   }
 
 }
diff --git a/tests/src/Kernel/ReadinessChecker/MissingProjectInfoTest.php b/tests/src/Kernel/ReadinessChecker/MissingProjectInfoTest.php
index e5ac3b3f1c..60e4cf6f4b 100644
--- a/tests/src/Kernel/ReadinessChecker/MissingProjectInfoTest.php
+++ b/tests/src/Kernel/ReadinessChecker/MissingProjectInfoTest.php
@@ -21,6 +21,14 @@ class MissingProjectInfoTest extends KernelTestBase {
     'automatic_updates',
   ];
 
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    $this->installConfig(['automatic_updates']);
+  }
+
   /**
    * Tests pending db updates readiness checks.
    */
-- 
GitLab