Passed
Push — master ( 6f4507...0144f9 )
by Evgenii
01:22
created

Command::resolvePlugins()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 1
dl 0
loc 18
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
namespace Helick\MUPluginsDiscovery;
4
5
use Symfony\Component\Finder\Finder;
6
use Symfony\Component\Finder\SplFileInfo;
7
use WP_CLI;
8
9
final class Command
10
{
11
    /**
12
     * Register the command.
13
     *
14
     * @return void
15
     */
16
    public static function register(): void
17
    {
18
        WP_CLI::add_command('mu-plugins discover', static::class);
19
    }
20
21
    /**
22
     * Rebuild the cached must-use plugins manifest.
23
     *
24
     * @when before_wp_load
25
     *
26
     * @return void
27
     */
28
    public function __invoke(): void
29
    {
30
        $finder = new Finder();
31
        $finder->in(getcwd() . '/web/content/mu-plugins')
32
               ->files()->name('*.php')
33
               ->sortByName();
34
35
        $pluginsFinder   = (clone $finder)->depth(1);
36
        $muPluginsFinder = (clone $finder)->depth(0);
37
38
        $plugins   = $this->resolvePlugins($pluginsFinder);
39
        $muPlugins = $this->resolvePlugins($muPluginsFinder);
40
41
        foreach (array_column($plugins, 'Name') as $pluginName) {
42
            WP_CLI::line('Discovered plugin: ' . $pluginName);
43
        }
44
45
        $this->write(
46
            getcwd() . '/bootstrap/cache/mu-plugins.php',
47
            array_merge($plugins, $muPlugins)
48
        );
49
50
        $this->write(
51
            getcwd() . '/bootstrap/cache/plugins.php',
52
            array_keys($plugins)
53
        );
54
55
        WP_CLI::success('The must-use plugins manifest generated successfully.');
56
    }
57
58
    /**
59
     * Resolve plugins from a given finder.
60
     *
61
     * @param Finder $finder
62
     *
63
     * @return array
64
     */
65
    private function resolvePlugins(Finder $finder): array
66
    {
67
        $results = iterator_to_array($finder, false);
68
69
        $files = array_map(function (SplFileInfo $file) {
70
            return $file->getRelativePathname();
71
        }, $results);
72
73
        $data = array_map(function (SplFileInfo $file) {
74
            return $this->extractPluginData($this->extractDocBlock($file->getContents()));
75
        }, $results);
76
77
        $plugins = array_combine($files, $data);
78
        $plugins = array_filter($plugins, function (array $data) {
0 ignored issues
show
Bug introduced by
It seems like $plugins can also be of type false; however, parameter $input of array_filter() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

78
        $plugins = array_filter(/** @scrutinizer ignore-type */ $plugins, function (array $data) {
Loading history...
79
            return !empty($data['Name']);
80
        });
81
82
        return $plugins;
83
    }
84
85
    /**
86
     * Extract the doc block from a given source.
87
     *
88
     * @param string $source
89
     *
90
     * @return string
91
     */
92
    private function extractDocBlock(string $source): string
93
    {
94
        $comments = array_filter(token_get_all($source), function ($token) {
95
            return in_array($token[0], [T_COMMENT, T_DOC_COMMENT], true);
96
        });
97
98
        $comment = array_shift($comments);
99
100
        return $comment[1];
101
    }
102
103
    /**
104
     * Extract the plugin data from a given source.
105
     *
106
     * @param string $source
107
     *
108
     * @return array
109
     */
110
    private function extractPluginData(string $source): array
111
    {
112
        $headers = [
113
            'Name'        => 'Plugin Name',
114
            'PluginURI'   => 'Plugin URI',
115
            'Version'     => 'Version',
116
            'Description' => 'Description',
117
            'Author'      => 'Author',
118
            'AuthorURI'   => 'Author URI',
119
            'TextDomain'  => 'Text Domain',
120
            'DomainPath'  => 'Domain Path',
121
            'Network'     => 'Network',
122
        ];
123
124
        $patterns = array_map(function (string $regex) {
125
            return '/^[ \t\/*#@]*' . preg_quote($regex, '/') . ':(.*)$/mi';
126
        }, $headers);
127
128
        $matches = array_map(function (string $pattern) use ($source) {
129
            return preg_match($pattern, $source, $match) ? $match[1] : '';
130
        }, $patterns);
131
132
        $matches = array_map(function (string $match) {
133
            return trim(preg_replace('/\s*(?:\*\/|\?>).*/', '', $match));
134
        }, $matches);
135
136
        $data = array_combine(array_keys($headers), $matches);
137
138
        $data['Network']    = ('true' === strtolower($data['Network']));
139
        $data['Title']      = $data['Name'];
140
        $data['AuthorName'] = $data['Author'];
141
142
        return $data;
143
    }
144
145
    /**
146
     * Write the given manifest array to disk.
147
     *
148
     * @param string $path
149
     * @param array  $manifest
150
     *
151
     * @return void
152
     */
153
    private function write(string $path, array $manifest): void
154
    {
155
        if (!is_writable($directory = dirname($path))) {
156
            WP_CLI::error("The {$directory} directory must be present and writable.");
157
        }
158
159
        file_put_contents(
160
            $path,
161
            '<?php return ' . var_export($manifest, true) . ';'
162
        );
163
    }
164
}
165