Passed
Pull Request — 8.x-1.x (#11)
by Frédéric G.
01:11
created

UnusedExtensions::checkThemes()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
c 0
b 0
f 0
dl 0
loc 21
rs 9.9332
cc 4
nc 6
nop 0
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Drupal\qa\Plugin\QaCheck\System;
6
7
use Drupal\Core\Config\ConfigFactoryInterface;
0 ignored issues
show
Bug introduced by
The type Drupal\Core\Config\ConfigFactoryInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Drupal\Core\Extension\ModuleExtensionList;
0 ignored issues
show
Bug introduced by
The type Drupal\Core\Extension\ModuleExtensionList was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use Drupal\Core\Extension\ThemeExtensionList;
0 ignored issues
show
Bug introduced by
The type Drupal\Core\Extension\ThemeExtensionList was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use Drupal\qa\Pass;
11
use Drupal\qa\Plugin\QaCheckBase;
12
use Drupal\qa\Plugin\QaCheckInterface;
13
use Drupal\qa\Result;
14
use Symfony\Component\DependencyInjection\ContainerInterface;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Depend...tion\ContainerInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
16
/**
17
 * SystemUnusedExtensions checks for useless modules or themes.
18
 *
19
 * They are the ones which are present in a project of which all members are
20
 * uninstalled. Due to the project mechanism, many modules may be uninstalled
21
 * without a standard way to remove them because they are deployed as part of a
22
 * project.
23
 *
24
 * This check does not cover install profiles, nor theme engines, which are in
25
 * limited quantity, especially theme engines.
26
 *
27
 * @QaCheck(
28
 *   id = "system.unused_extensions",
29
 *   label=@Translation("Unused non-core extensions"),
30
 *   details=@Translation("Unused modules and themes present on disk can represent a useless cost on most dimensions. Packages entirely unused should usually be removed. This does not necessarily hold in a multi-site filesystem layout."),
31
 *   usesBatch=false,
32
 *   steps=2,
33
 * )
34
 */
35
class UnusedExtensions extends QaCheckBase implements QaCheckInterface {
36
  const NAME = 'system.unused_extensions';
37
38
  /**
39
   * Extension doesn't have a package clause.
40
   */
41
  const NO_PACKAGE = 'no_package';
42
43
  /**
44
   * Extension doesn't have a project clause (normal for themes).
45
   */
46
  const NO_PROJECT = 'no_project';
47
48
  /**
49
   * The config.factory service.
50
   *
51
   * @var \Drupal\Core\Config\ConfigFactoryInterface
52
   */
53
  protected $config;
54
55
  /**
56
   * The element_list.module service.
57
   *
58
   * @var \Drupal\Core\Extension\ModuleExtensionList
59
   */
60
  protected $elm;
61
62
  /**
63
   * The extension_list.theme service.
64
   *
65
   * @var \Drupal\Core\Extension\ThemeExtensionList
66
   */
67
  protected $elt;
68
69
  /**
70
   * SystemUnusedExtensions constructor.
71
   *
72
   * @param array $configuration
73
   *   The plugin configuration.
74
   * @param string $id
75
   *   The plugin ID.
76
   * @param array $definition
77
   *   The plugin definition.
78
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config
79
   *   The config.factory service.
80
   * @param \Drupal\Core\Extension\ModuleExtensionList $elm
81
   *   The extension_list.module service.
82
   * @param \Drupal\Core\Extension\ThemeExtensionList $elt
83
   *   The extension_list.theme service.
84
   */
85
  public function __construct(
86
    array $configuration,
87
    string $id,
88
    array $definition,
89
    ConfigFactoryInterface $config,
90
    ModuleExtensionList $elm,
91
    ThemeExtensionList $elt
92
  ) {
93
    parent::__construct($configuration, $id, $definition);
94
    $this->config = $config;
95
    $this->elm = $elm;
96
    $this->elt = $elt;
97
  }
98
99
  /**
100
   * {@inheritdoc}
101
   */
102
  public static function create(
103
    ContainerInterface $container,
104
    array $configuration,
105
    $id,
106
    $definition
107
  ) {
108
    $elm = $container->get('extension.list.module');
109
    $elt = $container->get('extension.list.theme');
110
    $config = $container->get('config.factory');
111
    return new static($configuration, $id, $definition, $config, $elm, $elt);
112
  }
113
114
  /**
115
   * Identify projects entirely consisting of uninstalled modules.
116
   */
117
  protected function checkModules(): Result {
118
    $projects = [];
119
    foreach ($this->elm->getAllAvailableInfo() as $module => $info) {
120
      $project = $info['project'] ?? self::NO_PROJECT;
121
      $info['machine_name'] = $module;
122
      $projects[$project] = $info;
123
    }
124
    foreach ($this->elm->getAllInstalledInfo() as $module => $info) {
125
      $project = $info['project'] ?? 'no_project';
126
      unset($projects[$project]);
127
    }
128
129
    return new Result('modules', count($projects) === 0, array_keys($projects));
130
  }
131
132
  /**
133
   * Clear the base themes chain for a given theme.
134
   *
135
   * @param string $name
136
   *   The theme for which to clear the base chain.
137
   * @param array $all
138
   *   The list of all themes.
139
   * @param array $remaining
140
   *   The current list of possible themes to clear.
141
   *
142
   * @return array
143
   *   The list of possible themes to clear after these have been cleared.
144
   */
145
  protected function clearBaseThemes(string $name, array $all, array $remaining): array {
146
    $base = $all[$name]['base theme'] ?? '';
147
    if (empty($base)) {
148
      return $remaining;
149
    }
150
    unset($remaining[$base]);
151
    return $this->clearBaseThemes($base, $all, $remaining);
152
  }
153
154
  /**
155
   * Identify uninstalled themes not used as base themes for an enabled theme.
156
   */
157
  protected function checkThemes(): Result {
158
    $allThemes = $this->elt->getAllAvailableInfo();
159
    $activeThemes = $this->elt->getAllInstalledInfo();
160
161
    // Start with just the inactive themes.
162
    $remaining = array_diff_key($allThemes, $activeThemes);
163
164
    // Filter out core themes, which cannot be removed.
165
    foreach ($remaining as $name => $_) {
166
      if (($allThemes[$name]['package'] ?? self::NO_PACKAGE) === 'Core') {
167
        unset($remaining[$name]);
168
      }
169
    }
170
171
    // Filter out base themes of active themes.
172
    foreach ($activeThemes as $name => $info) {
173
      $remaining = $this->clearBaseThemes($name, $allThemes, $remaining);
174
    }
175
176
    $res = new Result('themes', count($remaining) === 0, array_keys($remaining));
177
    return $res;
178
  }
179
180
  /**
181
   * {@inheritdoc}
182
   */
183
  public function run(): Pass {
184
    $pass = parent::run();
185
    $pass->record($this->checkModules());
186
    $pass->life->modify();
187
    $pass->record($this->checkThemes());
188
    $pass->life->end();
189
    return $pass;
190
  }
191
192
}
193