setName('uninstall') ->addOption( 'all-yes', 'y', InputOption::VALUE_NONE, 'Assumes yes (or best approach) instead of prompting' ) ->addArgument( 'package', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'The package(s) that are desired to be removed. Use the "index" command for a list of packages' ) ->setDescription('Performs the uninstallation of plugins and themes') ->setHelp('The uninstall command allows to uninstall plugins and themes'); } /** * @return int */ protected function serve(): int { $input = $this->getInput(); $io = $this->getIO(); $this->gpm = new GPM(); $this->all_yes = $input->getOption('all-yes'); $packages = array_map('strtolower', $input->getArgument('package')); $this->data = ['total' => 0, 'not_found' => []]; $total = 0; foreach ($packages as $package) { $plugin = $this->gpm->getInstalledPlugin($package); $theme = $this->gpm->getInstalledTheme($package); if ($plugin || $theme) { $this->data[strtolower($package)] = $plugin ?: $theme; $total++; } else { $this->data['not_found'][] = $package; } } $this->data['total'] = $total; $io->newLine(); if (!$this->data['total']) { $io->writeln('Nothing to uninstall.'); $io->newLine(); return 0; } if (count($this->data['not_found'])) { $io->writeln('These packages were not found installed: ' . implode( ', ', $this->data['not_found'] ) . ''); } unset($this->data['not_found'], $this->data['total']); // Plugins need to be initialized in order to make clearcache to work. try { $this->initializePlugins(); } catch (Throwable $e) { $io->writeln("Some plugins failed to initialize: {$e->getMessage()}"); } $error = 0; foreach ($this->data as $slug => $package) { $io->writeln("Preparing to uninstall {$package->name} [v{$package->version}]"); $io->write(' |- Checking destination... '); $checks = $this->checkDestination($slug, $package); if (!$checks) { $io->writeln(" '- Installation failed or aborted."); $io->newLine(); $error = 1; } else { $uninstall = $this->uninstallPackage($slug, $package); if (!$uninstall) { $io->writeln(" '- Uninstallation failed or aborted."); $error = 1; } else { $io->writeln(" '- Success! "); } } } // clear cache after successful upgrade $this->clearCache(); return $error; } /** * @param string $slug * @param Local\Package|Remote\Package $package * @param bool $is_dependency * @return bool */ private function uninstallPackage($slug, $package, $is_dependency = false): bool { $io = $this->getIO(); if (!$slug) { return false; } //check if there are packages that have this as a dependency. Abort and show list $dependent_packages = $this->gpm->getPackagesThatDependOnPackage($slug); if (count($dependent_packages) > ($is_dependency ? 1 : 0)) { $io->newLine(2); $io->writeln('Uninstallation failed.'); $io->newLine(); if (count($dependent_packages) > ($is_dependency ? 2 : 1)) { $io->writeln('The installed packages ' . implode(', ', $dependent_packages) . ' depends on this package. Please remove those first.'); } else { $io->writeln('The installed package ' . implode(', ', $dependent_packages) . ' depends on this package. Please remove it first.'); } $io->newLine(); return false; } if (isset($package->dependencies)) { $dependencies = $package->dependencies; if ($is_dependency) { foreach ($dependencies as $key => $dependency) { if (in_array($dependency['name'], $this->dependencies, true)) { unset($dependencies[$key]); } } } elseif (count($dependencies) > 0) { $io->writeln(' `- Dependencies found...'); $io->newLine(); } foreach ($dependencies as $dependency) { $this->dependencies[] = $dependency['name']; if (is_array($dependency)) { $dependency = $dependency['name']; } if ($dependency === 'grav' || $dependency === 'php') { continue; } $dependencyPackage = $this->gpm->findPackage($dependency); $dependency_exists = $this->packageExists($dependency, $dependencyPackage); if ($dependency_exists == Installer::EXISTS) { $io->writeln("A dependency on {$dependencyPackage->name} [v{$dependencyPackage->version}] was found"); $question = new ConfirmationQuestion(" |- Uninstall {$dependencyPackage->name}? [y|N] ", false); $answer = $this->all_yes ? true : $io->askQuestion($question); if ($answer) { $uninstall = $this->uninstallPackage($dependency, $dependencyPackage, true); if (!$uninstall) { $io->writeln(" '- Uninstallation failed or aborted."); } else { $io->writeln(" '- Success! "); } $io->newLine(); } else { $io->writeln(" '- You decided not to uninstall {$dependencyPackage->name}."); $io->newLine(); } } } } $locator = Grav::instance()['locator']; $path = $locator->findResource($package->package_type . '://' . $slug); Installer::uninstall($path); $errorCode = Installer::lastErrorCode(); if ($errorCode && $errorCode !== Installer::IS_LINK && $errorCode !== Installer::EXISTS) { $io->writeln(" |- Uninstalling {$package->name} package... error "); $io->writeln(" | '- " . Installer::lastErrorMsg() . ''); return false; } $message = Installer::getMessage(); if ($message) { $io->writeln(" |- {$message}"); } if (!$is_dependency && $this->dependencies) { $io->writeln("Finishing up uninstalling {$package->name}"); } $io->writeln(" |- Uninstalling {$package->name} package... ok "); return true; } /** * @param string $slug * @param Local\Package|Remote\Package $package * @return bool */ private function checkDestination(string $slug, $package): bool { $io = $this->getIO(); $exists = $this->packageExists($slug, $package); if ($exists === Installer::IS_LINK) { $io->write("\x0D"); $io->writeln(' |- Checking destination... symbolic link'); if ($this->all_yes) { $io->writeln(" | '- Skipped automatically."); return false; } $question = new ConfirmationQuestion( " | '- Destination has been detected as symlink, delete symbolic link first? [y|N] ", false ); $answer = $io->askQuestion($question); if (!$answer) { $io->writeln(" | '- You decided not to delete the symlink automatically."); return false; } } $io->write("\x0D"); $io->writeln(' |- Checking destination... ok'); return true; } /** * Check if package exists * * @param string $slug * @param Local\Package|Remote\Package $package * @return int */ private function packageExists(string $slug, $package): int { $path = Grav::instance()['locator']->findResource($package->package_type . '://' . $slug); Installer::isValidDestination($path); return Installer::lastErrorCode(); } }