Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 66
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Plugin
0.00% covered (danger)
0.00%
0 / 66
0.00% covered (danger)
0.00%
0 / 5
240
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 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 generateManifest
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 1
132
1<?php
2/**
3 * Main class file for the Composer plugin that will implement a custom installer for Jetpack
4 * packages.
5 *
6 * @see https://getcomposer.org/doc/articles/custom-installers.md
7 * @package automattic/jetpack-composer-plugin
8 * */
9
10namespace Automattic\Jetpack\Composer;
11
12use Composer\Composer;
13use Composer\EventDispatcher\EventSubscriberInterface;
14use Composer\IO\IOInterface;
15use Composer\Plugin\PluginInterface;
16use Composer\Script\Event;
17use Composer\Script\ScriptEvents;
18use Composer\Util\Filesystem;
19
20/**
21 * This class is the entry point for the installer plugin. The Composer
22 * installation mechanism registers the plugin by calling its activate method.
23 */
24class Plugin implements PluginInterface, EventSubscriberInterface {
25
26    /**
27     * Installer instance.
28     *
29     * @var Manager|null
30     */
31    private $installer;
32
33    /**
34     * Activates the installer plugin at installation time.
35     *
36     * @param Composer    $composer the Composer global instance.
37     * @param IOInterface $io the IO interface global instance.
38     */
39    public function activate( Composer $composer, IOInterface $io ) {
40        $this->installer = new Manager( $io, $composer );
41        $composer->getInstallationManager()->addInstaller( $this->installer );
42    }
43
44    /**
45     * Deactivates the installer plugin.
46     *
47     * @param Composer    $composer the Composer global instance.
48     * @param IOInterface $io the IO interface global instance.
49     */
50    public function deactivate( Composer $composer, IOInterface $io ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
51        $composer->getInstallationManager()->removeInstaller( $this->installer );
52    }
53
54    /**
55     * Uninstalls the installer plugin.
56     *
57     * @param Composer    $composer the Composer global instance.
58     * @param IOInterface $io the IO interface global instance.
59     */
60    public function uninstall( Composer $composer, IOInterface $io ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
61    }
62
63    /**
64     * Tell composer to listen for events and do something with them.
65     *
66     * @return array List of subscribed events.
67     */
68    public static function getSubscribedEvents() {
69        return array(
70            ScriptEvents::POST_INSTALL_CMD => 'generateManifest',
71            ScriptEvents::POST_UPDATE_CMD  => 'generateManifest',
72        );
73    }
74
75    /**
76     * Generate the assets manifest.
77     *
78     * @param Event $event Script event object.
79     */
80    public function generateManifest( Event $event ) {
81        $composer   = $event->getComposer();
82        $filesystem = new Filesystem();
83        $io         = $event->getIO();
84        $io->info( 'Generating jetpack-library i18n map' );
85
86        $extra = $composer->getPackage()->getExtra();
87        if ( isset( $extra['wp-plugin-slug'] ) ) {
88            $todomain = $extra['wp-plugin-slug'];
89            $totype   = 'plugins';
90        } elseif ( isset( $extra['wp-theme-slug'] ) ) {
91            $todomain = $extra['wp-theme-slug'];
92            $totype   = 'themes';
93        } elseif ( isset( $extra['beta-plugin-slug'] ) ) {
94            $todomain = $extra['beta-plugin-slug'];
95            $totype   = 'plugins';
96        } elseif ( isset( $extra['beta-theme-slug'] ) ) {
97            $todomain = $extra['beta-theme-slug'];
98            $totype   = 'themes';
99        } else {
100            $io->warning( 'Skipping jetpack-library i18n map generation, .extra.wp-plugin-slug / .extra.wp-theme-slug is not set in composer.json' );
101            $filesystem->remove( 'jetpack_vendor/i18n-map.php' );
102            return;
103        }
104
105        $data = array(
106            'domain'   => $todomain,
107            'type'     => $totype,
108            'packages' => array(),
109            'paths'    => array(),
110        );
111        foreach ( $composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages() as $package ) {
112            if ( $package->getType() !== 'jetpack-library' ) {
113                continue;
114            }
115
116            $ver = $package->getVersion();
117            if ( isset( $extra['branch-alias'][ $ver ] ) ) {
118                $ver = $extra['branch-alias'][ $ver ];
119            }
120
121            // Composer's `getVersion()` seems to like to return a 4-component version, while semver wants only 3 components. Strip any extra components.
122            $ver = preg_replace( '/^(\d+\.\d+\.\d+)(?:\.\d+)+/', '$1', $ver );
123
124            if ( ! preg_match( '/^\d+\.\d+\.\d+(?:-[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)?(?:\+[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)?$/', $ver ) ) {
125                // Invalid version, skip it.
126                $ver = '0.0.0';
127            }
128
129            $extra = $package->getExtra();
130            if ( empty( $extra['textdomain'] ) ) {
131                $io->info( "  {$package->getName()} ($ver): no textdomain set" );
132            } elseif ( $extra['textdomain'] === $todomain ) {
133                // Nothing to alias: the package's strings are already in the
134                // plugin's domain, and emitting a self-alias would make
135                // Assets::filter_gettext() recurse infinitely on any
136                // untranslated string in it. The path is still needed — it is
137                // the prefix WordPress hashes to name the package's JavaScript
138                // translation files — so it goes in `paths`, which registers
139                // only that.
140                $data['paths'][ $extra['textdomain'] ] = array(
141                    'path' => 'jetpack_vendor/' . $package->getPrettyName(),
142                    'ver'  => $ver,
143                );
144                $io->info( "  {$package->getName()} ($ver): textdomain {$extra['textdomain']} matches the plugin domain, recording its path without an alias" );
145            } else {
146                $data['packages'][ $extra['textdomain'] ] = array(
147                    'path' => 'jetpack_vendor/' . $package->getPrettyName(),
148                    'ver'  => $ver,
149                );
150                $io->info( "  {$package->getName()} ($ver): textdomain is {$extra['textdomain']}, path is jetpack_vendor/{$package->getPrettyName()}" );
151            }
152        }
153
154        ksort( $data['packages'] );
155        ksort( $data['paths'] );
156
157        $code  = "<?php\n";
158        $code .= "// i18n-map.php @generated by automattic/jetpack-composer-plugin\n";
159        // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
160        $code .= 'return ' . var_export( $data, true ) . ";\n";
161
162        // Fixup syntax a little.
163        $code = str_replace( 'array (', 'array(', $code );
164        $code = preg_replace( '/ => \n\s*array\(/', ' => array(', $code );
165
166        $filesystem->ensureDirectoryExists( 'jetpack_vendor' );
167        $filesystem->filePutContentsIfModified( 'jetpack_vendor/i18n-map.php', $code );
168    }
169}