/**
  * Uploads a new file for the given FileField instance.
  *
  * @param string $name EAV attribute name
  * @throws \Cake\Network\Exception\NotFoundException When invalid slug is given,
  *  or when upload process could not be completed
  */
 public function upload($name)
 {
     $instance = $this->_getInstance($name);
     require_once Plugin::classPath('Field') . 'Lib/class.upload.php';
     $uploader = new \upload($this->request->data['Filedata']);
     if (!empty($instance->settings['extensions'])) {
         $exts = explode(',', $instance->settings['extensions']);
         $exts = array_map('trim', $exts);
         $exts = array_map('strtolower', $exts);
         if (!in_array(strtolower($uploader->file_src_name_ext), $exts)) {
             $this->_error(__d('field', 'Invalid file extension.'), 501);
         }
     }
     $response = '';
     $uploader->file_overwrite = false;
     $folder = normalizePath(WWW_ROOT . "/files/{$instance->settings['upload_folder']}/");
     $url = normalizePath("/files/{$instance->settings['upload_folder']}/", '/');
     $uploader->process($folder);
     if ($uploader->processed) {
         $response = json_encode(['file_url' => Router::url($url . $uploader->file_dst_name, true), 'file_size' => FileToolbox::bytesToSize($uploader->file_src_size), 'file_name' => $uploader->file_dst_name, 'mime_icon' => FileToolbox::fileIcon($uploader->file_src_mime)]);
     } else {
         $this->_error(__d('field', 'File upload error, details: {0}', $uploader->error), 502);
     }
     $this->viewBuilder()->layout('ajax');
     $this->title(__d('field', 'Upload File'));
     $this->set(compact('response'));
 }
 /**
  * Validation rules when editing a comment in backend.
  *
  * @param \Cake\Validation\Validator $validator The validator object
  * @return \Cake\Validation\Validator
  */
 public function validationAnonymous(Validator $validator)
 {
     $settings = Plugin::get('Comment')->settings;
     $validator = $this->validationDefault($validator);
     if ($settings['allow_anonymous']) {
         if ($settings['anonymous_name']) {
             $validator->requirePresence('author_name')->add('author_name', 'nameLength', ['rule' => ['minLength', 3], 'message' => __d('comment', 'Your name need to be at least 3 characters long.')]);
             if ($settings['anonymous_name_required']) {
                 $validator->notEmpty('author_name', __d('comment', 'You must provide your name.'));
             } else {
                 $validator->allowEmpty('author_name');
             }
         }
         if ($settings['anonymous_email']) {
             $validator->requirePresence('author_email')->add('author_email', 'validEmail', ['rule' => 'email', 'message' => __d('comment', 'e-Mail must be valid.')]);
             if ($settings['anonymous_email_required']) {
                 $validator->notEmpty('author_email', __d('comment', 'You must provide an email.'));
             } else {
                 $validator->allowEmpty('anonymous_email');
             }
         }
         if ($settings['anonymous_web']) {
             $validator->requirePresence('author_web')->add('author_web', 'validURL', ['rule' => 'url', 'message' => __d('comment', 'Website must be a valid URL.')]);
             if ($settings['anonymous_web_required']) {
                 $validator->notEmpty('author_web', __d('comment', 'You must provide a website URL.'));
             } else {
                 $validator->allowEmpty('author_web');
             }
         }
     }
     return $validator;
 }
 /**
  * Gets a list of counties flags suitable for select boxes.
  *
  * @return array
  */
 public static function flagsList()
 {
     $flags = [];
     $Folder = new Folder(Plugin::path('Locale') . 'webroot/img/flags/');
     foreach ($Folder->read()[1] as $icon) {
         $value = $icon;
         $label = str_replace_last('.gif', '', $icon);
         $flags[$value] = $label;
     }
     asort($flags);
     return $flags;
 }
 /**
  * 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')]);
     }
 }
 /**
  * First step of the installation process.
  *
  * User must select the language they want to use for the installation process.
  *
  * @return void
  */
 public function language()
 {
     $languages = ['en_US' => ['url' => '/installer/startup/requirements?locale=en_US', 'welcome' => 'Welcome to QuickAppsCMS', 'action' => 'Click here to install in English']];
     $Folder = new Folder(Plugin::classPath('Installer') . 'Locale');
     foreach ($Folder->read(false, true, true)[0] as $path) {
         $code = basename($path);
         $file = $path . '/installer.po';
         if (is_readable($file)) {
             I18n::locale($code);
             // trick for __d()
             $languages[$code] = ['url' => "/installer/startup/requirements?locale={$code}", 'welcome' => __d('installer', 'Welcome to QuickAppsCMS'), 'action' => __d('installer', 'Click here to install in English')];
         }
     }
     I18n::locale('en_US');
     $this->title('Welcome to QuickAppsCMS');
     $this->set('languages', $languages);
     $this->_step();
 }
 /**
  * Loads and registers plugin's namespace and loads its event listeners classes.
  *
  * This is used to allow plugins being installed to respond to events before
  * they are integrated to the system. Events such as `beforeInstall`,
  * `afterInstall`, etc.
  *
  * @param string $plugin Name of the plugin for which attach listeners
  * @param string $path Path to plugin's root directory (which contains "src")
  * @throws \Cake\Error\FatalErrorException On illegal usage of this method
  */
 protected function _attachListeners($plugin, $path)
 {
     $path = normalizePath("{$path}/");
     $eventsPath = normalizePath("{$path}/src/Event/");
     if (is_readable($eventsPath) && is_dir($eventsPath)) {
         $EventManager = EventManager::instance();
         $eventsFolder = new Folder($eventsPath);
         Plugin::load($plugin, ['autoload' => true, 'bootstrap' => false, 'routes' => false, 'path' => $path, 'classBase' => 'src', 'ignoreMissing' => true]);
         foreach ($eventsFolder->read(false, false, true)[1] as $classPath) {
             $className = preg_replace('/\\.php$/i', '', basename($classPath));
             $fullClassName = implode('\\', [$plugin, 'Event', $className]);
             if (class_exists($fullClassName)) {
                 $handler = new $fullClassName();
                 $this->_listeners[] = $handler;
                 $EventManager->on($handler);
             }
         }
     }
 }
 /**
  * Renders the help document of the given plugin.
  *
  * @param string $pluginName The plugin name
  * @return void
  * @throws \Cake\Network\Exception\NotFoundException When no help document was found
  */
 public function about($pluginName)
 {
     $about = false;
     if (Plugin::loaded($pluginName)) {
         $locale = I18n::locale();
         $templatePath = App::path('Template', $pluginName)[0] . 'Element/Help/';
         $lookFor = ["help_{$locale}", 'help'];
         foreach ($lookFor as $ctp) {
             if (is_readable($templatePath . "{$ctp}.ctp")) {
                 $about = "{$pluginName}.Help/{$ctp}";
                 break;
             }
         }
     }
     if ($about) {
         $this->set('about', $about);
     } else {
         throw new NotFoundException(__d('system', 'No help was found.'));
     }
     $this->title(__d('system', 'About "{0}"', $pluginName));
     $this->Breadcrumb->push('/admin/system/help')->push(__d('system', 'About {0}', $pluginName), '#');
 }
 /**
  * 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;
 }
 /**
  * Calculates comment's status using akismet.
  *
  * @param array $data Comment's data to be validated by Akismet
  * @return string Filtered comment's status
  */
 protected function _akismetStatus($data)
 {
     require_once Plugin::classPath('Comment') . 'Lib/Akismet.php';
     try {
         $akismet = new \Akismet(Router::url('/'), $this->config('settings.akismet_key'));
         if (!empty($data['author_name'])) {
             $akismet->setCommentAuthor($data['author_name']);
         }
         if (!empty($data['author_email'])) {
             $akismet->setCommentAuthorEmail($data['author_email']);
         }
         if (!empty($data['author_web'])) {
             $akismet->setCommentAuthorURL($data['author_web']);
         }
         if (!empty($data['body'])) {
             $akismet->setCommentContent($data['body']);
         }
         if ($akismet->isCommentSpam()) {
             return 'spam';
         }
     } catch (\Exception $ex) {
         return 'pending';
     }
     return $data['status'];
 }
    }
    $filter = $plugin->status;
    if ($plugin->isTheme) {
        $filter = $filter && in_array($plugin->name, [option('front_theme'), option('back_theme')]);
    }
    if (!$filter) {
        return;
    }
    if (!in_array("{$plugin->name}\\", array_keys($classLoader->getPrefixesPsr4()))) {
        $classLoader->addPsr4("{$plugin->name}\\", normalizePath("{$plugin->path}/src/"), true);
    }
    if (!in_array("{$plugin->name}\\Test\\", array_keys($classLoader->getPrefixesPsr4()))) {
        $classLoader->addPsr4("{$plugin->name}\\Test\\", normalizePath("{$plugin->path}/tests/"), true);
    }
    $info = ['autoload' => false, 'bootstrap' => true, 'routes' => true, 'path' => normalizePath("{$plugin->path}/"), 'classBase' => 'src', 'ignoreMissing' => true];
    Plugin::load($plugin->name, $info);
    foreach ($plugin->eventListeners as $fullClassName) {
        if (class_exists($fullClassName)) {
            if (str_ends_with($fullClassName, 'Shortcode')) {
                EventDispatcher::instance('Shortcode')->eventManager()->on(new $fullClassName());
            } else {
                EventDispatcher::instance()->eventManager()->on(new $fullClassName());
            }
        }
    }
    $pluginsPath[] = $info['path'];
});
if (empty($pluginsPath)) {
    die("Ops, something went wrong. Try to clear your site's snapshot and verify write permissions on /tmp directory.");
}
/**
 /**
  * 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;
 }
Beispiel #13
0
 /**
  * Renders a nested menu.
  *
  * This methods renders a HTML menu using a `threaded` result set:
  *
  * ```php
  * // In controller:
  * $this->set('links', $this->Links->find('threaded'));
  *
  * // In view:
  * echo $this->Menu->render('links');
  * ```
  *
  * ### Options:
  *
  * You can pass an associative array `key => value`. Any `key` not in
  * `$_defaultConfig` will be treated as an additional attribute for the top
  * level UL (root). If `key` is in `$_defaultConfig` it will temporally
  * overwrite default configuration parameters, it will be restored to its
  * default values after rendering completes:
  *
  * - `formatter`: Callable method used when formating each item.
  * - `activeClass`: CSS class to use when an item is active (its URL matches current URL).
  * - `firstItemClass`: CSS class for the first item.
  * - `lastItemClass`: CSS class for the last item.
  * - `hasChildrenClass`: CSS class to use when an item has children.
  * - `split`: Split menu into multiple root menus (multiple UL's)
  * - `templates`: The templates you want to use for this menu. Any templates
  *    will be merged on top of the already loaded templates. This option can
  *    either be a filename in App/config that contains the templates you want
  *    to load, or an array of templates to use.
  *
  * You can also pass a callable function as second argument which will be
  * used as formatter:
  *
  * ```php
  * echo $this->Menu->render($links, function ($link, $info) {
  *     // render $item here
  * });
  * ```
  *
  * Formatters receives two arguments, the item being rendered as first argument
  * and information abut the item (has children, depth, etc) as second.
  *
  * You can pass the ID or slug of a menu as fist argument to render that menu's
  * links:
  *
  * ```php
  * echo $this->Menu->render('management');
  *
  * // OR
  *
  * echo $this->Menu->render(1);
  * ```
  *
  * @param int|string|array|\Cake\Collection\Collection $items Nested items
  *  to render, given as a query result set or as an array list. Or an integer as
  *  menu ID in DB to render, or a string as menu Slug in DB to render.
  * @param callable|array $config An array of HTML attributes and options as
  *  described above or a callable function to use as `formatter`
  * @return string HTML
  * @throws \Cake\Error\FatalErrorException When loop invocation is detected,
  *  that is, when "render()" method is invoked within a callable method when
  *  rendering menus.
  */
 public function render($items, $config = [])
 {
     if ($this->_rendering) {
         throw new FatalErrorException(__d('menu', 'Loop detected, MenuHelper already rendering.'));
     }
     $items = $this->_prepareItems($items);
     if (empty($items)) {
         return '';
     }
     list($config, $attrs) = $this->_prepareOptions($config);
     $this->_rendering = true;
     $this->countItems($items);
     $this->config($config);
     if ($this->config('breadcrumbGuessing')) {
         $this->Link->config(['breadcrumbGuessing' => $this->config('breadcrumbGuessing')]);
     }
     $out = '';
     if (intval($this->config('split')) > 1) {
         $out .= $this->_renderPart($items, $config, $attrs);
     } else {
         $out .= $this->formatTemplate('root', ['attrs' => $this->templater()->formatAttributes($attrs), 'content' => $this->_render($items)]);
     }
     if ($this->config('beautify')) {
         include_once Plugin::classPath('Menu') . 'Lib/htmLawed.php';
         $tidy = is_bool($this->config('beautify')) ? '1t0n' : $this->config('beautify');
         $out = htmLawed($out, compact('tidy'));
     }
     $this->_clear();
     return $out;
 }
 /**
  * Disables a plugin.
  *
  * @return void
  */
 protected function _disable()
 {
     $enabledPlugins = plugin()->filter(function ($plugin) {
         return $plugin->status && !$plugin->isTheme;
     })->toArray();
     if (!count($enabledPlugins)) {
         $this->err(__d('installer', '<info>There are no active plugins!</info>'));
         $this->out();
         return;
     }
     $index = 1;
     $this->out();
     foreach ($enabledPlugins as $plugin) {
         $enabledPlugins[$index] = $plugin;
         $this->out(__d('installer', '[{0, number, integer}] {1}', [$index, $plugin->humanName]));
         $index++;
     }
     $this->out();
     $message = __d('installer', "Which plugin would you like to disable?\n[Q]uit");
     while (true) {
         $in = $this->in($message);
         if (strtoupper($in) === 'Q') {
             $this->err(__d('installer', 'Operation aborted'));
             break;
         } elseif (intval($in) < 1 || !isset($enabledPlugins[intval($in)])) {
             $this->err(__d('installer', 'Invalid option'));
         } else {
             $plugin = plugin($enabledPlugins[$in]->name());
             $this->hr();
             $this->out(__d('installer', '<info>The following plugin will be DISABLED</info>'));
             $this->hr();
             $this->out(__d('installer', 'Name:        {0}', $plugin->name));
             $this->out(__d('installer', 'Description: {0}', $plugin->composer['description']));
             $this->out(__d('installer', 'Status:      {0}', $plugin->status ? __d('installer', 'Active') : __d('installer', 'Disabled')));
             $this->out(__d('installer', 'Path:        {0}', $plugin->path));
             $this->hr();
             $this->out();
             $confirm = $this->in(__d('installer', 'Please type in "{0}" to disable this plugin', $enabledPlugins[$in]->name));
             if ($confirm === $enabledPlugins[$in]->name) {
                 $task = $this->dispatchShell("Installer.plugins toggle -p {$enabledPlugins[$in]->name} -s disable");
                 if ($task === 0) {
                     $this->out(__d('installer', 'Plugin disabled!'));
                     Plugin::dropCache();
                 } else {
                     $this->err(__d('installer', 'Plugin could not be disabled.'), 2);
                     $this->out();
                 }
             }
             break;
         }
     }
     $this->out();
 }
Beispiel #15
0
 /**
  * Gets the given (or in use) theme as a package object.
  *
  * ### Example:
  *
  * ```php
  * // current theme
  * $bgColor = theme()->settings['background_color'];
  *
  * // specific theme
  * $bgColor = theme('BlueTheme')->settings['background_color'];
  * ```
  *
  * @param string|null $name Name of the theme to get, or null to get the theme
  *  being used in current request
  * @return \CMS\Core\Package\PluginPackage
  * @throws \Cake\Error\FatalErrorException When theme could not be found
  */
 function theme($name = null)
 {
     if ($name === null) {
         $option = Router::getRequest()->isAdmin() ? 'back_theme' : 'front_theme';
         $name = option($option);
     }
     $theme = Plugin::get()->filter(function ($plugin) use($name) {
         return $plugin->isTheme && $plugin->name == $name;
     })->first();
     if ($theme) {
         return $theme;
     }
     throw new FatalErrorException(__d('cms', 'Theme "{0}" was not found', $name));
 }
Beispiel #16
0
 /**
  * {@inheritDoc}
  *
  * Workaround patch that allows plugins and themes provide their own independent
  * "settings.ctp" files so themes won't "override" plugin element (as themes are
  * actually plugins and may have their own "settings.ctp").
  *
  * The same goes for "help.ctp" template files. So themes and plugins can
  * provide help information.
  */
 protected function _getElementFileName($name, $pluginCheck = true)
 {
     list($plugin, $element) = $this->pluginSplit($name, $pluginCheck);
     if ($plugin && ($element === 'settings' || strpos($element, 'Help/help') !== false)) {
         return Plugin::classPath($plugin) . "Template/Element/{$element}{$this->_ext}";
     }
     return parent::_getElementFileName($name, $pluginCheck);
 }
 /**
  * Switch site's theme.
  *
  * @return void
  */
 protected function _change()
 {
     $disabledThemes = plugin()->filter(function ($theme) {
         return $theme->isTheme && !in_array($theme->name, [option('front_theme'), option('back_theme')]);
     })->toArray();
     if (!count($disabledThemes)) {
         $this->err(__d('installer', '<info>There are no disabled themes!</info>'));
         $this->out();
         return;
     }
     $index = 1;
     $this->out();
     foreach ($disabledThemes as $theme) {
         $disabledThemes[$index] = $theme;
         $this->out(__d('installer', '[{0, number, integer}] {1} [{2}]', [$index, $theme->humanName, $theme->isAdmin ? __d('installer', 'backend') : __d('installer', 'frontend')]));
         $index++;
     }
     $this->out();
     $message = __d('installer', "Which theme would you like to activate?\n[Q]uit");
     while (true) {
         $in = $this->in($message);
         if (strtoupper($in) === 'Q') {
             $this->err(__d('installer', 'Operation aborted'));
             break;
         } elseif (intval($in) < 1 || !isset($disabledThemes[intval($in)])) {
             $this->err(__d('installer', 'Invalid option'));
         } else {
             $task = $this->dispatchShell("Installer.themes change -t {$disabledThemes[$in]->name}");
             if ($task === 0) {
                 $this->out(__d('installer', 'Theme changed!'));
                 Plugin::dropCache();
             } else {
                 $this->err(__d('installer', 'Theme could not be changed.'), 2);
                 $this->out();
             }
             break;
         }
     }
     $this->out();
 }
Beispiel #18
0
 /**
  * {@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;
 }
 /**
  * Disables the given plugin.
  *
  * @param \CMS\Core\Package\PluginPackage $plugin The plugin to disable
  * @return bool True on success
  */
 protected function _disable(PluginPackage $plugin)
 {
     $requiredBy = Plugin::checkReverseDependency($plugin->name);
     if (!empty($requiredBy)) {
         $names = [];
         foreach ($requiredBy as $p) {
             $names[] = $p->name();
         }
         $this->err(__d('installer', 'Plugin "{0}" cannot be disabled as it is required by: {1}', $plugin->humanName, implode(', ', $names)));
         return false;
     }
     if (!$this->params['no-callbacks']) {
         $trigger = $this->_triggerBeforeEvents($plugin);
         if (!$trigger) {
             return false;
         }
     }
     return $this->_finish($plugin);
 }
 /**
  * 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;
 }
 /**
  * Fallback for template location when extending Comment UI API.
  *
  * If controller tries to render an unexisting template under its Template
  * directory, then we try to find that view under `Comment/Template/CommentUI`
  * directory.
  *
  * ### Example:
  *
  * Suppose you are using this trait to manage comments attached to `Persons`
  * entities. You would probably have a `Person` plugin and a `clean` controller
  * as follow:
  *
  *     // http://example.com/admin/person/comments_manager
  *     Person\Controller\CommentsManagerController::index()
  *
  * The above controller action will try to render
  * `/plugins/Person/Template/CommentsManager/index.ctp`. But if does not exists
  * then `<QuickAppsCorePath>/plugins/Comment/Template/CommentUI/index.ctp` will
  * be used instead.
  *
  * Of course you may create your own template and skip this fallback functionality.
  *
  * @param \Cake\Event\Event $event the event instance.
  * @return void
  */
 public function beforeRender(Event $event)
 {
     $plugin = (string) Inflector::camelize($event->subject()->request->params['plugin']);
     $controller = Inflector::camelize($event->subject()->request->params['controller']);
     $action = Inflector::underscore($event->subject()->request->params['action']);
     $prefix = '';
     if (!empty($event->subject()->request->params['prefix'])) {
         $prefix = Inflector::camelize($event->subject()->request->params['prefix']) . '/';
     }
     $templatePath = Plugin::classPath($plugin) . "Template/{$prefix}{$controller}/{$action}.ctp";
     if (!is_readable($templatePath)) {
         $alternativeTemplatePath = Plugin::classPath('Comment') . 'Template/CommentUI';
         if (is_readable("{$alternativeTemplatePath}/{$action}.ctp")) {
             $this->plugin = 'Comment';
             $this->viewBuilder()->templatePath('CommentUI');
         }
     }
     parent::beforeRender($event);
 }
Beispiel #22
0
 /**
  * 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;
 }