From fc21edb0785f2cac11dc592278fad97fffeeb082 Mon Sep 17 00:00:00 2001 From: brettp Date: Sun, 2 Jan 2011 23:00:23 +0000 Subject: 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 --- documentation/examples/plugins/manifest.xml | 93 ++ engine/classes/ElggPlugin.php | 739 ++++++++++++++- engine/classes/ElggPluginManifest.php | 71 ++ engine/classes/ElggPluginManifestParser18.php | 22 +- engine/classes/ElggPluginPackage.php | 25 +- engine/lib/plugins.php | 1134 ++++++++++++++---------- engine/lib/upgrades/2011010101.php | 75 ++ engine/tests/api/plugins.php | 28 +- engine/tests/test_files/plugin_18/manifest.xml | 11 +- languages/en.php | 14 +- version.php | 2 +- 11 files changed, 1715 insertions(+), 499 deletions(-) create mode 100644 documentation/examples/plugins/manifest.xml create mode 100644 engine/lib/upgrades/2011010101.php diff --git a/documentation/examples/plugins/manifest.xml b/documentation/examples/plugins/manifest.xml new file mode 100644 index 000000000..34dc82d2f --- /dev/null +++ b/documentation/examples/plugins/manifest.xml @@ -0,0 +1,93 @@ + + + My Plugin + Elgg + 1.0 + A concise description. + This is a longer, more interesting description of my plugin, its features, and other important information. + http://www.elgg.org/ + (C) Elgg 2010 + GNU Public License version 2 + + + elgg_version + 2009030802 + + + + elgg_release + 1.8-svn + + + + An example screenshot + graphics/plugin_ss1.png + + + + Another screenshot + graphics/plugin_ss2.png + + + Admin + ServiceAPI + + setup_function + teardown_function + simple + + + php_extension + gd + + + + php_ini + short_open_tag + off + + + + php_extension + made_up + 1.0 + + + + plugin + fake_plugin + 1.0 + + + + plugin + profile + 1.0 + + + + plugin + profile_api + 1.3 + lt + + + + plugin + profile_api + 1.0 + + + + plugin + profile_api + 1.3 + + + + php_extension + curl + 1.0 + + + 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,9 +24,703 @@ 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. * @@ -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 @@ -480,6 +480,77 @@ class ElggPluginManifest { return $normalized; } + /** + * 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 * 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. * - * - * - * - * - * - * - * - * - * + * @deprecated 1.8 * - * - * - * - * - * + * @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 @@ +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 @@ ServiceAPI - - setup_function - teardown_function - simple - + setup_function + teardown_function + simple + true php_extension @@ -94,4 +93,4 @@ 1.0 - \ No newline at end of file + diff --git a/languages/en.php b/languages/en.php index 42776cb3f..7199f5aa9 100644 --- a/languages/en.php +++ b/languages/en.php @@ -62,7 +62,7 @@ $english = array( 'InvalidClassException:NotValidElggStar' => "GUID:%d is not a valid %s", - 'PluginException:MisconfiguredPlugin' => "%s is a misconfigured plugin. It has been disabled. Please search the Elgg wiki for possible causes (http://docs.elgg.org/wiki/).", + 'PluginException:MisconfiguredPlugin' => "%s (guid: %s) is a misconfigured plugin. It has been disabled. Please search the Elgg wiki for possible causes (http://docs.elgg.org/wiki/).", 'PluginException:InvalidID' => "%s is an invalid plugin ID.", 'PluginException:InvalidPath' => "%s is an invalid plugin path.", 'PluginException:InvalidManifest' => 'Invalid manifest file for plugin %s', @@ -73,6 +73,12 @@ $english = array( 'ElggPluginPackage:InvalidPlugin:InvalidProvides' => 'Invalid provides type "%s"', 'ElggPluginPackage:InvalidPlugin:CircularDep' => 'Invalid %s dependency "%s" in plugin %s. Plugins cannot conflict with or require something they provide!', + 'ElggPlugin:Exception:CannotIncludeStart' => 'Cannot include start.php for plugin %s (guid: %s) at %s. Check permissions!', + 'ElggPlugin:Exception:CannotRegisterViews' => 'Cannot open views dir for plugin %s (guid: %s) at %s. Check permissions!', + 'ElggPlugin:Exception:CannotRegisterLanguages' => 'Cannot register languages for plugin %s (guid: %s) at %s. Check permissions!', + 'ElggPlugin:Exception:CannotRegisterClasses' => 'Cannot register classes for plugin %s (guid: %s) at %s. Check permissions!', + 'ElggPlugin:Exception:NoID' => 'No ID for plugin guid %s!', + 'PluginException:ParserError' => 'Error parsing manifest with API version %s in plugin %s.', 'PluginException:NoAvailableParser' => 'Cannot find a parser for manifest API version %s in plugin %s.', 'PluginException:ParserErrorMissingRequiredAttribute' => "Missing required '%s' attribute in manifest for plugin %s.", @@ -403,7 +409,7 @@ $english = array( 'profile:explainchangefields' => 'You can replace the existing profile fields with your own using the form below.

Give the new profile field a label, for example, \'Favorite team\', then select the field type (eg. text, url, tags), and click the \'Add\' button. To re-order the fields drag on the handle next to the field label. To edit a field label - click on the label\'s text to make it editable.
At any time you can revert back to the default profile set up, but you will loose any information already entered into custom fields on profile pages.', 'profile:editdefault:success' => 'Item successfully added to default profile', 'profile:editdefault:fail' => 'Default profile could not be saved', - + /** * Feeds @@ -569,7 +575,7 @@ $english = array( 'plugins:settings:save:fail' => "There was a problem saving settings for the %s plugin.", 'plugins:usersettings:save:ok' => "User settings for the %s plugin were saved successfully.", 'plugins:usersettings:save:fail' => "There was a problem saving user settings for the %s plugin.", - 'item:object:plugin' => 'Plugin configuration settings', + 'item:object:plugin' => 'Plugins', 'admin:plugins' => "Plugins", 'admin:plugins:description' => "This admin panel allows you to control and configure tools installed on your site.", @@ -733,7 +739,7 @@ $english = array( 'site' => 'Site', 'activity' => 'Activity', 'members' => 'Members', - + 'up' => 'Up', 'down' => 'Down', 'top' => 'Top', diff --git a/version.php b/version.php index 235180d1c..b9efe2301 100644 --- a/version.php +++ b/version.php @@ -11,7 +11,7 @@ // YYYYMMDD = Elgg Date // XX = Interim incrementer -$version = 2010121702; +$version = 2011010101; // Human-friendly version name $release = '1.8-svn'; -- cgit v1.2.3