diff options
| author | brettp <brettp@36083f99-b078-4883-b0ff-0f9b5a30f544> | 2010-12-03 03:11:49 +0000 | 
|---|---|---|
| committer | brettp <brettp@36083f99-b078-4883-b0ff-0f9b5a30f544> | 2010-12-03 03:11:49 +0000 | 
| commit | 4b10b550a2449827c643c896424eb0277c43b049 (patch) | |
| tree | a0b563666d98f3e32f2b502c2d6474afa3d17d86 /engine/classes | |
| parent | 4ad5937e6077a60ecc7ee7828fb8975af7491862 (diff) | |
| download | elgg-4b10b550a2449827c643c896424eb0277c43b049.tar.gz elgg-4b10b550a2449827c643c896424eb0277c43b049.tar.bz2 | |
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
Diffstat (limited to 'engine/classes')
| -rw-r--r-- | engine/classes/ElggPluginManifest.php | 299 | ||||
| -rw-r--r-- | engine/classes/ElggPluginManifestParser.php | 27 | ||||
| -rw-r--r-- | engine/classes/ElggPluginManifestParser17.php | 39 | ||||
| -rw-r--r-- | engine/classes/ElggPluginManifestParser18.php | 43 | ||||
| -rw-r--r-- | engine/classes/ElggPluginPackage.php | 763 | 
5 files changed, 1065 insertions, 106 deletions
| 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 <plugin-manifest> + * tag. + *   * @package    Elgg.Core   * @subpackage Plugins + * @since      1.8   */  class ElggPluginManifest { @@ -16,6 +23,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.  	 *  	 * @var int @@ -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 @@ +<?php +/** + * Manages plugin packages under mod. + * + * @todo This should eventually be merged into ElggPlugin. + * Currently ElggPlugin objects are only used to get and save + * plugin settings and user settings, so not every plugin + * has an ElggPlugin object.  It's not implemented in ElggPlugin + * right now because of conflicts with at least the constructor, + * enable(), disable(), and private settings. + * + * Around 1.9 or so we should each plugin over to using + * ElggPlugin and merge ElggPluginPackage and ElggPlugin. + * + * @package    Elgg.Core + * @subpackage Plugins + * @since      1.8 + */ +class ElggPluginPackage { + +	/** +	 * The required files in the package +	 * +	 * @var array +	 */ +	private $_requiredFiles = array( +		'start.php', 'manifest.xml' +	); + +	/** +	 * Valid types for provides. +	 * +	 * @var array +	 */ +	private $_providesSupportedTypes = array( +		'plugin', 'php_extension' +	); + +	/** +	 * The type of requires/conflicts supported +	 * +	 * @var array +	 */ +	private $_depsSupportedTypes = array( +		'elgg', 'elgg_release', 'php_extension', 'php_ini', 'plugin' +	); + +	/** +	 * An invalid plugin error. +	 */ +	private $_invalidPluginError = ''; + +	/** +	 * Any dependencies messages +	 */ +	private $_depsMsgs = array(); + +	/** +	 * The plugin's manifest object +	 * +	 * @var ElggPluginManifest +	 */ +	protected $manifest; + +	/** +	 * The plugin's full path +	 * +	 * @var string +	 */ +	protected $path; + +	/** +	 * Is the plugin valid? +	 * +	 * @var mixed Bool after validation check, null before. +	 */ +	protected $valid = null; + +	/** +	 * The plugin ID (dir name) +	 * +	 * @var string +	 */ +	protected $id; + +	/** +	 * Load a plugin package from mod/$id or by full path. +	 * +	 * @param string $plugin   The ID (directory name) or full path of the plugin. +	 * @param bool   $validate Automatically run isValid()? +	 * +	 * @return true +	 * @throws PluginException +	 */ +	public function __construct($plugin, $validate = true) { +		if (substr($plugin, 0, 1) == '/') { +			// this is a path +			$plugin = sanitise_filepath($plugin); + +			if (!is_dir($plugin)) { +				throw new PluginException(elgg_echo('PluginException:InvalidPath', array($plugin))); +			} + +			// the id is the last element of the array +			$path_array = explode('/', trim($plugin, '/')); +			$this->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 | 
