composer = $composer; $this->deploymentConfig = $deploymentConfig; $this->fullModuleList = $fullModuleList; $this->packageInfo = $this->objectManager->get(\Magento\Framework\Module\PackageInfoFactory::class)->create(); $this->collector = $collector; $this->dependencyChecker = $this->objectManager->get(\Magento\Framework\Module\DependencyChecker::class); $this->backupRollbackFactory = $this->objectManager->get(\Magento\Framework\Setup\BackupRollbackFactory::class); $this->moduleUninstaller = $moduleUninstaller; $this->moduleRegistryUninstaller = $moduleRegistryUninstaller; $this->maintenanceModeEnabler = $maintenanceModeEnabler ?: $this->objectManager->get(MaintenanceModeEnabler::class); } /** * @return PatchApplier */ private function getPatchApplier() { if (!$this->patchApplier) { $this->patchApplier = $this ->objectManager->get(PatchApplier::class); } return $this->patchApplier; } /** * {@inheritdoc} */ protected function configure() { $options = [ new InputOption( self::INPUT_KEY_REMOVE_DATA, 'r', InputOption::VALUE_NONE, 'Remove data installed by module(s)' ), new InputOption( self::INPUT_KEY_BACKUP_CODE, null, InputOption::VALUE_NONE, 'Take code and configuration files backup (excluding temporary files)' ), new InputOption( self::INPUT_KEY_BACKUP_MEDIA, null, InputOption::VALUE_NONE, 'Take media backup' ), new InputOption( self::INPUT_KEY_BACKUP_DB, null, InputOption::VALUE_NONE, 'Take complete database backup' ), new InputOption( self::INPUT_KEY_NON_COMPOSER_MODULE, null, InputOption::VALUE_NONE, 'All modules, that will be past here will be non composer based' ) ]; $this->setName('module:uninstall') ->setDescription('Uninstalls modules installed by composer') ->setDefinition($options); parent::configure(); } /** * {@inheritdoc} */ protected function isModuleRequired() { return true; } /** * {@inheritdoc} * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function execute(InputInterface $input, OutputInterface $output) { if (!$this->deploymentConfig->isAvailable()) { $output->writeln( 'You cannot run this command because the Magento application is not installed.' ); // we must have an exit code higher than zero to indicate something was wrong return Cli::RETURN_FAILURE; } $modules = $input->getArgument(self::INPUT_KEY_MODULES); if ($input->getOption(self::INPUT_KEY_NON_COMPOSER_MODULE)) { foreach ($modules as $moduleName) { $this->getPatchApplier()->revertDataPatches($moduleName); } return Cli::RETURN_SUCCESS; } // validate modules input $messages = $this->validate($modules); if (!empty($messages)) { $output->writeln($messages); // we must have an exit code higher than zero to indicate something was wrong return Cli::RETURN_FAILURE; } // check dependencies $dependencyMessages = $this->checkDependencies($modules); if (!empty($dependencyMessages)) { $output->writeln($dependencyMessages); // we must have an exit code higher than zero to indicate something was wrong return Cli::RETURN_FAILURE; } $helper = $this->getHelper('question'); $question = new ConfirmationQuestion( 'You are about to remove code and/or database tables. Are you sure?[y/N]', false ); if (!$helper->ask($input, $output, $question) && $input->isInteractive()) { return Cli::RETURN_FAILURE; } $result = $this->maintenanceModeEnabler->executeInMaintenanceMode( function () use ($input, $output, $modules, $helper) { try { $this->takeBackup($input, $output); $dbBackupOption = $input->getOption(self::INPUT_KEY_BACKUP_DB); if ($input->getOption(self::INPUT_KEY_REMOVE_DATA)) { $this->removeData($modules, $output, $dbBackupOption); } else { if (!empty($this->collector->collectUninstall())) { $question = new ConfirmationQuestion( 'You are about to remove a module(s) that might have database data. ' . 'Do you want to remove the data from database?[y/N]', false ); if ($helper->ask($input, $output, $question) || !$input->isInteractive()) { $this->removeData($modules, $output, $dbBackupOption); } } else { $output->writeln( 'You are about to remove a module(s) that might have database data. ' . 'Remove the database data manually after uninstalling, if desired.' ); } } $this->moduleRegistryUninstaller->removeModulesFromDb($output, $modules); $this->moduleRegistryUninstaller->removeModulesFromDeploymentConfig($output, $modules); $this->moduleUninstaller->uninstallCode($output, $modules); $this->cleanup($input, $output); return Cli::RETURN_SUCCESS; } catch (\Exception $e) { $output->writeln('' . $e->getMessage() . ''); $output->writeln('Please disable maintenance mode after you resolved above issues'); return Cli::RETURN_FAILURE; } }, $output, true ); return $result; } /** * Check backup options and take backup appropriately * * @param InputInterface $input * @param OutputInterface $output * @return void */ private function takeBackup(InputInterface $input, OutputInterface $output) { $time = time(); if ($input->getOption(self::INPUT_KEY_BACKUP_CODE)) { $codeBackup = $this->backupRollbackFactory->create($output); $codeBackup->codeBackup($time); } if ($input->getOption(self::INPUT_KEY_BACKUP_MEDIA)) { $mediaBackup = $this->backupRollbackFactory->create($output); $mediaBackup->codeBackup($time, Factory::TYPE_MEDIA); } if ($input->getOption(self::INPUT_KEY_BACKUP_DB)) { $dbBackup = $this->backupRollbackFactory->create($output); $this->setAreaCode(); $dbBackup->dbBackup($time); } } /** * Invoke remove data routine in each specified module * * @param string[] $modules * @param OutputInterface $output * @param bool $dbBackupOption * @return void */ private function removeData(array $modules, OutputInterface $output, $dbBackupOption) { if (!$dbBackupOption) { $output->writeln('You are removing data without a database backup.'); } else { $output->writeln('Removing data'); } $this->moduleUninstaller->uninstallData($output, $modules); } /** * Validate list of modules against installed composer packages and return error messages * * @param string[] $modules * @return string[] */ protected function validate(array $modules) { $messages = []; $unknownPackages = []; $unknownModules = []; $installedPackages = $this->composer->getRootRequiredPackages(); foreach ($modules as $module) { if (array_search($this->packageInfo->getPackageName($module), $installedPackages) === false) { $unknownPackages[] = $module; } if (!$this->fullModuleList->has($module)) { $unknownModules[] = $module; } } $unknownPackages = array_diff($unknownPackages, $unknownModules); if (!empty($unknownPackages)) { $text = count($unknownPackages) > 1 ? ' are not installed composer packages' : ' is not an installed composer package'; $messages[] = '' . implode(', ', $unknownPackages) . $text . ''; } if (!empty($unknownModules)) { $messages[] = 'Unknown module(s): ' . implode(', ', $unknownModules) . ''; } return $messages; } /** * Check for dependencies to modules, return error messages * * @param string[] $modules * @return string[] */ private function checkDependencies(array $modules) { $messages = []; $dependencies = $this->dependencyChecker->checkDependenciesWhenDisableModules( $modules, $this->fullModuleList->getNames() ); foreach ($dependencies as $module => $dependingModules) { if (!empty($dependingModules)) { $messages[] = "Cannot uninstall module '$module' because the following module(s) depend on it:" . PHP_EOL . "\t" . implode('' . PHP_EOL . "\t", array_keys($dependingModules)) . ""; } } return $messages; } /** * Sets area code to start a session for database backup and rollback * * @return void */ private function setAreaCode() { $areaCode = 'adminhtml'; /** @var \Magento\Framework\App\State $appState */ $appState = $this->objectManager->get(\Magento\Framework\App\State::class); $appState->setAreaCode($areaCode); /** @var \Magento\Framework\ObjectManager\ConfigLoaderInterface $configLoader */ $configLoader = $this->objectManager->get(\Magento\Framework\ObjectManager\ConfigLoaderInterface::class); $this->objectManager->configure($configLoader->load($areaCode)); } }