1<?php
2
3namespace Composer\Installers;
4
5use Composer\Composer;
6use Composer\Installer\BinaryInstaller;
7use Composer\Installer\LibraryInstaller;
8use Composer\IO\IOInterface;
9use Composer\Package\PackageInterface;
10use Composer\Repository\InstalledRepositoryInterface;
11use Composer\Util\Filesystem;
12use React\Promise\PromiseInterface;
13
14class Installer extends LibraryInstaller
15{
16
17    /**
18     * Package types to installer class map
19     *
20     * @var array
21     */
22    private $supportedTypes = array(
23        'aimeos'       => 'AimeosInstaller',
24        'asgard'       => 'AsgardInstaller',
25        'attogram'     => 'AttogramInstaller',
26        'agl'          => 'AglInstaller',
27        'annotatecms'  => 'AnnotateCmsInstaller',
28        'bitrix'       => 'BitrixInstaller',
29        'bonefish'     => 'BonefishInstaller',
30        'cakephp'      => 'CakePHPInstaller',
31        'chef'         => 'ChefInstaller',
32        'civicrm'      => 'CiviCrmInstaller',
33        'ccframework'  => 'ClanCatsFrameworkInstaller',
34        'cockpit'      => 'CockpitInstaller',
35        'codeigniter'  => 'CodeIgniterInstaller',
36        'concrete5'    => 'Concrete5Installer',
37        'craft'        => 'CraftInstaller',
38        'croogo'       => 'CroogoInstaller',
39        'dframe'       => 'DframeInstaller',
40        'dokuwiki'     => 'DokuWikiInstaller',
41        'dolibarr'     => 'DolibarrInstaller',
42        'decibel'      => 'DecibelInstaller',
43        'drupal'       => 'DrupalInstaller',
44        'elgg'         => 'ElggInstaller',
45        'eliasis'      => 'EliasisInstaller',
46        'ee3'          => 'ExpressionEngineInstaller',
47        'ee2'          => 'ExpressionEngineInstaller',
48        'ezplatform'   => 'EzPlatformInstaller',
49        'fuel'         => 'FuelInstaller',
50        'fuelphp'      => 'FuelphpInstaller',
51        'grav'         => 'GravInstaller',
52        'hurad'        => 'HuradInstaller',
53        'tastyigniter' => 'TastyIgniterInstaller',
54        'imagecms'     => 'ImageCMSInstaller',
55        'itop'         => 'ItopInstaller',
56        'joomla'       => 'JoomlaInstaller',
57        'kanboard'     => 'KanboardInstaller',
58        'kirby'        => 'KirbyInstaller',
59        'known'	       => 'KnownInstaller',
60        'kodicms'      => 'KodiCMSInstaller',
61        'kohana'       => 'KohanaInstaller',
62        'lms'      => 'LanManagementSystemInstaller',
63        'laravel'      => 'LaravelInstaller',
64        'lavalite'     => 'LavaLiteInstaller',
65        'lithium'      => 'LithiumInstaller',
66        'magento'      => 'MagentoInstaller',
67        'majima'       => 'MajimaInstaller',
68        'mantisbt'     => 'MantisBTInstaller',
69        'mako'         => 'MakoInstaller',
70        'maya'         => 'MayaInstaller',
71        'mautic'       => 'MauticInstaller',
72        'mediawiki'    => 'MediaWikiInstaller',
73        'miaoxing'     => 'MiaoxingInstaller',
74        'microweber'   => 'MicroweberInstaller',
75        'modulework'   => 'MODULEWorkInstaller',
76        'modx'         => 'ModxInstaller',
77        'modxevo'      => 'MODXEvoInstaller',
78        'moodle'       => 'MoodleInstaller',
79        'october'      => 'OctoberInstaller',
80        'ontowiki'     => 'OntoWikiInstaller',
81        'oxid'         => 'OxidInstaller',
82        'osclass'      => 'OsclassInstaller',
83        'pxcms'        => 'PxcmsInstaller',
84        'phpbb'        => 'PhpBBInstaller',
85        'pimcore'      => 'PimcoreInstaller',
86        'piwik'        => 'PiwikInstaller',
87        'plentymarkets'=> 'PlentymarketsInstaller',
88        'ppi'          => 'PPIInstaller',
89        'puppet'       => 'PuppetInstaller',
90        'radphp'       => 'RadPHPInstaller',
91        'phifty'       => 'PhiftyInstaller',
92        'porto'        => 'PortoInstaller',
93        'processwire'  => 'ProcessWireInstaller',
94        'redaxo'       => 'RedaxoInstaller',
95        'redaxo5'      => 'Redaxo5Installer',
96        'reindex'      => 'ReIndexInstaller',
97        'roundcube'    => 'RoundcubeInstaller',
98        'shopware'     => 'ShopwareInstaller',
99        'sitedirect'   => 'SiteDirectInstaller',
100        'silverstripe' => 'SilverStripeInstaller',
101        'smf'          => 'SMFInstaller',
102        'starbug'      => 'StarbugInstaller',
103        'sydes'        => 'SyDESInstaller',
104        'sylius'       => 'SyliusInstaller',
105        'symfony1'     => 'Symfony1Installer',
106        'tao'          => 'TaoInstaller',
107        'thelia'       => 'TheliaInstaller',
108        'tusk'         => 'TuskInstaller',
109        'typo3-cms'    => 'TYPO3CmsInstaller',
110        'typo3-flow'   => 'TYPO3FlowInstaller',
111        'userfrosting' => 'UserFrostingInstaller',
112        'vanilla'      => 'VanillaInstaller',
113        'whmcs'        => 'WHMCSInstaller',
114        'winter'       => 'WinterInstaller',
115        'wolfcms'      => 'WolfCMSInstaller',
116        'wordpress'    => 'WordPressInstaller',
117        'yawik'        => 'YawikInstaller',
118        'zend'         => 'ZendInstaller',
119        'zikula'       => 'ZikulaInstaller',
120        'prestashop'   => 'PrestashopInstaller'
121    );
122
123    /**
124     * Installer constructor.
125     *
126     * Disables installers specified in main composer extra installer-disable
127     * list
128     *
129     * @param IOInterface          $io
130     * @param Composer             $composer
131     * @param string               $type
132     * @param Filesystem|null      $filesystem
133     * @param BinaryInstaller|null $binaryInstaller
134     */
135    public function __construct(
136        IOInterface $io,
137        Composer $composer,
138        $type = 'library',
139        Filesystem $filesystem = null,
140        BinaryInstaller $binaryInstaller = null
141    ) {
142        parent::__construct($io, $composer, $type, $filesystem,
143            $binaryInstaller);
144        $this->removeDisabledInstallers();
145    }
146
147    /**
148     * {@inheritDoc}
149     */
150    public function getInstallPath(PackageInterface $package)
151    {
152        $type = $package->getType();
153        $frameworkType = $this->findFrameworkType($type);
154
155        if ($frameworkType === false) {
156            throw new \InvalidArgumentException(
157                'Sorry the package type of this package is not yet supported.'
158            );
159        }
160
161        $class = 'Composer\\Installers\\' . $this->supportedTypes[$frameworkType];
162        $installer = new $class($package, $this->composer, $this->getIO());
163
164        return $installer->getInstallPath($package, $frameworkType);
165    }
166
167    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
168    {
169        $installPath = $this->getPackageBasePath($package);
170        $io = $this->io;
171        $outputStatus = function () use ($io, $installPath) {
172            $io->write(sprintf('Deleting %s - %s', $installPath, !file_exists($installPath) ? '<comment>deleted</comment>' : '<error>not deleted</error>'));
173        };
174
175        $promise = parent::uninstall($repo, $package);
176
177        // Composer v2 might return a promise here
178        if ($promise instanceof PromiseInterface) {
179            return $promise->then($outputStatus);
180        }
181
182        // If not, execute the code right away as parent::uninstall executed synchronously (composer v1, or v2 without async)
183        $outputStatus();
184
185        return null;
186    }
187
188    /**
189     * {@inheritDoc}
190     */
191    public function supports($packageType)
192    {
193        $frameworkType = $this->findFrameworkType($packageType);
194
195        if ($frameworkType === false) {
196            return false;
197        }
198
199        $locationPattern = $this->getLocationPattern($frameworkType);
200
201        return preg_match('#' . $frameworkType . '-' . $locationPattern . '#', $packageType, $matches) === 1;
202    }
203
204    /**
205     * Finds a supported framework type if it exists and returns it
206     *
207     * @param  string       $type
208     * @return string|false
209     */
210    protected function findFrameworkType($type)
211    {
212        krsort($this->supportedTypes);
213
214        foreach ($this->supportedTypes as $key => $val) {
215            if ($key === substr($type, 0, strlen($key))) {
216                return substr($type, 0, strlen($key));
217            }
218        }
219
220        return false;
221    }
222
223    /**
224     * Get the second part of the regular expression to check for support of a
225     * package type
226     *
227     * @param  string $frameworkType
228     * @return string
229     */
230    protected function getLocationPattern($frameworkType)
231    {
232        $pattern = false;
233        if (!empty($this->supportedTypes[$frameworkType])) {
234            $frameworkClass = 'Composer\\Installers\\' . $this->supportedTypes[$frameworkType];
235            /** @var BaseInstaller $framework */
236            $framework = new $frameworkClass(null, $this->composer, $this->getIO());
237            $locations = array_keys($framework->getLocations());
238            $pattern = $locations ? '(' . implode('|', $locations) . ')' : false;
239        }
240
241        return $pattern ? : '(\w+)';
242    }
243
244    /**
245     * Get I/O object
246     *
247     * @return IOInterface
248     */
249    private function getIO()
250    {
251        return $this->io;
252    }
253
254    /**
255     * Look for installers set to be disabled in composer's extra config and
256     * remove them from the list of supported installers.
257     *
258     * Globals:
259     *  - true, "all", and "*" - disable all installers.
260     *  - false - enable all installers (useful with
261     *     wikimedia/composer-merge-plugin or similar)
262     *
263     * @return void
264     */
265    protected function removeDisabledInstallers()
266    {
267        $extra = $this->composer->getPackage()->getExtra();
268
269        if (!isset($extra['installer-disable']) || $extra['installer-disable'] === false) {
270            // No installers are disabled
271            return;
272        }
273
274        // Get installers to disable
275        $disable = $extra['installer-disable'];
276
277        // Ensure $disabled is an array
278        if (!is_array($disable)) {
279            $disable = array($disable);
280        }
281
282        // Check which installers should be disabled
283        $all = array(true, "all", "*");
284        $intersect = array_intersect($all, $disable);
285        if (!empty($intersect)) {
286            // Disable all installers
287            $this->supportedTypes = array();
288        } else {
289            // Disable specified installers
290            foreach ($disable as $key => $installer) {
291                if (is_string($installer) && key_exists($installer, $this->supportedTypes)) {
292                    unset($this->supportedTypes[$installer]);
293                }
294            }
295        }
296    }
297}
298