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