diff options
author | brettp <brettp@36083f99-b078-4883-b0ff-0f9b5a30f544> | 2011-01-02 23:00:23 +0000 |
---|---|---|
committer | brettp <brettp@36083f99-b078-4883-b0ff-0f9b5a30f544> | 2011-01-02 23:00:23 +0000 |
commit | fc21edb0785f2cac11dc592278fad97fffeeb082 (patch) | |
tree | 24047873840c5931766abaa5b15a73c0692e7565 /engine | |
parent | a826bea54e8934c19b2ada619d966cc7d9628b42 (diff) | |
download | elgg-fc21edb0785f2cac11dc592278fad97fffeeb082.tar.gz elgg-fc21edb0785f2cac11dc592278fad97fffeeb082.tar.bz2 |
Fixes #1986, #2170, #2225, #2759. Integrated ElggPluginPackage and ElggPluginManifest with ElggPlugin. System now uses ElggPlugin objects to determin plugins. Order is stored in private settings. This absolutely requires running upgrade.php.
git-svn-id: http://code.elgg.org/elgg/trunk@7817 36083f99-b078-4883-b0ff-0f9b5a30f544
Diffstat (limited to 'engine')
-rw-r--r-- | engine/classes/ElggPlugin.php | 739 | ||||
-rw-r--r-- | engine/classes/ElggPluginManifest.php | 71 | ||||
-rw-r--r-- | engine/classes/ElggPluginManifestParser18.php | 22 | ||||
-rw-r--r-- | engine/classes/ElggPluginPackage.php | 25 | ||||
-rw-r--r-- | engine/lib/plugins.php | 1134 | ||||
-rw-r--r-- | engine/lib/upgrades/2011010101.php | 75 | ||||
-rw-r--r-- | engine/tests/api/plugins.php | 28 | ||||
-rw-r--r-- | engine/tests/test_files/plugin_18/manifest.xml | 11 |
8 files changed, 1611 insertions, 494 deletions
diff --git a/engine/classes/ElggPlugin.php b/engine/classes/ElggPlugin.php index 887eb667f..fb9138ab9 100644 --- a/engine/classes/ElggPlugin.php +++ b/engine/classes/ElggPlugin.php @@ -9,6 +9,11 @@ * @subpackage Plugins.Settings */ class ElggPlugin extends ElggObject { + public $package; + public $manifest; + + private $path; + private $pluginID; /** * Set subtype to 'plugin' @@ -19,10 +24,704 @@ class ElggPlugin extends ElggObject { parent::initializeAttributes(); $this->attributes['subtype'] = "plugin"; + + // plugins must be public. + $this->access_id = ACCESS_PUBLIC; + } + + + /** + * Loads the plugin by GUID or path. + * + * @warning Unlike other ElggEntity objects, you cannot null instantiate + * ElggPlugin. You must point it to an actual plugin GUID or location. + * + * @param mixed $plugin The GUID of the ElggPlugin object or the path of + * the plugin to load. + */ + public function __construct($plugin) { + if (!$plugin) { + throw new PluginException(elgg_echo('PluginException:NullInstantiated')); + } + + // ElggEntity can be instantiated with a guid or an object. + // @todo plugins w/id 12345 + if (is_numeric($plugin) || is_object($plugin)) { + parent::__construct($plugin); + $this->path = get_config('plugins_path') . $this->getID(); + } else { + // not a full path, so assume an id + // use the default path + if (substr($plugin, 0, 1) != '/') { + $plugin = elgg_get_plugin_path() . $plugin; + } + + // path checking is done in the package + $plugin = sanitise_filepath($plugin); + $this->path = $plugin; + $path_parts = explode('/', rtrim($plugin, '/')); + $plugin_id = array_pop($path_parts); + $this->pluginID = $plugin_id; + + // check if we're loading an existing plugin + $existing_plugin = elgg_get_plugin_from_id($this->pluginID); + $existing_guid = null; + + if ($existing_plugin) { + $existing_guid = $existing_plugin->guid; + } + + // load the rest of the plugin + parent::__construct($existing_guid); + } + + // We have to let the entity load so we can manipulate it with the API. + // If the path is wrong or would cause an exception, catch it, + // disable the plugin, and emit an error. + try { + $this->package = new ElggPluginPackage($this->path, false); + $this->manifest = $this->package->getManifest(); + } catch (Exception $e) { + // we always have to allow the entity to load. + } + } + + + /** + * Save the plugin object. Make sure required values exist. + * + * @see ElggObject::save() + * @return bool + */ + public function save() { + // own by the current site so users can be deleted without affecting plugins + $site = get_config('site'); + $this->attributes['site_guid'] = $site->guid; + $this->attributes['owner_guid'] = $site->guid; + $this->attributes['container_guid'] = $site->guid; + $this->attributes['title'] = $this->pluginID; + + if (parent::save()) { + // make sure we have a priority + $priority = $this->getPriority(); + if ($priority === FALSE || $priority === NULL) { + return $this->setPriority('last'); + } + } else { + return false; + } + } + + + // Plugin ID + + /** + * Returns the ID (dir name) of this plugin + * + * @return string + */ + public function getID() { + return $this->title; + } + + + /** + * Sets the location of this plugin. + * + * @param path $id The path to the plugin's dir. + * @return bool + */ + public function setID($id) { + return $this->attributes['title'] = $id; + } + + + // Load Priority + + /** + * Gets the plugin's load priority. + * + * @return int + */ + public function getPriority() { + $name = elgg_namespace_plugin_private_setting('internal', 'priority'); + return $this->$name; + } + + + /** + * Sets the priority of the plugin + * + * @param mixed $priority The priority to set. One of +1, -1, first, last, or a number. + * If given a number, this will displace all plugins at that number + * and set their priorities +1 + * @param mixed $site_guid Optional site GUID. + * @return bool + */ + public function setPriority($priority, $site_guid = null) { + if (!$this->guid) { + return false; + } + + $db_prefix = get_config('dbprefix'); + $name = elgg_namespace_plugin_private_setting('internal', 'priority'); + // if no priority assume a priority of 0 + $old_priority = (int) $this->getPriority(); + $max_priority = elgg_get_max_plugin_priority(); + + if ($priority == $old_priority) { + return false; + } + + // there's nothing above the max. + if ($priority > $max_priority) { + $priority = $max_priority; + } + + // there's nothing below 1. + if ($priority < 1) { + $priority = 1; + } + + // (int) 0 matches (string) first, so cast to string. + $priority = (string) $priority; + + switch ($priority) { + case '+1': + $priority = $old_priority + 1; + break; + + case '-1': + $priority = $old_priority - 1; + break; + + case 'first': + $priority = 1; + break; + + case 'last': + $priority = $max_priority; + break; + } + + // should be a number by now + if ($priority) { + if (!is_numeric($priority)) { + return false; + } + + if ($priority > $old_priority) { + $op = '-'; + $where = "CAST(value as unsigned) BETWEEN $old_priority AND $priority"; + } else { + $op = '+'; + $where = "CAST(value as unsigned) BETWEEN $priority AND $old_priority"; + } + + // displace the ones affected by this change + $q = "UPDATE {$db_prefix}private_settings + SET value = CAST(value as unsigned) $op 1 + WHERE entity_guid != $this->guid + AND name = '$name' + AND $where"; + + if (!update_data($q)) { + return false; + } + + // set this priority + if ($this->set($name, $priority)) { + //return elgg_plugins_reindex_priorities(); + return true; + } else { + return false; + } + } + + return false; + } + + + // Plugin settings + + /** + * Returns a plugin setting + * + * @todo These need to be namespaced + * + * @param string $name The setting name + * @return mixed + */ + public function getSetting($name) { + return $this->$name; + } + + + /** + * Set a plugin setting for the plugin + * + * @todo This will only work once the plugin has a GUID. + * @todo These need to be namespaced. + * + * @param string $name The name to set + * @param string $value The value to set + * + * @return bool + */ + public function setSetting($name, $value) { + if ($this->guid) { + return false; + } + // Hook to validate setting + $value = elgg_trigger_plugin_hook('plugin:setting', 'plugin', array( + 'plugin' => $this->pluginID, + 'plugin_object' => $this, + 'name' => $name, + 'value' => $value + ), $value); + + return $this->$name = $value; } /** + * Removes a plugin setting name and value. + * + * @param string $name The setting name to remove + * + * @return bool + */ + public function removeSetting($name) { + return remove_private_setting($this->guid, $name); + } + + + /** + * Removes all settings for this plugin. + * + * @todo Should be a better way to do this without dropping to raw SQL. + * @todo If we could namespace the plugin settings this would be cleaner. + * @return bool + */ + public function removeAllSettings() { + $db_prefix = get_config('dbprefix'); + $ps_prefix = elgg_namespace_plugin_private_setting('setting', ''); + + $q = "DELETE FROM {$db_prefix}private_settings + WHERE entity_guid = $this->guid + AND name NOT LIKE '$ps_prefix%'"; + + return delete_data($q); + } + + + // User settings + + /** + * Returns a user's setting for this plugin + * + * @param int $user_guid The user GUID + * @param string $name The setting name + * + * @return mixed The setting string value or false + */ + public function getUserSetting($user_guid, $name) { + $name = elgg_namespace_plugin_private_setting('user_setting', $name, $this->getID()); + return get_private_setting($user_guid, $name); + } + + /** + * Sets a user setting for a plugin + * + * @param int $user_guid The user GUID + * @param string $name The setting name + * @param string $value The setting value + * + * @return mixed The new setting ID or false + */ + public function setUserSetting($user_guid, $name, $value) { + $name = elgg_namespace_plugin_private_setting('user_setting', $name, $this->getID()); + return set_private_setting($user_guid, $name, $value); + } + + + /** + * Removes a user setting name and value. + * + * @param int $user_guid The user GUID + * @param string $name The user setting name + * + * @return bool + */ + public function removeUserSetting($user_guid, $name) { + $name = elgg_namespace_plugin_private_setting('user_setting', $name, $this->getID()); + return remove_private_setting($user_guid, $name); + } + + + /** + * Removes all User Settings for this plugin + * + * Use {@link removeAllUsersSettings()} to remove all user + * settings for all users. (Note the plural 'Users'.) + * + * @param int $user_guid The user GUID to remove user settings. + * @return bool + */ + public function removeAllUserSettings($user_guid) { + $db_prefix = get_config('dbprefix'); + $ps_prefix = elgg_namespace_plugin_private_setting('user_setting', '', $this->getID()); + + $q = "DELETE FROM {$db_prefix}private_settings + WHERE entity_guid = $user_guid + AND name LIKE '$ps_prefix%'"; + + return delete_data($q); + } + + + /** + * Removes this plugin's user settings for all users. + * + * Use {@link removeAllUserSettings()} if you just want to remove + * settings for a single user. + * + * @return bool + */ + public function removeAllUsersSettings() { + $db_prefix = get_config('dbprefix'); + $ps_prefix = elgg_namespace_plugin_private_setting('user_setting', '', $this->getID()); + + $q = "DELETE FROM {$db_prefix}private_settings + WHERE name LIKE '$ps_prefix%'"; + + return delete_data($q); + } + + + // validation + + /** + * Returns if the plugin is complete, meaning has all required files + * and Elgg can read them and they make sense. + * + * @todo bad name? This could be confused with isValid() from ElggPackage. + * + * @return bool + */ + public function isValid() { + if (!$this->getID()) { + return false; + } + + if (!$this->package instanceof ElggPluginPackage) { + return false; + } + + if (!$this->package->isValid()) { + return false; + } + + return true; + } + + + /** + * Is this plugin active? + * + * @param int $site_guid Optional site guid. + * @return bool + */ + public function isActive($site_guid = null) { + if (!$this->guid) { + return false; + } + + if ($site_guid) { + $site = get_entity($site_guid); + + if (!($site instanceof ElggSite)) { + return false; + } + } else { + $site = get_config('site'); + } + + return check_entity_relationship($this->guid, 'active_plugin', $site->guid); + } + + + /** + * Checks if this plugin can be activated on the current + * Elgg installation. + * + * @param mixed $site_guid Optional site guid + * @return bool + */ + public function canActivate($site_guid = null) { + if ($this->package) { + return $this->package->isValid() && $this->package->checkDependencies(); + } + + return false; + } + + + // activating and deactivating + + /** + * Actives the plugin for the current site. + * + * @param mixed $site_guid Optional site GUID. + * @return bool + */ + public function activate($site_guid = null) { + if ($this->isActive($site_guid)) { + return false; + } + // set in the db, now perform tasks and emit events + if ($this->setStatus(true, $site_guid)) { + // emit an event. returning false will make this not be activated. + // we need to do this after it's been fully activated + // or the deactivate will be confused. + $params = array( + 'plugin_id' => $this->pluginID, + 'plugin_entity' => $this + ); + + $return = elgg_trigger_event('activate', 'plugin', $params); + + // if there are any on_enable functions, start the plugin now and run them + // Note: this will not run re-run the init hooks! + $functions = $this->manifest->getOnActivate(); + if ($return && $functions) { + $flags = ELGG_PLUGIN_INCLUDE_START | ELGG_PLUGIN_REGISTER_CLASSES + | ELGG_PLUGIN_REGISTER_LANGUAGES | ELGG_PLUGIN_REGISTER_VIEWS; + + $this->start($flags); + foreach ($functions as $function) { + if (!is_callable($function)) { + $return = false; + } else { + $on_enable = call_user_func($function); + // allow null to mean "I don't care" like other subsystems + $return = ($on_disable === false) ? false: true; + } + + if ($return === false) { + break; + } + } + } + + if ($return === false) { + $this->deactivate($site_guid); + } + + return $return; + } + + return false; + } + + + /** + * Deactivates the plugin. + * + * @param mixed $site_guid Optional site GUID. + * @return bool + */ + public function deactivate($site_guid = null) { + if (!$this->isActive($site_guid)) { + return false; + } + + // emit an event. returning false will cause this to not be deactivated. + $params = array( + 'plugin_id' => $this->pluginID, + 'plugin_entity' => $this + ); + + $return = elgg_trigger_event('deactivate', 'plugin', $params); + + // run any deactivate functions + // check for the manifest in case we haven't fully loaded the plugin. + if ($this->manifest) { + $functions = $this->manifest->getOnDeactivate(); + } else { + $functions = array(); + } + + if ($return && $functions) { + foreach ($functions as $function) { + if (!is_callable($function)) { + $return = false; + } else { + $on_enable = call_user_func($function); + // allow null to mean "I don't care" like other subsystems + $return = ($on_disable === false) ? false : true; + } + + if ($return === false) { + break; + } + } + } + + if ($return === false) { + return false; + } else { + return $this->setStatus(false, $site_guid); + } + } + + + /** + * Start the plugin. + * + * @param int $flags Start flags for the plugin. See the constants in lib/plugins.php for details. + * @return true + * @throws PluginException + */ + public function start($flags) { + if (!$this->canActivate()) { + return false; + } + + // include start file + if ($flags & ELGG_PLUGIN_INCLUDE_START) { + $this->includeStart(); + } + + // include views + if ($flags & ELGG_PLUGIN_REGISTER_VIEWS) { + $this->registerViews(); + } + + // include languages + if ($flags & ELGG_PLUGIN_REGISTER_LANGUAGES) { + $this->registerLanguages(); + } + + // include classes + if ($flags & ELGG_PLUGIN_REGISTER_CLASSES) { + $this->registerClasses(); + } + + return true; + } + + + // start helpers + + /** + * Includes the plugin's start file + * + * @throws PluginException + * @return true + */ + protected function includeStart() { + $start = "$this->path/start.php"; + if (!include($start)) { + $msg = elgg_echo('ElggPlugin:Exception:CannotIncludeStart', + array($this->getID(), $this->guid, $this->path)); + throw new PluginException($msg); + } + + return true; + } + + /** + * Registers the plugin's views + * + * @throws PluginException + * @return true + */ + protected function registerViews() { + $view_dir = "$this->path/views/"; + + // plugins don't have to have views. + if (!is_dir($view_dir)) { + return true; + } + + // but if they do, they have to be readable + $handle = opendir($view_dir); + if (!$handle) { + $msg = elgg_echo('ElggPlugin:Exception:CannotRegisterViews', + array($this->getID(), $this->guid, $view_dir)); + throw new PluginException($msg); + } + + while (FALSE !== ($view_type = readdir($handle))) { + $view_type_dir = $view_dir . $view_type; + + if ('.' !== substr($view_type, 0, 1) && is_dir($view_type_dir)) { + if (autoregister_views('', $view_type_dir, $view_dir, $view_type)) { + elgg_register_viewtype($view_type); + } else { + $msg = elgg_echo('ElggPlugin:Exception:CannotRegisterViews', + array($this->getID(), $view_type_dir)); + throw new PluginException($msg); + } + } + } + + return true; + } + + /** + * Registers the plugin's languages + * + * @throws PluginException + * @return true + */ + protected function registerLanguages() { + $languages_path = "$this->path/languages"; + + // don't need to have classes + if (!is_dir($languages_path)) { + return true; + } + + // but need to have working ones. + if (!register_translations($languages_path)) { + $msg = elgg_echo('ElggPlugin:Exception:CannotRegisterLanguages', + array($this->getID(), $this->guid, $languages_path)); + throw new PluginException($msg); + } + + return true; + } + + /** + * Registers the plugin's classes + * + * @throws PluginException + * @return true + */ + protected function registerClasses() { + $classes_path = "$this->path/classes"; + + // don't need to have classes + if (!is_dir($classes_path)) { + return true; + } + + // but need to have working ones. + if (!elgg_register_classes($classes_path)) { + $msg = elgg_echo('ElggPlugin:Exception:CannotRegisterClasses', + array($this->getID(), $this->guid, $classes_path)); + throw new PluginException($msg); + } + + return true; + } + + + // generic helpers and overrides + + /** * Get a value from private settings. * * @param string $name Name @@ -30,8 +729,16 @@ class ElggPlugin extends ElggObject { * @return mixed */ public function get($name) { + // rewrite for old and inaccurate plugin:setting + if (strstr($name, 'plugin:setting:')) { + $msg = 'Direct access of user settings is deprecated. Use ElggPlugin->getUserSetting()'; + elgg_deprecated_notice($msg, 1.8); + $name = str_replace('plugin:setting:', '', $name); + $name = elgg_namespace_plugin_private_setting('user_setting', $name); + } + // See if its in our base attribute - if (isset($this->attributes[$name])) { + if (array_key_exists($name, $this->attributes)) { return $this->attributes[$name]; } @@ -69,4 +776,34 @@ class ElggPlugin extends ElggObject { return true; } + + /** + * Sets the plugin to active or inactive for $site_guid. + * + * @param bool $active Set to active or inactive + * @param mixed $site_guid Int for specific site, null for current site. + * + * @return bool + */ + private function setStatus($active, $site_guid = null) { + if (!$this->guid) { + return false; + } + + if ($site_guid) { + $site = get_entity($site_guid); + + if (!($site instanceof ElggSite)) { + return false; + } + } else { + $site = get_config('site'); + } + + if ($active) { + return add_entity_relationship($this->guid, 'active_plugin', $site->guid); + } else { + return remove_entity_relationship($this->guid, 'active_plugin', $site->guid); + } + } }
\ No newline at end of file diff --git a/engine/classes/ElggPluginManifest.php b/engine/classes/ElggPluginManifest.php index 801769eb9..9fcdaaf55 100644 --- a/engine/classes/ElggPluginManifest.php +++ b/engine/classes/ElggPluginManifest.php @@ -481,6 +481,77 @@ class ElggPluginManifest { } /** + * Returns the functions to run upon activation + * + * @return array + */ + public function getOnActivate() { + $functions = $this->parser->getAttribute('on_activate'); + + if (!$functions) { + $functions = array(); + } + + return $functions; + } + + /** + * Returns the functions to run upon deactivation + * + * @return array + */ + public function getOnDeactivate() { + $functions = $this->parser->getAttribute('on_deactivate'); + + if (!$functions) { + $functions = array(); + } + + return $functions; + } + + /** + * Returns the admin interface to use. + * + * @return string simple or advanced + */ + public function getAdminInterface() { + $interface = $this->parser->getAttribute('admin_interface'); + + switch ($interface) { + case 'simple': + case 'advanced': + return $interface; + + default: + return 'advanced'; + } + } + + /** + * Returns the admin interface to use. + * + * @return bool + */ + public function getActivateOnInstall() { + $activate = $this->parser->getAttribute('activate_on_install'); + switch (strtolower($activate)) { + case 'yes': + case 'true': + case 'on': + case 1: + return true; + + case 'no': + case 'false': + case 'off': + case 0: + case '': + return false; + } + } + + /** * Normalizes an array into the structure specified * * @param array $struct The struct to normalize $element to. diff --git a/engine/classes/ElggPluginManifestParser18.php b/engine/classes/ElggPluginManifestParser18.php index 9a4cfb2b7..1f5b51bb5 100644 --- a/engine/classes/ElggPluginManifestParser18.php +++ b/engine/classes/ElggPluginManifestParser18.php @@ -15,7 +15,8 @@ class ElggPluginManifestParser18 extends ElggPluginManifestParser { protected $validAttributes = array( 'name', 'author', 'version', 'blurb', 'description', 'website', 'copyright', 'license', 'requires', 'screenshot', - 'category', 'conflicts', 'provides', 'admin' + 'category', 'conflicts', 'provides', 'on_activate', 'on_deactivate', + 'admin_interface', 'activate_on_install' ); /** @@ -45,26 +46,19 @@ class ElggPluginManifestParser18 extends ElggPluginManifestParser { case 'website': case 'copyright': case 'license': + case 'admin_interface': + case 'activate_on_install': $parsed[$element->name] = $element->content; break; // arrays + case 'on_activate': + case 'on_deactivate': case 'category': - $parsed['category'][] = $element->content; - break; - - case 'admin': - $parsed['admin'] = array(); - if (!isset($element->children)) { - return false; - } - - foreach ($element->children as $child_element) { - $parsed['admin'][$child_element->name] = $child_element->content; - } - + $parsed[$element->name][] = $element->content; break; + // 3d arrays case 'screenshot': case 'provides': case 'conflicts': diff --git a/engine/classes/ElggPluginPackage.php b/engine/classes/ElggPluginPackage.php index 8bbacce22..6301ad1f2 100644 --- a/engine/classes/ElggPluginPackage.php +++ b/engine/classes/ElggPluginPackage.php @@ -239,16 +239,6 @@ class ElggPluginPackage { return true; } - /** - * Checks if this plugin can be activated on the current - * Elgg installation. - * - * @return bool - */ - public function canActivate() { - return $this->checkDependencies(); - } - /************ * Manifest * @@ -261,7 +251,9 @@ class ElggPluginPackage { */ public function getManifest() { if (!$this->manifest) { - $this->loadManifest(); + if (!$this->loadManifest()) { + return false; + } } return $this->manifest; @@ -275,9 +267,14 @@ class ElggPluginPackage { */ private function loadManifest() { $file = $this->path . 'manifest.xml'; - $this->manifest = new ElggPluginManifest($file, $this->id); - if ($this->manifest) { + try { + $this->manifest = new ElggPluginManifest($file, $this->id); + } catch (Exception $e) { + return false; + } + + if ($this->manifest instanceof ElggPluginManifest) { return true; } @@ -307,7 +304,7 @@ class ElggPluginPackage { public function checkDependencies($full_report = false) { $requires = $this->getManifest()->getRequires(); $conflicts = $this->getManifest()->getConflicts(); - $enabled_plugins = get_installed_plugins('enabled'); + $enabled_plugins = elgg_get_plugins('active'); $report = array(); foreach (array('requires', 'conflicts') as $dep_type) { diff --git a/engine/lib/plugins.php b/engine/lib/plugins.php index 29f55926c..cd74353de 100644 --- a/engine/lib/plugins.php +++ b/engine/lib/plugins.php @@ -7,202 +7,296 @@ * @subpackage Plugins */ -/// Cache enabled plugins per page -$ENABLED_PLUGINS_CACHE = NULL; +/** + * Tells ElggPlugin::start() to include the start.php file. + */ +define('ELGG_PLUGIN_INCLUDE_START', 1); /** - * Returns a list of plugins to load, in the order that they should be loaded. + * Tells ElggPlugin::start() to automatically register the plugin's views. + */ +define('ELGG_PLUGIN_REGISTER_VIEWS', 2); + +/** + * Tells ElggPlugin::start() to automatically register the plugin's languages. + */ +define('ELGG_PLUGIN_REGISTER_LANGUAGES', 4); + +/** + * Tells ElggPlugin::start() to automatically register the plugin's classes. + */ +define('ELGG_PLUGIN_REGISTER_CLASSES', 8); + +/** + * Prefix for plugin setting names * - * @return array List of plugins + * @todo Can't namespace these because many plugins directly call + * private settings via $entity->$name. */ -function get_plugin_list() { - global $CONFIG; +//define('ELGG_PLUGIN_SETTING_PREFIX', 'plugin:setting:'); - if (!empty($CONFIG->pluginlistcache)) { - return $CONFIG->pluginlistcache; - } +/** + * Prefix for plugin user setting names + */ +define('ELGG_PLUGIN_USER_SETTING_PREFIX', 'plugin:user_setting:'); - if ($site = get_entity($CONFIG->site_guid)) { - $pluginorder = $site->pluginorder; - if (!empty($pluginorder)) { - $plugins = unserialize($pluginorder); +/** + * Internal settings prefix + * + * @todo This could be resolved by promoting ElggPlugin to a 5th type. + */ +define('ELGG_PLUGIN_INTERNAL_PREFIX', 'elgg:internal:'); - $CONFIG->pluginlistcache = $plugins; - return $plugins; - } else { - // this only runs on install, otherwise uses serialized plugin order - $plugins = array(); - - if ($handle = opendir($CONFIG->pluginspath)) { - while ($mod = readdir($handle)) { - // must be directory and not begin with a . - if (substr($mod, 0, 1) !== '.' && is_dir($CONFIG->pluginspath . "/" . $mod)) { - $plugins[] = $mod; - } - } - } - sort($plugins); +/** + * Returns a list of plugin IDs (dir names) from a dir. + * + * @param string $dir A dir to scan for plugins. Defaults to config's plugins_path. + * + * @return array + */ +function elgg_get_plugin_ids_in_dir($dir = null) { + if (!$dir) { + $dir = elgg_get_plugin_path(); + } + + $plugin_idss = array(); + $handle = opendir($dir); - $CONFIG->pluginlistcache = $plugins; - return $plugins; + if ($handle) { + while ($plugin_id = readdir($handle)) { + // must be directory and not begin with a . + if (substr($plugin_id, 0, 1) !== '.' && is_dir($dir . $plugin_id)) { + $plugin_ids[] = $plugin_id; + } } } - return false; + sort($plugin_ids); + + return $plugin_ids; } /** - * Regenerates the list of known plugins and saves it to the current site - * - * Important: You should regenerate simplecache and the viewpath cache after executing this function - * otherwise you may experience view display artifacts. Do this with the following code: + * Discovers plugins in the plugins_path setting and creates ElggPlugin + * entities for them if they don't exist. If there are plugins with entities + * but not actual files, will disable the ElggPlugin entities and mark as inactive. + * The ElggPlugin object holds config data, so don't delete. * - * elgg_view_regenerate_simplecache(); - * elgg_filepath_cache_reset(); - * - * @param array $pluginorder Optionally, a list of existing plugins and their orders - * - * @return array The new list of plugins and their orders + * @todo Crappy name? + * @return bool */ -function regenerate_plugin_list($pluginorder = FALSE) { - global $CONFIG; - - $CONFIG->pluginlistcache = NULL; +function elgg_generate_plugin_entities() { + $site = get_config('site'); + $dir = elgg_get_plugin_path(); + + $options = array( + 'type' => 'object', + 'subtype' => 'plugin', + 'limit' => ELGG_ENTITIES_NO_VALUE + ); + + $old_ia = elgg_set_ignore_access(true); + $old_access = access_get_show_hidden_status(); + access_show_hidden_entities(true); + $known_plugins = elgg_get_entities_from_relationship($options); + + if (!$known_plugins) { + $known_plugins = array(); + } - if ($site = get_entity($CONFIG->site_guid)) { - if (empty($pluginorder)) { - $pluginorder = $site->pluginorder; - $pluginorder = unserialize($pluginorder); - } else { - ksort($pluginorder); + // map paths to indexes + $id_map = array(); + foreach ($known_plugins as $i => $plugin) { + // if the ID is wrong, delete the plugin because we can never load it. + $id = $plugin->getID(); + if (!$id) { + $plugin->delete(); + unset($known_plugins[$i]); + continue; } + $id_map[$plugin->getID()] = $i; + } - if (empty($pluginorder)) { - $pluginorder = array(); - } + $physical_plugins = elgg_get_plugin_ids_in_dir($dir); - $max = 0; - if (sizeof($pluginorder)) { - foreach ($pluginorder as $key => $plugin) { - if (is_dir($CONFIG->pluginspath . "/" . $plugin)) { - if ($key > $max) { - $max = $key; - } - } else { - unset($pluginorder[$key]); - } - } - } - // Add new plugins to the end - if ($handle = opendir($CONFIG->pluginspath)) { - while ($mod = readdir($handle)) { - // must be directory and not begin with a . - if (substr($mod, 0, 1) !== '.' && is_dir($CONFIG->pluginspath . "/" . $mod)) { - if (!in_array($mod, $pluginorder)) { - $max = $max + 10; - $pluginorder[$max] = $mod; - } - } + if (!$physical_plugins) { + return false; + } + + $new_plugin_priority = elgg_get_max_plugin_priority() + 1; + + // check real plugins against known ones + foreach ($physical_plugins as $plugin_id) { + // is this already in the db? + if (array_key_exists($plugin_id, $id_map)) { + $index = $id_map[$plugin_id]; + $plugin = $known_plugins[$index]; + // was this plugin deleted and its entity disabled? + if ($plugin->enabled != 'yes') { + $plugin->enable(); + $plugin->deactivate(); } - } - ksort($pluginorder); + // remove from the list of plugins to disable + unset($known_plugins[$index]); + } else { + // add new plugins + $plugin = new ElggPlugin($plugin_id); + $plugin->save(); + $plugin->setPriority($new_plugin_priority); - // Now reorder the keys .. - $key = 10; - $plugins = array(); - if (sizeof($pluginorder)) { - foreach ($pluginorder as $plugin) { - $plugins[$key] = $plugin; - $key = $key + 10; - } + $new_plugin_priority++; } + } - $plugins = serialize($plugins); + // everything remaining in $known_plugins needs to be disabled + // because they are entities, but their dirs were removed. + // don't delete the entities because they hold settings. + foreach ($known_plugins as $plugin) { + $plugin->deactivate(); + $plugin->disable(); + } + + access_show_hidden_entities($old_access); + elgg_set_ignore_access($old_ia); - $site->pluginorder = $plugins; + return true; +} - return $plugins; +/** + * Returns an ElggPlugin object with the path $path. + * + * @param string $id The id (dir name) of the plugin. NOT the guid. + * @return mixed ElggPlugin or false. + */ +function elgg_get_plugin_from_id($id) { + $id = sanitize_string($id); + $db_prefix = get_config('dbprefix'); + + $options = array( + 'type' => 'object', + 'subtype' => 'plugin', + 'joins' => array("JOIN {$db_prefix}objects_entity oe on oe.guid = e.guid"), + 'wheres' => array("oe.title = '$id'"), + 'limit' => 1 + ); + + $plugins = elgg_get_entities($options); + + if ($plugins) { + return $plugins[0]; } - return FALSE; + return false; +} + +/** + * Returns if a plugin exists in the system. + * + * @warning This checks only plugins that are registered in the system! + * If the plugin cache is outdated, be sure to regenerate it with + * {@link elgg_generate_plugin_objects()} first. + * + * @param string $id The plugin ID. + * @return bool + */ +function elgg_plugin_exists($id) { + $plugin = elgg_get_plugin_from_id($id); + + return ($plugin) ? true : false; } +/** + * Returns the highest priority of the plugins + * + * @return int + */ +function elgg_get_max_plugin_priority() { + $db_prefix = get_config('dbprefix'); + $priority = elgg_namespace_plugin_private_setting('internal', 'priority'); + $plugin_subtype = get_subtype_id('object', 'plugin'); + + $q = "SELECT MAX(CAST(ps.value AS unsigned)) as max + FROM {$db_prefix}entities e, {$db_prefix}private_settings ps + WHERE ps.name = '$priority' + AND ps.entity_guid = e.guid + AND e.type = 'object' and e.subtype = $plugin_subtype"; + + $data = get_data($q); + if ($data) { + return $data[0]->max; + } + + // can't have a priority of 0. + return 1; +} /** - * For now, loads plugins directly + * Loads all active plugins in the order specified in the tool admin panel. * - * @todo Add proper plugin handler that launches plugins in an - * admin-defined order and activates them on admin request + * @note This is called on every page load and includes additional checking that plugins + * are fit to be loaded. If a plugin is active and problematic, it will be disabled + * and a visible error emitted. * - * @return void + * @return bool */ -function load_plugins() { +function elgg_load_plugins() { global $CONFIG; - if (empty($CONFIG->pluginspath)) { - return; + $plugins_path = elgg_get_plugin_path(); + $start_flags = ELGG_PLUGIN_INCLUDE_START + | ELGG_PLUGIN_REGISTER_VIEWS + | ELGG_PLUGIN_REGISTER_LANGUAGES + | ELGG_PLUGIN_REGISTER_CLASSES; + + if (!$plugins_path) { + return false; } // temporary disable all plugins if there is a file called 'disabled' in the plugin dir - if (file_exists($CONFIG->pluginspath . "disabled")) { - return; + if (file_exists("$plugins_path/disabled")) { + return false; } - - // See if we have cached values for things + + // Load view caches if available $cached_view_paths = elgg_filepath_cache_load('views'); $cached_view_types = elgg_filepath_cache_load('view_types'); $cached_view_info = is_string($cached_view_paths) && is_string($cached_view_types); + if ($cached_view_info) { $CONFIG->views = unserialize($cached_view_paths); $CONFIG->view_types = unserialize($cached_view_types); + + // don't need to register views + $start_flags = $start_flags & ~ELGG_PLUGIN_REGISTER_VIEWS; } - $plugins = get_plugin_list(); - - if (sizeof($plugins)) { - foreach ($plugins as $mod) { - if (is_plugin_enabled($mod)) { - if (file_exists($CONFIG->pluginspath . $mod)) { - if (!include($CONFIG->pluginspath . $mod . "/start.php")) { - // automatically disable the bad plugin - disable_plugin($mod); - - // register error rather than rendering the site unusable with exception - register_error(sprintf(elgg_echo('PluginException:MisconfiguredPlugin'), $mod)); - - // continue loading remaining plugins - continue; - } - - if (!$cached_view_info) { - $view_dir = $CONFIG->pluginspath . $mod . '/views/'; - - if (is_dir($view_dir) && ($handle = opendir($view_dir))) { - while (FALSE !== ($view_type = readdir($handle))) { - $view_type_dir = $view_dir . $view_type; - - if ('.' !== substr($view_type, 0, 1) && is_dir($view_type_dir)) { - if (autoregister_views('', $view_type_dir, $view_dir, $view_type)) { - // add the valid view type. - if (!in_array($view_type, $CONFIG->view_types)) { - $CONFIG->view_types[] = $view_type; - } - } - } - } - } - } - - if (is_dir($CONFIG->pluginspath . "$mod/classes")) { - elgg_register_classes($CONFIG->pluginspath . "$mod/classes"); - } - - if (is_dir($CONFIG->pluginspath . $mod . "/languages")) { - register_translations($CONFIG->pluginspath . $mod . "/languages/"); - } - } + $return = true; + $plugins = elgg_get_plugins('active'); + if ($plugins) { + foreach ($plugins as $plugin) { + // check if plugin can be started and try to start it. + // if anything is bad, disable it and emit a message. + if (!$plugin->isValid()) { + $plugin->deactivate(); + $msg = elgg_echo('PluginException:MisconfiguredPlugin', array($plugin->getID(), $plugin->guid)); + register_error($msg); + $return = false; + + continue; + } + + try { + $plugin->start($start_flags); + } catch (Exception $e) { + $plugin->deactivate(); + $msg = elgg_echo('PluginException:CannotStart', + array($plugin->getID(), $plugin->guid, $e->getMessage())); + register_error($msg); + $return = false; + + continue; } } } @@ -212,21 +306,257 @@ function load_plugins() { elgg_filepath_cache_save('views', serialize($CONFIG->views)); elgg_filepath_cache_save('view_types', serialize($CONFIG->view_types)); } + + return $return; +} + +/** + * Returns an ordered list of plugins + * + * @param string $status The status of the plugins. active, inactive, or all. + * @param bool $include_deleted Include physically deleted (and so inactive and disabled) plugins? + * @param mixed $site_guid Optional site guid + * @return array + */ +function elgg_get_plugins($status = 'active', $include_deleted = false, $site_guid = NULL) { + $db_prefix = get_config('dbprefix'); + $priority = elgg_namespace_plugin_private_setting('internal', 'priority'); + + if (!$site_guid) { + $site = get_config('site'); + $site_guid = $site->guid; + } + + // grab plugins + $options = array( + 'type' => 'object', + 'subtype' => 'plugin', + 'limit' => ELGG_ENTITIES_NO_VALUE, + 'joins' => array("JOIN {$db_prefix}private_settings ps on ps.entity_guid = e.guid"), + 'wheres' => array("ps.name = '$priority'"), + 'order_by' => "CAST(ps.value as unsigned), e.guid" + ); + + switch ($status) { + case 'active': + $options['relationship'] = 'active_plugin'; + $options['relationship_guid'] = $site_guid; + $options['inverse_relationship'] = true; + break; + + case 'inactive': + $options['wheres'][] = "NOT EXISTS ( + SELECT 1 FROM {$db_prefix}entity_relationships active_er + WHERE active_er.guid_one = e.guid + AND active_er.relationship = 'active_plugin' + AND active_er.guid_two = $site_guid)"; + break; + + case 'all': + default: + break; + } + + if ($include_deleted) { + $old_id = elgg_set_ignore_access(true); + } + + $plugins = elgg_get_entities_from_relationship($options); + + if ($include_deleted) { + elgg_set_ignore_access($old_ia); + } + + return $plugins; +} + +/** + * Reorder plugins to an order specified by the array. + * Plugins not included in this array will be appended to the end. + * + * @note This doesn't use the ElggPlugin->setPriority() method because + * all plugins are being changed and we don't want it to automatically + * reorder plugins. + * + * @param array $order An array of plugin ids in the order to set them + * @return bool + */ +function elgg_set_plugin_priorities(array $order) { + $name = elgg_namespace_plugin_private_setting('internal', 'priority'); + + $plugins = elgg_get_plugins('any', true); + if (!$plugins) { + return false; + } + + $return = true; + + // reindex to get standard counting. no need to increment by 10. + // though we do start with 1 + $order = array_values($order); + + foreach ($plugins as $plugin) { + $plugin_id = $plugin->getID(); + + if (!in_array($plugin_id, $order)) { + $missing_plugins[] = $plugin; + continue; + } + + $priority = array_search($plugin_id, $order) + 1; + + if (!$plugin->set($name, $priority)) { + $return = false; + break; + } + } + + // set the missing plugins priorities + if ($return && $missing_plugins) { + if (!$priority) { + $priority = 0; + } + foreach ($missing_plugins as $plugin) { + $priority++; + if (!$plugin->set($name, $priority)) { + $return = false; + break; + } + } + } + + return $return; +} + +/** + * Reindexes all plugin priorities starting at 1. + * + * @todo Can this be done in a single sql command? + * @return bool + */ +function elgg_plugins_reindex_priorities() { + return elgg_set_plugin_priorities(array()); +} + +/** + * Returns a list of plugins to load, in the order that they should be loaded. + * + * @deprecated 1.8 + * + * @return array List of plugins + */ +function get_plugin_list() { + elgg_deprecated_notice('get_plugin_list() is deprecated by elgg_get_plugin_ids_in_dir() or elgg_get_plugins()', 1.8); + + $plugins = elgg_get_plugins('any'); + + $list = array(); + if ($plugins) { + foreach ($plugins as $i => $plugin) { + // in <=1.7 this returned indexed by multiples of 10. + // uh...sure...why not. + $index = ($i + 1) * 10; + $list[$index] = $plugin->getID(); + } + } + + return $list; +} + +/** + * Regenerates the list of known plugins and saves it to the current site + * + * Important: You should regenerate simplecache and the viewpath cache after executing this function + * otherwise you may experience view display artifacts. Do this with the following code: + * + * elgg_view_regenerate_simplecache(); + * elgg_filepath_cache_reset(); + * + * @deprecated 1.8 + * + * @param array $pluginorder Optionally, a list of existing plugins and their orders + * + * @return array The new list of plugins and their orders + */ +function regenerate_plugin_list($pluginorder = FALSE) { + $msg = 'regenerate_plugin_list() is (sorta) deprecated by elgg_generate_plugin_entities() and' + . ' elgg_set_plugin_priorities().'; + elgg_deprecated_notice($msg, 1.8); + + // they're probably trying to set it? + if ($pluginorder) { + if (elgg_generate_plugin_entities()) { + // sort the plugins by the index numerically since we used + // weird indexes in the old system. + ksort($pluginorder, SORT_NUMERIC); + return elgg_set_plugin_priorities($pluginorder); + } + return false; + } else { + // they're probably trying to regenerate from disk? + return elgg_generate_plugin_entities(); + } +} + + +/** + * Loads plugins + * + * @deprecate 1.8 + * + * @return bool + */ +function load_plugins() { + elgg_deprecated_notice('load_plugins() is deprecated by elgg_load_plugins()', 1.8); + + return elgg_load_plugins(); +} + + +/** + * Namespaces a string to be used as a private setting for a plugin. + * + * @param string $type The type of value: user_setting or internal. + * @param string $name The name to namespace. + * @param string $id The plugin's ID to namespace with. Required for user_setting. + * @return string + */ +function elgg_namespace_plugin_private_setting($type, $name, $id = null) { + switch ($type) { +// case 'setting': +// $name = ELGG_PLUGIN_SETTING_PREFIX . $name; +// break; + + case 'user_setting': + if (!$id) { + $id = elgg_get_calling_plugin_id(); + } + $name = ELGG_PLUGIN_USER_SETTING_PREFIX . "$id:$name"; + break; + + case 'internal': + $name = ELGG_PLUGIN_INTERNAL_PREFIX . $name; + break; + } + + return $name; } /** * Get the name of the most recent plugin to be called in the * call stack (or the plugin that owns the current page, if any). * - * i.e., if the last plugin was in /mod/foobar/, get_plugin_name would return foo_bar. + * i.e., if the last plugin was in /mod/foobar/, this would return foo_bar. * * @param boolean $mainfilename If set to true, this will instead determine the * context from the main script filename called by * the browser. Default = false. * + * @since 1.8 + * * @return string|false Plugin name, or false if no plugin name was called */ -function get_plugin_name($mainfilename = false) { +function elgg_get_calling_plugin_id($mainfilename = false) { if (!$mainfilename) { if ($backtrace = debug_backtrace()) { foreach ($backtrace as $step) { @@ -254,31 +584,38 @@ function get_plugin_name($mainfilename = false) { } /** - * Load and parse a plugin manifest from a plugin XML file. + * Get the name of the most recent plugin to be called in the + * call stack (or the plugin that owns the current page, if any). * - * Example file: + * i.e., if the last plugin was in /mod/foobar/, get_plugin_name would return foo_bar. * - * <plugin_manifest> - * <!-- Basic information --> - * <field key="name" value="My Plugin" /> - * <field key="description" value="My Plugin's concise description" /> - * <field key="version" value="1.0" /> - * <field key="category" value="theme" /> - * <field key="category" value="bundled" /> - * <field key="screenshot" value="path/relative/to/my_plugin.jpg" /> - * <field key="screenshot" value="path/relative/to/my_plugin_2.jpg" /> + * @deprecated 1.8 * - * <field key="author" value="Curverider Ltd" /> - * <field key="website" value="http://www.elgg.org/" /> - * <field key="copyright" value="(C) Curverider 2008-2010" /> - * <field key="licence" value="GNU Public License version 2" /> - * </plugin_manifest> + * @param boolean $mainfilename If set to true, this will instead determine the + * context from the main script filename called by + * the browser. Default = false. * - * @param string $plugin Plugin name. + * @return string|false Plugin name, or false if no plugin name was called + */ +function get_plugin_name($mainfilename = false) { + elgg_deprecated_notice('get_plugin_name() is deprecated by elgg_get_calling_plugin_id()', 1.8); + + return elgg_get_calling_plugin_id($mainfilename); +} + +/** + * Load and parse a plugin manifest from a plugin XML file. + * + * @example plugins/manifest.xml Example 1.8-style manifest file. + * + * @deprecated 1.8 * + * @param string $plugin Plugin name. * @return array of values */ function load_plugin_manifest($plugin) { + elgg_deprecated_notice('load_plugin_manifest() is deprecated by ElggPlugin->getManifest()', 1.8); + $xml_file = elgg_get_plugin_path() . "$plugin/manifest.xml"; try { @@ -294,11 +631,14 @@ function load_plugin_manifest($plugin) { * This function checks a plugin manifest 'elgg_version' value against the current install * returning TRUE if the elgg_version is >= the current install's version. * - * @param string $manifest_elgg_version_string The build version (eg 2009010201). + * @deprecated 1.8 * + * @param string $manifest_elgg_version_string The build version (eg 2009010201). * @return bool */ function check_plugin_compatibility($manifest_elgg_version_string) { + elgg_deprecated_notice('check_plugin_compatibility() is deprecated by ElggPlugin->canActivate()', 1.8); + $version = get_version(); if (strpos($manifest_elgg_version_string, '.') === false) { @@ -327,6 +667,7 @@ function check_plugin_compatibility($manifest_elgg_version_string) { * @param string $name A specific provided name to return. Requires $provide_type. * * @return array + * @since 1.8 */ function elgg_get_plugins_provides($type = null, $name = null) { static $provides = null; @@ -377,6 +718,7 @@ function elgg_get_plugins_provides($type = null, $name = null) { * @param string $comparison The comparison operator to use in version_compare() * * @return bool + * @since 1.8 */ function elgg_check_plugins_provides($type, $name, $version = null, $comparison = 'ge') { if (!$provided = elgg_get_plugins_provides($type, $name)) { @@ -395,25 +737,33 @@ function elgg_check_plugins_provides($type, $name, $version = null, $comparison /** * Shorthand function for finding the plugin settings. * - * @param string $plugin_name Optional plugin name, if not specified - * then it is detected from where you are calling from. + * @deprecated 1.8 + * + * @param string $plugin_id Optional plugin id, if not specified + * then it is detected from where you are calling. * * @return mixed */ -function find_plugin_settings($plugin_name = "") { - $options = array('type' => 'object', 'subtype' => 'plugin', 'limit' => 9999); - $plugins = elgg_get_entities($options); - $plugin_name = sanitise_string($plugin_name); - if (!$plugin_name) { - $plugin_name = get_plugin_name(); +function find_plugin_settings($plugin_id = null) { + elgg_deprecated_notice('find_plugin_setting() is deprecated by elgg_get_calling_plugin_entity() or elgg_get_plugin_from_id()', 1.8); + if ($plugin_id) { + return elgg_get_plugin_from_id($plugin_id); + } else { + return elgg_get_calling_plugin_entity(); } +} - if ($plugins) { - foreach ($plugins as $plugin) { - if (strcmp($plugin->title, $plugin_name) == 0) { - return $plugin; - } - } +/** + * Returns the ElggPlugin entity of the last plugin called. + * + * @return mixed ElggPlugin or false + * @since 1.8 + */ +function elgg_get_calling_plugin_entity() { + $plugin_id = elgg_get_calling_plugin_id(); + + if ($plugin_id) { + return elgg_get_plugin_from_id($plugin_id); } return false; @@ -422,34 +772,41 @@ function find_plugin_settings($plugin_name = "") { /** * Find the plugin settings for a user. * - * @param string $plugin_name Plugin name. - * @param int $user_guid The guid who's settings to retrieve. + * @param string $plugin_id Plugin name. + * @param int $user_guid The guid who's settings to retrieve. * * @return array of settings in an associative array minus prefix. */ -function find_plugin_usersettings($plugin_name = "", $user_guid = 0) { - $plugin_name = sanitise_string($plugin_name); +function find_plugin_usersettings($plugin_id = null, $user_guid = 0) { + $plugin_id = sanitise_string($plugin_id); $user_guid = (int)$user_guid; + $db_prefix = get_config('db_prefix'); + $ps_prefix = elgg_namespace_plugin_private_setting('user_setting', "$plugin_id:"); + $ps_prefix_len = strlen($ps_prefix); - if (!$plugin_name) { - $plugin_name = get_plugin_name(); + if (!$plugin_id) { + $plugin_id = elgg_get_calling_plugin_id(); } if ($user_guid == 0) { $user_guid = get_loggedin_userid(); } - // Get metadata for user - $all_metadata = get_all_private_settings($user_guid); //get_metadata_for_entity($user_guid); - if ($all_metadata) { - $prefix = "plugin:settings:$plugin_name:"; + // Get private settings for user + $q = "SELECT * FROM {$db_prefix}private_settings + WHERE entity_guid = $user_guid + AND name LIKE '$ps_prefix$plugin_id'"; + + $private_settings = get_data($q); + if ($private_settings) { $return = new stdClass; - foreach ($all_metadata as $key => $meta) { - $name = substr($key, strlen($prefix)); - $value = $meta; + foreach ($private_settings as $setting) { + $name = substr($setting->name, $ps_prefix_len); + $value = $setting->value; - if (strpos($key, $prefix) === 0) { + // @todo why? + if (strpos($key, $ps_prefix) === 0) { $return->$name = $value; } } @@ -463,21 +820,21 @@ function find_plugin_usersettings($plugin_name = "", $user_guid = 0) { /** * Set a user specific setting for a plugin. * - * @param string $name The name - note, can't be "title". - * @param mixed $value The value. - * @param int $user_guid Optional user. - * @param string $plugin_name Optional plugin name, if not specified then it - * is detected from where you are calling from. + * @param string $name The name - note, can't be "title". + * @param mixed $value The value. + * @param int $user_guid Optional user. + * @param string $plugin_id Optional plugin name, if not specified then it + * is detected from where you are calling from. * * @return bool */ -function set_plugin_usersetting($name, $value, $user_guid = 0, $plugin_name = "") { - $plugin_name = sanitise_string($plugin_name); +function set_plugin_usersetting($name, $value, $user_guid = 0, $plugin_id = "") { + $plugin_id = sanitise_string($plugin_id); $user_guid = (int)$user_guid; $name = sanitise_string($name); - if (!$plugin_name) { - $plugin_name = get_plugin_name(); + if (!$plugin_id) { + $plugin_id = elgg_get_calling_plugin_id(); } $user = get_entity($user_guid); @@ -486,19 +843,17 @@ function set_plugin_usersetting($name, $value, $user_guid = 0, $plugin_name = "" } if (($user) && ($user instanceof ElggUser)) { - $prefix = "plugin:settings:$plugin_name:$name"; - //$user->$prefix = $value; - //$user->save(); + $name = elgg_namespace_plugin_private_setting('user_setting', "$plugin_id:$name"); // Hook to validate setting $value = elgg_trigger_plugin_hook('plugin:usersetting', 'user', array( 'user' => $user, - 'plugin' => $plugin_name, + 'plugin' => $plugin_id, 'name' => $name, 'value' => $value ), $value); - return set_private_setting($user->guid, $prefix, $value); + return set_private_setting($user->guid, $name, $value); } return false; @@ -507,18 +862,18 @@ function set_plugin_usersetting($name, $value, $user_guid = 0, $plugin_name = "" /** * Clears a user-specific plugin setting * - * @param str $name Name of the plugin setting - * @param int $user_guid Defaults to logged in user - * @param str $plugin_name Defaults to contextual plugin name + * @param str $name Name of the plugin setting + * @param int $user_guid Defaults to logged in user + * @param str $plugin_id Defaults to contextual plugin name * * @return bool Success */ -function clear_plugin_usersetting($name, $user_guid = 0, $plugin_name = '') { - $plugin_name = sanitise_string($plugin_name); +function clear_plugin_usersetting($name, $user_guid = 0, $plugin_id = '') { + $plugin_id = sanitise_string($plugin_id); $name = sanitise_string($name); - if (!$plugin_name) { - $plugin_name = get_plugin_name(); + if (!$plugin_id) { + $plugin_id = elgg_get_calling_plugin_id(); } $user = get_entity((int) $user_guid); @@ -527,7 +882,7 @@ function clear_plugin_usersetting($name, $user_guid = 0, $plugin_name = '') { } if (($user) && ($user instanceof ElggUser)) { - $prefix = "plugin:settings:$plugin_name:$name"; + $prefix = elgg_namespace_plugin_private_setting('user_setting', "$plugin_id:$name"); return remove_private_setting($user->getGUID(), $prefix); } @@ -538,20 +893,20 @@ function clear_plugin_usersetting($name, $user_guid = 0, $plugin_name = '') { /** * Get a user specific setting for a plugin. * - * @param string $name The name. - * @param int $user_guid Guid of owning user - * @param string $plugin_name Optional plugin name, if not specified + * @param string $name The name. + * @param int $user_guid Guid of owning user + * @param string $plugin_id Optional plugin name, if not specified * then it is detected from where you are calling from. * * @return mixed */ -function get_plugin_usersetting($name, $user_guid = 0, $plugin_name = "") { - $plugin_name = sanitise_string($plugin_name); +function get_plugin_usersetting($name, $user_guid = 0, $plugin_id = "") { + $plugin_id = sanitise_string($plugin_id); $user_guid = (int)$user_guid; $name = sanitise_string($name); - if (!$plugin_name) { - $plugin_name = get_plugin_name(); + if (!$plugin_id) { + $plugin_id = elgg_get_calling_plugin_id(); } $user = get_entity($user_guid); @@ -560,8 +915,8 @@ function get_plugin_usersetting($name, $user_guid = 0, $plugin_name = "") { } if (($user) && ($user instanceof ElggUser)) { - $prefix = "plugin:settings:$plugin_name:$name"; - return get_private_setting($user->guid, $prefix); + $name = elgg_namespace_plugin_private_setting('user_setting', "$plugin_id:$name"); + return get_private_setting($user->guid, $name); } return false; @@ -570,143 +925,126 @@ function get_plugin_usersetting($name, $user_guid = 0, $plugin_name = "") { /** * Set a setting for a plugin. * - * @param string $name The name - note, can't be "title". - * @param mixed $value The value. - * @param string $plugin_name Optional plugin name, if not specified - * then it is detected from where you are calling from. + * @param string $name The name - note, can't be "title". + * @param mixed $value The value. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. * * @return int|false */ -function set_plugin_setting($name, $value, $plugin_name = "") { - if (!$plugin_name) { - $plugin_name = get_plugin_name(); +function set_plugin_setting($name, $value, $plugin_id = null) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); } - $plugin = find_plugin_settings($plugin_name); if (!$plugin) { - $plugin = new ElggPlugin(); - } - - if ($name != 'title') { - // Hook to validate setting - $value = elgg_trigger_plugin_hook('plugin:setting', 'plugin', array( - 'plugin' => $plugin_name, - 'name' => $name, - 'value' => $value - ), $value); - - $plugin->title = $plugin_name; - $plugin->access_id = ACCESS_PUBLIC; - $plugin->save(); - $plugin->$name = $value; - - return $plugin->getGUID(); + return false; } - return false; + return $plugin->setSetting($name, $value); } /** * Get setting for a plugin. * - * @param string $name The name. - * @param string $plugin_name Optional plugin name, if not specified - * then it is detected from where you are calling from. + * @param string $name The name. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. * * @return mixed */ -function get_plugin_setting($name, $plugin_name = "") { - $plugin = find_plugin_settings($plugin_name); +function get_plugin_setting($name, $plugin_id = "") { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); + } - if ($plugin) { - return $plugin->$name; + if (!$plugin) { + return false; } - return false; + return $plugin->getSetting($name); } /** * Clear a plugin setting. * - * @param string $name The name. - * @param string $plugin_name Optional plugin name, if not specified - * then it is detected from where you are calling from. + * @param string $name The name. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. * * @return bool */ -function clear_plugin_setting($name, $plugin_name = "") { - $plugin = find_plugin_settings($plugin_name); +function clear_plugin_setting($name, $plugin_id = "") { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); + } - if ($plugin) { - return remove_private_setting($plugin->guid, $name); + if (!$plugin) { + return false; } - return FALSE; + return $plugin->removeSetting($name); } /** * Clear all plugin settings. * - * @param string $plugin_name Optional plugin name, if not specified - * then it is detected from where you are calling from. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. * * @return bool * @since 1.7.0 */ -function clear_all_plugin_settings($plugin_name = "") { - $plugin = find_plugin_settings($plugin_name); +function clear_all_plugin_settings($plugin_id = "") { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); + } if ($plugin) { - return remove_all_private_settings($plugin->guid); + $plugin->removeAllSettings(); } - return FALSE; + return false; } /** * Return an array of installed plugins. * + * @deprecated 1.8 + * * @param string $status any|enabled|disabled * @return array */ -function get_installed_plugins($status = 'any') { +function get_installed_plugins($status = 'all') { global $CONFIG; - $installed_plugins = array(); + elgg_deprecated_notice('get_installed_plugins() was deprecated by elgg_get_plugins()', 1.8); - if (!empty($CONFIG->pluginspath)) { - $plugins = get_plugin_list(); + $plugins = elgg_get_plugins($status); - foreach ($plugins as $mod) { - // require manifest. - if (!$manifest = load_plugin_manifest($mod)) { - continue; - } - - $enabled = is_plugin_enabled($mod); - - switch ($status) { - case 'enabled': - if ($enabled != true) { - continue 2; - } - break; - - case 'disabled': - if ($enabled == true) { - continue 2; - } - break; + if (!$plugins) { + return array(); + } - case 'any': - default: - break; - } + $installed_plugins = array(); - $installed_plugins[$mod] = array(); - $installed_plugins[$mod]['active'] = $enabled; - $installed_plugins[$mod]['manifest'] = $manifest; + foreach ($plugins as $plugin) { + if (!$plugin->isValid()) { + continue; } + + $installed_plugins[$plugin->getID()] = array( + 'active' => $plugin->isActive(), + 'manifest' => $plugin->manifest->getManifest() + ); } return $installed_plugins; @@ -721,82 +1059,36 @@ function get_installed_plugins($status = 'any') { * elgg_view_regenerate_simplecache(); * elgg_filepath_cache_reset(); * + * @deprecated 1.8 + * * @param string $plugin The plugin name. * @param int $site_guid The site id, if not specified then this is detected. * * @return array * @throws InvalidClassException */ -function enable_plugin($plugin, $site_guid = 0) { - global $CONFIG, $ENABLED_PLUGINS_CACHE; +function enable_plugin($plugin, $site_guid = null) { + elgg_deprecated_notice('enable_plugin() was deprecated by ElggPlugin->activate()', 1.8); $plugin = sanitise_string($plugin); - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - $site = get_entity($site_guid); - if (!($site instanceof ElggSite)) { - $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($site_guid, "ElggSite")); - throw new InvalidClassException($msg); - } - - if (!$plugin_info = load_plugin_manifest($plugin)) { - return FALSE; + $site_guid = (int) $site_guid; + if (!$site_guid) { + $site = get_config('site'); + $site_guid = $site->guid; } - // getMetadata() doesn't return an array if only one plugin is enabled - if ($enabled = $site->enabled_plugins) { - if (!is_array($enabled)) { - $enabled = array($enabled); - } - } else { - $enabled = array(); + try { + $plugin = new ElggPlugin($plugin); + } catch(Exception $e) { + return false; } - $enabled[] = $plugin; - $enabled = array_unique($enabled); - - if ($return = $site->setMetaData('enabled_plugins', $enabled)) { - - // for other plugins that want to hook into this. - $params = array('plugin' => $plugin, 'manifest' => $plugin_info); - if ($return && !elgg_trigger_event('enable', 'plugin', $params)) { - $return = FALSE; - } - - // for this plugin's on_enable - if ($return && isset($plugin_info['on_enable'])) { - // pull in the actual plugin's start so the on_enable function is callable - // NB: this will not run re-run the init hooks! - $start = "{$CONFIG->pluginspath}$plugin/start.php"; - if (!file_exists($start) || !include($start)) { - $return = FALSE; - } - - // need language files for the messages - $translations = "{$CONFIG->pluginspath}$plugin/languages/"; - register_translations($translations); - if (!is_callable($plugin_info['on_enable'])) { - $return = FALSE; - } else { - $on_enable = call_user_func($plugin_info['on_enable']); - // allow null to mean "I don't care" like other subsystems - $return = ($on_disable === FALSE) ? FALSE : TRUE; - } - } - - // disable the plugin if the on_enable or trigger results failed - if (!$return) { - array_pop($enabled); - $site->setMetaData('enabled_plugins', $enabled); - } - - $ENABLED_PLUGINS_CACHE = $enabled; + if (!$plugin->canActivate($site_guid)) { + return false; } - return $return; + return $plugin->activate($site_guid); } /** @@ -808,6 +1100,8 @@ function enable_plugin($plugin, $site_guid = 0) { * elgg_view_regenerate_simplecache(); * elgg_filepath_cache_reset(); * + * @deprecated 1.8 + * * @param string $plugin The plugin name. * @param int $site_guid The site id, if not specified then this is detected. * @@ -815,118 +1109,53 @@ function enable_plugin($plugin, $site_guid = 0) { * @throws InvalidClassException */ function disable_plugin($plugin, $site_guid = 0) { - global $CONFIG, $ENABLED_PLUGINS_CACHE; + elgg_deprecated_notice('disable_plugin() was deprecated by ElggPlugin->deactivate()', 1.8); $plugin = sanitise_string($plugin); - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - $site = get_entity($site_guid); - if (!($site instanceof ElggSite)) { - $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($site_guid, "ElggSite")); - throw new InvalidClassException(); - } - if (!$plugin_info = load_plugin_manifest($plugin)) { - return FALSE; - } - - // getMetadata() doesn't return an array if only one plugin is enabled - if ($enabled = $site->enabled_plugins) { - if (!is_array($enabled)) { - $enabled = array($enabled); - } - } else { - $enabled = array(); - } - - $old_enabled = $enabled; - - // remove the disabled plugin from the array - if (FALSE !== $i = array_search($plugin, $enabled)) { - unset($enabled[$i]); + $site_guid = (int) $site_guid; + if (!$site_guid) { + $site = get_config('site'); + $site_guid = $site->guid; } - // if we're unsetting all the plugins, this will return an empty array. - // it will fail with FALSE, though. - $return = (FALSE === $site->enabled_plugins = $enabled) ? FALSE : TRUE; - - if ($return) { - // for other plugins that want to hook into this. - $params = array('plugin' => $plugin, 'manifest' => $plugin_info); - if ($return && !elgg_trigger_event('disable', 'plugin', $params)) { - $return = FALSE; - } - - // for this plugin's on_disable - if ($return && isset($plugin_info['on_disable'])) { - if (!is_callable($plugin_info['on_disable'])) { - $return = FALSE; - } else { - $on_disable = call_user_func($plugin_info['on_disable']); - // allow null to mean "I don't care" like other subsystems - $return = ($on_disable === FALSE) ? FALSE : TRUE; - } - } - - // disable the plugin if the on_enable or trigger results failed - if (!$return) { - $site->enabled_plugins = $old_enabled; - $ENABLED_PLUGINS_CACHE = $old_enabled; - } else { - $ENABLED_PLUGINS_CACHE = $enabled; - } + try { + $plugin = new ElggPlugin($plugin); + } catch(Exception $e) { + return false; } - return $return; + return $plugin->deactivate($site_guid); } /** * Return whether a plugin is enabled or not. * + * @deprecated 1.8 + * * @param string $plugin The plugin name. * @param int $site_guid The site id, if not specified then this is detected. * * @return bool - * @throws InvalidClassException */ function is_plugin_enabled($plugin, $site_guid = 0) { - global $CONFIG, $ENABLED_PLUGINS_CACHE; + elgg_deprecated_notice('is_plugin_enabled() was deprecated by ElggPlugin->isActive()', 1.8); - if (!file_exists($CONFIG->pluginspath . $plugin)) { - return false; - } + $plugin = sanitise_string($plugin); $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; + if (!$site_guid) { + $site = get_config('site'); + $site_guid = $site->guid; } - if (!$ENABLED_PLUGINS_CACHE) { - $site = get_entity($site_guid); - if (!($site instanceof ElggSite)) { - $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($site_guid, "ElggSite")); - throw new InvalidClassException($msg); - } - - $enabled_plugins = $site->enabled_plugins; - if ($enabled_plugins && !is_array($enabled_plugins)) { - $enabled_plugins = array($enabled_plugins); - } - $ENABLED_PLUGINS_CACHE = $enabled_plugins; - } - - if (is_array($ENABLED_PLUGINS_CACHE)) { - foreach ($ENABLED_PLUGINS_CACHE as $e) { - if ($e == $plugin) { - return true; - } - } + try { + $plugin = new ElggPlugin($plugin); + } catch(Exception $e) { + return false; } - return false; + return $plugin->isActive($site_guid); } /** @@ -935,7 +1164,6 @@ function is_plugin_enabled($plugin, $site_guid = 0) { * @return void */ function plugin_run_once() { - // Register a class add_subtype("object", "plugin", "ElggPlugin"); } @@ -978,4 +1206,4 @@ function plugin_init() { elgg_register_action('admin/plugins/reorder', '', 'admin'); } -elgg_register_event_handler('init', 'system', 'plugin_init'); +elgg_register_event_handler('init', 'system', 'plugin_init');
\ No newline at end of file diff --git a/engine/lib/upgrades/2011010101.php b/engine/lib/upgrades/2011010101.php new file mode 100644 index 000000000..eac5810c4 --- /dev/null +++ b/engine/lib/upgrades/2011010101.php @@ -0,0 +1,75 @@ +<?php +/** + * Migrate plugins to the new system using ElggPlugin and private settings + */ + +$old_ia = elgg_set_ignore_access(true); + +$site = get_config('site'); +$old_plugin_order = unserialize($site->pluginorder); +$old_enabled_plugins = $site->enabled_plugins; + +$db_prefix = get_config('dbprefix'); +$plugin_subtype_id = get_subtype_id('object', 'plugin'); + +// easy one first: make sure the the site owns all plugin entities. +$q = "UPDATE {$db_prefix}entities e + SET owner_guid = $site->guid, container_guid = $site->guid + WHERE e.type = 'object' AND e.subtype = $plugin_subtype_id"; + +$r = update_data($q); + +// rewrite all plugin:setting:* to ELGG_PLUGIN_USER_SETTING_PREFIX . * +$q = "UPDATE {$db_prefix}private_settings + SET name = replace(name, 'plugin:setting', '" . ELGG_PLUGIN_USER_SETTING_PREFIX . "') + WHERE name LIKE 'plugin:setting:%'"; + +$r = update_data($q); + +// grab current plugin GUIDs to add a temp priority +$q = "SELECT * FROM {$db_prefix}entities e + JOIN {$db_prefix}objects_entity oe ON e.guid = oe.guid + WHERE e.type = 'object' AND e.subtype = $plugin_subtype_id"; + +$plugins = get_data($q); + +foreach ($plugins as $plugin) { + $priority = elgg_namespace_plugin_private_setting('internal', 'priority'); + set_private_setting($plugin->guid, $priority, 0); +} + +// force regenerating plugin entities +elgg_generate_plugin_entities(); + +// set the priorities for all plugins +// this function rewrites it to a normal index so use the current one. +elgg_set_plugin_priorities($old_plugin_order); + +// add relationships for enabled plugins +if ($old_enabled_plugins) { + // they might only have one plugin enabled. + if (!is_array($old_enabled_plugins)) { + $old_enabled_plugins = array($old_enabled_plugins); + } + + // sometimes there were problems and you'd get 1000s of enabled plugins. + $old_enabled_plugins = array_unique($old_enabled_plugins); + + foreach ($old_enabled_plugins as $plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + + if ($plugin) { + $plugin->activate(); + } + } +} + +// invalidate caches +elgg_invalidate_simplecache(); +elgg_filepath_cache_reset(); + +// clean up. +remove_metadata($site->guid, 'pluginorder'); +remove_metadata($site->guid, 'enabled_plugins'); + +elgg_set_ignore_access($old_id);
\ No newline at end of file diff --git a/engine/tests/api/plugins.php b/engine/tests/api/plugins.php index c99609559..997d69fb7 100644 --- a/engine/tests/api/plugins.php +++ b/engine/tests/api/plugins.php @@ -100,11 +100,10 @@ class ElggCorePluginsAPITest extends ElggCoreUnitTest { array('type' => 'php_extension', 'name' => 'big_math', 'version' => 1.0) ), - 'admin' => array( - 'on_enable' => 'setup_function', - 'on_disable' => 'teardown_function', - 'interface_type' => 'simple' - ) + 'on_activate' => array('setup_function'), + 'on_deactivate' => array('teardown_function'), + 'admin_interface' => 'simple', + 'activate_on_install' => true ); $this->assertEqual($this->manifest18->getManifest(), $manifest_array); @@ -118,7 +117,8 @@ class ElggCorePluginsAPITest extends ElggCoreUnitTest { 'website' => 'http://www.elgg.org/', 'copyright' => '(C) Elgg 2010', 'license' => 'GNU Public License version 2', - 'elgg_version' => '2009030702' + 'elgg_version' => '2009030702', + 'name' => 'Plugin Test 17', ); $this->assertEqual($this->manifest17->getManifest(), $manifest_array); @@ -261,6 +261,22 @@ class ElggCorePluginsAPITest extends ElggCoreUnitTest { $this->assertEqual($this->manifest17->getConflicts(), array()); } + public function testElggPluginManifestGetOnActivate() { + $this->assertEqual($this->manifest18->getOnActivate(), array('setup_function')); + } + + public function testElggPluginManifestGetOnDeactivate() { + $this->assertEqual($this->manifest18->getOnDeactivate(), array('teardown_function')); + } + + public function testElggPluginManifestGetAdminInterface() { + $this->assertEqual($this->manifest18->getAdminInterface(), 'simple'); + } + + public function testElggPluginManifestGetActivateOnInstall() { + $this->assertEqual($this->manifest18->getActivateOnInstall(), true); + } + // ElggPluginPackage public function testElggPluginPackageDetectIDFromPath() { $this->assertEqual($this->package18->getID(), 'plugin_18'); diff --git a/engine/tests/test_files/plugin_18/manifest.xml b/engine/tests/test_files/plugin_18/manifest.xml index 454a418f6..69166c89c 100644 --- a/engine/tests/test_files/plugin_18/manifest.xml +++ b/engine/tests/test_files/plugin_18/manifest.xml @@ -34,11 +34,10 @@ <category>ServiceAPI</category> - <admin> - <on_enable>setup_function</on_enable> - <on_disable>teardown_function</on_disable> - <interface_type>simple</interface_type> - </admin> + <on_activate>setup_function</on_activate> + <on_deactivate>teardown_function</on_deactivate> + <admin_interface>simple</admin_interface> + <activate_on_install>true</activate_on_install> <requires> <type>php_extension</type> @@ -94,4 +93,4 @@ <version>1.0</version> </provides> -</plugin_manifest>
\ No newline at end of file +</plugin_manifest> |