aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engine/classes/ElggPlugin.php1
-rw-r--r--engine/classes/ElggPluginManifest.php287
-rw-r--r--engine/classes/ElggPluginManifestParser.php91
-rw-r--r--engine/classes/ElggPluginManifestParser17.php54
-rw-r--r--engine/classes/ElggPluginManifestParser18.php119
-rw-r--r--engine/lib/plugins.php63
-rw-r--r--engine/tests/api/plugins.php213
7 files changed, 792 insertions, 36 deletions
diff --git a/engine/classes/ElggPlugin.php b/engine/classes/ElggPlugin.php
index 76276181b..887eb667f 100644
--- a/engine/classes/ElggPlugin.php
+++ b/engine/classes/ElggPlugin.php
@@ -21,6 +21,7 @@ class ElggPlugin extends ElggObject {
$this->attributes['subtype'] = "plugin";
}
+
/**
* Get a value from private settings.
*
diff --git a/engine/classes/ElggPluginManifest.php b/engine/classes/ElggPluginManifest.php
new file mode 100644
index 000000000..395208c95
--- /dev/null
+++ b/engine/classes/ElggPluginManifest.php
@@ -0,0 +1,287 @@
+<?php
+/**
+ * Parses Elgg manifest.xml files.
+ *
+ * This requires an ElggPluginManifestParser class implementation
+ * as $this->parser.
+ *
+ * @package Elgg.Core
+ * @subpackage Plugins
+ */
+class ElggPluginManifest {
+
+ /**
+ * The parser object
+ */
+ protected $parser;
+
+ /**
+ * The API version of the manifest.
+ *
+ * @var int
+ */
+ protected $apiVersion;
+
+ /**
+ * The optional plugin id this manifest belongs to.
+ *
+ * @var string
+ */
+ protected $pluginID;
+
+ /**
+ * Load a manifest file, XmlElement or path to manifest.xml file
+ *
+ * @param mixed $manifest A string, XmlElement, or path of a manifest file.
+ * @param string $plugin_id Optional ID of the owning plugin. Used to
+ * fill in some values automatically.
+ */
+ public function __construct($manifest, $plugin_id = null) {
+ if ($plugin_id) {
+ $this->pluginID = $plugin_id;
+ }
+
+ // see if we need to construct the xml object.
+ if ($manifest instanceof XmlElement) {
+ $manifest_obj = $manifest;
+ } else {
+ if (substr(trim($manifest), 0, 1) == '<') {
+ // this is a string
+ $raw_xml = $manifest;
+ } elseif (is_readable($manifest)) {
+ // this is a file
+ $raw_xml = file_get_contents($manifest);
+ }
+
+ $manifest_obj = xml_to_object($raw_xml);
+ }
+
+ if (!$manifest_obj) {
+ throw new PluginException(elgg_echo('PluginException:InvalidManifest', array($this->getPluginID())));
+ }
+
+ // set manifest api version
+ if (isset($manifest_obj->attributes['version'])) {
+ $this->apiVersion = (float)$manifest_obj->attributes['version'];
+ } else {
+ $this->apiVersion = 1.7;
+ }
+
+ switch ($this->apiVersion) {
+ case 1.8:
+ $this->parser = new ElggPluginManifestParser18($manifest_obj, $this);
+ break;
+
+ case 1.7:
+ $this->parser = new ElggPluginManifestParser17($manifest_obj, $this);
+ break;
+
+ default:
+ throw new PluginException(elgg_echo('PluginException:NoAvailableParser',
+ array($this->apiVersion, $this->getPluginID())));
+ break;
+ }
+
+ if (!$this->parser->parse()) {
+ throw new PluginException(elgg_echo('PluginException:ParserError',
+ array($this->apiVersion, $this->getPluginID())));
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the API version in use.
+ *
+ * @return int
+ */
+ public function getApiVersion() {
+ return $this->apiVersion;
+ }
+
+ /**
+ * Returns the plugin ID.
+ *
+ * @return string
+ */
+ public function getPluginID() {
+ if ($this->pluginID) {
+ return $this->pluginID;
+ } else {
+ return elgg_echo('unknown');
+ }
+ }
+
+ /**
+ * Returns the manifest array.
+ *
+ * Used for backward compatibility. Specific
+ * methods should be called instead.
+ *
+ * @return array
+ */
+ public function getManifest() {
+ 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;
+ }
+
+ /**
+ * Returns the plugin name
+ *
+ * @return string
+ */
+ public function getName() {
+ $name = $this->parser->getAttribute('name');
+
+ if (!$name && $this->pluginID) {
+ $name = ucwords(str_replace('_', ' ', $pluginID));
+ }
+
+ return $name;
+ }
+
+ /**
+ * Return the description
+ *
+ * @return string
+ */
+ public function getDescription() {
+ return $this->parser->getAttribute('description');
+ }
+
+ /**
+ * Return the short description
+ *
+ * @return string
+ */
+ public function getBlurb() {
+ $blurb = $this->parser->getAttribute('blurb');
+
+ if (!$blurb) {
+ $blurb = elgg_get_excerpt($this->getDescription());
+ }
+
+ return $blurb;
+ }
+
+ /**
+ * Returns the license
+ *
+ * @return sting
+ */
+ public function getLicense() {
+ return $this->parser->getAttribute('license');
+ }
+
+
+ /**
+ * Returns the version of the plugin.
+ *
+ * @return float
+ */
+ public function getVersion() {
+ return $this->parser->getAttribute('version');
+ }
+
+ /**
+ * Returns the plugin author.
+ *
+ * @return string
+ */
+ public function getAuthor() {
+ return $this->parser->getAttribute('author');
+ }
+
+ /**
+ * Return the copyright
+ *
+ * @return string
+ */
+ public function getCopyright() {
+ return $this->parser->getAttribute('copyright');
+ }
+
+ /**
+ * Return the website
+ *
+ * @return string
+ */
+ public function getWebsite() {
+ return $this->parser->getAttribute('website');
+ }
+
+ /**
+ * Return the categories listed for this plugin
+ *
+ * @return array
+ */
+ public function getCategories() {
+ $cats = $this->parser->getAttribute('categories');
+
+ if (!is_array($cats)) {
+ $cats = array();
+ }
+
+ return $cats;
+ }
+
+ /**
+ * Return the screenshots listed.
+ *
+ * @return array
+ */
+ public function getScreenshots() {
+ $ss = $this->parser->getAttribute('screenshots');
+
+ if (!is_array($ss)) {
+ $ss = array();
+ }
+
+ return $ss;
+ }
+
+ /**
+ * Return the list of provides by this plugin.
+ *
+ * @return array
+ */
+ public function getProvides() {
+ $provides = $this->parser->getAttribute('provides');
+
+ // always provide ourself if we can
+ if ($this->pluginID) {
+ $provides[] = array('name' => $this->getPluginID(), 'version' => $this->getVersion);
+ }
+
+ return $provides;
+ }
+} \ No newline at end of file
diff --git a/engine/classes/ElggPluginManifestParser.php b/engine/classes/ElggPluginManifestParser.php
new file mode 100644
index 000000000..0ce3e3024
--- /dev/null
+++ b/engine/classes/ElggPluginManifestParser.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * Parent class for manifest parsers.
+ *
+ * @package Elgg.Core
+ * @subpackage Plugins
+ *
+ */
+abstract class ElggPluginManifestParser {
+ /**
+ * The XmlElement object
+ *
+ * @var XmlElement
+ */
+ protected $manifestObject;
+
+ /**
+ * The manifest array
+ *
+ * @var array
+ */
+ protected $manifest;
+
+ /**
+ * All valid manifest attributes with default values.
+ *
+ * @var array
+ */
+ protected $validAttributes;
+
+ /**
+ * The object we're doing parsing for.
+ *
+ * @var object
+ */
+ protected $caller;
+
+ /**
+ * Loads the manifest XML to be parsed.
+ *
+ * @param XmlElement $xml The Manifest XML to be parsed
+ * @param object $caller The object calling this parser.
+ */
+ public function __construct(XmlElement $xml, $caller) {
+ $this->manifestObject = $xml;
+ $this->caller = $caller;
+ }
+
+ /**
+ * Returns the manifest XML object
+ *
+ * @return XmlElement
+ */
+ public function getManifestObject() {
+ return $this->manifestObject;
+ }
+
+ /**
+ * Return the parsed manifest array
+ *
+ * @return array
+ */
+ public function getManifest() {
+ return $this->manifest;
+ }
+
+ /**
+ * Return an attribute in the manifest.
+ *
+ * @param string $name Attribute name
+ * @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];
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Parse the XML object into an array
+ *
+ * @return bool
+ */
+ abstract public function parse();
+} \ No newline at end of file
diff --git a/engine/classes/ElggPluginManifestParser17.php b/engine/classes/ElggPluginManifestParser17.php
new file mode 100644
index 000000000..49b91ef52
--- /dev/null
+++ b/engine/classes/ElggPluginManifestParser17.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Plugin manifest.xml parser for Elgg 1.7 and lower.
+ *
+ * @package Elgg.Core
+ * @subpackage Plugins
+ */
+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,
+
+ // were never really used and not enforced in code.
+ 'requires' => null,
+ 'recommends' => null,
+ 'conflicts' => null
+ );
+
+ /**
+ * Parse a manifest object from 1.7 or earlier.
+ *
+ * @return void
+ */
+ public function parse() {
+ foreach ($this->manifestObject->children as $element) {
+ $key = $element->attributes['key'];
+ $value = $element->attributes['value'];
+
+ // create arrays if multiple fields are set
+ if (array_key_exists($key, $elements)) {
+ if (!is_array($elements[$key])) {
+ $orig = $elements[$key];
+ $elements[$key] = array($orig);
+ }
+
+ $elements[$key][] = $value;
+ } else {
+ $elements[$key] = $value;
+ }
+ }
+
+ $this->manifest = $elements;
+
+ return true;
+ }
+} \ No newline at end of file
diff --git a/engine/classes/ElggPluginManifestParser18.php b/engine/classes/ElggPluginManifestParser18.php
new file mode 100644
index 000000000..1d4e9daed
--- /dev/null
+++ b/engine/classes/ElggPluginManifestParser18.php
@@ -0,0 +1,119 @@
+<?php
+/**
+ * Plugin manifest.xml parser for Elgg 1.8 and above.
+ *
+ * @package Elgg.Core
+ * @subpackage Plugins
+ */
+class ElggPluginManifestParser18 extends ElggPluginManifestParser {
+ /**
+ * The valid top level attributes and defaults for a 1.8 manifest array.
+ *
+ * @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'
+ )
+ );
+
+ /**
+ * Required attributes for a valid 1.8 manifest
+ *
+ * @var array
+ */
+ protected $requiredAttributes = array(
+ 'name', 'author', 'version', 'description', 'depends'
+ );
+
+ /**
+ * Parse a manifest object from 1.8 and later
+ *
+ * @return void
+ */
+ public function parse() {
+ $parsed = array();
+ 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':
+ case 'website':
+ case 'copyright':
+ case 'license':
+ $parsed[$element->name] = $element->content;
+ break;
+
+ // arrays
+ case 'screenshot':
+ if (isset($element->attributes['description'])) {
+ $description = elgg_echo($element->attributes['description']);
+ }
+ $parsed['screenshots'][] = array(
+ 'description' => $description,
+ 'path' => $element->content
+ );
+ break;
+
+ case 'admin':
+ $parsed['admin'] = array();
+ if (!isset($element->children)) {
+ return false;
+ }
+
+ foreach ($element->children as $child_element) {
+ $parsed['admin'][$child_element->name] = $child_element->content;
+ }
+
+ break;
+
+ case 'provides':
+ case 'conflicts':
+ case 'depends':
+ if (!isset($element->children)) {
+ return false;
+ }
+
+ $info = array();
+ foreach ($element->children as $child_element) {
+ $info[$child_element->name] = $child_element->content;
+ }
+
+ $parsed[$element->name][] = $info;
+ break;
+ }
+ }
+
+ // check we have all the required fields
+ foreach ($this->requiredAttributes as $attr) {
+ if (!array_key_exists($attr, $parsed)) {
+ throw new PluginException(elgg_echo('PluginException:ParserErrorMissingRequiredAttribute',
+ array($attr, $this->caller->getPluginID())));
+ }
+ }
+
+ $this->manifest = $parsed;
+
+ return true;
+ }
+} \ No newline at end of file
diff --git a/engine/lib/plugins.php b/engine/lib/plugins.php
index 80b68eb5c..163a9fa82 100644
--- a/engine/lib/plugins.php
+++ b/engine/lib/plugins.php
@@ -273,43 +273,15 @@ function get_plugin_name($mainfilename = false) {
* @return array of values
*/
function load_plugin_manifest($plugin) {
- global $CONFIG;
-
- $xml = xml_to_object(file_get_contents($CONFIG->pluginspath . $plugin . "/manifest.xml"));
-
- if ($xml) {
- // set up some defaults to normalize expected values to arrays
- $elements = array(
- 'screenshot' => array(),
- 'category' => array()
- );
-
- foreach ($xml->children as $element) {
- $key = $element->attributes['key'];
- $value = $element->attributes['value'];
+ $xml_file = get_config('pluginspath') . "$plugin/manifest.xml";
- // create arrays if multiple fields are set
- if (array_key_exists($key, $elements)) {
- if (!is_array($elements[$key])) {
- $orig = $elements[$key];
- $elements[$key] = array($orig);
- }
-
- $elements[$key][] = $value;
- } else {
- $elements[$key] = $value;
- }
- }
-
- // handle plugins that don't define a name
- if (!isset($elements['name'])) {
- $elements['name'] = ucwords($plugin);
- }
-
- return $elements;
+ try {
+ $manifest = ElggPluginManifest($xml_file);
+ } catch(Exception $e) {
+ return false;
}
- return false;
+ return $manifest->getManifest();
}
/**
@@ -858,6 +830,23 @@ function plugin_run_once() {
add_subtype("object", "plugin", "ElggPlugin");
}
+
+/**
+ * Runs unit tests for the entity objects.
+ *
+ * @param sting $hook unit_test
+ * @param string $type system
+ * @param mixed $value Array of tests
+ * @param mixed $params Params
+ *
+ * @return array
+ */
+function plugins_test($hook, $type, $value, $params) {
+ global $CONFIG;
+ $value[] = $CONFIG->path . 'engine/tests/api/plugins.php';
+ return $value;
+}
+
/**
* Initialise the file modules.
* Listens to system boot and registers any appropriate file types and classes
@@ -867,14 +856,16 @@ function plugin_run_once() {
function plugin_init() {
run_function_once("plugin_run_once");
+ elgg_register_plugin_hook_handler('unit_test', 'system', 'plugins_test');
+
elgg_register_action("plugins/settings/save", '', 'admin');
elgg_register_action("plugins/usersettings/save");
-
+
elgg_register_action('admin/plugins/enable', '', 'admin');
elgg_register_action('admin/plugins/disable', '', 'admin');
elgg_register_action('admin/plugins/enableall', '', 'admin');
elgg_register_action('admin/plugins/disableall', '', 'admin');
-
+
elgg_register_action('admin/plugins/reorder', '', 'admin');
}
diff --git a/engine/tests/api/plugins.php b/engine/tests/api/plugins.php
new file mode 100644
index 000000000..66e87c91e
--- /dev/null
+++ b/engine/tests/api/plugins.php
@@ -0,0 +1,213 @@
+<?php
+/**
+ * Elgg Plugins Test
+ *
+ * @package Elgg
+ * @subpackage Test
+ */
+class ElggCorePluginsAPITest extends ElggCoreUnitTest {
+ var $manifest_file_18 = <<<___END
+<?xml version="1.0" encoding="UTF-8"?>
+<plugin_manifest version="1.8">
+ <name>Test Manifest</name>
+ <author>Anyone</author>
+ <version>1.0</version>
+ <blurb>A concise description.</blurb>
+ <description>A longer, more interesting description.</description>
+ <website>http://www.elgg.org/</website>
+ <copyright>(C) Elgg 2010</copyright>
+ <license>GNU Public License version 2</license>
+ <depends>
+ <type>elgg</type>
+ <value>2009030802</value>
+ </depends>
+
+ <screenshot description="Fun things to do 1">graphics/plugin_ss1.png</screenshot>
+ <screenshot description="Fun things to do 2">graphics/plugin_ss2.png</screenshot>
+
+ <admin>
+ <on_enable>setup_function</on_enable>
+ <on_disable>teardown_function</on_disable>
+ <interface_type>simple</interface_type>
+ </admin>
+
+ <depends>
+ <type>php_extension</type>
+ <value>gd</value>
+ </depends>
+
+ <depends>
+ <type>php_ini</type>
+ <name>safe_mode</name>
+ <value>off</value>
+ </depends>
+
+ <conflicts>
+ <type>plugin</type>
+ <value>profile</value>
+ </conflicts>
+
+ <provides>
+ <name>profile_api</name>
+ <version>1.3</version>
+ </provides>
+
+</plugin_manifest>
+___END;
+
+ // 1.8 manifest object
+ var $manifest18;
+
+ var $manifest_file_17 = <<<___END
+<?xml version="1.0" encoding="UTF-8"?>
+<plugin_manifest>
+ <field key="author" value="Anyone" />
+ <field key="version" value="1.0" />
+ <field key="description" value="A 1.7-style manifest" />
+ <field key="website" value="http://www.elgg.org/" />
+ <field key="copyright" value="(C) Elgg2008-2009" />
+ <field key="license" value="GNU Public License version 2" />
+ <field key="elgg_version" value="2009030702" />
+</plugin_manifest>
+___END;
+
+ // 1.7 manifest object
+ var $manifest17;
+
+ public function __construct() {
+ parent::__construct();
+
+ $this->manifest18 = new ElggPluginManifest($this->manifest_file_18, 'unit_test');
+ $this->manifest17 = new ElggPluginManifest($this->manifest_file_17);
+ }
+
+ /**
+ * Called after each test method.
+ */
+ public function tearDown() {
+ // do not allow SimpleTest to interpret Elgg notices as exceptions
+ $this->swallowErrors();
+ }
+
+
+ // generic tests
+ public function testElggPluginManifestFromString() {
+ $manifest = new ElggPluginManifest($this->manifest_file_17);
+
+ $this->assertIsA($manifest, 'ElggPluginManifest');
+ }
+
+ public function testElggPluginManifestFromFile() {
+ $file = get_config('dataroot') . '/manifest_test.xml';
+ $fp = fopen($file, 'wb');
+ fputs($fp, $this->manifest_file_17);
+ fclose($fp);
+
+ $manifest = new ElggPluginManifest($file);
+
+ $this->assertIsA($manifest, 'ElggPluginManifest');
+
+ unlink($file);
+ }
+
+ public function testElggPluginManifestFromXML() {
+ $xml = xml_to_object($this->manifest_file_17);
+ $manifest = new ElggPluginManifest($xml);
+
+ $this->assertIsA($manifest, 'ElggPluginManifest');
+ }
+
+
+
+ // 1.8 interface
+
+ public function testElggPluginManifest18() {
+ $manifest_array = array(
+ 'name' => 'Test Manifest',
+ 'author' => 'Anyone',
+ 'version' => '1.0',
+ 'blurb' => 'A concise description.',
+ 'description' => 'A longer, more interesting description.',
+ 'website' => 'http://www.elgg.org/',
+ 'copyright' => '(C) Elgg 2010',
+ 'license' => 'GNU Public License version 2',
+
+ 'depends' => array(
+ array('type' => 'elgg', 'value' => '2009030802'),
+ array('type' => 'php_extension', 'value' => 'gd'),
+ array('type' => 'php_ini', 'name' => 'safe_mode', 'value' => 'off'),
+ ),
+
+ 'screenshots' => array(
+ array('description' => 'Fun things to do 1', 'path' => 'graphics/plugin_ss1.png'),
+ array('description' => 'Fun things to do 2', 'path' => 'graphics/plugin_ss2.png'),
+ ),
+
+ 'conflicts' => array(
+ array('type' => 'plugin', 'value' => 'profile')
+ ),
+
+ 'provides' => array(
+ array('name' => 'profile_api', 'version' => 1.3)
+ ),
+
+ 'admin' => array(
+ 'on_enable' => 'setup_function',
+ 'on_disable' => 'teardown_function',
+ 'interface_type' => 'simple'
+ )
+ );
+
+ $this->assertEqual($this->manifest18->getManifest(), $manifest_array);
+ }
+
+ public function testElggPluginManifestGetApiVersion() {
+ $this->assertEqual($this->manifest18->getApiVersion(), 1.8);
+ }
+
+ public function testElggPluginManifestGetName() {
+ $this->assertEqual($this->manifest18->getName(), 'Test Manifest');
+ }
+
+ public function testElggPluginManifestGetAuthor() {
+ $this->assertEqual($this->manifest18->getAuthor(), 'Anyone');
+ }
+
+ public function testElggPluginManifestGetVersion() {
+ $this->assertEqual($this->manifest18->getVersion(), 1.0);
+ }
+
+ public function testElggPluginManifestGetBlurb() {
+ $this->assertEqual($this->manifest18->getBlurb(), 'A concise description.');
+ }
+
+ public function testElggPluginManifestGetWebsite() {
+ $this->assertEqual($this->manifest18->getWebsite(), 'http://www.elgg.org/');
+ }
+
+ public function testElggPluginManifestGetCopyright() {
+ $this->assertEqual($this->manifest18->getCopyright(), '(C) Elgg 2010');
+ }
+
+ public function testElggPluginManifestGetLicense() {
+ $this->assertEqual($this->manifest18->getLicense(), 'GNU Public License version 2');
+ }
+
+
+ // 1.7 interface
+
+ public function testElggPluginManifest17() {
+ $manifest_array = array(
+ 'author' => 'Anyone',
+ 'version' => '1.0',
+ 'description' => 'A 1.7-style manifest',
+ 'website' => 'http://www.elgg.org/',
+ 'copyright' => '(C) Elgg2008-2009',
+ 'license' => 'GNU Public License version 2',
+ 'elgg_version' => '2009030702'
+ );
+
+ $this->assertEqual($this->manifest17->getManifest(), $manifest_array);
+ }
+
+}