1<?php
2
3/*
4 * This file is part of Component Installer.
5 *
6 * (c) Rob Loach (http://robloach.net)
7 *
8 * For the full copyright and license information, please view the LICENSE.md
9 * file that was distributed with this source code.
10 */
11
12namespace ComponentInstaller;
13
14use Composer\Installer\LibraryInstaller;
15use Composer\Script\Event;
16use Composer\Package\PackageInterface;
17use Composer\Package\AliasPackage;
18
19/**
20 * Component Installer for Composer.
21 */
22class Installer extends LibraryInstaller
23{
24
25    /**
26     * The location where Components are to be installed.
27     */
28    protected $componentDir;
29
30    /**
31     * {@inheritDoc}
32     *
33     * Components are supported by all packages. This checks wheteher or not the
34     * entire package is a "component", as well as injects the script to act
35     * on components embedded in packages that are not just "component" types.
36     */
37    public function supports($packageType)
38    {
39        // Components are supported by all package types. We will just act on
40        // the root package's scripts if available.
41        $rootPackage = isset($this->composer) ? $this->composer->getPackage() : null;
42        if (isset($rootPackage)) {
43            // Ensure we get the root package rather than its alias.
44            while ($rootPackage instanceof AliasPackage) {
45                $rootPackage = $rootPackage->getAliasOf();
46            }
47
48            // Make sure the root package can override the available scripts.
49            if (method_exists($rootPackage, 'setScripts')) {
50                $scripts = $rootPackage->getScripts();
51                // Act on the "post-autoload-dump" command so that we can act on all
52                // the installed packages.
53                $scripts['post-autoload-dump']['component-installer'] = 'ComponentInstaller\\Installer::postAutoloadDump';
54                $rootPackage->setScripts($scripts);
55            }
56        }
57
58        // State support for "component" package types.
59        return $packageType == 'component';
60    }
61
62    /**
63     * Gets the destination Component directory.
64     *
65     * @param PackageInterface $package
66     * @return string
67     *   The path to where the final Component should be installed.
68     */
69    public function getComponentPath(PackageInterface $package)
70    {
71        // Parse the pretty name for the vendor and package name.
72        $name = $prettyName = $package->getPrettyName();
73        if (strpos($prettyName, '/') !== false) {
74            list($vendor, $name) = explode('/', $prettyName);
75            unset($vendor);
76        }
77
78        // First look for an override in root package's extra, then try the package's extra
79        $rootPackage = $this->composer->getPackage();
80        $rootExtras = $rootPackage ? $rootPackage->getExtra() : array();
81        $customComponents = isset($rootExtras['component']) ? $rootExtras['component'] : array();
82
83        if (isset($customComponents[$prettyName]) && is_array($customComponents[$prettyName])) {
84            $component = $customComponents[$prettyName];
85        }
86        else {
87            $extra = $package->getExtra();
88            $component = isset($extra['component']) ? $extra['component'] : array();
89        }
90
91        // Allow the component to define its own name.
92        if (isset($component['name'])) {
93            $name = $component['name'];
94        }
95
96        // Find where the package should be located.
97        return $this->getComponentDir() . DIRECTORY_SEPARATOR . $name;
98    }
99
100    /**
101     * Initialize the Component directory, as well as the vendor directory.
102     */
103    protected function initializeVendorDir()
104    {
105        $this->componentDir = $this->getComponentDir();
106        $this->filesystem->ensureDirectoryExists($this->componentDir);
107        parent::initializeVendorDir();
108    }
109
110    /**
111     * Retrieves the Installer's provided component directory.
112     */
113    public function getComponentDir()
114    {
115        $config = $this->composer->getConfig();
116        return $config->has('component-dir') ? $config->get('component-dir') : 'components';
117    }
118
119    /**
120     * Remove both the installed code and files from the Component directory.
121     *
122     * @param PackageInterface $package
123     */
124    public function removeCode(PackageInterface $package)
125    {
126        $this->removeComponent($package);
127        parent::removeCode($package);
128    }
129
130    /**
131     * Remove a Component's files from the Component directory.
132     *
133     * @param PackageInterface $package
134     * @return bool
135     */
136    public function removeComponent(PackageInterface $package)
137    {
138        $path = $this->getComponentPath($package);
139        return $this->filesystem->remove($path);
140    }
141
142    /**
143     * Before installing the Component, be sure its destination is clear first.
144     *
145     * @param PackageInterface $package
146     */
147    public function installCode(PackageInterface $package)
148    {
149        $this->removeComponent($package);
150        parent::installCode($package);
151    }
152
153    /**
154     * Script callback; Acted on after the autoloader is dumped.
155     *
156     * @param Event $event
157     */
158    public static function postAutoloadDump(Event $event)
159    {
160        // Retrieve basic information about the environment and present a
161        // message to the user.
162        $composer = $event->getComposer();
163        $io = $event->getIO();
164        $io->write('<info>Compiling component files</info>');
165
166        // Set up all the processes.
167        $processes = array(
168            // Copy the assets to the Components directory.
169            "ComponentInstaller\\Process\\CopyProcess",
170            // Build the require.js file.
171            "ComponentInstaller\\Process\\RequireJsProcess",
172            // Build the require.css file.
173            "ComponentInstaller\\Process\\RequireCssProcess",
174            // Compile the require-built.js file.
175            "ComponentInstaller\\Process\\BuildJsProcess",
176        );
177
178        // Initialize and execute each process in sequence.
179        foreach ($processes as $class) {
180            if(!class_exists($class)){
181                $io->write("<warning>Process class '$class' not found, skipping this process</warning>");
182                continue;
183            }
184
185            /** @var \ComponentInstaller\Process\Process $process */
186            $process = new $class($composer, $io);
187            // When an error occurs during initialization, end the process.
188            if (!$process->init()) {
189                $io->write("<warning>An error occurred while initializing the '$class' process.</warning>");
190                break;
191            }
192            $process->process();
193        }
194    }
195}
196