290 lines
9.5KB

  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\composer;
  8. use Composer\Package\PackageInterface;
  9. use Composer\Installer\LibraryInstaller;
  10. use Composer\Repository\InstalledRepositoryInterface;
  11. use Composer\Script\CommandEvent;
  12. use Composer\Util\Filesystem;
  13. /**
  14. * @author Qiang Xue <qiang.xue@gmail.com>
  15. * @since 2.0
  16. */
  17. class Installer extends LibraryInstaller
  18. {
  19. const EXTRA_BOOTSTRAP = 'bootstrap';
  20. const EXTENSION_FILE = 'yiisoft/extensions.php';
  21. /**
  22. * @inheritdoc
  23. */
  24. public function supports($packageType)
  25. {
  26. return $packageType === 'yii2-extension';
  27. }
  28. /**
  29. * @inheritdoc
  30. */
  31. public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
  32. {
  33. // install the package the normal composer way
  34. parent::install($repo, $package);
  35. // add the package to yiisoft/extensions.php
  36. $this->addPackage($package);
  37. // ensure the yii2-dev package also provides Yii.php in the same place as yii2 does
  38. if ($package->getName() == 'yiisoft/yii2-dev') {
  39. $this->linkBaseYiiFiles();
  40. }
  41. }
  42. /**
  43. * @inheritdoc
  44. */
  45. public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
  46. {
  47. parent::update($repo, $initial, $target);
  48. $this->removePackage($initial);
  49. $this->addPackage($target);
  50. // ensure the yii2-dev package also provides Yii.php in the same place as yii2 does
  51. if ($initial->getName() == 'yiisoft/yii2-dev') {
  52. $this->linkBaseYiiFiles();
  53. }
  54. }
  55. /**
  56. * @inheritdoc
  57. */
  58. public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
  59. {
  60. // uninstall the package the normal composer way
  61. parent::uninstall($repo, $package);
  62. // remove the package from yiisoft/extensions.php
  63. $this->removePackage($package);
  64. // remove links for Yii.php
  65. if ($package->getName() == 'yiisoft/yii2-dev') {
  66. $this->removeBaseYiiFiles();
  67. }
  68. }
  69. protected function addPackage(PackageInterface $package)
  70. {
  71. $extension = [
  72. 'name' => $package->getName(),
  73. 'version' => $package->getVersion(),
  74. ];
  75. $alias = $this->generateDefaultAlias($package);
  76. if (!empty($alias)) {
  77. $extension['alias'] = $alias;
  78. }
  79. $extra = $package->getExtra();
  80. if (isset($extra[self::EXTRA_BOOTSTRAP])) {
  81. $extension['bootstrap'] = $extra[self::EXTRA_BOOTSTRAP];
  82. }
  83. $extensions = $this->loadExtensions();
  84. $extensions[$package->getName()] = $extension;
  85. $this->saveExtensions($extensions);
  86. }
  87. protected function generateDefaultAlias(PackageInterface $package)
  88. {
  89. $fs = new Filesystem;
  90. $vendorDir = $fs->normalizePath($this->vendorDir);
  91. $autoload = $package->getAutoload();
  92. $aliases = [];
  93. if (!empty($autoload['psr-0'])) {
  94. foreach ($autoload['psr-0'] as $name => $path) {
  95. $name = str_replace('\\', '/', trim($name, '\\'));
  96. if (!$fs->isAbsolutePath($path)) {
  97. $path = $this->vendorDir . '/' . $package->getPrettyName() . '/' . $path;
  98. }
  99. $path = $fs->normalizePath($path);
  100. if (strpos($path . '/', $vendorDir . '/') === 0) {
  101. $aliases["@$name"] = '<vendor-dir>' . substr($path, strlen($vendorDir)) . '/' . $name;
  102. } else {
  103. $aliases["@$name"] = $path . '/' . $name;
  104. }
  105. }
  106. }
  107. if (!empty($autoload['psr-4'])) {
  108. foreach ($autoload['psr-4'] as $name => $path) {
  109. if (is_array($path)) {
  110. // ignore psr-4 autoload specifications with multiple search paths
  111. // we can not convert them into aliases as they are ambiguous
  112. continue;
  113. }
  114. $name = str_replace('\\', '/', trim($name, '\\'));
  115. if (!$fs->isAbsolutePath($path)) {
  116. $path = $this->vendorDir . '/' . $package->getPrettyName() . '/' . $path;
  117. }
  118. $path = $fs->normalizePath($path);
  119. if (strpos($path . '/', $vendorDir . '/') === 0) {
  120. $aliases["@$name"] = '<vendor-dir>' . substr($path, strlen($vendorDir));
  121. } else {
  122. $aliases["@$name"] = $path;
  123. }
  124. }
  125. }
  126. return $aliases;
  127. }
  128. protected function removePackage(PackageInterface $package)
  129. {
  130. $packages = $this->loadExtensions();
  131. unset($packages[$package->getName()]);
  132. $this->saveExtensions($packages);
  133. }
  134. protected function loadExtensions()
  135. {
  136. $file = $this->vendorDir . '/' . static::EXTENSION_FILE;
  137. if (!is_file($file)) {
  138. return [];
  139. }
  140. // invalidate opcache of extensions.php if exists
  141. if (function_exists('opcache_invalidate')) {
  142. opcache_invalidate($file, true);
  143. }
  144. $extensions = require($file);
  145. $vendorDir = str_replace('\\', '/', $this->vendorDir);
  146. $n = strlen($vendorDir);
  147. foreach ($extensions as &$extension) {
  148. if (isset($extension['alias'])) {
  149. foreach ($extension['alias'] as $alias => $path) {
  150. $path = str_replace('\\', '/', $path);
  151. if (strpos($path . '/', $vendorDir . '/') === 0) {
  152. $extension['alias'][$alias] = '<vendor-dir>' . substr($path, $n);
  153. }
  154. }
  155. }
  156. }
  157. return $extensions;
  158. }
  159. protected function saveExtensions(array $extensions)
  160. {
  161. $file = $this->vendorDir . '/' . static::EXTENSION_FILE;
  162. if (!file_exists(dirname($file))) {
  163. mkdir(dirname($file), 0777, true);
  164. }
  165. $array = str_replace("'<vendor-dir>", '$vendorDir . \'', var_export($extensions, true));
  166. file_put_contents($file, "<?php\n\n\$vendorDir = dirname(__DIR__);\n\nreturn $array;\n");
  167. // invalidate opcache of extensions.php if exists
  168. if (function_exists('opcache_invalidate')) {
  169. opcache_invalidate($file, true);
  170. }
  171. }
  172. protected function linkBaseYiiFiles()
  173. {
  174. $yiiDir = $this->vendorDir . '/yiisoft/yii2';
  175. if (!file_exists($yiiDir)) {
  176. mkdir($yiiDir, 0777, true);
  177. }
  178. foreach (['Yii.php', 'BaseYii.php', 'classes.php'] as $file) {
  179. file_put_contents($yiiDir . '/' . $file, <<<EOF
  180. <?php
  181. /**
  182. * This is a link provided by the yiisoft/yii2-dev package via yii2-composer plugin.
  183. *
  184. * @link http://www.yiiframework.com/
  185. * @copyright Copyright (c) 2008 Yii Software LLC
  186. * @license http://www.yiiframework.com/license/
  187. */
  188. return require(__DIR__ . '/../yii2-dev/framework/$file');
  189. EOF
  190. );
  191. }
  192. }
  193. protected function removeBaseYiiFiles()
  194. {
  195. $yiiDir = $this->vendorDir . '/yiisoft/yii2';
  196. foreach (['Yii.php', 'BaseYii.php', 'classes.php'] as $file) {
  197. if (file_exists($yiiDir . '/' . $file)) {
  198. unlink($yiiDir . '/' . $file);
  199. }
  200. }
  201. if (file_exists($yiiDir)) {
  202. rmdir($yiiDir);
  203. }
  204. }
  205. public static function postCreateProject($event)
  206. {
  207. $params = $event->getComposer()->getPackage()->getExtra();
  208. if (isset($params[__METHOD__]) && is_array($params[__METHOD__])) {
  209. foreach ($params[__METHOD__] as $method => $args) {
  210. call_user_func_array([__CLASS__, $method], (array) $args);
  211. }
  212. }
  213. }
  214. /**
  215. * Sets the correct permission for the files and directories listed in the extra section.
  216. * @param array $paths the paths (keys) and the corresponding permission octal strings (values)
  217. */
  218. public static function setPermission(array $paths)
  219. {
  220. foreach ($paths as $path => $permission) {
  221. echo "chmod('$path', $permission)...";
  222. if (is_dir($path) || is_file($path)) {
  223. try {
  224. if (chmod($path, octdec($permission))) {
  225. echo "done.\n";
  226. };
  227. } catch (\Exception $e) {
  228. echo $e->getMessage() . "\n";
  229. }
  230. } else {
  231. echo "file not found.\n";
  232. }
  233. }
  234. }
  235. /**
  236. * Generates a cookie validation key for every app config listed in "config" in extra section.
  237. * You can provide one or multiple parameters as the configuration files which need to have validation key inserted.
  238. */
  239. public static function generateCookieValidationKey()
  240. {
  241. $configs = func_get_args();
  242. $key = self::generateRandomString();
  243. foreach ($configs as $config) {
  244. if (is_file($config)) {
  245. $content = preg_replace('/(("|\')cookieValidationKey("|\')\s*=>\s*)(""|\'\')/', "\\1'$key'", file_get_contents($config));
  246. file_put_contents($config, $content);
  247. }
  248. }
  249. }
  250. protected static function generateRandomString()
  251. {
  252. if (!extension_loaded('openssl')) {
  253. throw new \Exception('The OpenSSL PHP extension is required by Yii2.');
  254. }
  255. $length = 32;
  256. $bytes = openssl_random_pseudo_bytes($length);
  257. return strtr(substr(base64_encode($bytes), 0, $length), '+/=', '_-.');
  258. }
  259. }