UpdateCommand.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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\Console\ConsoleCommand;
  12. use Grav\Common\GPM\Upgrader;
  13. use Symfony\Component\Console\Input\ArrayInput;
  14. use Symfony\Component\Console\Input\InputArgument;
  15. use Symfony\Component\Console\Input\InputOption;
  16. use Symfony\Component\Console\Question\ConfirmationQuestion;
  17. class UpdateCommand extends ConsoleCommand
  18. {
  19. /** @var array */
  20. protected $data;
  21. protected $extensions;
  22. protected $updatable;
  23. /** @var string */
  24. protected $destination;
  25. /** @var string */
  26. protected $file;
  27. /** @var array */
  28. protected $types = ['plugins', 'themes'];
  29. /** @var GPM */
  30. protected $gpm;
  31. /** @var string */
  32. protected $all_yes;
  33. protected $overwrite;
  34. /** @var Upgrader */
  35. protected $upgrader;
  36. protected function configure()
  37. {
  38. $this
  39. ->setName('update')
  40. ->addOption(
  41. 'force',
  42. 'f',
  43. InputOption::VALUE_NONE,
  44. 'Force re-fetching the data from remote'
  45. )
  46. ->addOption(
  47. 'destination',
  48. 'd',
  49. InputOption::VALUE_OPTIONAL,
  50. 'The grav instance location where the updates should be applied to. By default this would be where the grav cli has been launched from',
  51. GRAV_ROOT
  52. )
  53. ->addOption(
  54. 'all-yes',
  55. 'y',
  56. InputOption::VALUE_NONE,
  57. 'Assumes yes (or best approach) instead of prompting'
  58. )
  59. ->addOption(
  60. 'overwrite',
  61. 'o',
  62. InputOption::VALUE_NONE,
  63. 'Option to overwrite packages if they already exist'
  64. )
  65. ->addOption(
  66. 'plugins',
  67. 'p',
  68. InputOption::VALUE_NONE,
  69. 'Update only plugins'
  70. )
  71. ->addOption(
  72. 'themes',
  73. 't',
  74. InputOption::VALUE_NONE,
  75. 'Update only themes'
  76. )
  77. ->addArgument(
  78. 'package',
  79. InputArgument::IS_ARRAY | InputArgument::OPTIONAL,
  80. 'The package or packages that is desired to update. By default all available updates will be applied.'
  81. )
  82. ->setDescription('Detects and performs an update of plugins and themes when available')
  83. ->setHelp('The <info>update</info> command updates plugins and themes when a new version is available');
  84. }
  85. protected function serve()
  86. {
  87. $this->upgrader = new Upgrader($this->input->getOption('force'));
  88. $local = $this->upgrader->getLocalVersion();
  89. $remote = $this->upgrader->getRemoteVersion();
  90. if ($local !== $remote) {
  91. $this->output->writeln('<yellow>WARNING</yellow>: A new version of Grav is available. You should update Grav before updating plugins and themes. If you continue without updating Grav, some plugins or themes may stop working.');
  92. $this->output->writeln('');
  93. $questionHelper = $this->getHelper('question');
  94. $question = new ConfirmationQuestion('Continue with the update process? [Y|n] ', true);
  95. $answer = $questionHelper->ask($this->input, $this->output, $question);
  96. if (!$answer) {
  97. $this->output->writeln('<red>Update aborted. Exiting...</red>');
  98. exit;
  99. }
  100. }
  101. $this->gpm = new GPM($this->input->getOption('force'));
  102. $this->all_yes = $this->input->getOption('all-yes');
  103. $this->overwrite = $this->input->getOption('overwrite');
  104. $this->displayGPMRelease();
  105. $this->destination = realpath($this->input->getOption('destination'));
  106. if (!Installer::isGravInstance($this->destination)) {
  107. $this->output->writeln('<red>ERROR</red>: ' . Installer::lastErrorMsg());
  108. exit;
  109. }
  110. if ($this->input->getOption('plugins') === false && $this->input->getOption('themes') === false) {
  111. $list_type = ['plugins' => true, 'themes' => true];
  112. } else {
  113. $list_type['plugins'] = $this->input->getOption('plugins');
  114. $list_type['themes'] = $this->input->getOption('themes');
  115. }
  116. if ($this->overwrite) {
  117. $this->data = $this->gpm->getInstallable($list_type);
  118. $description = ' can be overwritten';
  119. } else {
  120. $this->data = $this->gpm->getUpdatable($list_type);
  121. $description = ' need updating';
  122. }
  123. $only_packages = array_map('strtolower', $this->input->getArgument('package'));
  124. if (!$this->overwrite && !$this->data['total']) {
  125. $this->output->writeln('Nothing to update.');
  126. exit;
  127. }
  128. $this->output->write("Found <green>{$this->gpm->countInstalled()}</green> packages installed of which <magenta>{$this->data['total']}</magenta>{$description}");
  129. $limit_to = $this->userInputPackages($only_packages);
  130. $this->output->writeln('');
  131. unset($this->data['total'], $limit_to['total']);
  132. // updates review
  133. $slugs = [];
  134. $index = 0;
  135. foreach ($this->data as $packages) {
  136. foreach ($packages as $slug => $package) {
  137. if (!array_key_exists($slug, $limit_to) && \count($only_packages)) {
  138. continue;
  139. }
  140. if (!$package->available) {
  141. $package->available = $package->version;
  142. }
  143. $this->output->writeln(
  144. // index
  145. str_pad($index++ + 1, 2, '0', STR_PAD_LEFT) . '. ' .
  146. // name
  147. '<cyan>' . str_pad($package->name, 15) . '</cyan> ' .
  148. // version
  149. "[v<magenta>{$package->version}</magenta> -> v<green>{$package->available}</green>]"
  150. );
  151. $slugs[] = $slug;
  152. }
  153. }
  154. if (!$this->all_yes) {
  155. // prompt to continue
  156. $this->output->writeln('');
  157. $questionHelper = $this->getHelper('question');
  158. $question = new ConfirmationQuestion('Continue with the update process? [Y|n] ', true);
  159. $answer = $questionHelper->ask($this->input, $this->output, $question);
  160. if (!$answer) {
  161. $this->output->writeln('<red>Update aborted. Exiting...</red>');
  162. exit;
  163. }
  164. }
  165. // finally update
  166. $install_command = $this->getApplication()->find('install');
  167. $args = new ArrayInput([
  168. 'command' => 'install',
  169. 'package' => $slugs,
  170. '-f' => $this->input->getOption('force'),
  171. '-d' => $this->destination,
  172. '-y' => true
  173. ]);
  174. $command_exec = $install_command->run($args, $this->output);
  175. if ($command_exec != 0) {
  176. $this->output->writeln('<red>Error:</red> An error occurred while trying to install the packages');
  177. exit;
  178. }
  179. }
  180. /**
  181. * @param array $only_packages
  182. *
  183. * @return array
  184. */
  185. private function userInputPackages($only_packages)
  186. {
  187. $found = ['total' => 0];
  188. $ignore = [];
  189. if (!\count($only_packages)) {
  190. $this->output->writeln('');
  191. } else {
  192. foreach ($only_packages as $only_package) {
  193. $find = $this->gpm->findPackage($only_package);
  194. if (!$find || (!$this->overwrite && !$this->gpm->isUpdatable($find->slug))) {
  195. $name = $find->slug ?? $only_package;
  196. $ignore[$name] = $name;
  197. } else {
  198. $found[$find->slug] = $find;
  199. $found['total']++;
  200. }
  201. }
  202. if ($found['total']) {
  203. $list = $found;
  204. unset($list['total']);
  205. $list = array_keys($list);
  206. if ($found['total'] !== $this->data['total']) {
  207. $this->output->write(", only <magenta>{$found['total']}</magenta> will be updated");
  208. }
  209. $this->output->writeln('');
  210. $this->output->writeln('Limiting updates for only <cyan>' . implode('</cyan>, <cyan>',
  211. $list) . '</cyan>');
  212. }
  213. if (\count($ignore)) {
  214. $this->output->writeln('');
  215. $this->output->writeln('Packages not found or not requiring updates: <red>' . implode('</red>, <red>',
  216. $ignore) . '</red>');
  217. }
  218. }
  219. return $found;
  220. }
  221. }