aboutsummaryrefslogtreecommitdiff
path: root/engine/classes
diff options
context:
space:
mode:
authorbrettp <brettp@36083f99-b078-4883-b0ff-0f9b5a30f544>2010-12-03 03:11:49 +0000
committerbrettp <brettp@36083f99-b078-4883-b0ff-0f9b5a30f544>2010-12-03 03:11:49 +0000
commit4b10b550a2449827c643c896424eb0277c43b049 (patch)
treea0b563666d98f3e32f2b502c2d6474afa3d17d86 /engine/classes
parent4ad5937e6077a60ecc7ee7828fb8975af7491862 (diff)
downloadelgg-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.php299
-rw-r--r--engine/classes/ElggPluginManifestParser.php27
-rw-r--r--engine/classes/ElggPluginManifestParser17.php39
-rw-r--r--engine/classes/ElggPluginManifestParser18.php43
-rw-r--r--engine/classes/ElggPluginPackage.php763
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