From 4b10b550a2449827c643c896424eb0277c43b049 Mon Sep 17 00:00:00 2001 From: brettp Date: Fri, 3 Dec 2010 03:11:49 +0000 Subject: Refs #1986 #2170 #2225. Added semantic manifest.xml support and unit tests. Also added plugin dependencies system. See engine/tests/test_files/plugin_18/manifest.xml for examples. Not closing tickets pending discussion. git-svn-id: http://code.elgg.org/elgg/trunk@7512 36083f99-b078-4883-b0ff-0f9b5a30f544 --- engine/classes/ElggPluginManifest.php | 299 ++++++++-- engine/classes/ElggPluginManifestParser.php | 27 +- engine/classes/ElggPluginManifestParser17.php | 39 +- engine/classes/ElggPluginManifestParser18.php | 43 +- engine/classes/ElggPluginPackage.php | 763 +++++++++++++++++++++++++ engine/lib/plugins.php | 81 +++ engine/tests/api/plugins.php | 250 ++++---- engine/tests/test_files/plugin_17/manifest.xml | 10 + engine/tests/test_files/plugin_17/start.php | 0 engine/tests/test_files/plugin_18/manifest.xml | 97 ++++ engine/tests/test_files/plugin_18/start.php | 0 languages/en.php | 61 +- 12 files changed, 1466 insertions(+), 204 deletions(-) create mode 100644 engine/classes/ElggPluginPackage.php create mode 100644 engine/tests/test_files/plugin_17/manifest.xml create mode 100644 engine/tests/test_files/plugin_17/start.php create mode 100644 engine/tests/test_files/plugin_18/manifest.xml create mode 100644 engine/tests/test_files/plugin_18/start.php diff --git a/engine/classes/ElggPluginManifest.php b/engine/classes/ElggPluginManifest.php index 395208c95..ccd0d984a 100644 --- a/engine/classes/ElggPluginManifest.php +++ b/engine/classes/ElggPluginManifest.php @@ -2,11 +2,18 @@ /** * Parses Elgg manifest.xml files. * + * Normalizes the values from the ElggManifestParser object. + * * This requires an ElggPluginManifestParser class implementation * as $this->parser. * + * To add new parser versions, name them ElggPluginManifestParserXX + * where XX is the version specified in the top-level + * tag. + * * @package Elgg.Core * @subpackage Plugins + * @since 1.8 */ class ElggPluginManifest { @@ -15,6 +22,72 @@ class ElggPluginManifest { */ protected $parser; + /** + * The expected structure of a requires element + */ + private $_depsRequiresStructPlugin = array( + 'type' => '', + 'name' => '', + 'version' => '', + 'comparison' => 'ge' + ); + + /* + * The expected structure of elgg and elgg_release requires element + */ + private $_depsRequiresStructElgg = array( + 'type' => '', + 'version' => '', + 'comparison' => 'ge' + ); + + /** + * The expected structure of a requires php_ini dependency element + */ + private $_depsRequiresStructPhpIni = array( + 'type' => '', + 'name' => '', + 'value' => '', + 'comparison' => '=' + ); + + /** + * The expected structure of a requires php_extension dependency element + */ + private $_depsRequiresStructPhpExtension = array( + 'type' => '', + 'name' => '', + 'version' => '', + 'comparison' => '=' + ); + + /** + * The expected structure of a conflicts depedency element + */ + private $_depsConflictsStruct = array( + 'type' => '', + 'name' => '', + 'version' => '', + 'comparison' => '=' + ); + + /** + * The expected structure of a provides dependency element. + */ + private $_depsProvidesStruct = array( + 'type' => '', + 'name' => '', + 'version' => '' + ); + + /** + * The expected structure of a screenshot element + */ + private $_screenshotStruct = array( + 'description' => '', + 'path' => '' + ); + /** * The API version of the manifest. * @@ -48,7 +121,7 @@ class ElggPluginManifest { if (substr(trim($manifest), 0, 1) == '<') { // this is a string $raw_xml = $manifest; - } elseif (is_readable($manifest)) { + } elseif (is_file($manifest)) { // this is a file $raw_xml = file_get_contents($manifest); } @@ -57,7 +130,8 @@ class ElggPluginManifest { } if (!$manifest_obj) { - throw new PluginException(elgg_echo('PluginException:InvalidManifest', array($this->getPluginID()))); + throw new PluginException(elgg_echo('PluginException:InvalidManifest', + array($this->getPluginID()))); } // set manifest api version @@ -67,19 +141,20 @@ class ElggPluginManifest { $this->apiVersion = 1.7; } - switch ($this->apiVersion) { - case 1.8: - $this->parser = new ElggPluginManifestParser18($manifest_obj, $this); - break; + $parser_class_name = 'ElggPluginManifestParser' . str_replace('.', '', $this->apiVersion); - case 1.7: - $this->parser = new ElggPluginManifestParser17($manifest_obj, $this); - break; + // @todo currently the autoloader freaks out if a class doesn't exist. + try { + $class_exists = class_exists($parser_class_name); + } catch (Exception $e) { + $class_exists = false; + } - default: - throw new PluginException(elgg_echo('PluginException:NoAvailableParser', + if ($class_exists) { + $this->parser = new $parser_class_name($manifest_obj, $this); + } else { + throw new PluginException(elgg_echo('PluginException:NoAvailableParser', array($this->apiVersion, $this->getPluginID()))); - break; } if (!$this->parser->parse()) { @@ -124,35 +199,9 @@ class ElggPluginManifest { return $this->parser->getManifest(); } - /** - * Returns the dependencies listed. - * - * @return array - */ - public function getDepends() { - $deps = $this->parser->getAttribute('depends'); - - if (!is_array($deps)) { - $deps = array(); - } - - return $deps; - } - - /** - * Returns the conflicts listed - * - * @return array - */ - public function getConflicts() { - $conflicts = $this->parser->getAttribute('conflicts'); - - if (!is_array($conflicts)) { - $conflicts = array(); - } - - return $conflicts; - } + /*************************************** + * Parsed and Normalized Manifest Data * + ***************************************/ /** * Returns the plugin name @@ -163,19 +212,20 @@ class ElggPluginManifest { $name = $this->parser->getAttribute('name'); if (!$name && $this->pluginID) { - $name = ucwords(str_replace('_', ' ', $pluginID)); + $name = ucwords(str_replace('_', ' ', $this->pluginID)); } return $name; } + /** * Return the description * * @return string */ public function getDescription() { - return $this->parser->getAttribute('description'); + return elgg_echo($this->parser->getAttribute('description')); } /** @@ -184,9 +234,7 @@ class ElggPluginManifest { * @return string */ public function getBlurb() { - $blurb = $this->parser->getAttribute('blurb'); - - if (!$blurb) { + if (!$blurb = elgg_echo($this->parser->getAttribute('blurb'))) { $blurb = elgg_get_excerpt($this->getDescription()); } @@ -245,9 +293,7 @@ class ElggPluginManifest { * @return array */ public function getCategories() { - $cats = $this->parser->getAttribute('categories'); - - if (!is_array($cats)) { + if (!$cats = $this->parser->getAttribute('category')) { $cats = array(); } @@ -260,13 +306,16 @@ class ElggPluginManifest { * @return array */ public function getScreenshots() { - $ss = $this->parser->getAttribute('screenshots'); - - if (!is_array($ss)) { + if (!$ss = $this->parser->getAttribute('screenshot')) { $ss = array(); } - return $ss; + $normalized = array(); + foreach ($ss as $s) { + $normalized[] = $this->buildStruct($this->_screenshotStruct, $s); + } + + return $normalized; } /** @@ -275,13 +324,151 @@ class ElggPluginManifest { * @return array */ public function getProvides() { - $provides = $this->parser->getAttribute('provides'); + if (!$provides = $this->parser->getAttribute('provides')) { + $provides = array(); + } // always provide ourself if we can if ($this->pluginID) { - $provides[] = array('name' => $this->getPluginID(), 'version' => $this->getVersion); + $provides[] = array( + 'type' => 'plugin', + 'name' => $this->getPluginID(), + 'version' => $this->getVersion() + ); + } + + $normalized = array(); + foreach ($provides as $provide) { + $normalized[] = $this->buildStruct($this->_depsProvidesStruct, $provide); + } + + return $normalized; + } + + /** + * Returns the dependencies listed. + * + * @return array + */ + public function getRequires() { + if (!$reqs = $this->parser->getAttribute('requires')) { + $reqs = array(); + } + + $normalized = array(); + foreach ($reqs as $req) { + + switch ($req['type']) { + case 'elgg': + case 'elgg_release': + $struct = $this->_depsRequiresStructElgg; + break; + + case 'plugin': + $struct = $this->_depsRequiresStructPlugin; + break; + + case 'php_extension': + $struct = $this->_depsRequiresStructPhpExtension; + break; + + case 'php_ini': + $struct = $this->_depsRequiresStructPhpIni; + + // also normalize boolean values + if (isset($req['value'])) { + switch (strtolower($normalized_req['value'])) { + case 'yes': + case 'true': + case 'on': + case 1: + $normalized_req['value'] = 1; + break; + + case 'no': + case 'false': + case 'off': + case 0: + case '': + $normalized_req['value'] = 0; + break; + } + } + + break; + } + + $normalized_req = $this->buildStruct($struct, $req); + + // normalize comparison operators + switch ($normalized_req['comparison']) { + case '<': + $normalized_req['comparison'] = 'lt'; + break; + + case '<=': + $normalized_req['comparison'] = 'le'; + break; + + case '>': + $normalized_req['comparison'] = 'gt'; + break; + + case '>=': + $normalized_req['comparison'] = 'ge'; + break; + + case '==': + case 'eq': + $normalized_req['comparison'] = '='; + break; + + case '<>': + case 'ne': + $normalized_req['comparison'] = '!='; + break; + } + + $normalized[] = $normalized_req; + } + + return $normalized; + } + + /** + * Returns the conflicts listed + * + * @return array + */ + public function getConflicts() { + if (!$conflicts = $this->parser->getAttribute('conflicts')) { + $conflicts = array(); + } + + $normalized = array(); + + foreach ($conflicts as $conflict) { + $normalized[] = $this->buildStruct($this->_depsConflictsStruct, $conflict); + } + + return $normalized; + } + + /** + * Normalizes an array into the structure specified + * + * @param array $struct The struct to normalize $element to. + * @param array $array The array + * + * @return array + */ + protected function buildStruct(array $struct, array $array) { + $return = array(); + + foreach ($struct as $index => $default) { + $return[$index] = elgg_get_array_value($index, $array, $default); } - return $provides; + return $return; } } \ No newline at end of file diff --git a/engine/classes/ElggPluginManifestParser.php b/engine/classes/ElggPluginManifestParser.php index 0ce3e3024..dce46cbb4 100644 --- a/engine/classes/ElggPluginManifestParser.php +++ b/engine/classes/ElggPluginManifestParser.php @@ -2,9 +2,24 @@ /** * Parent class for manifest parsers. * + * Converts manifest.xml files or strings to an array. + * + * This should be extended by a class that does the actual work + * to convert based on the manifest.xml version. + * + * This class only parses XML to an XmlEntity object and + * an array. The array should be used primarily to extract + * information since it is quicker to parse once and store + * values from the XmlElement object than to parse the object + * each time. + * + * The array should be an exact representation of the manifest.xml + * file or string. Any normalization needs to be done in the + * calling class / function. + * * @package Elgg.Core * @subpackage Plugins - * + * @since 1.8 */ abstract class ElggPluginManifestParser { /** @@ -38,7 +53,7 @@ abstract class ElggPluginManifestParser { /** * Loads the manifest XML to be parsed. * - * @param XmlElement $xml The Manifest XML to be parsed + * @param XmlElement $xml The Manifest XML object to be parsed * @param object $caller The object calling this parser. */ public function __construct(XmlElement $xml, $caller) { @@ -71,12 +86,8 @@ abstract class ElggPluginManifestParser { * @return mixed */ public function getAttribute($name) { - if (array_key_exists($name, $this->validAttributes)) { - if (isset($this->manifest[$name])) { - return $this->manifest[$name]; - } else { - return $this->validAttributes[$name]; - } + if (in_array($name, $this->validAttributes) && isset($this->manifest[$name])) { + return $this->manifest[$name]; } return false; diff --git a/engine/classes/ElggPluginManifestParser17.php b/engine/classes/ElggPluginManifestParser17.php index 49b91ef52..f439b5af0 100644 --- a/engine/classes/ElggPluginManifestParser17.php +++ b/engine/classes/ElggPluginManifestParser17.php @@ -4,24 +4,18 @@ * * @package Elgg.Core * @subpackage Plugins + * @since 1.8 */ class ElggPluginManifestParser17 extends ElggPluginManifestParser { /** * The valid top level attributes and defaults for a 1.7 manifest */ protected $validAttributes = array( - 'author' => null, - 'version' => null, - 'description' => null, - 'website' => null, - 'copyright' => null, - 'license' => 'GNU Public License version 2', - 'elgg_version' => null, + 'author', 'version', 'description', 'website', + 'copyright', 'license', 'elgg_version', // were never really used and not enforced in code. - 'requires' => null, - 'recommends' => null, - 'conflicts' => null + 'requires', 'recommends', 'conflicts' ); /** @@ -30,6 +24,10 @@ class ElggPluginManifestParser17 extends ElggPluginManifestParser { * @return void */ public function parse() { + if (!isset($this->manifestObject->children)) { + return false; + } + foreach ($this->manifestObject->children as $element) { $key = $element->attributes['key']; $value = $element->attributes['value']; @@ -47,8 +45,27 @@ class ElggPluginManifestParser17 extends ElggPluginManifestParser { } } - $this->manifest = $elements; + if (!$this->manifest = $elements) { + return false; + } return true; } + + /** + * Return an attribute in the manifest. + * + * Overrides ElggPluginManifestParser::getAttribute() because before 1.8 + * there were no rules...weeeeeeeee! + * + * @param string $name Attribute name + * @return mixed + */ + public function getAttribute($name) { + if (isset($this->manifest[$name])) { + return $this->manifest[$name]; + } + + return false; + } } \ No newline at end of file diff --git a/engine/classes/ElggPluginManifestParser18.php b/engine/classes/ElggPluginManifestParser18.php index 1d4e9daed..54aa9603f 100644 --- a/engine/classes/ElggPluginManifestParser18.php +++ b/engine/classes/ElggPluginManifestParser18.php @@ -4,6 +4,7 @@ * * @package Elgg.Core * @subpackage Plugins + * @since 1.8 */ class ElggPluginManifestParser18 extends ElggPluginManifestParser { /** @@ -12,23 +13,9 @@ class ElggPluginManifestParser18 extends ElggPluginManifestParser { * @var array */ protected $validAttributes = array( - 'name' => null, - 'author' => null, - 'version' => null, - 'blurb' => null, - 'description' => null, - 'website' => null, - 'copyright' => null, - 'license' => 'GNU Public License version 2', - 'depends' => array(), - 'screenshots' => array(), - 'conflicts' => array(), - 'provides' => array(), - 'admin' => array( - 'on_enable' => null, - 'on_disable' => null, - 'interface_type' => 'advanced' - ) + 'name', 'author', 'version', 'blurb', 'description', + 'website', 'copyright', 'license', 'requires', 'screenshot', + 'category', 'conflicts', 'provides', 'admin' ); /** @@ -37,7 +24,7 @@ class ElggPluginManifestParser18 extends ElggPluginManifestParser { * @var array */ protected $requiredAttributes = array( - 'name', 'author', 'version', 'description', 'depends' + 'name', 'author', 'version', 'description', 'requires' ); /** @@ -50,11 +37,8 @@ class ElggPluginManifestParser18 extends ElggPluginManifestParser { foreach ($this->manifestObject->children as $element) { switch ($element->name) { // single elements - // translatable case 'blurb': case 'description': - $element->content = elgg_echo($element->content); - case 'name': case 'author': case 'version': @@ -65,14 +49,8 @@ class ElggPluginManifestParser18 extends ElggPluginManifestParser { break; // arrays - case 'screenshot': - if (isset($element->attributes['description'])) { - $description = elgg_echo($element->attributes['description']); - } - $parsed['screenshots'][] = array( - 'description' => $description, - 'path' => $element->content - ); + case 'category': + $parsed['category'][] = $element->content; break; case 'admin': @@ -87,9 +65,10 @@ class ElggPluginManifestParser18 extends ElggPluginManifestParser { break; + case 'screenshot': case 'provides': case 'conflicts': - case 'depends': + case 'requires': if (!isset($element->children)) { return false; } @@ -112,7 +91,9 @@ class ElggPluginManifestParser18 extends ElggPluginManifestParser { } } - $this->manifest = $parsed; + if (!$this->manifest = $parsed) { + return false; + } return true; } diff --git a/engine/classes/ElggPluginPackage.php b/engine/classes/ElggPluginPackage.php new file mode 100644 index 000000000..c00df7f8d --- /dev/null +++ b/engine/classes/ElggPluginPackage.php @@ -0,0 +1,763 @@ +id = array_pop($path_array); + $this->path = $plugin; + } else { + // this is a plugin name + + // strict plugin names + if (preg_match('/[^a-z0-9\.\-_]/i', $id)) { + throw new PluginException(elgg_echo('PluginException:InvalidID', array($plugin))); + } + + $this->id = $plugin; + $this->path = get_config('pluginspath') . "$plugin/"; + } + + if ($validate && !$this->isValid()) { + if ($this->_invalidPluginError) { + throw new PluginException(elgg_echo('PluginException:InvalidPlugin:Details', + array($plugin, $this->_invalidPluginError))); + } else { + throw new PluginException(elgg_echo('PluginException:InvalidPlugin', array($plugin))); + } + } + + return true; + } + + /******************************** + * Validation and sanity checks * + ********************************/ + + /** + * Checks if this is a valid Elgg plugin. + * + * Checks for requires files as defined at the start of this + * class. Will check require manifest fields via ElggPluginManifest + * for Elgg 1.8 plugins. + * + * @note This doesn't check dependencies or conflicts. + * Use {@link ElggPluginPackage::canActivate()} or + * {@link ElggPluginPackage::checkDependencies()} for that. + * + * @return bool + */ + public function isValid() { + if (isset($this->valid)) { + return $this->valid; + } + + $valid = true; + + // check required files. + $have_req_files = true; + foreach ($this->_requiredFiles as $file) { + if (!is_readable($this->path . $file)) { + $have_req_files = false; + $this->_invalidPluginError = + elgg_echo('ElggPluginPackage:InvalidPlugin:MissingFile', array($file)); + break; + } + } + + // check required files + if (!$have_req_files) { + $valid = false; + } + + // check for valid manifest. + if (!$this->_loadManifest()) { + $valid = false; + } + + // can't require or conflict with yourself or something you provide. + // make sure provides are all valid. + if (!$this->_isSaneDeps()) { + $valid = false; + } + + $this->valid = $valid; + + return $valid; + } + + /** + * Check the plugin doesn't require or conflict with itself + * or something provides. Also check that it only list + * valid provides. Deps are checked in checkDependencies() + * + * @note Plugins always provide themselves. + * + * @todo Don't let them require and conflict the same thing + * + * @return bool + */ + private function _isSaneDeps() { + $conflicts = $this->getManifest()->getConflicts(); + $requires = $this->getManifest()->getRequires(); + $provides = $this->getManifest()->getProvides(); + + foreach ($provides as $provide) { + // only valid provide types + if (!in_array($provide['type'], $this->_providesSupportedTypes)) { + $this->_invalidPluginError = + elgg_echo('ElggPluginPackage:InvalidPlugin:InvalidProvides', array($provide['type'])); + return false; + } + + // doesn't conflict or require any of its provides + $name = $provide['name']; + foreach (array('conflicts', 'requires') as $dep_type) { + foreach (${$dep_type} as $dep) { + if (!in_array($dep['type'], $this->_depsSupportedTypes)) { + $this->_invalidPluginError = + elgg_echo('ElggPluginPackage:InvalidPlugin:InvalidDependency', array($dep['type'])); + return false; + } + + // make sure nothing is providing something it conflicts or requires. + if ($dep['name'] == $name) { + $version_compare = version_compare($provide['version'], $dep['version'], $dep['comparison']); + + if ($version_compare) { + $this->_invalidPluginError = + elgg_echo('ElggPluginPackage:InvalidPlugin:CircularDep', + array($dep['type'], $dep['name'], $this->id)); + + return false; + } + } + } + } + } + + return true; + } + + /** + * Checks if this plugin can be activated on the current + * Elgg installation. + * + * @return bool + */ + public function canActivate() { + return $this->checkDependencies(); + } + + + /************ + * Manifest * + ************/ + + /** + * Returns a parsed manifest file. + * + * @return ElggPluginManifest + */ + public function getManifest() { + if (!$this->manifest) { + $this->_loadManifest(); + } + + return $this->manifest; + } + + /** + * Loads the manifest into this->manifest as an + * ElggPluginManifest object. + * + * @return bool + */ + private function _loadManifest() { + $file = $this->path . 'manifest.xml'; + if ($this->manifest = new ElggPluginManifest($file, $this->id)) { + return true; + } + + return false; + } + + + /*********************** + * Dependencies system * + ***********************/ + + /** + * Returns if the Elgg system meets the plugin's dependency + * requirements. This includes both requires and conflicts. + * + * Full reports can be requested. The results are returned + * as an array of arrays in the form array( + * 'type' => requires|conflicts, + * 'dep' => array( dependency array ), + * 'status' => bool if depedency is met, + * 'comment' => optional comment to display to the user. + * ) + * + * @param bool $full_report Return a full report. + * @return bool|array + */ + public function checkDependencies($full_report = false) { + $requires = $this->getManifest()->getRequires(); + $conflicts = $this->getManifest()->getConflicts(); + $enabled_plugins = get_installed_plugins('enabled'); + $report = array(); + + foreach (array('requires', 'conflicts') as $dep_type) { + $inverse = ($dep_type == 'conflicts') ? true : false; + + foreach (${$dep_type} as $dep) { + switch ($dep['type']) { + case 'elgg': + $result = $this->_checkDepElgg($dep, get_version()); + break; + + case 'elgg_release': + $result = $this->_checkDepElgg($dep, get_version(true)); + break; + + case 'plugin': + $result = $this->_checkDepPlugin($dep, $enabled_plugins, $inverse); + break; + + case 'php_extension': + $result = $this->_checkDepPhpExtension($dep); + break; + + case 'php_ini': + $result = $this->_checkDepPhpIni($dep); + break; + } + + // unless we're doing a full report, break as soon as we fail. + if (!$full_report && !$result) { + return $result; + } else { + // build report element and comment + if ($dep_type == 'requires') { + $comment = ''; + } elseif ($dep_type == 'conflicts') { + $comment = ''; + } + + $report[] = array( + 'type' => $dep_type, + 'dep' => $dep, + 'status' => $result, + 'comment' => $comment + ); + } + } + } + + if ($full_report) { + return $report; + } + + return true; + } + + /** + * Checks if $plugins meets the requirement by $dep. + * + * @param array $dep An Elgg manifest.xml deps array + * @param array $plugins A list of plugins as returned by get_installed_plugins(); + * @param bool $inverse Inverse the results to use as a conflicts. + * @return bool + */ + private function _checkDepPlugin(array $dep, array $plugins, $inverse = false) { + $r = elgg_check_plugins_provides('plugin', $dep['name'], $dep['version'], $dep['comparison']); + + if ($inverse) { + $r = !$r; + } + + return $r; + } + + /** + * Checks if $elgg_version meets the requirement by $dep. + * + * @param array $dep An Elgg manifest.xml deps array + * @param array $elgg_version An Elgg version (either YYYYMMDDXX or X.Y.Z) + * @param bool $inverse Inverse the result to use as a conflicts. + * @return bool + */ + private function _checkDepElgg(array $dep, $elgg_version, $inverse = false) { + $r = version_compare($elgg_version, $dep['version'], $dep['comparison']); + + if ($inverse) { + $r = !$r; + } + + return $r; + } + + /** + * Checks if the PHP extension in $dep is loaded. + * + * @todo Can this be merged with the plugin checker? + * + * @param array $dep An Elgg manifest.xml deps array + * @return bool + */ + private function _checkDepPhpExtension(array $dep) { + $name = $dep['name']; + $version = $dep['version']; + $comparison = $dep['comparison']; + + // not enabled. + $r = extension_loaded($name); + + // enabled. check version. + $ext_version = phpversion($name); + + if ($version && !version_compare($ext_version, $version, $comparison)) { + $r = false; + } + + // some php extensions can be emulated, so check provides. + if ($r == false) { + $r = elgg_check_plugins_provides('php_extension', $name, $version, $comparison); + } + + return $r; + } + + /** + * Check if the PHP ini setting satisfies $dep. + * + * @param array $dep An Elgg manifest.xml deps array + * @return bool + */ + private function _checkDepPhpIni($dep) { + $name = $dep['name']; + $value = $dep['value']; + $comparison = $dep['comparison']; + + // ini_get() normalizes truthy values to 1 but falsey values to 0 or ''. + // version_compare() considers '' < 0, so normalize '' to 0. + // ElggPluginManifest normalizes all bool values and '' to 1 or 0. + $setting = ini_get($name); + + if ($setting === '') { + $setting = 0; + } + + $r = version_compare($setting, $value, $comparison); + + return $r; + } + + + /************************************** + * Detailed reports for requirements. * + **************************************/ + + + /** + * Returns a report of the dependencies with human + * readable statuses. + * + * @return array + */ + public function getDependenciesReport() { + $requires = $this->getManifest()->getRequires(); + $conflicts = $this->getManifest()->getConflicts(); + $enabled_plugins = get_installed_plugins('enabled'); + + $status = true; + $messages = array(); + + $return = array( + array( + 'type' => 'requires', + 'dep' => $dep, + 'status' => 'bool', + 'comment' => '' + ) + ); + + foreach ($requires as $require) { + switch ($require['type']) { + case 'elgg': + $result = $this->_checkRequiresElgg($require, get_version()); + break; + + case 'elgg_release': + $result = $this->_checkRequiresElgg($require, get_version(true)); + break; + + case 'plugin': + $result = $this->_checkDepsPlugin($require, $enabled_plugins); + break; + + case 'php_extension': + $result = $this->_checkRequiresPhpExtension($require); + break; + + case 'php_ini': + $result = $this->_checkRequiresPhpIni($require); + break; + + default: + $result = array( + 'status' => false, + 'message' => elgg_echo('ElggPluginPackage:UnknownDep', + array($require['type'], $this->getManifest()->getPluginID())) + ); + break; + } + + if (!$result['status']) { + $status = false; + $messages[] = $result['message']; + } + } + + foreach ($conflicts as $conflict) { + + } + + $return = array( + 'status' => $status, + 'messages' => $messages + ); + + return $return; + } + + /** + * Checks if $plugins meets the requirement by $require. + * + * Returns an array in the form array('status' => bool, 'message' => 'Any messages') + * + * @param array $require An Elgg manifest.xml requires array + * @param array $plugins A list of plugins as returned by get_installed_plugins(); + * @return array + */ + private function _checkRequiresPlugin(array $require, array $plugins = array()) { + $status = true; + $message = ''; + + $name = $require['name']; + $version = $require['version']; + $comparison = $require['comparison']; + + // not enabled. + if (!array_key_exists($name, $plugins)) { + $status = false; + + if ($version) { + $message = elgg_echo("ElggPluginPackage:Requires:Plugin:NotEnabled:$comparison", + array($this->getManifest()->getPluginID(), $name, $version)); + } else { + $message = elgg_echo('ElggPluginPackage:Requires:Plugin:NotEnabled:NoVersion', + array($this->getManifest()->getPluginID(), $name)); + } + } + + // enabled. check version. + if ($status != false) { + $requires_plugin_info = $plugins[$name]; + + //@todo boot strapping until we can migrate everything over to ElggPluginPackage. + $plugin_package = new ElggPluginPackage($name); + $plugin_version = $plugin_package->getManifest()->getVersion(); + + if ($version && !version_compare($plugin_version, $version, $comparison)) { + $status = false; + + $message = elgg_echo("ElggPluginPackage:Requires:Plugin:$comparison", + array($this->getManifest()->getPluginID(), $name, $version, $plugin_version)); + } + } + + // if all else fails check with the provides + if ($status == false) { + if (elgg_check_plugins_provides('plugin', $name)) { + // it's provided. check version if asked. + $status = true; + $message = ''; + + if ($version && !elgg_check_plugins_provides('plugin', $name, $version, $comparison)) { + // change the message to something more meaningful + $provide = elgg_get_plugins_provides('plugin', $name); + $plugin_version = "{$provide['provided_by']}:$name={$provide['version']}"; + + $status = false; + $message = elgg_echo("ElggPluginPackage:Requires:Plugin:$comparison", + array($this->getManifest()->getPluginID(), $name, $version, $plugin_version)); + } + } + } + + return array( + 'status' => $status, + 'message' => $message + ); + } + + /** + * Checks if $elgg_version meets the requirement by $require. + * + * Returns an array in the form array('status' => bool, 'message' => 'Any messages') + * + * @param array $require An Elgg manifest.xml requires array + * @param array $elgg_version An Elgg version (either YYYYMMDDXX or X.Y.Z) + * @return array + */ + private function _checkRequiresElgg(array $require, $elgg_version) { + $status = true; + $message = ''; + $version = $require['version']; + $comparison = $require['comparison']; + + if (!version_compare($elgg_version, $version, $comparison)) { + $status = false; + $message = elgg_echo("ElggPluginPackage:Requires:Elgg:$comparison", + array($this->getManifest()->getPluginID(), $version)); + } + + return array( + 'status' => $status, + 'message' => $message + ); + } + + /** + * Checks if the PHP extension in $require is loaded. + * + * @todo Can this be merged with the plugin checker? + * + * @param array $require An Elgg manifest.xml deps array + * @return array + */ + private function _checkRequiresPhpExtension($require) { + $status = true; + $message = ''; + + $name = $require['name']; + $version = $require['version']; + $comparison = $require['comparison']; + + // not enabled. + if (!extension_loaded($name)) { + $status = false; + if ($version) { + $message = elgg_echo("ElggPluginPackage:Requires:PhpExtension:NotInstalled:$comparison", + array($this->getManifest()->getPluginID(), $name, $version)); + } else { + $message = elgg_echo('ElggPluginPackage:Requires:PhpExtension:NotInstalled:NoVersion', + array($this->getManifest()->getPluginID(), $name)); + } + } + + // enabled. check version. + if ($status != false) { + $ext_version = phpversion($name); + + if ($version && !version_compare($ext_version, $version, $comparison)) { + $status = false; + $message = elgg_echo("ElggPluginPackage:Requires:PhpExtension:$comparison", + array($this->getManifest()->getPluginID(), $name, $version)); + } + } + + // some php extensions can be emulated, so check provides. + if ($status == false) { + if (elgg_check_plugins_provides('php_extension', $name)) { + // it's provided. check version if asked. + $status = true; + $message = ''; + + if ($version && !elgg_check_plugins_provides('php_extension', $name, $version, $comparison)) { + // change the message to something more meaningful + $provide = elgg_get_plugins_provides('php_extension', $name); + $plugin_version = "{$provide['provided_by']}:$name={$provide['version']}"; + + $status = false; + $message = elgg_echo("ElggPluginPackage:Requires:PhpExtension:$comparison", + array($this->getManifest()->getPluginID(), $name, $version, $plugin_version)); + } + } + } + + return array( + 'status' => $status, + 'message' => $message + ); + } + + + /** + * Check if the PHP ini setting satisfies $require. + * + * @param array $require An Elgg manifest.xml requires array + * @return array + */ + private function _checkRequiresPhpIni($require) { + $status = true; + $message = ''; + + $name = $require['name']; + $value = $require['value']; + $comparison = $require['comparison']; + + // ini_get() normalizes truthy values to 1 but falsey values to 0 or ''. + // version_compare() considers '' < 0, so normalize '' to 0. + // ElggPluginManifest normalizes all bool values and '' to 1 or 0. + $setting = ini_get($name); + + if ($setting === '') { + $setting = 0; + } + + if (!version_compare($setting, $value, $comparison)) { + $status = false; + $message = elgg_echo("ElggPluginPackage:Requires:PhpIni:$comparison", + array($this->getManifest()->getPluginID(), $name, $value, $setting)); + } + + return array( + 'status' => $status, + 'message' => $message + ); + } + + /** + * Activate the plugin. + * + * @note This method is activate() to avoid clashing with ElggEntity::enable() + * + * @return bool + */ + public function activate() { + return enable_plugin($this->getID()); + } + + /** + * Deactivate the plugin. + * + * @note This method is deactivate() to avoid clashing with ElggEntity::disable() + * + * @return bool + */ + public function deactivate() { + return disable_plugin($this->getID()); + } + + /** + * Returns the Plugin ID + * + * @return string + */ + public function getID() { + return $this->id; + } + +} \ No newline at end of file diff --git a/engine/lib/plugins.php b/engine/lib/plugins.php index 7624b1768..1c91a7776 100644 --- a/engine/lib/plugins.php +++ b/engine/lib/plugins.php @@ -305,6 +305,87 @@ function check_plugin_compatibility($manifest_elgg_version_string) { return false; } +/** + * Returns an array of all provides from all active plugins. + * + * Array in the form array( + * 'provide_type' => array( + * 'provided_name' => array( + * 'version' => '1.8', + * 'provided_by' => 'provider_plugin_id' + * ) + * ) + * ) + * + * @param string $type The type of provides to return + * @param string $name A specific provided name to return. Requires $provide_type. + * + * @return array + */ +function elgg_get_plugins_provides($type = null, $name = null) { + static $provides = null; + $active_plugins = get_installed_plugins('enabled'); + + if (!isset($provides)) { + $provides = array(); + + foreach ($active_plugins as $plugin_id => $plugin_info) { + // @todo remove this when fully converted to ElggPluginPackage. + $package = new ElggPluginPackage($plugin_id); + + if ($plugin_provides = $package->getManifest()->getProvides()) { + foreach ($plugin_provides as $provided) { + $provides[$provided['type']][$provided['name']] = array( + 'version' => $provided['version'], + 'provided_by' => $plugin_id + ); + } + } + } + } + + if ($type && $name) { + if (isset($provides[$type][$name])) { + return $provides[$type][$name]; + } else { + return false; + } + } elseif ($type) { + if (isset($provides[$type])) { + return $provides[$type]; + } else { + return false; + } + } + + return $provides; +} + +/** + * Checks if a plugin is currently providing $type and $name, and optionally + * checking a version. + * + * @param string $type The type of the provide + * @param string $name The name of the provide + * @param string $version A version to check against + * @param string $comparison The comparison operator to use in version_compare() + * + * @return bool + */ +function elgg_check_plugins_provides($type, $name, $version = null, $comparison = 'ge') { + if (!$provided = elgg_get_plugins_provides($type, $name)) { + return false; + } + + if ($provided) { + if ($version) { + return version_compare($provided['version'], $version, $comparison); + } else { + return true; + } + } +} + /** * Shorthand function for finding the plugin settings. * diff --git a/engine/tests/api/plugins.php b/engine/tests/api/plugins.php index 66e87c91e..61e2cdde6 100644 --- a/engine/tests/api/plugins.php +++ b/engine/tests/api/plugins.php @@ -2,83 +2,30 @@ /** * Elgg Plugins Test * - * @package Elgg - * @subpackage Test + * @package Elgg.Core + * @subpackage Plugins.Test */ class ElggCorePluginsAPITest extends ElggCoreUnitTest { - var $manifest_file_18 = <<<___END - - - Test Manifest - Anyone - 1.0 - A concise description. - A longer, more interesting description. - http://www.elgg.org/ - (C) Elgg 2010 - GNU Public License version 2 - - elgg - 2009030802 - - - graphics/plugin_ss1.png - graphics/plugin_ss2.png - - - setup_function - teardown_function - simple - - - - php_extension - gd - - - - php_ini - safe_mode - off - - - - plugin - profile - - - - profile_api - 1.3 - - - -___END; - // 1.8 manifest object var $manifest18; - var $manifest_file_17 = <<<___END - - - - - - - - - - -___END; + // 1.8 package at test_files/plugin_18/ + var $package18; // 1.7 manifest object var $manifest17; + // 1.7 package at test_files/plugin_17/ + var $package17; + public function __construct() { parent::__construct(); - $this->manifest18 = new ElggPluginManifest($this->manifest_file_18, 'unit_test'); - $this->manifest17 = new ElggPluginManifest($this->manifest_file_17); + $this->manifest18 = new ElggPluginManifest(get_config('path') . 'engine/tests/test_files/plugin_18/manifest.xml', 'plugin_test_18'); + $this->manifest17 = new ElggPluginManifest(get_config('path') . 'engine/tests/test_files/plugin_17/manifest.xml', 'plugin_test_17'); + + $this->package18 = new ElggPluginPackage(get_config('path') . 'engine/tests/test_files/plugin_18'); + $this->package17 = new ElggPluginPackage(get_config('path') . 'engine/tests/test_files/plugin_17'); } /** @@ -89,38 +36,30 @@ ___END; $this->swallowErrors(); } - // generic tests public function testElggPluginManifestFromString() { - $manifest = new ElggPluginManifest($this->manifest_file_17); + $manifest_file = file_get_contents(get_config('path') . 'engine/tests/test_files/plugin_17/manifest.xml'); + $manifest = new ElggPluginManifest($manifest_file); $this->assertIsA($manifest, 'ElggPluginManifest'); } public function testElggPluginManifestFromFile() { - $file = get_config('dataroot') . '/manifest_test.xml'; - $fp = fopen($file, 'wb'); - fputs($fp, $this->manifest_file_17); - fclose($fp); - + $file = get_config('path') . 'engine/tests/test_files/plugin_17/manifest.xml'; $manifest = new ElggPluginManifest($file); $this->assertIsA($manifest, 'ElggPluginManifest'); - - unlink($file); } - public function testElggPluginManifestFromXML() { - $xml = xml_to_object($this->manifest_file_17); + public function testElggPluginManifestFromXMLEntity() { + $xml = xml_to_object($manifest_file = file_get_contents(get_config('path') . 'engine/tests/test_files/plugin_17/manifest.xml')); $manifest = new ElggPluginManifest($xml); $this->assertIsA($manifest, 'ElggPluginManifest'); } - - + // exact manifest values // 1.8 interface - public function testElggPluginManifest18() { $manifest_array = array( 'name' => 'Test Manifest', @@ -132,23 +71,33 @@ ___END; 'copyright' => '(C) Elgg 2010', 'license' => 'GNU Public License version 2', - 'depends' => array( - array('type' => 'elgg', 'value' => '2009030802'), - array('type' => 'php_extension', 'value' => 'gd'), - array('type' => 'php_ini', 'name' => 'safe_mode', 'value' => 'off'), + 'requires' => array( + array('type' => 'elgg', 'version' => '3009030802', 'comparison' => 'lt'), + array('type' => 'elgg_release', 'version' => '1.8-svn'), + array('type' => 'php_extension', 'name' => 'gd'), + array('type' => 'php_ini', 'name' => 'short_open_tag', 'value' => 'off'), + array('type' => 'php_extension', 'name' => 'made_up', 'version' => '1.0'), + array('type' => 'plugin', 'name' => 'fake_plugin', 'version' => '1.0'), + array('type' => 'plugin', 'name' => 'profile', 'version' => '1.0'), + array('type' => 'plugin', 'name' => 'profile_api', 'version' => '1.3', 'comparison' => 'lt'), ), - 'screenshots' => array( + 'screenshot' => array( array('description' => 'Fun things to do 1', 'path' => 'graphics/plugin_ss1.png'), array('description' => 'Fun things to do 2', 'path' => 'graphics/plugin_ss2.png'), ), + 'category' => array( + 'Admin', 'ServiceAPI' + ), + 'conflicts' => array( - array('type' => 'plugin', 'value' => 'profile') + array('type' => 'plugin', 'name' => 'profile_api', 'version' => 1.0) ), 'provides' => array( - array('name' => 'profile_api', 'version' => 1.3) + array('type' => 'plugin', 'name' => 'profile_api', 'version' => 1.3), + array('type' => 'php_extension', 'name' => 'big_math', 'version' => 1.0) ), 'admin' => array( @@ -161,53 +110,160 @@ ___END; $this->assertEqual($this->manifest18->getManifest(), $manifest_array); } + public function testElggPluginManifest17() { + $manifest_array = array( + 'author' => 'Anyone', + 'version' => '1.0', + 'description' => 'A 1.7-style manifest.', + 'website' => 'http://www.elgg.org/', + 'copyright' => '(C) Elgg 2010', + 'license' => 'GNU Public License version 2', + 'elgg_version' => '2009030702' + ); + + $this->assertEqual($this->manifest17->getManifest(), $manifest_array); + } + + public function testElggPluginManifestGetApiVersion() { $this->assertEqual($this->manifest18->getApiVersion(), 1.8); + $this->assertEqual($this->manifest17->getApiVersion(), 1.7); } + public function testElggPluginManifestGetPluginID() { + $this->assertEqual($this->manifest18->getPluginID(), 'plugin_test_18'); + $this->assertEqual($this->manifest17->getPluginID(), 'plugin_test_17'); + } + + + // normalized attributes public function testElggPluginManifestGetName() { $this->assertEqual($this->manifest18->getName(), 'Test Manifest'); + $this->assertEqual($this->manifest17->getName(), 'Plugin Test 17'); } public function testElggPluginManifestGetAuthor() { $this->assertEqual($this->manifest18->getAuthor(), 'Anyone'); + $this->assertEqual($this->manifest17->getAuthor(), 'Anyone'); } public function testElggPluginManifestGetVersion() { $this->assertEqual($this->manifest18->getVersion(), 1.0); + $this->assertEqual($this->manifest17->getVersion(), 1.0); } public function testElggPluginManifestGetBlurb() { $this->assertEqual($this->manifest18->getBlurb(), 'A concise description.'); + $this->assertEqual($this->manifest17->getBlurb(), 'A 1.7-style manifest.'); } public function testElggPluginManifestGetWebsite() { $this->assertEqual($this->manifest18->getWebsite(), 'http://www.elgg.org/'); + $this->assertEqual($this->manifest17->getWebsite(), 'http://www.elgg.org/'); } public function testElggPluginManifestGetCopyright() { $this->assertEqual($this->manifest18->getCopyright(), '(C) Elgg 2010'); + $this->assertEqual($this->manifest18->getCopyright(), '(C) Elgg 2010'); } public function testElggPluginManifestGetLicense() { $this->assertEqual($this->manifest18->getLicense(), 'GNU Public License version 2'); + $this->assertEqual($this->manifest17->getLicense(), 'GNU Public License version 2'); } - // 1.7 interface + public function testElggPluginManifestGetRequires() { + $requires = array( + array('type' => 'elgg', 'version' => '3009030802', 'comparison' => 'lt'), + array('type' => 'elgg_release', 'version' => '1.8-svn', 'comparison' => 'ge'), + array('type' => 'php_extension', 'name' => 'gd', 'version' => '', 'comparison' => '='), + array('type' => 'php_ini', 'name' => 'short_open_tag', 'value' => 'off', 'comparison' => '='), + array('type' => 'php_extension', 'name' => 'made_up', 'version' => '1.0', 'comparison' => '='), + array('type' => 'plugin', 'name' => 'fake_plugin', 'version' => '1.0', 'comparison' => 'ge'), + array('type' => 'plugin', 'name' => 'profile', 'version' => '1.0', 'comparison' => 'ge'), + array('type' => 'plugin', 'name' => 'profile_api', 'version' => '1.3', 'comparison' => 'lt'), + ); - public function testElggPluginManifest17() { - $manifest_array = array( - 'author' => 'Anyone', - 'version' => '1.0', - 'description' => 'A 1.7-style manifest', - 'website' => 'http://www.elgg.org/', - 'copyright' => '(C) Elgg2008-2009', - 'license' => 'GNU Public License version 2', - 'elgg_version' => '2009030702' + $this->assertEqual($this->package18->getManifest()->getRequires(), $requires); + + $this->assertEqual($this->package17->getManifest()->getRequires(), array()); + } + + public function testElggPluginManifestGetDescription() { + $this->assertEqual($this->package18->getManifest()->getDescription(), 'A longer, more interesting description.'); + $this->assertEqual($this->package17->getManifest()->getDescription(), 'A 1.7-style manifest.'); + } + + public function testElggPluginManifestGetDescriptionTranslated() { + $en = array( + $this->package18->getManifest()->getDescription() => 'A translated 1.8 description!', + $this->package17->getManifest()->getDescription() => 'A translated 1.7 description!', ); - $this->assertEqual($this->manifest17->getManifest(), $manifest_array); + add_translation('en', $en); + + $this->assertEqual($this->package18->getManifest()->getDescription(), 'A translated 1.8 description!'); + $this->assertEqual($this->package17->getManifest()->getDescription(), 'A translated 1.7 description!'); + } + + public function testElggPluginManifestGetCategories() { + $categories = array( + 'Admin', 'ServiceAPI' + ); + + $this->assertEqual($this->package18->getManifest()->getCategories(), $categories); + $this->assertEqual($this->package17->getManifest()->getCategories(), array()); + } + + public function testElggPluginManifestGetScreenshots() { + $screenshots = array( + array('description' => 'Fun things to do 1', 'path' => 'graphics/plugin_ss1.png'), + array('description' => 'Fun things to do 2', 'path' => 'graphics/plugin_ss2.png'), + ); + + $this->assertEqual($this->package18->getManifest()->getScreenshots(), $screenshots); + $this->assertEqual($this->package17->getManifest()->getScreenshots(), array()); + } + + public function testElggPluginManifestGetProvides() { + $provides = array( + array('type' => 'plugin', 'name' => 'profile_api', 'version' => 1.3), + array('type' => 'php_extension', 'name' => 'big_math', 'version' => 1.0), + array('type' => 'plugin', 'name' => 'plugin_18', 'version' => 1.0) + ); + + $this->assertEqual($this->package18->getManifest()->getProvides(), $provides); + + + $provides = array( + array('type' => 'plugin', 'name' => 'plugin_17', 'version' => '1.0') + ); + + $this->assertEqual($this->package17->getManifest()->getProvides(), $provides); } + public function testElggPluginManifestGetConflicts() { + $conflicts = array( + array( + 'type' => 'plugin', + 'name' => 'profile_api', + 'version' => '1.0', + 'comparison' => '=' + ) + ); + + $this->assertEqual($this->manifest18->getConflicts(), $conflicts); + $this->assertEqual($this->manifest17->getConflicts(), array()); + } + + // ElggPluginPackage + public function testElggPluginPackageDetectIDFromPath() { + $this->assertEqual($this->package18->getID(), 'plugin_18'); + } + + public function testElggPluginPackageDetectIDFromPluginID() { + $package = new ElggPluginPackage('profile'); + $this->assertEqual($package->getID(), 'profile'); + } } diff --git a/engine/tests/test_files/plugin_17/manifest.xml b/engine/tests/test_files/plugin_17/manifest.xml new file mode 100644 index 000000000..bb178ab93 --- /dev/null +++ b/engine/tests/test_files/plugin_17/manifest.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/engine/tests/test_files/plugin_17/start.php b/engine/tests/test_files/plugin_17/start.php new file mode 100644 index 000000000..e69de29bb diff --git a/engine/tests/test_files/plugin_18/manifest.xml b/engine/tests/test_files/plugin_18/manifest.xml new file mode 100644 index 000000000..182117a50 --- /dev/null +++ b/engine/tests/test_files/plugin_18/manifest.xml @@ -0,0 +1,97 @@ + + + Test Manifest + Anyone + 1.0 + A concise description. + A longer, more interesting description. + http://www.elgg.org/ + (C) Elgg 2010 + GNU Public License version 2 + + + elgg + 3009030802 + lt + + + + elgg_release + 1.8-svn + + + + Fun things to do 1 + graphics/plugin_ss1.png + + + + Fun things to do 2 + 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 + big_math + 1.0 + + + \ No newline at end of file diff --git a/engine/tests/test_files/plugin_18/start.php b/engine/tests/test_files/plugin_18/start.php new file mode 100644 index 000000000..e69de29bb diff --git a/languages/en.php b/languages/en.php index 7155cf223..b1796f51f 100644 --- a/languages/en.php +++ b/languages/en.php @@ -63,6 +63,65 @@ $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:InvalidID' => "%s is an invalid plugin ID.", + 'PluginException:InvalidPath' => "%s is an invalid plugin path.", + 'PluginException:InvalidManifest' => 'Invalid manifest file for plugin %s', + 'PluginException:InvalidPlugin' => '%s is not a valid plugin.', + 'PluginException:InvalidPlugin:Details' => '%s is not a valid plugin: %s', + 'ElggPluginPackage:InvalidPlugin:MissingFile' => 'Missing file %s in package', + 'ElggPluginPackage:InvalidPlugin:InvalidDependency' => 'Invalid dependency type "%s"', + '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!', + + '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.", + + 'ElggPluginPackage:UnknownDep' => 'Unknown dependency %s in plugin %s', + + 'ElggPluginPackage:Requires:Plugin:lt' => '%s requires the %s plugin below version %s. Version %s is installed.', + 'ElggPluginPackage:Requires:Plugin:le' => '%s requires the %s plugin version %s or below. Version %s is installed.', + 'ElggPluginPackage:Requires:Plugin:=' => '%s requires the %s plugin version %s. Version %s is installed.', + 'ElggPluginPackage:Requires:Plugin:!=' => '%s requires the %s plugin not equal to version %s. Version %s is installed.', + 'ElggPluginPackage:Requires:Plugin:ge' => '%s requires the %s plugin version %s or higher. Version %s is installed.', + 'ElggPluginPackage:Requires:Plugin:gt' => '%s requires the %s plugin higher than version %s. Version %s is installed.', + + 'ElggPluginPackage:Requires:Plugin:NotEnabled:lt' => '%s requires the %s plugin below version %s.', + 'ElggPluginPackage:Requires:Plugin:NotEnabled:le' => '%s requires the %s plugin version %s or below.', + 'ElggPluginPackage:Requires:Plugin:NotEnabled:=' => '%s requires the %s plugin version %s.', + 'ElggPluginPackage:Requires:Plugin:NotEnabled:!=' => '%s requires the %s plugin not equal to version %s.', + 'ElggPluginPackage:Requires:Plugin:NotEnabled:ge' => '%s requires the %s plugin version %s or higher.', + 'ElggPluginPackage:Requires:Plugin:NotEnabled:gt' => '%s requires the %s plugin higher than version %s.', + 'ElggPluginPackage:Requires:Plugin:NotEnabled:NoVersion' => '%s requires the %s plugin.', + + 'ElggPluginPackage:Requires:Elgg:lt' => '%s requires Elgg below version %s.', + 'ElggPluginPackage:Requires:Elgg:le' => '%s requires Elgg %s or below.', + 'ElggPluginPackage:Requires:Elgg:=' => '%s requires Elgg version %s.', + 'ElggPluginPackage:Requires:Elgg:!=' => '%s requires Elgg not equal to version %s.', + 'ElggPluginPackage:Requires:Elgg:ge' => '%s requires Elgg version %s or higher.', + 'ElggPluginPackage:Requires:Elgg:gt' => '%s requires Elgg higher than version %s.', + + 'ElggPluginPackage:Requires:PhpExtension:NotInstalled:lt' => '%s requires the %s PHP extension below version %s.', + 'ElggPluginPackage:Requires:PhpExtension:NotInstalled:le' => '%s requires the %s PHP extension version %s or below.', + 'ElggPluginPackage:Requires:PhpExtension:NotInstalled:=' => '%s requires the %s PHP extension version %s.', + 'ElggPluginPackage:Requires:PhpExtension:NotInstalled:!=' => '%s requires the %s PHP extension not equal to version %s.', + 'ElggPluginPackage:Requires:PhpExtension:NotInstalled:ge' => '%s requires the %s PHP extension version %s or higher.', + 'ElggPluginPackage:Requires:PhpExtension:NotInstalled:gt' => '%s requires the %s PHP extension higher than version %s.', + 'ElggPluginPackage:Requires:PhpExtension:NotInstalled:NoVersion' => '%s requires the %s PHP extension.', + + 'ElggPluginPackage:Requires:PhpExtension:lt' => '%s requires the %s PHP extension below version %s. Version %s is installed.', + 'ElggPluginPackage:Requires:PhpExtension:le' => '%s requires the %s PHP extension version %s or below. Version %s is installed.', + 'ElggPluginPackage:Requires:PhpExtension:=' => '%s requires the %s PHP extension version %s. Version %s is installed.', + 'ElggPluginPackage:Requires:PhpExtension:!=' => '%s requires the %s PHP extension not equal to version %s. Version %s is installed.', + 'ElggPluginPackage:Requires:PhpExtension:ge' => '%s requires the %s PHP extension version %s or higher. Version %s is installed.', + 'ElggPluginPackage:Requires:PhpExtension:gt' => '%s requires the %s PHP extension higher than version %s. Version %s is installed.', + + 'ElggPluginPackage:Requires:PhpIni:lt' => '%s requires the %s php.ini option set less than %s. Currently set to %s.', + 'ElggPluginPackage:Requires:PhpIni:le' => '%s requires the %s php.ini option set less than or equal to %s. Currently set to %s.', + 'ElggPluginPackage:Requires:PhpIni:=' => '%s requires the %s php.ini option set to %s. Currently set to %s.', + 'ElggPluginPackage:Requires:PhpIni:!=' => '%s requires the %s php.ini option not set to %s. Currently set to %s.', + 'ElggPluginPackage:Requires:PhpIni:ge' => '%s requires the %s php.ini option set greater than or equal to %s. Currently set to %s.', + 'ElggPluginPackage:Requires:PhpIni:gt' => '%s requires the %s php.ini option set greater than %s. Currently set to %s.', 'InvalidParameterException:NonElggUser' => "Passing a non-ElggUser to an ElggUser constructor!", @@ -770,7 +829,7 @@ Once you have logged in, we highly recommend that you change your password. 'upgrading' => 'Upgrading...', 'upgrade:db' => 'Your database was upgraded.', 'upgrade:core' => 'Your elgg installation was upgraded.', - + 'deprecated:function' => '%s() was deprecated by %s()', /** -- cgit v1.2.3