/**
  * 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;
 }