├── .scrutinizer.yml ├── composer.json ├── LICENSE.md ├── CHANGELOG.md ├── README.md ├── Plugin.php └── Installer.php /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | checks: 2 | php: true 3 | 4 | filter: 5 | paths: 6 | - "src/*" 7 | 8 | tools: 9 | php_code_coverage: 10 | enabled: true 11 | 12 | build: 13 | nodes: 14 | analysis: 15 | environment: 16 | php: 7.4.12 17 | 18 | tests: 19 | override: 20 | - php-scrutinizer-run 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yiisoft/yii2-composer", 3 | "description": "The composer plugin for Yii extension installer", 4 | "keywords": ["yii2", "composer", "extension installer"], 5 | "type": "composer-plugin", 6 | "license": "BSD-3-Clause", 7 | "support": { 8 | "issues": "https://github.com/yiisoft/yii2-composer/issues", 9 | "forum": "https://www.yiiframework.com/forum/", 10 | "wiki": "https://www.yiiframework.com/wiki/", 11 | "irc": "ircs://irc.libera.chat:6697/yii", 12 | "source": "https://github.com/yiisoft/yii2-composer" 13 | }, 14 | "authors": [ 15 | { 16 | "name": "Qiang Xue", 17 | "email": "qiang.xue@gmail.com" 18 | }, 19 | { 20 | "name": "Carsten Brandt", 21 | "email": "mail@cebe.cc" 22 | } 23 | ], 24 | "require": { 25 | "composer-plugin-api": "^1.0 | ^2.0" 26 | }, 27 | "require-dev": { 28 | "composer/composer": "^1.0 | ^2.0@dev", 29 | "phpunit/phpunit": "<7" 30 | }, 31 | "autoload": { 32 | "psr-4": { "yii\\composer\\": "" } 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { "tests\\": "tests" } 36 | }, 37 | "extra": { 38 | "class": "yii\\composer\\Plugin", 39 | "branch-alias": { 40 | "dev-master": "2.0.x-dev" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Yii Software LLC nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Yii Framework 2 composer extension Change Log 2 | ============================================= 3 | 4 | 2.0.12 under development 5 | ------------------------ 6 | 7 | - no changes in this release. 8 | 9 | 10 | 2.0.11 February 13, 2025 11 | ------------------------ 12 | 13 | - Enh #38: Use `random_bytes` instead of `openssl_random_pseudo_bytes` if available (fetus_hina) 14 | 15 | 16 | 2.0.10 June 24, 2020 17 | -------------------- 18 | 19 | - Enh #31: Add Composer 2 parallel unzip compatibility (samdark) 20 | 21 | 22 | 2.0.9 April 20, 2020 23 | -------------------- 24 | 25 | - Bug #30: Fix PHP error when upgrading/downgrading a Yii2 extension (brandonkelly) 26 | - Enh #27: Support for Composer 2 (brandonkelly, cebe) 27 | 28 | 29 | 2.0.8 July 16, 2019 30 | ------------------- 31 | 32 | - Bug #23: Fixed another an error that would occur if the Zend OPcache extension was installed, but its "restrict_api" setting was enabled (Lachee) 33 | 34 | 35 | 2.0.7 July 05, 2018 36 | ------------------- 37 | 38 | - Bug #18: Fixed an error that would occur if the Zend OPcache extension was installed, but its "restrict_api" setting was enabled (angrybrad) 39 | 40 | 41 | 2.0.6 March 21, 2018 42 | -------------------- 43 | 44 | - Bug #16: Upgrade notes were not shown when upgrading from a patch version (cebe) 45 | 46 | 47 | 2.0.5 December 20, 2016 48 | ----------------------- 49 | 50 | - Bug #11: `generateCookieValidationKey()` now saves config file only when `cookieValidationKey` was generated (rob006) 51 | - Enh #10: Added `yii\composer\Installer::postInstall()` method (rob006) 52 | - Enh #12: Added `yii\composer\Installer::copyFiles()` method (rob006) 53 | - Enh #14: A note about yii UPGRADE notes file is shown after upgrading Yii to make user aware of it (cebe) 54 | 55 | 56 | 2.0.4 February 06, 2016 57 | ----------------------- 58 | 59 | - Bug #7735: Composer failed to install extensions with multiple base paths in "psr-4" autoload section (cebe) 60 | - Enh #2: Better error handling for the case when installer is unable to change permissions (dbavscc) 61 | - Enh #3: `loadExtensions()` and `saveExtensions()` now access `EXTENSION_FILE` constant with late static binding (karneds) 62 | 63 | 64 | 2.0.3 March 01, 2015 65 | -------------------- 66 | 67 | - no changes in this release. 68 | 69 | 70 | 2.0.2 January 11, 2015 71 | ---------------------- 72 | 73 | - no changes in this release. 74 | 75 | 76 | 2.0.1 December 07, 2014 77 | ----------------------- 78 | 79 | - no changes in this release. 80 | 81 | 82 | 2.0.0 October 12, 2014 83 | ---------------------- 84 | 85 | - no changes in this release. 86 | 87 | 88 | 2.0.0-rc September 27, 2014 89 | --------------------------- 90 | 91 | - Bug #3438: Fixed support for non-lowercase package names (cebe) 92 | - Chg: Added `yii\composer\Installer::postCreateProject()` and modified the syntax of calling installer methods in composer.json (qiangxue) 93 | 94 | 2.0.0-beta April 13, 2014 95 | ------------------------- 96 | 97 | - Bug #1480: Fixed issue with creating extensions.php when php opcache is enabled (cebe) 98 | - Enh: Added support for installing packages conforming to PSR-4 standard (qiangxue) 99 | 100 | 2.0.0-alpha, December 1, 2013 101 | ----------------------------- 102 | 103 | - Initial release. 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

Yii 2 Composer Installer

6 |
7 |

8 | 9 | This is the composer installer for [Yii framework 2.0](https://www.yiiframework.com) extensions. 10 | It implements a new composer package type named `yii2-extension`, 11 | which should be used by all Yii 2 extensions if they are distributed as composer packages. 12 | 13 | For license information check the [LICENSE](LICENSE.md)-file. 14 | 15 | [![Latest Stable Version](https://poser.pugx.org/yiisoft/yii2-composer/v/stable.png)](https://packagist.org/packages/yiisoft/yii2-composer) 16 | [![Total Downloads](https://poser.pugx.org/yiisoft/yii2-composer/downloads.png)](https://packagist.org/packages/yiisoft/yii2-composer) 17 | [![Build status](https://github.com/yiisoft/yii2-composer/workflows/build/badge.svg)](https://github.com/yiisoft/yii2-composer/actions?query=workflow%3Abuild) 18 | 19 | 20 | Usage 21 | ----- 22 | 23 | The Yii 2 Composer Installer is automatically installed with when installing the framework via Composer. 24 | 25 | To use Yii 2 composer installer, simply set the package `type` to be `yii2-extension` in your `composer.json`, 26 | like the following: 27 | 28 | ```json 29 | { 30 | "type": "yii2-extension", 31 | "require": { 32 | "yiisoft/yii2": "~2.0.0" 33 | }, 34 | ... 35 | } 36 | ``` 37 | 38 | You may specify a bootstrapping class in the `extra` section. The `init()` method of the class will be executed each time 39 | the Yii 2 application is responding to a request. For example, 40 | 41 | ```json 42 | { 43 | "type": "yii2-extension", 44 | ..., 45 | "extra": { 46 | "bootstrap": "yii\\jui\\Extension" 47 | } 48 | } 49 | ``` 50 | 51 | The `Installer` class also implements a static method `postCreateProject()` that can be called after 52 | a Yii 2 project is created, through the `post-create-project-cmd` composer script. 53 | A similar method exists for running tasks after each `composer install` call, which is `postInstall()`. 54 | These methods allow to run other `Installer` class methods like `setPermission()` or `generateCookieValidationKey()`, 55 | depending on the corresponding parameters set in the `extra` section of the `composer.json` file. 56 | For example, 57 | 58 | ```json 59 | { 60 | "name": "yiisoft/yii2-app-basic", 61 | "type": "project", 62 | ... 63 | "scripts": { 64 | "post-create-project-cmd": [ 65 | "yii\\composer\\Installer::postCreateProject" 66 | ], 67 | "post-install-cmd": [ 68 | "yii\\composer\\Installer::postInstall" 69 | ] 70 | }, 71 | "extra": { 72 | "yii\\composer\\Installer::postCreateProject": { 73 | "setPermission": [ 74 | { 75 | "runtime": "0777", 76 | "web/assets": "0777", 77 | "yii": "0755" 78 | } 79 | ] 80 | }, 81 | "yii\\composer\\Installer::postInstall": { 82 | "copyFiles": [ 83 | { 84 | "config/templates/console-local.php": "config/console-local.php", 85 | "config/templates/web-local.php": "config/web-local.php", 86 | "config/templates/db-local.php": "config/db-local.php", 87 | "config/templates/cache.json": ["runtime/cache.json", true] 88 | } 89 | ], 90 | "generateCookieValidationKey": [ 91 | "config/web-local.php" 92 | ] 93 | } 94 | } 95 | } 96 | ``` 97 | -------------------------------------------------------------------------------- /Plugin.php: -------------------------------------------------------------------------------- 1 | 25 | * @since 2.0 26 | */ 27 | class Plugin implements PluginInterface, EventSubscriberInterface 28 | { 29 | /** 30 | * @var Installer 31 | */ 32 | private $_installer; 33 | /** 34 | * @var array noted package updates. 35 | */ 36 | private $_packageUpdates = []; 37 | /** 38 | * @var string path to the vendor directory. 39 | */ 40 | private $_vendorDir; 41 | 42 | 43 | /** 44 | * @inheritdoc 45 | */ 46 | public function activate(Composer $composer, IOInterface $io) 47 | { 48 | $this->_installer = new Installer($io, $composer); 49 | $composer->getInstallationManager()->addInstaller($this->_installer); 50 | $this->_vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/'); 51 | $file = $this->_vendorDir . '/yiisoft/extensions.php'; 52 | if (!is_file($file)) { 53 | @mkdir(dirname($file), 0777, true); 54 | file_put_contents($file, "getInstallationManager()->removeInstaller($this->_installer); 64 | } 65 | 66 | /** 67 | * @inheritdoc 68 | */ 69 | public function uninstall(Composer $composer, IOInterface $io) 70 | { 71 | } 72 | 73 | /** 74 | * @inheritdoc 75 | * @return array The event names to listen to. 76 | */ 77 | public static function getSubscribedEvents() 78 | { 79 | return [ 80 | PackageEvents::POST_PACKAGE_UPDATE => 'checkPackageUpdates', 81 | ScriptEvents::POST_UPDATE_CMD => 'showUpgradeNotes', 82 | ]; 83 | } 84 | 85 | 86 | /** 87 | * Listen to POST_PACKAGE_UPDATE event and take note of the package updates. 88 | * @param PackageEvent $event 89 | */ 90 | public function checkPackageUpdates(PackageEvent $event) 91 | { 92 | $operation = $event->getOperation(); 93 | if ($operation instanceof UpdateOperation) { 94 | $this->_packageUpdates[$operation->getInitialPackage()->getName()] = [ 95 | 'from' => $operation->getInitialPackage()->getVersion(), 96 | 'fromPretty' => $operation->getInitialPackage()->getPrettyVersion(), 97 | 'to' => $operation->getTargetPackage()->getVersion(), 98 | 'toPretty' => $operation->getTargetPackage()->getPrettyVersion(), 99 | 'direction' => $this->_isUpgrade($event, $operation) ? 'up' : 'down', 100 | ]; 101 | } 102 | } 103 | 104 | /** 105 | * @param PackageEvent $event 106 | * @param UpdateOperation $operation 107 | * @return bool 108 | */ 109 | private function _isUpgrade(PackageEvent $event, UpdateOperation $operation) 110 | { 111 | // Composer 1.7.0+ 112 | if (method_exists('Composer\Package\Version\VersionParser', 'isUpgrade')) { 113 | return VersionParser::isUpgrade( 114 | $operation->getInitialPackage()->getVersion(), 115 | $operation->getTargetPackage()->getVersion() 116 | ); 117 | } 118 | 119 | return $event->getPolicy()->versionCompare( 120 | $operation->getInitialPackage(), 121 | $operation->getTargetPackage(), 122 | '<' 123 | ); 124 | } 125 | 126 | /** 127 | * Listen to POST_UPDATE_CMD event to display information about upgrade notes if appropriate. 128 | * @param Script\Event $event 129 | */ 130 | public function showUpgradeNotes(Script\Event $event) 131 | { 132 | $packageName = 'yiisoft/yii2'; 133 | if (!isset($this->_packageUpdates[$packageName])) { 134 | return; 135 | } 136 | 137 | $package = $this->_packageUpdates['yiisoft/yii2']; 138 | 139 | // do not show a notice on up/downgrades between dev versions 140 | // avoid messages like from version dev-master to dev-master 141 | if ($package['fromPretty'] == $package['toPretty']) { 142 | return; 143 | } 144 | 145 | $io = $event->getIO(); 146 | 147 | // print the relevant upgrade notes for the upgrade 148 | // - only on upgrade, not on downgrade 149 | // - only if the "from" version is non-dev, otherwise we have no idea which notes to show 150 | if ($package['direction'] === 'up' && $this->isNumericVersion($package['fromPretty'])) { 151 | 152 | $notes = $this->findUpgradeNotes($packageName, $package['fromPretty']); 153 | if ($notes !== false && empty($notes)) { 154 | // no relevent upgrade notes, do not show anything. 155 | return; 156 | } 157 | 158 | $this->printUpgradeIntro($io, $package); 159 | 160 | if ($notes) { 161 | // safety check: do not display notes if they are too many 162 | if (count($notes) > 250) { 163 | $io->write("\n The relevant notes for your upgrade are too long to be displayed here."); 164 | } else { 165 | $io->write("\n " . trim(implode("\n ", $notes))); 166 | } 167 | } 168 | 169 | $io->write("\n You can find the upgrade notes for all versions online at:"); 170 | } else { 171 | $this->printUpgradeIntro($io, $package); 172 | $io->write("\n You can find the upgrade notes online at:"); 173 | } 174 | $this->printUpgradeLink($io, $package); 175 | } 176 | 177 | /** 178 | * Print link to upgrade notes 179 | * @param IOInterface $io 180 | * @param array $package 181 | */ 182 | private function printUpgradeLink($io, $package) 183 | { 184 | $maxVersion = $package['direction'] === 'up' ? $package['toPretty'] : $package['fromPretty']; 185 | // make sure to always show a valid link, even if $maxVersion is something like dev-master 186 | if (!$this->isNumericVersion($maxVersion)) { 187 | $maxVersion = 'master'; 188 | } 189 | $io->write(" https://github.com/yiisoft/yii2/blob/$maxVersion/framework/UPGRADE.md\n"); 190 | } 191 | 192 | /** 193 | * Print upgrade intro 194 | * @param IOInterface $io 195 | * @param array $package 196 | */ 197 | private function printUpgradeIntro($io, $package) 198 | { 199 | $io->write("\n Seems you have " 200 | . ($package['direction'] === 'up' ? 'upgraded' : 'downgraded') 201 | . ' Yii Framework from version ' 202 | . $package['fromPretty'] . ' to ' . $package['toPretty'] . '.' 203 | ); 204 | $io->write("\n Please check the upgrade notes for possible incompatible changes"); 205 | $io->write(' and adjust your application code accordingly.'); 206 | } 207 | 208 | /** 209 | * Read upgrade notes from a files and returns an array of lines 210 | * @param string $packageName 211 | * @param string $fromVersion until which version to read the notes 212 | * @return array|false 213 | */ 214 | private function findUpgradeNotes($packageName, $fromVersion) 215 | { 216 | if (preg_match('/^([0-9]\.[0-9]+\.?[0-9]*)/', $fromVersion, $m)) { 217 | $fromVersionMajor = $m[1]; 218 | } else { 219 | $fromVersionMajor = $fromVersion; 220 | } 221 | 222 | $upgradeFile = $this->_vendorDir . '/' . $packageName . '/UPGRADE.md'; 223 | if (!is_file($upgradeFile) || !is_readable($upgradeFile)) { 224 | return false; 225 | } 226 | $lines = preg_split('~\R~', file_get_contents($upgradeFile)); 227 | $relevantLines = []; 228 | $consuming = false; 229 | // whether an exact match on $fromVersion has been encountered 230 | $foundExactMatch = false; 231 | foreach($lines as $line) { 232 | if (preg_match('/^Upgrade from Yii ([0-9]\.[0-9]+\.?[0-9\.]*)/i', $line, $matches)) { 233 | if ($matches[1] === $fromVersion) { 234 | $foundExactMatch = true; 235 | } 236 | if (version_compare($matches[1], $fromVersion, '<') && ($foundExactMatch || version_compare($matches[1], $fromVersionMajor, '<'))) { 237 | break; 238 | } 239 | $consuming = true; 240 | } 241 | if ($consuming) { 242 | $relevantLines[] = $line; 243 | } 244 | } 245 | return $relevantLines; 246 | } 247 | 248 | /** 249 | * Check whether a version is numeric, e.g. 2.0.10. 250 | * @param string $version 251 | * @return bool 252 | */ 253 | private function isNumericVersion($version) 254 | { 255 | return (bool) preg_match('~^([0-9]\.[0-9]+\.?[0-9\.]*)~', $version); 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /Installer.php: -------------------------------------------------------------------------------- 1 | 20 | * @since 2.0 21 | */ 22 | class Installer extends LibraryInstaller 23 | { 24 | const EXTRA_BOOTSTRAP = 'bootstrap'; 25 | const EXTENSION_FILE = 'yiisoft/extensions.php'; 26 | 27 | 28 | /** 29 | * @inheritdoc 30 | */ 31 | public function supports($packageType) 32 | { 33 | return $packageType === 'yii2-extension'; 34 | } 35 | 36 | /** 37 | * @inheritdoc 38 | */ 39 | public function install(InstalledRepositoryInterface $repo, PackageInterface $package) 40 | { 41 | $afterInstall = function () use ($package) { 42 | // add the package to yiisoft/extensions.php 43 | $this->addPackage($package); 44 | // ensure the yii2-dev package also provides Yii.php in the same place as yii2 does 45 | if ($package->getName() == 'yiisoft/yii2-dev') { 46 | $this->linkBaseYiiFiles(); 47 | } 48 | }; 49 | 50 | // install the package the normal composer way 51 | $promise = parent::install($repo, $package); 52 | 53 | // Composer v2 might return a promise here 54 | if ($promise instanceof PromiseInterface) { 55 | return $promise->then($afterInstall); 56 | } 57 | 58 | // If not, execute the code right away as parent::install executed synchronously (composer v1, or v2 without async) 59 | $afterInstall(); 60 | } 61 | 62 | /** 63 | * @inheritdoc 64 | */ 65 | public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) 66 | { 67 | $afterUpdate = function () use ($initial, $target) { 68 | $this->removePackage($initial); 69 | $this->addPackage($target); 70 | // ensure the yii2-dev package also provides Yii.php in the same place as yii2 does 71 | if ($initial->getName() == 'yiisoft/yii2-dev') { 72 | $this->linkBaseYiiFiles(); 73 | } 74 | }; 75 | 76 | // update the package the normal composer way 77 | $promise = parent::update($repo, $initial, $target); 78 | 79 | // Composer v2 might return a promise here 80 | if ($promise instanceof PromiseInterface) { 81 | return $promise->then($afterUpdate); 82 | } 83 | 84 | // If not, execute the code right away as parent::update executed synchronously (composer v1, or v2 without async) 85 | $afterUpdate(); 86 | } 87 | 88 | /** 89 | * @inheritdoc 90 | */ 91 | public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) 92 | { 93 | $afterUninstall = function () use ($package) { 94 | // remove the package from yiisoft/extensions.php 95 | $this->removePackage($package); 96 | // remove links for Yii.php 97 | if ($package->getName() == 'yiisoft/yii2-dev') { 98 | $this->removeBaseYiiFiles(); 99 | } 100 | }; 101 | 102 | // uninstall the package the normal composer way 103 | $promise = parent::uninstall($repo, $package); 104 | 105 | // Composer v2 might return a promise here 106 | if ($promise instanceof PromiseInterface) { 107 | return $promise->then($afterUninstall); 108 | } 109 | 110 | // If not, execute the code right away as parent::uninstall executed synchronously (composer v1, or v2 without async) 111 | $afterUninstall(); 112 | } 113 | 114 | protected function addPackage(PackageInterface $package) 115 | { 116 | $extension = [ 117 | 'name' => $package->getName(), 118 | 'version' => $package->getVersion(), 119 | ]; 120 | 121 | $alias = $this->generateDefaultAlias($package); 122 | if (!empty($alias)) { 123 | $extension['alias'] = $alias; 124 | } 125 | $extra = $package->getExtra(); 126 | if (isset($extra[self::EXTRA_BOOTSTRAP])) { 127 | $extension['bootstrap'] = $extra[self::EXTRA_BOOTSTRAP]; 128 | } 129 | 130 | $extensions = $this->loadExtensions(); 131 | $extensions[$package->getName()] = $extension; 132 | $this->saveExtensions($extensions); 133 | } 134 | 135 | protected function generateDefaultAlias(PackageInterface $package) 136 | { 137 | $fs = new Filesystem; 138 | $vendorDir = $fs->normalizePath($this->vendorDir); 139 | $autoload = $package->getAutoload(); 140 | 141 | $aliases = []; 142 | 143 | if (!empty($autoload['psr-0'])) { 144 | foreach ($autoload['psr-0'] as $name => $path) { 145 | $name = str_replace('\\', '/', trim($name, '\\')); 146 | if (!$fs->isAbsolutePath($path)) { 147 | $path = $this->vendorDir . '/' . $package->getPrettyName() . '/' . $path; 148 | } 149 | $path = $fs->normalizePath($path); 150 | if (strpos($path . '/', $vendorDir . '/') === 0) { 151 | $aliases["@$name"] = '' . substr($path, strlen($vendorDir)) . '/' . $name; 152 | } else { 153 | $aliases["@$name"] = $path . '/' . $name; 154 | } 155 | } 156 | } 157 | 158 | if (!empty($autoload['psr-4'])) { 159 | foreach ($autoload['psr-4'] as $name => $path) { 160 | if (is_array($path)) { 161 | // ignore psr-4 autoload specifications with multiple search paths 162 | // we can not convert them into aliases as they are ambiguous 163 | continue; 164 | } 165 | $name = str_replace('\\', '/', trim($name, '\\')); 166 | if (!$fs->isAbsolutePath($path)) { 167 | $path = $this->vendorDir . '/' . $package->getPrettyName() . '/' . $path; 168 | } 169 | $path = $fs->normalizePath($path); 170 | if (strpos($path . '/', $vendorDir . '/') === 0) { 171 | $aliases["@$name"] = '' . substr($path, strlen($vendorDir)); 172 | } else { 173 | $aliases["@$name"] = $path; 174 | } 175 | } 176 | } 177 | 178 | return $aliases; 179 | } 180 | 181 | protected function removePackage(PackageInterface $package) 182 | { 183 | $packages = $this->loadExtensions(); 184 | unset($packages[$package->getName()]); 185 | $this->saveExtensions($packages); 186 | } 187 | 188 | protected function loadExtensions() 189 | { 190 | $file = $this->vendorDir . '/' . static::EXTENSION_FILE; 191 | if (!is_file($file)) { 192 | return []; 193 | } 194 | // invalidate opcache of extensions.php if exists 195 | if (function_exists('opcache_invalidate')) { 196 | @opcache_invalidate($file, true); 197 | } 198 | $extensions = require($file); 199 | 200 | $vendorDir = str_replace('\\', '/', $this->vendorDir); 201 | $n = strlen($vendorDir); 202 | 203 | foreach ($extensions as &$extension) { 204 | if (isset($extension['alias'])) { 205 | foreach ($extension['alias'] as $alias => $path) { 206 | $path = str_replace('\\', '/', $path); 207 | if (strpos($path . '/', $vendorDir . '/') === 0) { 208 | $extension['alias'][$alias] = '' . substr($path, $n); 209 | } 210 | } 211 | } 212 | } 213 | 214 | return $extensions; 215 | } 216 | 217 | protected function saveExtensions(array $extensions) 218 | { 219 | $file = $this->vendorDir . '/' . static::EXTENSION_FILE; 220 | if (!file_exists(dirname($file))) { 221 | mkdir(dirname($file), 0777, true); 222 | } 223 | $array = str_replace("'", '$vendorDir . \'', var_export($extensions, true)); 224 | file_put_contents($file, "vendorDir . '/yiisoft/yii2'; 234 | if (!file_exists($yiiDir)) { 235 | mkdir($yiiDir, 0777, true); 236 | } 237 | foreach (['Yii.php', 'BaseYii.php', 'classes.php'] as $file) { 238 | file_put_contents($yiiDir . '/' . $file, <<vendorDir . '/yiisoft/yii2'; 258 | foreach (['Yii.php', 'BaseYii.php', 'classes.php'] as $file) { 259 | if (file_exists($yiiDir . '/' . $file)) { 260 | unlink($yiiDir . '/' . $file); 261 | } 262 | } 263 | if (file_exists($yiiDir)) { 264 | rmdir($yiiDir); 265 | } 266 | } 267 | 268 | /** 269 | * Special method to run tasks defined in `[extra][yii\composer\Installer::postCreateProject]` key in `composer.json` 270 | * 271 | * @param Event $event 272 | */ 273 | public static function postCreateProject($event) 274 | { 275 | static::runCommands($event, __METHOD__); 276 | } 277 | 278 | /** 279 | * Special method to run tasks defined in `[extra][yii\composer\Installer::postInstall]` key in `composer.json` 280 | * 281 | * @param Event $event 282 | * @since 2.0.5 283 | */ 284 | public static function postInstall($event) 285 | { 286 | static::runCommands($event, __METHOD__); 287 | } 288 | 289 | /** 290 | * Special method to run tasks defined in `[extra][$extraKey]` key in `composer.json` 291 | * 292 | * @param Event $event 293 | * @param string $extraKey 294 | * @since 2.0.5 295 | */ 296 | protected static function runCommands($event, $extraKey) 297 | { 298 | $params = $event->getComposer()->getPackage()->getExtra(); 299 | if (isset($params[$extraKey]) && is_array($params[$extraKey])) { 300 | foreach ($params[$extraKey] as $method => $args) { 301 | call_user_func_array([__CLASS__, $method], (array) $args); 302 | } 303 | } 304 | } 305 | 306 | /** 307 | * Sets the correct permission for the files and directories listed in the extra section. 308 | * @param array $paths the paths (keys) and the corresponding permission octal strings (values) 309 | */ 310 | public static function setPermission(array $paths) 311 | { 312 | foreach ($paths as $path => $permission) { 313 | echo "chmod('$path', $permission)..."; 314 | if (is_dir($path) || is_file($path)) { 315 | try { 316 | if (chmod($path, octdec($permission))) { 317 | echo "done.\n"; 318 | }; 319 | } catch (\Exception $e) { 320 | echo $e->getMessage() . "\n"; 321 | } 322 | } else { 323 | echo "file not found.\n"; 324 | } 325 | } 326 | } 327 | 328 | /** 329 | * Generates a cookie validation key for every app config listed in "config" in extra section. 330 | * You can provide one or multiple parameters as the configuration files which need to have validation key inserted. 331 | */ 332 | public static function generateCookieValidationKey() 333 | { 334 | $configs = func_get_args(); 335 | $key = self::generateRandomString(); 336 | foreach ($configs as $config) { 337 | if (is_file($config)) { 338 | $content = preg_replace('/(("|\')cookieValidationKey("|\')\s*=>\s*)(""|\'\')/', "\\1'$key'", file_get_contents($config), -1, $count); 339 | if ($count > 0) { 340 | file_put_contents($config, $content); 341 | } 342 | } 343 | } 344 | } 345 | 346 | protected static function generateRandomString() 347 | { 348 | $length = 32; 349 | $bytes = self::generateRandomBytes($length); 350 | return strtr(substr(base64_encode($bytes), 0, $length), '+/=', '_-.'); 351 | } 352 | 353 | protected static function generateRandomBytes($length) 354 | { 355 | if (function_exists('random_bytes')) { 356 | return random_bytes($length); 357 | } 358 | 359 | if (extension_loaded('openssl')) { 360 | return openssl_random_pseudo_bytes($length); 361 | } 362 | 363 | throw new \Exception('PHP >= 7.0 or the OpenSSL PHP extension is required by Yii2.'); 364 | } 365 | 366 | /** 367 | * Copy files to specified locations. 368 | * @param array $paths The source files paths (keys) and the corresponding target locations 369 | * for copied files (values). Location can be specified as an array - first element is target 370 | * location, second defines whether file can be overwritten (by default method don't overwrite 371 | * existing files). 372 | * @since 2.0.5 373 | */ 374 | public static function copyFiles(array $paths) 375 | { 376 | foreach ($paths as $source => $target) { 377 | // handle file target as array [path, overwrite] 378 | $target = (array) $target; 379 | echo "Copying file $source to $target[0] - "; 380 | 381 | if (!is_file($source)) { 382 | echo "source file not found.\n"; 383 | continue; 384 | } 385 | 386 | if (is_file($target[0]) && empty($target[1])) { 387 | echo "target file exists - skip.\n"; 388 | continue; 389 | } elseif (is_file($target[0]) && !empty($target[1])) { 390 | echo "target file exists - overwrite - "; 391 | } 392 | 393 | try { 394 | if (!is_dir(dirname($target[0]))) { 395 | mkdir(dirname($target[0]), 0777, true); 396 | } 397 | if (copy($source, $target[0])) { 398 | echo "done.\n"; 399 | } 400 | } catch (\Exception $e) { 401 | echo $e->getMessage() . "\n"; 402 | } 403 | } 404 | } 405 | } 406 | --------------------------------------------------------------------------------