UninstallCommand.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. <?php
  2. /**
  3. * @package Grav\Console\Gpm
  4. *
  5. * @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Console\Gpm;
  9. use Grav\Common\GPM\GPM;
  10. use Grav\Common\GPM\Installer;
  11. use Grav\Common\GPM\Remote\Package;
  12. use Grav\Common\Grav;
  13. use Grav\Console\ConsoleCommand;
  14. use Symfony\Component\Console\Input\InputArgument;
  15. use Symfony\Component\Console\Input\InputOption;
  16. use Symfony\Component\Console\Question\ConfirmationQuestion;
  17. class UninstallCommand extends ConsoleCommand
  18. {
  19. /** @var array */
  20. protected $data;
  21. /** @var GPM */
  22. protected $gpm;
  23. /** @var string */
  24. protected $destination;
  25. /** @var string */
  26. protected $file;
  27. /** @var string */
  28. protected $tmp;
  29. /** @var array */
  30. protected $dependencies = [];
  31. /** @var string */
  32. protected $all_yes;
  33. protected function configure()
  34. {
  35. $this
  36. ->setName('uninstall')
  37. ->addOption(
  38. 'all-yes',
  39. 'y',
  40. InputOption::VALUE_NONE,
  41. 'Assumes yes (or best approach) instead of prompting'
  42. )
  43. ->addArgument(
  44. 'package',
  45. InputArgument::IS_ARRAY | InputArgument::REQUIRED,
  46. 'The package(s) that are desired to be removed. Use the "index" command for a list of packages'
  47. )
  48. ->setDescription('Performs the uninstallation of plugins and themes')
  49. ->setHelp('The <info>uninstall</info> command allows to uninstall plugins and themes');
  50. }
  51. protected function serve()
  52. {
  53. $this->gpm = new GPM();
  54. $this->all_yes = $this->input->getOption('all-yes');
  55. $packages = array_map('strtolower', $this->input->getArgument('package'));
  56. $this->data = ['total' => 0, 'not_found' => []];
  57. $total = 0;
  58. foreach ($packages as $package) {
  59. $plugin = $this->gpm->getInstalledPlugin($package);
  60. $theme = $this->gpm->getInstalledTheme($package);
  61. if ($plugin || $theme) {
  62. $this->data[strtolower($package)] = $plugin ?: $theme;
  63. $total++;
  64. } else {
  65. $this->data['not_found'][] = $package;
  66. }
  67. }
  68. $this->data['total'] = $total;
  69. $this->output->writeln('');
  70. if (!$this->data['total']) {
  71. $this->output->writeln('Nothing to uninstall.');
  72. $this->output->writeln('');
  73. exit;
  74. }
  75. if (count($this->data['not_found'])) {
  76. $this->output->writeln('These packages were not found installed: <red>' . implode('</red>, <red>',
  77. $this->data['not_found']) . '</red>');
  78. }
  79. unset($this->data['not_found'], $this->data['total']);
  80. foreach ($this->data as $slug => $package) {
  81. $this->output->writeln("Preparing to uninstall <cyan>{$package->name}</cyan> [v{$package->version}]");
  82. $this->output->write(' |- Checking destination... ');
  83. $checks = $this->checkDestination($slug, $package);
  84. if (!$checks) {
  85. $this->output->writeln(" '- <red>Installation failed or aborted.</red>");
  86. $this->output->writeln('');
  87. } else {
  88. $uninstall = $this->uninstallPackage($slug, $package);
  89. if (!$uninstall) {
  90. $this->output->writeln(" '- <red>Uninstallation failed or aborted.</red>");
  91. } else {
  92. $this->output->writeln(" '- <green>Success!</green> ");
  93. }
  94. }
  95. }
  96. // clear cache after successful upgrade
  97. $this->clearCache();
  98. }
  99. /**
  100. * @param string $slug
  101. * @param Package $package
  102. *
  103. * @return bool
  104. */
  105. private function uninstallPackage($slug, $package, $is_dependency = false)
  106. {
  107. if (!$slug) {
  108. return false;
  109. }
  110. //check if there are packages that have this as a dependency. Abort and show list
  111. $dependent_packages = $this->gpm->getPackagesThatDependOnPackage($slug);
  112. if (count($dependent_packages) > ($is_dependency ? 1 : 0)) {
  113. $this->output->writeln('');
  114. $this->output->writeln('');
  115. $this->output->writeln('<red>Uninstallation failed.</red>');
  116. $this->output->writeln('');
  117. if (\count($dependent_packages) > ($is_dependency ? 2 : 1)) {
  118. $this->output->writeln('The installed packages <cyan>' . implode('</cyan>, <cyan>', $dependent_packages) . '</cyan> depends on this package. Please remove those first.');
  119. } else {
  120. $this->output->writeln('The installed package <cyan>' . implode('</cyan>, <cyan>', $dependent_packages) . '</cyan> depends on this package. Please remove it first.');
  121. }
  122. $this->output->writeln('');
  123. return false;
  124. }
  125. if (isset($package->dependencies)) {
  126. $dependencies = $package->dependencies;
  127. if ($is_dependency) {
  128. foreach ($dependencies as $key => $dependency) {
  129. if (\in_array($dependency['name'], $this->dependencies, true)) {
  130. unset($dependencies[$key]);
  131. }
  132. }
  133. } else {
  134. if (\count($dependencies) > 0) {
  135. $this->output->writeln(' `- Dependencies found...');
  136. $this->output->writeln('');
  137. }
  138. }
  139. $questionHelper = $this->getHelper('question');
  140. foreach ($dependencies as $dependency) {
  141. $this->dependencies[] = $dependency['name'];
  142. if (\is_array($dependency)) {
  143. $dependency = $dependency['name'];
  144. }
  145. if ($dependency === 'grav' || $dependency === 'php') {
  146. continue;
  147. }
  148. $dependencyPackage = $this->gpm->findPackage($dependency);
  149. $dependency_exists = $this->packageExists($dependency, $dependencyPackage);
  150. if ($dependency_exists == Installer::EXISTS) {
  151. $this->output->writeln("A dependency on <cyan>{$dependencyPackage->name}</cyan> [v{$dependencyPackage->version}] was found");
  152. $question = new ConfirmationQuestion(" |- Uninstall <cyan>{$dependencyPackage->name}</cyan>? [y|N] ", false);
  153. $answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question);
  154. if ($answer) {
  155. $uninstall = $this->uninstallPackage($dependency, $dependencyPackage, true);
  156. if (!$uninstall) {
  157. $this->output->writeln(" '- <red>Uninstallation failed or aborted.</red>");
  158. } else {
  159. $this->output->writeln(" '- <green>Success!</green> ");
  160. }
  161. $this->output->writeln('');
  162. } else {
  163. $this->output->writeln(" '- <yellow>You decided not to uninstall {$dependencyPackage->name}.</yellow>");
  164. $this->output->writeln('');
  165. }
  166. }
  167. }
  168. }
  169. $locator = Grav::instance()['locator'];
  170. $path = $locator->findResource($package->package_type . '://' . $slug);
  171. Installer::uninstall($path);
  172. $errorCode = Installer::lastErrorCode();
  173. if ($errorCode && $errorCode !== Installer::IS_LINK && $errorCode !== Installer::EXISTS) {
  174. $this->output->writeln(" |- Uninstalling {$package->name} package... <red>error</red> ");
  175. $this->output->writeln(" | '- <yellow>" . Installer::lastErrorMsg() . '</yellow>');
  176. return false;
  177. }
  178. $message = Installer::getMessage();
  179. if ($message) {
  180. $this->output->writeln(" |- {$message}");
  181. }
  182. if (!$is_dependency && $this->dependencies) {
  183. $this->output->writeln("Finishing up uninstalling <cyan>{$package->name}</cyan>");
  184. }
  185. $this->output->writeln(" |- Uninstalling {$package->name} package... <green>ok</green> ");
  186. return true;
  187. }
  188. /**
  189. * @param string $slug
  190. * @param Package $package
  191. *
  192. * @return bool
  193. */
  194. private function checkDestination($slug, $package)
  195. {
  196. $questionHelper = $this->getHelper('question');
  197. $exists = $this->packageExists($slug, $package);
  198. if ($exists == Installer::IS_LINK) {
  199. $this->output->write("\x0D");
  200. $this->output->writeln(' |- Checking destination... <yellow>symbolic link</yellow>');
  201. if ($this->all_yes) {
  202. $this->output->writeln(" | '- <yellow>Skipped automatically.</yellow>");
  203. return false;
  204. }
  205. $question = new ConfirmationQuestion(" | '- Destination has been detected as symlink, delete symbolic link first? [y|N] ",
  206. false);
  207. $answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question);
  208. if (!$answer) {
  209. $this->output->writeln(" | '- <red>You decided not to delete the symlink automatically.</red>");
  210. return false;
  211. }
  212. }
  213. $this->output->write("\x0D");
  214. $this->output->writeln(' |- Checking destination... <green>ok</green>');
  215. return true;
  216. }
  217. /**
  218. * Check if package exists
  219. *
  220. * @param string $slug
  221. * @param Package $package
  222. * @return int
  223. */
  224. private function packageExists($slug, $package)
  225. {
  226. $path = Grav::instance()['locator']->findResource($package->package_type . '://' . $slug);
  227. Installer::isValidDestination($path);
  228. return Installer::lastErrorCode();
  229. }
  230. }