<?php
namespace Croogo\Core;
use App\Controller\AppController;
use Aura\Intl\Package;
use Cake\Cache\Cache;
use Cake\Core\App;
use Cake\Core\BasePlugin;
use Cake\Core\ClassLoader;
use Cake\Core\Configure;
use Cake\Core\Exception\Exception;
use Cake\Core\Exception\MissingPluginException;
use Cake\Core\Plugin;
use Cake\Core\PluginApplicationInterface;
use Cake\Database\SchemaCache;
use Cake\Database\Type;
use Cake\Datasource\ConnectionManager;
use Cake\Datasource\Exception\MissingDatasourceConfigException;
use Cake\Filesystem\Folder;
use Cake\I18n\I18n;
use Cake\I18n\MessagesFileLoader;
use Cake\Log\Log;
use Cake\Log\LogTrait;
use Cake\ORM\TableRegistry;
use Cake\Utility\Hash;
use Cake\Utility\Inflector;
use Cake\Utility\Text;
use Croogo\Core\Event\EventManager;
use Croogo\Settings\Configure\Engine\DatabaseConfig;
use InvalidArgumentException;
use Migrations\Migrations;
/**
* PluginManager utility class
*
* @category Component
* @package Croogo.Extensions.Lib
* @version 1.4
* @since 1.4
* @author Fahad Ibnay Heylaal <contact@fahad19.com>
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
* @link http://www.croogo.org
*/
class PluginManager extends Plugin
{
use LogTrait;
/**
* List of migration errors
* Updated in case of errors when running migrations
*
* @var array
*/
public $migrationErrors = [];
/**
* PluginActivation class
*
* @var object
*/
protected $_PluginActivation = null;
/**
* MigrationVersion class
*
* @var \Migrations\Migrations
*/
protected $_Migrations = null;
/**
* Core plugins
*
* Typically these plugins must be active and should not be deactivated
*
* @var array
* @access public
*/
public static $corePlugins = [
'Croogo/Acl',
'Croogo/Core',
'Croogo/Extensions',
'Croogo/Install',
'Croogo/Settings',
'Migrations',
'Search',
];
/**
* Bundled plugins providing core functionalities but could be deactivated
*
* @var array
* @access public
*/
public static $bundledPlugins = [
'Croogo/Blocks',
'Croogo/Contacts',
'Croogo/Dashboards',
'Croogo/FileManager',
'Croogo/Meta',
'Croogo/Menus',
'Croogo/Nodes',
'Croogo/Taxonomy',
'Croogo/Users',
];
/**
* __construct
*/
public function __construct($migrations = null)
{
if (!is_null($migrations)) {
$this->_Migrations = $migrations;
}
}
/**
* Get instance
*/
public static function instance()
{
static $self = null;
if ($self === null) {
$self = new static();
}
return $self;
}
/**
* AppController setter
*
* @return void
*/
public function setController(AppController $controller)
{
$this->_Controller = $controller;
}
/**
* Get plugin aliases (folder names)
*
* @return array
*/
public function getPlugins($type = 'plugin')
{
$plugins = [];
$this->folder = new Folder;
$registered = Configure::read('plugins');
$pluginPaths = Hash::merge(App::path('Plugin'), $registered);
unset($pluginPaths['Croogo']); //Otherwise we get croogo plugins twice!
foreach ($pluginPaths as $pluginName => $pluginPath) {
$this->folder->path = $pluginPath;
if (!file_exists($this->folder->path)) {
continue;
}
if ((
$type === 'plugin' && $this->_isCroogoPlugin($pluginPath)
) || (
$type === 'theme' && $this->_isCroogoTheme($pluginPath)
)
) {
$plugins[$pluginName] = $pluginPath;
continue;
}
$pluginFolders = $this->folder->read();
foreach ($pluginFolders[0] as $pluginFolder) {
if (substr($pluginFolder, 0, 1) != '.') {
if ($type === 'plugin' && !$this->_isCroogoPlugin($pluginPath, $pluginFolder)) {
continue;
}
if ($type === 'theme' && !$this->_isCroogoTheme($pluginPath, $pluginFolder)) {
continue;
}
$pluginName = array_search($pluginPath . $pluginFolder, $registered);
if ($pluginName) {
$plugins[$pluginName] = $pluginPath . $pluginFolder;
} else {
$plugins[$pluginFolder] = $pluginPath . $pluginFolder;
}
}
}
}
return $plugins;
}
/**
* Checks wether $pluginDir/$path is a Croogo theme
*
* @param string $pluginDir plugin directory
* @param string $path plugin alias
* @return bool true if path is a Croogo plugin
*/
protected function _isCroogoTheme($pluginDir, $path = '')
{
$dir = $pluginDir . $path . DS;
$themeConfigs = [
'config' . DS . 'theme.json',
'webroot' . DS . 'theme.json',
];
$composerFile = $dir . 'composer.json';
if (file_exists($composerFile)) {
$json = json_decode(file_get_contents($composerFile));
if (property_exists($json, 'type') && $json->type === 'croogo-theme') {
return true;
}
}
foreach ($themeConfigs as $themeManifestFile) {
if (!file_exists($dir . $themeManifestFile)) {
continue;
}
return true;
}
return false;
}
/**
* Checks wether $pluginDir/$path is a Croogo plugin
*
* @param string $pluginDir plugin directory
* @param string $path plugin alias
* @return bool true if path is a Croogo plugin
*/
protected function _isCroogoPlugin($pluginDir, $path = '')
{
$dir = $pluginDir . $path . DS;
$composerFile = $dir . 'composer.json';
if (file_exists($dir . 'config' . DS . 'plugin.json')) {
return true;
}
if (file_exists($composerFile) && !$this->_isCroogoTheme($pluginDir, $path)) {
$pluginData = json_decode(file_get_contents($composerFile), true);
if (isset($pluginData['require']['croogo/core']) ||
isset($pluginData['require']['croogo/croogo'])
) {
return true;
}
}
return false;
}
/**
* @param $pluginDir
* @param $path
*
* @return bool
*/
protected function _pluginName($pluginDir, $path)
{
$pluginPath = $pluginDir . $path . DS;
$manifestFile = $pluginPath . DS . 'config' . DS . 'plugin.json';
if (file_exists($manifestFile)) {
$manifestData = json_decode(file_get_contents($manifestFile), true);
if (isset($manifestData['name'])) {
return $manifestData['name'];
}
}
$composerFile = $pluginPath . $alias . DS . 'composer.json';
if (file_exists($composerFile)) {
$composerData = json_decode(file_get_contents($composerFile), true);
if (isset($composerData['name'])) {
return $composerData['name'];
}
}
return false;
}
/**
* Checks whether $plugin is builtin
*
* @param string $plugin plugin alias
* @return bool true if $plugin is builtin
*/
protected function _isBuiltin($plugin)
{
return in_array($plugin, static::$bundledPlugins) || in_array($plugin, static::$corePlugins);
}
/**
* @param $alias
* @param $pluginPath
* @param bool $ignoreMigration
*
* @return array|bool
*/
protected function _loadData($alias, $pluginPath, $ignoreMigration = true)
{
$active = $this->isActive($alias);
$manifestFile = $pluginPath . DS . 'config' . DS . 'plugin.json';
$hasManifest = file_exists($manifestFile);
$composerFile = $pluginPath . DS . 'composer.json';
$hasComposer = file_exists($composerFile);
if ($hasManifest || $hasComposer) {
$pluginData = [
'name' => $alias,
'needMigration' => false,
'active' => $active,
];
if ($hasManifest) {
$manifestData = json_decode(file_get_contents($manifestFile), true);
if (empty($manifestData)) {
$this->log($alias . 'plugin.json exists but cannot be decoded.');
return $pluginData;
}
$pluginData = array_merge($manifestData, $pluginData);
}
if ($hasComposer) {
$composerData = json_decode(file_get_contents($composerFile), true);
$type = isset($composerData['type']) ? $composerData['type'] : null;
$isCroogoPlugin = isset($composerData['require']['Croogo/Core']) || $type == 'croogo-plugin';
if ($isCroogoPlugin) {
if (isset($composerData['name'])) {
$composerData['vendor'] = $composerData['name'];
unset($composerData['name']);
}
$pluginData = Hash::merge($pluginData, $composerData);
}
}
if (!$ignoreMigration) {
$pluginData['needMigration'] = $this->needMigration($alias, $active);
}
return $pluginData;
} elseif ($this->_isBuiltin($alias)) {
if (!$ignoreMigration && $this->needMigration($alias, $active)) {
$pluginData = [
'name' => $alias,
'description' => "Croogo $alias plugin",
'active' => true,
'needMigration' => true,
];
return $pluginData;
}
}
return false;
}
/**
* Get the content of plugin.json file of a plugin
*
* @param string $plugin name of plugin
* @return array|bool array of plugin manifest or boolean false
*/
public function getData($plugin = null, $ignoreMigrations = true)
{
if (!static::available($plugin)) {
return false;
}
return $this->_loadData($plugin, static::path($plugin), $ignoreMigrations);
}
/**
* Get a list of plugins available with all available meta data including migration status.
* Plugin without metadata are excluded.
*
* @return array array of plugins, listed according to bootstrap order
*/
public function plugins($ignoreMigrations = true)
{
$pluginAliases = $this->getPlugins();
$allPlugins = [];
foreach ($pluginAliases as $pluginAlias => $pluginPath) {
$pluginData = $this->getData($pluginAlias, $ignoreMigrations);
if (!$pluginData) {
$pluginData = $this->_loadData($pluginAlias, $pluginPath, $ignoreMigrations);
}
$allPlugins[$pluginAlias] = $pluginData;
}
$activePlugins = [];
$bootstraps = explode(',', Configure::read('Hook.bootstraps'));
foreach ($bootstraps as $pluginAlias) {
if ($pluginData = $this->getData($pluginAlias, $ignoreMigrations)) {
$activePlugins[$pluginAlias] = $pluginData;
}
}
$plugins = [];
foreach ($activePlugins as $plugin => $pluginData) {
$plugins[$plugin] = $pluginData;
}
$plugins = Hash::merge($plugins, $allPlugins);
return $plugins;
}
/**
* Check if plugin is dependent on any other plugin.
* If yes, check if that plugin is available in plugins directory.
*
* @param string $plugin plugin alias
* @return bool
*/
public function checkDependency($plugin = null)
{
$dependencies = $this->getDependencies($plugin);
$pluginPaths = App::path('plugins');
foreach ($dependencies as $p) {
$check = false;
foreach ($pluginPaths as $pluginPath) {
if (is_dir($pluginPath . $p)) {
$check = true;
}
}
if (!$check) {
return false;
}
}
return true;
}
/**
* getDependencies
*
* @param string $plugin plugin alias (underscrored)
* @return array list of plugin that $plugin depends on
*/
public function getDependencies($plugin)
{
$pluginData = $this->getData($plugin);
if (!isset($pluginData['dependencies']['plugins'])) {
$pluginData['dependencies']['plugins'] = [];
}
$dependencies = [];
foreach ($pluginData['dependencies']['plugins'] as $i => $plugin) {
$dependencies[] = Inflector::camelize($plugin);
}
return $dependencies;
}
/**
* Check if plugin is dependent on any other plugin.
* If yes, check if that plugin is available in plugins directory.
*
* @param string $plugin plugin alias (underscrored)
* @return bool
*/
public function checkPluginDependency($plugin = null)
{
return $this->checkDependency($plugin);
}
/**
* Check if plugin is active
*
* @param string $plugin Plugin name (underscored)
* @return bool
*/
public static function isActive($plugin)
{
$configureKeys = [
'Hook.bootstraps',
];
$plugin = [Inflector::underscore($plugin), Inflector::camelize($plugin)];
foreach ($configureKeys as $configureKey) {
$hooks = explode(',', Configure::read($configureKey));
foreach ($hooks as $hook) {
if (in_array($hook, $plugin)) {
return true;
}
}
}
// check for manually loaded plugins
foreach ($plugin as $item) {
if ($loaded = Plugin::isLoaded($item)) {
return $loaded;
}
}
return false;
}
/**
* Check if a plugin need a database migration
*
* @param string $plugin Plugin name or 'app'
* @param string $isActive If the plugin is active
* @return bool
*/
public function needMigration($plugin, $isActive)
{
if (!$isActive) {
return false;
}
if (($plugin !== 'app') && (!static::available($plugin))) {
return false;
}
$options = [
'connection' => static::migrationConnectionName()
];
if ($plugin !== 'app') {
$options['plugin'] = $plugin;
}
$status = $this->_getMigrations()->status($options);
if ($status) {
return Hash::check($status, '{n}[status=down]');
}
return false;
}
/**
* Migrate a plugin
*
* @param string $plugin Plugin name
* @return bool Success of the migration
*/
public function migrate($plugin)
{
if (($plugin !== 'app') && (!static::available($plugin))) {
return false;
}
if (!$this->needMigration($plugin, true)) {
return true;
}
$connectionName = static::migrationConnectionName();
$options = [
'connection' => $connectionName,
];
if ($plugin !== 'app') {
$options['plugin'] = $plugin;
}
try {
$migrated = $this->_getMigrations()
->migrate($options);
$connection = ConnectionManager::get($connectionName);
$schemaCache = new SchemaCache($connection);
$schemaCache->clear();
return $migrated;
} catch (\Exception $e) {
$this->migrationErrors[] = $e->getMessage();
}
}
/**
* @param $plugin
*
* @return bool
*/
public function seed($plugin)
{
$options = [
'connection' => static::migrationConnectionName()
];
if ($plugin !== 'app') {
$options['plugin'] = $plugin;
}
return $this->_getMigrations()
->seed($options);
}
/**
* @param $plugin
*
* @return bool
*/
public function unmigrate($plugin)
{
if (($plugin !== 'app') && (!static::available($plugin))) {
return false;
}
$options = [
'connection' => static::migrationConnectionName(),
'target' => 0
];
if ($plugin !== 'app') {
$options['plugin'] = $plugin;
}
return $this->_getMigrations()
->rollback($options);
}
/**
* @return \Migrations\Migrations
*/
protected function _getMigrations()
{
$this->_Migrations = new Migrations();
return $this->_Migrations;
}
/**
* Loads plugin's bootstrap.php file
*
* @param string $plugin Plugin name
* @return void
*/
public function addBootstrap($plugin)
{
$hookBootstraps = Configure::read('Hook.bootstraps');
if (!$hookBootstraps) {
$plugins = [];
} else {
$plugins = explode(',', $hookBootstraps);
$names = [Inflector::underscore($plugin), Inflector::camelize($plugin)];
if ($intersect = array_intersect($names, $plugins)) {
$plugin = current($intersect);
}
}
if (array_search($plugin, $plugins) !== false) {
$plugins = (array)$hookBootstraps;
} else {
$plugins[] = $plugin;
}
$this->_saveBootstraps($plugins);
}
/**
* Plugin name will be removed from Hook.bootstraps
*
* @param string $plugin Plugin name
* @return void
*/
public function removeBootstrap($plugin)
{
$hookBootstraps = Configure::read('Hook.bootstraps');
if (!$hookBootstraps) {
return;
}
$plugins = explode(',', $hookBootstraps);
$names = [Inflector::underscore($plugin), Inflector::camelize($plugin)];
if ($intersect = array_intersect($names, $plugins)) {
$plugin = current($intersect);
$k = array_search($plugin, $plugins);
unset($plugins[$k]);
}
$this->_saveBootstraps($plugins);
}
/**
* Get PluginActivation class
*
* @param string $plugin
* @return object
*/
public function getActivator($plugin = null)
{
$plugin = Inflector::camelize($plugin);
if (!isset($this->_PluginActivation)) {
$className = 'PluginActivation';
$registered = Configure::read('plugins');
$pluginPaths = Hash::merge(App::path('Plugin'), $registered);
unset($pluginPaths['Croogo']); //Otherwise we get croogo plugins twice!
if (isset($pluginPaths[$plugin])) {
$activationFile = $pluginPaths[$plugin] . 'config/PluginActivation.php';
$configFile = $pluginPaths[$plugin] . 'config' . DS . $className . '.php';
if (
(file_exists($configFile) && include $configFile) ||
(file_exists($activationFile) && include $activationFile)
) {
$fqcn = App::className($plugin . '.' . $className, 'Config');
if (!$fqcn) {
$this->log(sprintf(
'Unable to load PluginActivation class. Expected class name is %s\\Config\\PluginActivation',
str_replace('/', '\\', $plugin),
LOG_CRIT
));
return null;
}
$this->_PluginActivation = new $fqcn;
return $this->_PluginActivation;
}
}
foreach ($pluginPaths as $path) {
$configFile = $path . DS . $plugin . DS . 'config' . DS . $className . '.php';
if (file_exists($configFile) && include $configFile) {
$fqcn = App::className($plugin . '.' . $className, 'Config');
if (!$fqcn) {
$this->log(sprintf(
'Unable to load PluginActivation class. Expected class name is %s\\Config\\PluginActivation',
str_replace('/', '\\', $plugin),
LOG_CRIT
));
return null;
}
$this->_PluginActivation = new $fqcn;
return $this->_PluginActivation;
}
}
}
return $this->_PluginActivation;
}
/**
* Activate plugin
*
* @param string $plugin Plugin name
* @return bool true when successful, false or error message when failed
*/
public function activate($plugin, $dependencyList = [])
{
if (Plugin::isLoaded($plugin)) {
return true;
}
$pluginActivation = $this->getActivator($plugin);
if (!isset($pluginActivation) ||
(isset($pluginActivation) &&
method_exists($pluginActivation, 'beforeActivation') &&
$pluginActivation->beforeActivation($this->_Controller))
) {
$pluginData = $this->getData($plugin);
$missingPlugins = [];
if (!empty($pluginData['dependencies']['plugins'])) {
foreach ($pluginData['dependencies']['plugins'] as $requiredPlugin) {
$requiredPlugin = ucfirst($requiredPlugin);
if (!Plugin::isLoaded($requiredPlugin)) {
$dependencyList[] = $plugin;
if ($this->activate($requiredPlugin, $dependencyList) !== true) {
$missingPlugins[] = $requiredPlugin;
}
}
}
}
if (!empty($missingPlugins)) {
return __dn(
'croogo',
'Plugin "%2$s" requires the "%3$s" plugin to be installed.',
'Plugin "%2$s" requires the %3$s plugins to be installed.',
count($missingPlugins),
$plugin,
Text::toList($missingPlugins)
);
}
try {
static::load($plugin);
} catch (MissingPluginException $e) {
return __d('croogo', 'Plugin "%s" could not be actived.', $plugin);
}
$this->addBootstrap($plugin);
if (isset($pluginActivation) && method_exists($pluginActivation, 'onActivation')) {
$pluginActivation->onActivation($this->_Controller);
}
Cache::clear(false, 'croogo_menus');
Cache::delete('file_map', '_cake_core_');
return true;
}
}
/**
* Return a list of plugins that uses $plugin
*
* @return array|bool Boolean false or Array of plugin names
*/
public function usedBy($plugin)
{
$deps = Configure::read('pluginDeps');
if (empty($deps['usedBy'][$plugin])) {
return false;
}
$usedBy = array_filter($deps['usedBy'][$plugin], ['Croogo\\Core\\Plugin', 'loaded']);
if (!empty($usedBy)) {
return $usedBy;
}
return false;
}
/**
* Deactivate plugin
*
* @param string $plugin Plugin name
* @return bool true when successful, false or error message when failed
*/
public function deactivate($plugin)
{
if (!Plugin::isLoaded($plugin)) {
return __d('croogo', 'Plugin "%s" is not active.', $plugin);
}
$pluginActivation = $this->getActivator($plugin);
if (!isset($pluginActivation) ||
(isset($pluginActivation) &&
method_exists($pluginActivation, 'beforeDeactivation') &&
$pluginActivation->beforeDeactivation($this->_Controller))
) {
$this->removeBootstrap($plugin);
if (isset($pluginActivation) && method_exists($pluginActivation, 'onDeactivation')) {
$pluginActivation->onDeactivation($this->_Controller);
}
static::unload($plugin);
Cache::clear(false, 'croogo_menus');
Cache::delete('file_map', '_cake_core_');
return true;
} else {
return __d('croogo', 'Plugin could not be deactivated. Please, try again.');
}
}
/**
* Cache plugin dependency list
*/
public static function cacheDependencies()
{
$pluginDeps = Cache::read('pluginDeps', 'cached_settings');
if (!$pluginDeps) {
$self = self::instance();
$plugins = Plugin::loaded();
$dependencies = $usedBy = [];
foreach ($plugins as $plugin) {
$dependencies[$plugin] = $self->getDependencies($plugin);
foreach ($dependencies[$plugin] as $dependent) {
$usedBy[$dependent][] = $plugin;
}
}
$pluginDeps = compact('dependencies', 'usedBy');
Cache::write('pluginDeps', $pluginDeps, 'cached_settings');
}
Configure::write('pluginDeps', $pluginDeps);
}
/**
* @param string $name
*
* @return bool|mixed
*/
public static function bootstrap($name)
{
$plugin = static::getCollection()->get($name);
if (!$plugin->isEnabled('bootstrap')) {
return false;
}
// Disable bootstrapping for this plugin as it will have
// been bootstrapped.
$plugin->disable('bootstrap');
return static::_includeFile(
$plugin->getConfigPath() . 'bootstrap.php',
true
);
}
/**
* Loads a plugin and optionally loads bootstrapping and routing files.
*
* This method is identical to Plugin::load() with extra functionality
* that loads event configuration when Plugin/Config/events.php is present.
*
* @param mixed $plugin name of plugin, or array of plugin and its config
* @return void
* @see Plugin::load()
*/
public static function load($plugin, array $config = [])
{
if (is_array($plugin)) {
foreach ($plugin as $name => $conf) {
list($name, $conf) = is_numeric($name) ? [$conf, $config] : [$name, $conf];
static::load($name, $conf);
}
return;
}
$config += [
'autoload' => false,
'bootstrap' => false,
'routes' => false,
'console' => true,
'classBase' => 'src',
'ignoreMissing' => false,
'events' => false,
'name' => $plugin
];
if (!isset($config['path'])) {
$config['path'] = static::getCollection()->findPath($plugin);
}
$config['classPath'] = $config['path'] . $config['classBase'] . DIRECTORY_SEPARATOR;
if (!isset($config['configPath'])) {
$config['configPath'] = $config['path'] . 'config' . DIRECTORY_SEPARATOR;
}
$pluginClass = str_replace('/', '\\', $plugin) . '\\Plugin';
if (class_exists($pluginClass)) {
$instance = new $pluginClass($config);
} else {
// Use stub plugin as this method will be removed long term.
$instance = new BasePlugin($config);
}
static::getCollection()->add($instance);
if ($config['autoload'] === true) {
if (empty(static::$_loader)) {
static::$_loader = new ClassLoader();
static::$_loader->register();
}
static::$_loader->addNamespace(
str_replace('/', '\\', $plugin),
$config['path'] . $config['classBase'] . DIRECTORY_SEPARATOR
);
static::$_loader->addNamespace(
str_replace('/', '\\', $plugin) . '\Test',
$config['path'] . 'tests' . DIRECTORY_SEPARATOR
);
}
if ($config['bootstrap'] === true) {
static::bootstrap($plugin);
}
if (in_array('cached_settings', Cache::configured())) {
Cache::delete('EventHandlers', 'cached_settings');
}
}
/**
* Forgets a loaded plugin or all of them if first parameter is null
*
* This method is identical to Plugin::load() with extra functionality
* that unregister event listeners when a plugin in unloaded.
*
* @param string $plugin name of the plugin to forget
* @return void
*/
public static function unload($plugin = null)
{
if (is_array($plugin)) {
foreach ($plugin as $name) {
if ($name === null) {
static::getCollection()->clear();
} else {
static::getCollection()->remove($plugin);
}
}
return;
}
$eventManager = EventManager::instance();
if ($eventManager instanceof EventManager) {
if ($plugin == null) {
$activePlugins = static::loaded();
foreach ($activePlugins as $activePlugin) {
$eventManager->detachPluginSubscribers($activePlugin);
}
} else {
$eventManager->detachPluginSubscribers($plugin);
}
}
Cache::delete('EventHandlers', 'cached_settings');
}
/**
* Delete plugin
*
* @param string $plugin Plugin name
* @return bool true when successful, false or array of error messages when failed
* @throws InvalidArgumentException
*/
public function delete($plugin)
{
if (empty($plugin)) {
throw new InvalidArgumentException(__d('croogo', 'Invalid plugin'));
}
$pluginPath = ROOT . DS . 'plugins' . DS . $plugin;
if (is_link($pluginPath)) {
return unlink($pluginPath);
}
$folder = new Folder();
$result = $folder->delete($pluginPath);
if ($result !== true) {
return $folder->errors();
}
return true;
}
/**
* Move plugin up or down in the bootstrap order
*
* @param string $dir valid values 'up' or 'down'
* @param string $plugin plugin alias
* @param array $bootstraps current bootstrap order
* @return array|string array when successful, string contains error message
*/
protected function _move($dir, $plugin, $bootstraps)
{
$index = array_search($plugin, $bootstraps);
if ($dir === 'up') {
if ($index) {
$swap = $bootstraps[$index - 1];
}
if ($index == 0 || $this->_isBuiltin($swap)) {
return __d('croogo', '%s is already at the first position', $plugin);
}
$before = array_slice($bootstraps, 0, $index - 1);
$after = array_slice($bootstraps, $index + 1);
$dependencies = $this->getDependencies($plugin);
if (in_array($swap, $dependencies)) {
return __d('croogo', 'Plugin %s depends on %s', $plugin, $swap);
}
$reordered = array_merge($before, (array)$plugin, (array)$swap);
} elseif ($dir === 'down') {
if ($index >= count($bootstraps) - 1) {
return __d('croogo', '%s is already at the last position', $plugin);
}
$swap = $bootstraps[$index + 1];
$before = array_slice($bootstraps, 0, $index);
$after = array_slice($bootstraps, $index + 2);
$dependencies = $this->getDependencies($swap);
if (in_array($plugin, $dependencies)) {
return __d('croogo', 'Plugin %s depends on %s', $swap, $plugin);
}
$reordered = array_merge($before, (array)$swap, (array)$plugin);
} else {
return __d('croogo', 'Invalid direction');
}
$reordered = array_merge($reordered, $after);
if ($this->_isBuiltin($swap)) {
return __d('croogo', 'Plugin %s cannot be reordered', $swap);
}
return $reordered;
}
/**
* Write Hook.bootstraps settings to database and json file
*
* @param array $bootstraps array of plugin aliases
* @return bool
* @throws Exception
*/
protected function _saveBootstraps($bootstraps)
{
static $Setting = null;
if (empty($Setting)) {
if (!Configure::read('Croogo.installed')) {
throw new Exception('Unable to save Hook.bootstraps when Croogo is not fully installed');
}
$Settings = TableRegistry::get('Croogo/Settings.Settings');
}
return $Settings->write('Hook.bootstraps', implode(',', $bootstraps));
}
/**
* Move plugin in the bootstrap order
*
* @param string $dir direction 'up' or 'down'
* @param string $plugin plugin alias
* @param array $bootstraps array of plugin aliases
* @return string|bool true when successful, string contains error message
*/
public function move($dir, $plugin, $bootstraps = null)
{
if (empty($bootstraps)) {
$bootstraps = explode(',', Configure::read('Hook.bootstraps'));
}
$reordered = $this->_move(strtolower($dir), $plugin, $bootstraps);
if (is_string($reordered)) {
return $reordered;
}
return $this->_saveBootstraps($reordered);
}
/**
* Returns the filesystem path for a plugin
*
* @param string $plugin name of the plugin in CamelCase format
* @return string path to the plugin folder
* @throws \Cake\Core\Exception\MissingPluginException if the folder for plugin was not found or plugin has not
* been loaded
*/
public static function path($plugin)
{
if (strstr($plugin, 'Croogo/')) {
return realpath(parent::path('Croogo/Core') . '..' . DS . substr($plugin, 7) . DS) . DS;
}
$path = Configure::read('plugins.' . $plugin);
if ($path) {
return $path;
}
$paths = App::path('Plugin');
$pluginPath = str_replace('/', DIRECTORY_SEPARATOR, $plugin);
foreach ($paths as $path) {
if (!is_dir($path . $pluginPath)) {
continue;
}
return $path . $pluginPath;
}
return parent::path($plugin);
}
/**
* Loads the events file for a plugin, or all plugins configured to load their respective events file
*
* @param string|null $plugin name of the plugin, if null will operate on all plugins having enabled the
* loading of events files
* @return bool
*/
public static function events($plugin = null)
{
if ($plugin === null) {
foreach (Plugin::loaded() as $p) {
static::events($p);
}
return true;
}
$instance = static::$plugins->get($plugin);
//debug($instance);
//if ((!isset($instance->events)) || ($instance->events === false)) {
// return false;
//}
if (!file_exists($instance->getConfigPath() . 'events.php')) {
return false;
}
return Configure::load($plugin . '.events');
}
/**
* Check whether a plugin can be loaded without the having to specify the path as an option.
*
* @param string $plugin name of the plugin
* @return bool
*/
public static function available($plugin)
{
if (Plugin::isLoaded($plugin)) {
return true;
}
try {
if (!static::path($plugin)) {
return false;
}
return true;
} catch (MissingPluginException $exception) {
}
return false;
}
/**
* @return string
*/
protected static function migrationConnectionName()
{
if (static::getConnectionConfiguration('default', false)) {
return 'default';
}
return static::getConnectionConfiguration('default')['name'];
}
/**
* @param $connectionName
* @param bool $useAliases
*
* @return array|bool
*/
protected static function getConnectionConfiguration($connectionName, $useAliases = true)
{
try {
return ConnectionManager::get($connectionName, $useAliases)->config();
} catch (MissingDatasourceConfigException $exception) {
}
return false;
}
/**
* @param PluginApplicationInterface $app
* @return void
*/
public static function setup(PluginApplicationInterface $app)
{
$dbConfigExists = false;
if (file_exists(ROOT . DS . 'config' . DS . 'database.php')) {
Configure::load('database', 'default');
ConnectionManager::drop('default');
ConnectionManager::config(Configure::consume('Datasources'));
}
try {
$defaultConnection = ConnectionManager::get('default');
$dbConfigExists = $defaultConnection->connect();
} catch (Exception $e) {
$dbConfigExists = false;
}
// Map our custom types
Type::map('params', 'Croogo\Core\Database\Type\ParamsType');
Type::map('encoded', 'Croogo\Core\Database\Type\EncodedType');
Type::map('link', 'Croogo\Core\Database\Type\LinkType');
/**
* Cache configuration
*/
$defaultCacheConfig = Cache::getConfig('default');
$defaultEngine = $defaultCacheConfig['className'];
$defaultPrefix = Hash::get($defaultCacheConfig, 'prefix', 'cake_');
$cacheConfig = [
'duration' => '+1 hour',
'path' => CACHE . 'queries' . DS,
'className' => $defaultEngine,
'prefix' => $defaultPrefix,
] + $defaultCacheConfig;
Configure::write('Croogo.Cache.defaultEngine', $defaultEngine);
Configure::write('Croogo.Cache.defaultPrefix', $defaultPrefix);
Configure::write('Croogo.Cache.defaultConfig', $cacheConfig);
$configured = Cache::configured();
if (!in_array('cached_settings', $configured)) {
Cache::setConfig('cached_settings', array_merge(
Configure::read('Croogo.Cache.defaultConfig'),
['groups' => ['settings']]
));
}
/**
* Default Acl plugin. Custom Acl plugin should override this value.
*/
Configure::write('Site.acl_plugin', 'Croogo/Acl');
/**
* Default API Route Prefix. This can be overriden in settings.
*/
Configure::write('Croogo.Api.path', 'api');
/**
* Admin theme
*/
Configure::write('Site.admin_theme', 'Croogo/Core');
/**
* Timezone
*/
$timezone = Configure::read('Site.timezone');
if (!$timezone) {
$timezone = 'UTC';
}
date_default_timezone_set($timezone);
/**
* Settings
*/
Configure::config('settings', new DatabaseConfig());
try {
Configure::load('settings', 'settings');
} catch (\Exception $e) {
Log::error($e->getMessage());
Log::error('You can ignore the above error during installation');
}
}
/**
* @param $app
* @return void
*/
public static function croogoBootstrap($app)
{
Configure::write(
'DebugKit.panels',
array_merge((array)Configure::read('DebugKit.panels'), [
'Croogo/Core.Plugins',
'Croogo/Core.ViewHelpers',
'Croogo/Core.Components',
])
);
Croogo::hookComponent('*', [
'Croogo' => [
'className' => 'Croogo/Core.Croogo',
'priority' => 5
]
]);
Croogo::hookComponent('*', 'Croogo/Acl.Filter');
Croogo::hookComponent('*', [
'Security' => [
'blackHoleCallback' => '_securityError',
],
]);
Croogo::hookComponent('*', 'Acl.Acl');
Croogo::hookComponent('*', 'Auth');
Croogo::hookComponent('*', 'Flash');
//Croogo::hookComponent('*', 'RequestHandler');
Croogo::hookComponent('*', 'Croogo/Core.Theme');
Croogo::hookHelper('*', 'Croogo/Core.Js');
Croogo::hookHelper('*', 'Croogo/Core.Layout');
// Make sure that the Croogo event manager is the global one
EventManager::instance();
time(function () {
/**
* Locale
*/
$siteLocale = Configure::read('Site.locale');
Configure::write('App.defaultLocale', $siteLocale);
I18n::setLocale($siteLocale);
/**
* Assets
*/
if (Configure::check('Site.asset_timestamp')) {
$timestamp = Configure::read('Site.asset_timestamp');
Configure::write(
'Asset.timestamp',
is_numeric($timestamp) ? (bool)$timestamp : $timestamp
);
unset($timestamp);
}
/**
* List of core plugins
*/
$corePlugins = [
'Croogo/Settings',
'Croogo/Acl',
'Croogo/Blocks',
'Croogo/Comments',
'Croogo/Contacts',
'Croogo/Menus',
'Croogo/Meta',
'Croogo/Nodes',
'Croogo/Taxonomy',
'Croogo/Users',
'Croogo/Wysiwyg',
'Croogo/Ckeditor',
'Croogo/Dashboards',
];
Configure::write('Core.corePlugins', $corePlugins);
}, 'Setting base configuration');
/**
* Use old translation format for the croogo domain
*/
$siteLocale = Configure::read('App.defaultLocale');
I18n::config('croogo', function ($domain, $locale) {
$loader = new MessagesFileLoader($domain, $locale, 'po');
$package = new Package('sprintf', 'default');
$localePackage = $loader();
if ($localePackage) {
$package->setMessages($localePackage->getMessages());
}
return $package;
});
time(function () use ($app) {
/**
* Load required plugins
*/
if (!Plugin::isLoaded('Acl')) {
$app->addPlugin('Acl', ['bootstrap' => true]);
}
if (!Plugin::isLoaded('BootstrapUI')) {
$app->addPlugin('BootstrapUI');
}
/**
* Extensions
*/
$app->addPlugin('Croogo/Extensions', [
'autoload' => true,
'bootstrap' => true,
'routes' => true,
'events' => true
]);
}, 'Loading dependencies');
/**
* Plugins
*/
$aclPlugin = Configure::read('Site.acl_plugin');
$pluginBootstraps = Configure::read('Hook.bootstraps');
$plugins = array_filter(explode(',', $pluginBootstraps));
if (!in_array($aclPlugin, $plugins)) {
$plugins = Hash::merge((array)$aclPlugin, $plugins);
}
$themes = [Configure::read('Site.theme'), Configure::read('Site.admin_theme')];
time(function () use ($app, $plugins, $themes) {
$option = [
'autoload' => true,
'bootstrap' => true,
'ignoreMissing' => true,
'routes' => true,
'events' => true
];
foreach ($plugins as $plugin) {
$plugin = Inflector::camelize($plugin);
if (Plugin::isLoaded($plugin)) {
continue;
}
try {
PluginManager::load($plugin, $option);
} catch (MissingPluginException $e) {
Log::error('Plugin not found during bootstrap: ' . $plugin);
continue;
}
}
foreach ($themes as $theme) {
if ($theme && !Plugin::isLoaded($theme) && PluginManager::available($theme)) {
PluginManager::load($theme, [
'autoload' => true,
'bootstrap' => true,
'routes' => true,
'events' => true,
'ignoreMissing' => true
]);
}
}
}, 'plugins-loading-configured', 'Loading configured plugins: ' . implode(', ', $plugins + $themes));
// FIXME DispatcherFactory::add('Croogo/Core.HomePage');
time(function () {
PluginManager::events();
EventManager::loadListeners();
}, 'Registering plugin listeners');
$setupFile = ROOT . '/config/croogo.php';
if (file_exists ($setupFile)) {
require_once $setupFile;
}
time(function () {
Croogo::dispatchEvent('Croogo.bootstrapComplete');
}, 'event-Croogo.bootstrapComplete', 'Event: Croogo.bootstrapComplete');
}
}