Passed
Push — master ( 685208...ae77ad )
by Evgenii
02:28 queued 01:01
created

Plugin::discover()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 17
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 28
rs 9.7
1
<?php
2
3
namespace Helick\MUPluginsDiscovery;
4
5
use Composer\Composer;
6
use Composer\EventDispatcher\EventSubscriberInterface;
7
use Composer\IO\IOInterface;
8
use Composer\Plugin\PluginInterface;
9
use Symfony\Component\Finder\Finder;
10
use Symfony\Component\Finder\SplFileInfo;
11
12
final class Plugin implements PluginInterface, EventSubscriberInterface
13
{
14
    /**
15
     * The composer instance.
16
     *
17
     * @var Composer
18
     */
19
    private $composer;
20
21
    /**
22
     * The IO instance.
23
     *
24
     * @var IOInterface
25
     */
26
    private $io;
27
28
    /**
29
     * @inheritDoc
30
     */
31
    public function activate(Composer $composer, IOInterface $io)
32
    {
33
        $this->composer = $composer;
34
        $this->io       = $io;
35
    }
36
37
    /**
38
     * @inheritDoc
39
     */
40
    public static function getSubscribedEvents()
41
    {
42
        return [
43
            'post-autoload-dump' => ['discover'],
44
        ];
45
    }
46
47
    /**
48
     * Rebuild the cached must-use plugins manifest.
49
     *
50
     * @return void
51
     */
52
    public function discover(): void
53
    {
54
        $finder = new Finder();
55
        $finder->in(getcwd() . '/web/content/mu-plugins')
56
               ->files()->name('*.php')
57
               ->sortByName();
58
59
        $pluginsFinder   = (clone $finder)->depth(1);
60
        $muPluginsFinder = (clone $finder)->depth(0);
61
62
        $plugins   = $this->resolvePlugins($pluginsFinder);
63
        $muPlugins = $this->resolvePlugins($muPluginsFinder);
64
65
        foreach (array_column($plugins, 'Name') as $pluginName) {
66
            $this->io->write('Discovered plugin: ' . $pluginName, true, IOInterface::DEBUG);
67
        }
68
69
        $this->write(
70
            getcwd() . '/bootstrap/cache/mu-plugins.php',
71
            array_merge($plugins, $muPlugins)
72
        );
73
74
        $this->write(
75
            getcwd() . '/bootstrap/cache/plugins.php',
76
            array_keys($plugins)
77
        );
78
79
        $this->io->write('The must-use plugins manifest generated successfully.');
80
    }
81
82
    /**
83
     * Resolve plugins from a given finder.
84
     *
85
     * @param Finder $finder
86
     *
87
     * @return array
88
     */
89
    private function resolvePlugins(Finder $finder): array
90
    {
91
        $results = iterator_to_array($finder, false);
92
93
        $files = array_map(function (SplFileInfo $file) {
94
            return $file->getRelativePathname();
95
        }, $results);
96
97
        $data = array_map(function (SplFileInfo $file) {
98
            return $this->extractPluginData($this->extractDocBlock($file->getContents()));
99
        }, $results);
100
101
        $plugins = array_combine($files, $data);
102
        $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

102
        $plugins = array_filter(/** @scrutinizer ignore-type */ $plugins, function (array $data) {
Loading history...
103
            return !empty($data['Name']);
104
        });
105
106
        return $plugins;
107
    }
108
109
    /**
110
     * Extract the doc block from a given source.
111
     *
112
     * @param string $source
113
     *
114
     * @return string
115
     */
116
    private function extractDocBlock(string $source): string
117
    {
118
        $comments = array_filter(token_get_all($source), function ($token) {
119
            return in_array($token[0], [T_COMMENT, T_DOC_COMMENT], true);
120
        });
121
122
        $comment = array_shift($comments);
123
124
        return $comment[1];
125
    }
126
127
    /**
128
     * Extract the plugin data from a given source.
129
     *
130
     * @param string $source
131
     *
132
     * @return array
133
     */
134
    private function extractPluginData(string $source): array
135
    {
136
        $headers = [
137
            'Name'        => 'Plugin Name',
138
            'PluginURI'   => 'Plugin URI',
139
            'Version'     => 'Version',
140
            'Description' => 'Description',
141
            'Author'      => 'Author',
142
            'AuthorURI'   => 'Author URI',
143
            'TextDomain'  => 'Text Domain',
144
            'DomainPath'  => 'Domain Path',
145
            'Network'     => 'Network',
146
        ];
147
148
        $patterns = array_map(function (string $regex) {
149
            return '/^[ \t\/*#@]*' . preg_quote($regex, '/') . ':(.*)$/mi';
150
        }, $headers);
151
152
        $matches = array_map(function (string $pattern) use ($source) {
153
            return preg_match($pattern, $source, $match) ? $match[1] : '';
154
        }, $patterns);
155
156
        $matches = array_map(function (string $match) {
157
            return trim(preg_replace('/\s*(?:\*\/|\?>).*/', '', $match));
158
        }, $matches);
159
160
        $data = array_combine(array_keys($headers), $matches);
161
162
        $data['Network']    = ('true' === strtolower($data['Network']));
163
        $data['Title']      = $data['Name'];
164
        $data['AuthorName'] = $data['Author'];
165
166
        return $data;
167
    }
168
169
    /**
170
     * Write the given manifest array to disk.
171
     *
172
     * @param string $path
173
     * @param array  $manifest
174
     *
175
     * @return void
176
     */
177
    private function write(string $path, array $manifest): void
178
    {
179
        if (!is_writable($directory = dirname($path))) {
180
            $this->io->writeError("The {$directory} directory must be present and writable.");
181
        }
182
183
        file_put_contents(
184
            $path,
185
            '<?php return ' . var_export($manifest, true) . ';'
186
        );
187
    }
188
}
189