/** * Runs uninstallation logic inside a safe transactional thread. This prevent * DB inconsistencies on uninstall failure. * * @return bool True on success, false otherwise */ protected function _runTransactional() { // to avoid any possible issue snapshot(); if (!is_writable(TMP)) { $this->err(__d('installer', 'Enable write permissions in /tmp directory before uninstall any plugin or theme.')); return false; } if (!$this->params['plugin']) { $this->err(__d('installer', 'No plugin/theme was given to remove.')); return false; } $this->loadModel('System.Plugins'); try { $plugin = plugin($this->params['plugin']); $pluginEntity = $this->Plugins->find()->where(['name' => $this->params['plugin']])->limit(1)->first(); } catch (\Exception $ex) { $plugin = $pluginEntity = false; } if (!$plugin || !$pluginEntity) { $this->err(__d('installer', 'Plugin "{0}" was not found.', $this->params['plugin'])); return false; } $this->_plugin = $plugin; $type = $plugin->isTheme ? 'theme' : 'plugin'; if ($plugin->isTheme && in_array($plugin->name, [option('front_theme'), option('back_theme')])) { $this->err(__d('installer', '{0} "{1}" is currently being used and cannot be removed.', $type == 'plugin' ? __d('installer', 'The plugin') : __d('installer', 'The theme'), $plugin->humanName)); return false; } $requiredBy = Plugin::checkReverseDependency($this->params['plugin']); if (!empty($requiredBy)) { $names = []; foreach ($requiredBy as $p) { $names[] = $p->name(); } $this->err(__d('installer', '{0} "{1}" cannot be removed as it is required by: {2}', $type == 'plugin' ? __d('installer', 'The plugin') : __d('installer', 'The theme'), $plugin->humanName, implode(', ', $names))); return false; } if (!$this->_canBeDeleted($plugin->path)) { return false; } if (!$this->params['no-callbacks']) { try { $event = $this->trigger("Plugin.{$plugin->name}.beforeUninstall"); if ($event->isStopped() || $event->result === false) { $this->err(__d('installer', 'Task was explicitly rejected by {0}.', $type == 'plugin' ? __d('installer', 'the plugin') : __d('installer', 'the theme'))); return false; } } catch (\Exception $e) { $this->err(__d('installer', 'Internal error, {0} did not respond to "beforeUninstall" callback correctly.', $type == 'plugin' ? __d('installer', 'the plugin') : __d('installer', 'the theme'))); return false; } } if (!$this->Plugins->delete($pluginEntity)) { $this->err(__d('installer', '{0} "{1}" could not be unregistered from DB.', $type == 'plugin' ? __d('installer', 'The plugin') : __d('installer', 'The theme'), $plugin->humanName)); return false; } $this->_removeOptions(); $this->_clearAcoPaths(); $folder = new Folder($plugin->path); $folder->delete(); snapshot(); if (!$this->params['no-callbacks']) { try { $this->trigger("Plugin.{$plugin->name}.afterUninstall"); } catch (\Exception $e) { $this->err(__d('installer', '{0} did not respond to "afterUninstall" callback.', $type == 'plugin' ? __d('installer', 'The plugin') : __d('installer', 'The theme'))); } } Plugin::unload($plugin->name); Plugin::dropCache(); return true; }