/** * Look for plugin/themes awaiting for installation and sets a flash message * with instructions about how to proceed. * * @param string $type Possible values `plugin` (default) or `theme`, defaults * to "plugin" * @return void */ protected function _awaitingPlugins($type = 'plugin') { $type = !in_array($type, ['plugin', 'theme']) ? 'plugin' : $type; $ignoreThemes = $type === 'plugin'; $plugins = Plugin::scan($ignoreThemes); foreach ($plugins as $name => $path) { if (Plugin::exists($name) || $type == 'theme' && !str_ends_with($name, 'Theme')) { unset($plugins[$name]); } } if (!empty($plugins)) { $this->Flash->set(__d('system', '{0} are awaiting for installation', $type == 'plugin' ? __d('system', 'Some plugins') : __d('system', 'Some themes')), ['element' => 'System.stashed_plugins', 'params' => compact('plugins')]); } }
/** * Validates the content of working directory. * * @return bool True on success */ protected function _validateContent() { if (!$this->_workingDir) { return false; } $errors = []; if (!is_readable("{$this->_workingDir}src") || !is_dir("{$this->_workingDir}src")) { $errors[] = __d('installer', 'Invalid package, missing "src" directory.'); } if (!is_readable("{$this->_workingDir}composer.json")) { $errors[] = __d('installer', 'Invalid package, missing "composer.json" file.'); } else { $jsonErrors = Plugin::validateJson("{$this->_workingDir}composer.json", true); if (!empty($jsonErrors)) { $errors[] = __d('installer', 'Invalid "composer.json".'); $errors = array_merge($errors, (array) $jsonErrors); } else { $json = (new File("{$this->_workingDir}composer.json"))->read(); $json = json_decode($json, true); list(, $pluginName) = packageSplit($json['name'], true); if ($this->params['theme'] && !str_ends_with($pluginName, 'Theme')) { $this->err(__d('installer', 'The given package is not a valid theme.')); return false; } elseif (!$this->params['theme'] && str_ends_with($pluginName, 'Theme')) { $this->err(__d('installer', 'The given package is not a valid plugin.')); return false; } $this->_plugin = ['name' => $pluginName, 'packageName' => $json['name'], 'type' => str_ends_with($pluginName, 'Theme') ? 'theme' : 'plugin', 'composer' => $json]; if (Plugin::exists($this->_plugin['name'])) { $exists = plugin($this->_plugin['name']); if ($exists->status) { $errors[] = __d('installer', '{0} "{1}" is already installed.', [$this->_plugin['type'] == 'plugin' ? __d('installer', 'The plugin') : __d('installer', 'The theme'), $this->_plugin['name']]); } else { $errors[] = __d('installer', '{0} "{1}" is already installed but disabled, maybe you want try to enable it?.', [$this->_plugin['type'] == 'plugin' ? __d('installer', 'The plugin') : __d('installer', 'The theme'), $this->_plugin['name']]); } } if ($this->_plugin['type'] == 'theme' && !is_readable("{$this->_workingDir}webroot/screenshot.png")) { $errors[] = __d('installer', 'Missing "screenshot.png" file.'); } if (isset($json['require'])) { $checker = new RuleChecker($json['require']); if (!$checker->check()) { $errors[] = __d('installer', '{0} "{1}" depends on other packages, plugins or libraries that were not found: {2}', [$this->_plugin['type'] == 'plugin' ? __d('installer', 'The plugin') : __d('installer', 'The theme'), $this->_plugin['name'], $checker->fail(true)]); } } } } if (!file_exists(ROOT . '/plugins') || !is_dir(ROOT . '/plugins') || !is_writable(ROOT . '/plugins')) { $errors[] = __d('installer', 'Write permissions required for directory: {0}.', [ROOT . '/plugins/']); } foreach ($errors as $message) { $this->err($message); } return empty($errors); }
/** * Tries to get a QuickAppsCMS plugin. * * @param string $package Full package name * @return bool|\CMS\Core\Package\PluginPackage */ protected static function _getPlugin($package) { list(, $plugin) = packageSplit($package, true); if (Plugin::exists($plugin)) { return new PluginPackage(quickapps("plugins.{$plugin}.name"), quickapps("plugins.{$plugin}.path")); } return false; }
/** * Switch site's theme. * * @return void */ public function main() { if (empty($this->params['theme'])) { $this->err(__d('installer', 'You must provide a theme.')); return false; } if (!Plugin::exists($this->params['theme'])) { $this->err(__d('installer', 'Theme "{0}" was not found.', $this->params['theme'])); return false; } $plugin = plugin($this->params['theme']); if (!$plugin->isTheme) { $this->err(__d('installer', '"{0}" is not a theme.', $plugin->humanName)); return false; } if (in_array($this->params['theme'], [option('front_theme'), option('back_theme')])) { $this->err(__d('installer', 'Theme "{0}" is already active.', $plugin->humanName)); return false; } // MENTAL NOTE: As theme is "inactive" its listeners are not attached to the // system, so we need to manually attach them in order to trigger callbacks. if (!$this->params['no-callbacks']) { $this->_attachListeners($plugin->name, "{$plugin->path}/"); try { $event = $this->trigger("Plugin.{$plugin->name}.beforeActivate"); if ($event->isStopped() || $event->result === false) { $this->err(__d('installer', 'Task was explicitly rejected by the theme.')); $this->_detachListeners(); return false; } } catch (\Exception $ex) { $this->err(__d('installer', 'Internal error, theme did not respond to "beforeActivate" callback properly.')); $this->_detachListeners(); return false; } } if (isset($plugin->composer['extra']['admin']) && $plugin->composer['extra']['admin']) { $prefix = 'back_'; $previousTheme = option('back_theme'); } else { $prefix = 'front_'; $previousTheme = option('front_theme'); } $this->loadModel('System.Options'); $this->loadModel('System.Plugins'); if ($this->Plugins->updateAll(['status' => 0], ['name' => $previousTheme]) && $this->Plugins->updateAll(['status' => 1], ['name' => $this->params['theme']])) { if ($this->Options->update("{$prefix}theme", $this->params['theme'])) { $this->_copyBlockPositions($this->params['theme'], $previousTheme); } else { $this->err(__d('installer', 'Internal error, the option "{0}" could not be persisted on database.', "{$prefix}theme")); $this->_detachListeners(); return false; } } else { $this->err(__d('installer', 'Internal error, unable to turnoff current theme ({0}) and active new one ({1}).', $previousTheme, $this->params['theme'])); return false; } if (!$this->params['no-callbacks']) { try { $this->trigger("Plugin.{$plugin->name}.afterActivate"); } catch (\Exception $e) { $this->err(__d('installer', 'Theme did not respond to "afterActivate" callback.')); } } return true; }
/** * {@inheritDoc} * * It will look for plugin's version in the following places: * * - Plugin's "composer.json" file. * - Plugin's "VERSION.txt" file (or any file matching "/version?(\.\w+)/i"). * - Composer's "installed.json" file. * * If not found `dev-master` is returned by default. If plugin is not registered * on QuickAppsCMS (not installed) an empty string will be returned instead. * * @return string Plugin's version, for instance `1.2.x-dev` */ public function version() { if (parent::version() !== null) { return parent::version(); } if (!Plugin::exists($this->name())) { $this->_version = ''; return $this->_version; } // from composer.json if (!empty($this->composer['version'])) { $this->_version = $this->composer['version']; return $this->_version; } // from version.txt $files = glob($this->path . '/*', GLOB_NOSORT); foreach ($files as $file) { $fileName = basename(strtolower($file)); if (preg_match('/version?(\\.\\w+)/i', $fileName)) { $versionFile = file($file); $version = trim(array_pop($versionFile)); $this->_version = $version; return $this->_version; } } // from installed.json $installedJson = normalizePath(VENDOR_INCLUDE_PATH . "composer/installed.json"); if (is_readable($installedJson)) { $json = (array) json_decode(file_get_contents($installedJson), true); foreach ($json as $pkg) { if (isset($pkg['version']) && strtolower($pkg['name']) === strtolower($this->_packageName)) { $this->_version = $pkg['version']; return $this->_version; } } } $this->_version = 'dev-master'; return $this->_version; }
/** * This method should never be used unless you know what are you doing. * * Populates the "acos" DB with information of every installed plugin, or * for the given plugin. It will automatically extracts plugin's controllers * and actions for creating a tree structure as follow: * * - PluginName * - Admin * - PrivateController * - index * - some_action * - ControllerName * - index * - another_action * * After tree is created you should be able to change permissions using * User's permissions section in backend. * * @param string $for Optional, build ACOs for the given plugin, or all plugins * if not given * @param bool $sync Whether to sync the tree or not. When syncing all invalid * ACO entries will be removed from the tree, also new ones will be added. When * syn is set to false only new ACO entries will be added, any invalid entry * will remain in the tree. Defaults to false * @return bool True on success, false otherwise */ public static function buildAcos($for = null, $sync = false) { if (function_exists('ini_set')) { ini_set('max_execution_time', 300); } elseif (function_exists('set_time_limit')) { set_time_limit(300); } if ($for === null) { $plugins = plugin()->toArray(); } else { try { $plugins = [plugin($for)]; } catch (\Exception $e) { return false; } } $added = []; foreach ($plugins as $plugin) { if (!Plugin::exists($plugin->name)) { continue; } $aco = new AcoManager($plugin->name); $controllerDir = normalizePath("{$plugin->path}/src/Controller/"); $folder = new Folder($controllerDir); $controllers = $folder->findRecursive('.*Controller\\.php'); foreach ($controllers as $controller) { $controller = str_replace([$controllerDir, '.php'], '', $controller); $className = $plugin->name . '\\' . 'Controller\\' . str_replace(DS, '\\', $controller); $methods = static::_controllerMethods($className); if (!empty($methods)) { $path = explode('Controller\\', $className)[1]; $path = str_replace_last('Controller', '', $path); $path = str_replace('\\', '/', $path); foreach ($methods as $method) { if ($aco->add("{$path}/{$method}")) { $added[] = "{$plugin->name}/{$path}/{$method}"; } } } } } if ($sync && isset($aco)) { $aco->Acos->recover(); $existingPaths = static::paths($for); foreach ($existingPaths as $exists) { if (!in_array($exists, $added)) { $aco->remove($exists); } } $validLeafs = $aco->Acos->find()->select(['id'])->where(['id NOT IN' => $aco->Acos->find()->select(['parent_id'])->where(['parent_id IS NOT' => null])]); $aco->Acos->Permissions->deleteAll(['aco_id NOT IN' => $validLeafs]); } return true; }