Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
CustomAutoloaderPlugin
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 7
380
0.00% covered (danger)
0.00%
0 / 1
 activate
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 deactivate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 uninstall
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSubscribedEvents
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 postAutoloadDump
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
 determineSuffix
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
 isRequiredByRoot
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2/**
3 * Custom Autoloader Composer Plugin, hooks into composer events to generate the custom autoloader.
4 *
5 * @package automattic/jetpack-autoloader
6 */
7
8namespace Automattic\Jetpack\Autoloader;
9
10use Composer\Composer;
11use Composer\EventDispatcher\EventSubscriberInterface;
12use Composer\IO\IOInterface;
13use Composer\Plugin\PluginInterface;
14use Composer\Script\Event;
15use Composer\Script\ScriptEvents;
16
17/**
18 * Class CustomAutoloaderPlugin.
19 *
20 * @package automattic/jetpack-autoloader
21 */
22class CustomAutoloaderPlugin implements PluginInterface, EventSubscriberInterface {
23
24    /**
25     * IO object.
26     *
27     * @var IOInterface IO object.
28     */
29    private $io;
30
31    /**
32     * Composer object.
33     *
34     * @var Composer Composer object.
35     */
36    private $composer;
37
38    /**
39     * Do nothing.
40     *
41     * @param Composer    $composer Composer object.
42     * @param IOInterface $io IO object.
43     */
44    public function activate( Composer $composer, IOInterface $io ) {
45        $this->composer = $composer;
46        $this->io       = $io;
47    }
48
49    /**
50     * Do nothing.
51     * phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
52     *
53     * @param Composer    $composer Composer object.
54     * @param IOInterface $io IO object.
55     */
56    public function deactivate( Composer $composer, IOInterface $io ) {
57        /*
58         * Intentionally left empty. This is a PluginInterface method.
59         * phpcs:enable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
60         */
61    }
62
63    /**
64     * Do nothing.
65     * phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
66     *
67     * @param Composer    $composer Composer object.
68     * @param IOInterface $io IO object.
69     */
70    public function uninstall( Composer $composer, IOInterface $io ) {
71        /*
72         * Intentionally left empty. This is a PluginInterface method.
73         * phpcs:enable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
74         */
75    }
76
77    /**
78     * Tell composer to listen for events and do something with them.
79     *
80     * @return array List of subscribed events.
81     */
82    public static function getSubscribedEvents() {
83        return array(
84            ScriptEvents::POST_AUTOLOAD_DUMP => 'postAutoloadDump',
85        );
86    }
87
88    /**
89     * Generate the custom autolaoder.
90     *
91     * @param Event $event Script event object.
92     */
93    public function postAutoloadDump( Event $event ) {
94        // When the autoloader is not required by the root package we don't want to execute it.
95        // This prevents unwanted transitive execution that generates unused autoloaders or
96        // at worst throws fatal executions.
97        if ( ! $this->isRequiredByRoot() ) {
98            return;
99        }
100
101        $config = $this->composer->getConfig();
102
103        if ( 'vendor' !== $config->raw()['config']['vendor-dir'] ) {
104            $this->io->writeError( "\n<error>An error occurred while generating the autoloader files:", true );
105            $this->io->writeError( 'The project\'s composer.json or composer environment set a non-default vendor directory.', true );
106            $this->io->writeError( 'The default composer vendor directory must be used.</error>', true );
107            exit( 0 );
108        }
109
110        $installationManager = $this->composer->getInstallationManager();
111        $repoManager         = $this->composer->getRepositoryManager();
112        $localRepo           = $repoManager->getLocalRepository();
113        $package             = $this->composer->getPackage();
114        $optimize            = $event->getFlags()['optimize'];
115        $suffix              = $this->determineSuffix();
116
117        $generator = new AutoloadGenerator( $this->io );
118        $generator->dump( $this->composer, $config, $localRepo, $package, $installationManager, 'composer', $optimize, $suffix );
119    }
120
121    /**
122     * Determine the suffix for the autoloader class.
123     *
124     * Reuses an existing suffix from vendor/autoload_packages.php or vendor/autoload.php if possible.
125     *
126     * @return string Suffix.
127     */
128    private function determineSuffix() {
129        $config     = $this->composer->getConfig();
130        $vendorPath = $config->get( 'vendor-dir' );
131
132        // Command line.
133        $suffix = $config->get( 'autoloader-suffix' );
134        if ( $suffix ) {
135            return $suffix;
136        }
137
138        // Reuse our own suffix, if any.
139        if ( is_readable( $vendorPath . '/autoload_packages.php' ) ) {
140            $content = file_get_contents( $vendorPath . '/autoload_packages.php' );
141            if ( preg_match( '/^namespace Automattic\\\\Jetpack\\\\Autoloader\\\\jp([^;\s]+?)(?:\\\\al[^;\s]+)?;/m', $content, $match ) ) {
142                return $match[1];
143            }
144        }
145
146        // Reuse Composer's suffix, if any.
147        if ( is_readable( $vendorPath . '/autoload.php' ) ) {
148            $content = file_get_contents( $vendorPath . '/autoload.php' );
149            if ( preg_match( '{ComposerAutoloaderInit([^:\s]+)::}', $content, $match ) ) {
150                return $match[1];
151            }
152        }
153
154        // Generate a random suffix.
155        return md5( uniqid( '', true ) );
156    }
157
158    /**
159     * Checks to see whether or not the root package is the one that required the autoloader.
160     *
161     * @return bool
162     */
163    private function isRequiredByRoot() {
164        $package  = $this->composer->getPackage();
165        $requires = $package->getRequires();
166        if ( ! is_array( $requires ) ) { // @phan-suppress-current-line PhanRedundantCondition -- Earlier Composer versions may not have guaranteed this.
167            $requires = array();
168        }
169        $devRequires = $package->getDevRequires();
170        if ( ! is_array( $devRequires ) ) { // @phan-suppress-current-line PhanRedundantCondition -- Earlier Composer versions may not have guaranteed this.
171            $devRequires = array();
172        }
173        $requires = array_merge( $requires, $devRequires );
174
175        if ( empty( $requires ) ) {
176            $this->io->writeError( "\n<error>The package is not required and this should never happen?</error>", true );
177            exit( 0 );
178        }
179
180        foreach ( $requires as $require ) {
181            if ( 'automattic/jetpack-autoloader' === $require->getTarget() ) {
182                return true;
183            }
184        }
185
186        return false;
187    }
188}