From 8173f672f06ad2783d3d0112e7b285d2240f488b Mon Sep 17 00:00:00 2001 From: brettp Date: Tue, 30 Nov 2010 03:56:34 +0000 Subject: Refs #1986 #2170 #2225 Added ElggPluginManifest, ElggPluginManifestParser, and its parser classes for 1.7 and 1.8 style manifests. Changed load_plugin_manifest() to use new parser. Added initial unit tests. git-svn-id: http://code.elgg.org/elgg/trunk@7481 36083f99-b078-4883-b0ff-0f9b5a30f544 --- engine/classes/ElggPlugin.php | 1 + engine/classes/ElggPluginManifest.php | 287 ++++++++++++++++++++++++++ engine/classes/ElggPluginManifestParser.php | 91 ++++++++ engine/classes/ElggPluginManifestParser17.php | 54 +++++ engine/classes/ElggPluginManifestParser18.php | 119 +++++++++++ engine/lib/plugins.php | 63 +++--- engine/tests/api/plugins.php | 213 +++++++++++++++++++ 7 files changed, 792 insertions(+), 36 deletions(-) create mode 100644 engine/classes/ElggPluginManifest.php create mode 100644 engine/classes/ElggPluginManifestParser.php create mode 100644 engine/classes/ElggPluginManifestParser17.php create mode 100644 engine/classes/ElggPluginManifestParser18.php create mode 100644 engine/tests/api/plugins.php 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 @@ +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 @@ +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 @@ + 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 @@ + 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 @@ + + + Test Manifest + Anyone + 1.0 + A concise description. + A longer, more interesting description. + http://www.elgg.org/ + (C) Elgg 2010 + GNU Public License version 2 + + elgg + 2009030802 + + + graphics/plugin_ss1.png + graphics/plugin_ss2.png + + + setup_function + teardown_function + simple + + + + php_extension + gd + + + + php_ini + safe_mode + off + + + + plugin + profile + + + + profile_api + 1.3 + + + +___END; + + // 1.8 manifest object + var $manifest18; + + var $manifest_file_17 = <<<___END + + + + + + + + + + +___END; + + // 1.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); + } + +} -- cgit v1.2.3