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 ++++++++++++++++++++++++++ 5 files changed, 1065 insertions(+), 106 deletions(-) create mode 100644 engine/classes/ElggPluginPackage.php (limited to 'engine/classes') 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 -- cgit v1.2.3