PageRenderTime 42ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/engine/classes/ElggPluginManifest.php

https://github.com/fragilbert/Elgg
PHP | 717 lines | 386 code | 97 blank | 234 comment | 46 complexity | 9b635fa56a4b1463944e4a6a89c572e0 MD5 | raw file
Possible License(s): MIT, BSD-3-Clause, LGPL-2.1, GPL-2.0
  1. <?php
  2. /**
  3. * Parses Elgg manifest.xml files.
  4. *
  5. * Normalizes the values from the ElggManifestParser object.
  6. *
  7. * This requires an ElggPluginManifestParser class implementation
  8. * as $this->parser.
  9. *
  10. * To add new parser versions, name them ElggPluginManifestParserXX
  11. * where XX is the version specified in the top-level <plugin_manifest>
  12. * tag's XML namespace.
  13. *
  14. * @package Elgg.Core
  15. * @subpackage Plugins
  16. * @since 1.8
  17. */
  18. class ElggPluginManifest {
  19. /**
  20. * The parser object
  21. *
  22. * @var ElggPluginManifestParser18
  23. */
  24. protected $parser;
  25. /**
  26. * The root for plugin manifest namespaces.
  27. * This is in the format http://www.elgg.org/plugin_manifest/<version>
  28. */
  29. protected $namespace_root = 'http://www.elgg.org/plugin_manifest/';
  30. /**
  31. * The expected structure of a plugins requires element
  32. */
  33. private $depsStructPlugin = array(
  34. 'type' => '',
  35. 'name' => '',
  36. 'version' => '',
  37. 'comparison' => 'ge'
  38. );
  39. /**
  40. * The expected structure of a priority element
  41. */
  42. private $depsStructPriority = array(
  43. 'type' => '',
  44. 'priority' => '',
  45. 'plugin' => ''
  46. );
  47. /*
  48. * The expected structure of elgg_version and elgg_release requires element
  49. */
  50. private $depsStructElgg = array(
  51. 'type' => '',
  52. 'version' => '',
  53. 'comparison' => 'ge'
  54. );
  55. /**
  56. * The expected structure of a requires php_version dependency element
  57. */
  58. private $depsStructPhpVersion = array(
  59. 'type' => '',
  60. 'version' => '',
  61. 'comparison' => 'ge'
  62. );
  63. /**
  64. * The expected structure of a requires php_ini dependency element
  65. */
  66. private $depsStructPhpIni = array(
  67. 'type' => '',
  68. 'name' => '',
  69. 'value' => '',
  70. 'comparison' => '='
  71. );
  72. /**
  73. * The expected structure of a requires php_extension dependency element
  74. */
  75. private $depsStructPhpExtension = array(
  76. 'type' => '',
  77. 'name' => '',
  78. 'version' => '',
  79. 'comparison' => '='
  80. );
  81. /**
  82. * The expected structure of a conflicts depedency element
  83. */
  84. private $depsConflictsStruct = array(
  85. 'type' => '',
  86. 'name' => '',
  87. 'version' => '',
  88. 'comparison' => '='
  89. );
  90. /**
  91. * The expected structure of a provides dependency element.
  92. */
  93. private $depsProvidesStruct = array(
  94. 'type' => '',
  95. 'name' => '',
  96. 'version' => ''
  97. );
  98. /**
  99. * The expected structure of a screenshot element
  100. */
  101. private $screenshotStruct = array(
  102. 'description' => '',
  103. 'path' => ''
  104. );
  105. /**
  106. * The expected structure of a contributor element
  107. */
  108. private $contributorStruct = array(
  109. 'name' => '',
  110. 'email' => '',
  111. 'website' => '',
  112. 'username' => '',
  113. 'description' => '',
  114. );
  115. /**
  116. * The API version of the manifest.
  117. *
  118. * @var int
  119. */
  120. protected $apiVersion;
  121. /**
  122. * The optional plugin id this manifest belongs to.
  123. *
  124. * @var string
  125. */
  126. protected $pluginID;
  127. /**
  128. * Load a manifest file, XmlElement or path to manifest.xml file
  129. *
  130. * @param mixed $manifest A string, XmlElement, or path of a manifest file.
  131. * @param string $plugin_id Optional ID of the owning plugin. Used to
  132. * fill in some values automatically.
  133. *
  134. * @throws PluginException
  135. */
  136. public function __construct($manifest, $plugin_id = null) {
  137. if ($plugin_id) {
  138. $this->pluginID = $plugin_id;
  139. }
  140. // see if we need to construct the xml object.
  141. if ($manifest instanceof ElggXMLElement) {
  142. $manifest_obj = $manifest;
  143. } else {
  144. $raw_xml = '';
  145. if (substr(trim($manifest), 0, 1) == '<') {
  146. // this is a string
  147. $raw_xml = $manifest;
  148. } elseif (is_file($manifest)) {
  149. // this is a file
  150. $raw_xml = file_get_contents($manifest);
  151. }
  152. if ($raw_xml) {
  153. $manifest_obj = xml_to_object($raw_xml);
  154. } else {
  155. $manifest_obj = null;
  156. }
  157. }
  158. if (!$manifest_obj) {
  159. throw new PluginException(elgg_echo('PluginException:InvalidManifest',
  160. array($this->getPluginID())));
  161. }
  162. // set manifest api version
  163. if (isset($manifest_obj->attributes['xmlns'])) {
  164. $namespace = $manifest_obj->attributes['xmlns'];
  165. $version = str_replace($this->namespace_root, '', $namespace);
  166. } else {
  167. $version = 1.7;
  168. }
  169. $this->apiVersion = $version;
  170. $parser_class_name = 'ElggPluginManifestParser' . str_replace('.', '', $this->apiVersion);
  171. // @todo currently the autoloader freaks out if a class doesn't exist.
  172. try {
  173. $class_exists = class_exists($parser_class_name);
  174. } catch (Exception $e) {
  175. $class_exists = false;
  176. }
  177. if ($class_exists) {
  178. $this->parser = new $parser_class_name($manifest_obj, $this);
  179. } else {
  180. throw new PluginException(elgg_echo('PluginException:NoAvailableParser',
  181. array($this->apiVersion, $this->getPluginID())));
  182. }
  183. if (!$this->parser->parse()) {
  184. throw new PluginException(elgg_echo('PluginException:ParserError',
  185. array($this->apiVersion, $this->getPluginID())));
  186. }
  187. }
  188. /**
  189. * Returns the API version in use.
  190. *
  191. * @return int
  192. */
  193. public function getApiVersion() {
  194. return $this->apiVersion;
  195. }
  196. /**
  197. * Returns the plugin ID.
  198. *
  199. * @return string
  200. */
  201. public function getPluginID() {
  202. if ($this->pluginID) {
  203. return $this->pluginID;
  204. } else {
  205. return elgg_echo('unknown');
  206. }
  207. }
  208. /**
  209. * Returns the manifest array.
  210. *
  211. * Used for backward compatibility. Specific
  212. * methods should be called instead.
  213. *
  214. * @return array
  215. */
  216. public function getManifest() {
  217. return $this->parser->getManifest();
  218. }
  219. /***************************************
  220. * Parsed and Normalized Manifest Data *
  221. ***************************************/
  222. /**
  223. * Returns the plugin name
  224. *
  225. * @return string
  226. */
  227. public function getName() {
  228. $name = $this->parser->getAttribute('name');
  229. if (!$name && $this->pluginID) {
  230. $name = ucwords(str_replace('_', ' ', $this->pluginID));
  231. }
  232. return $name;
  233. }
  234. /**
  235. * Return the plugin ID required by the author. If getPluginID() does
  236. * not match this, the plugin should not be started.
  237. *
  238. * @return string empty string if not empty/not defined
  239. */
  240. public function getID() {
  241. return trim((string) $this->parser->getAttribute('id'));
  242. }
  243. /**
  244. * Return the description
  245. *
  246. * @return string
  247. */
  248. public function getDescription() {
  249. return $this->parser->getAttribute('description');
  250. }
  251. /**
  252. * Return the short description
  253. *
  254. * @return string
  255. */
  256. public function getBlurb() {
  257. $blurb = $this->parser->getAttribute('blurb');
  258. if (!$blurb) {
  259. $blurb = elgg_get_excerpt($this->getDescription());
  260. }
  261. return $blurb;
  262. }
  263. /**
  264. * Returns the license
  265. *
  266. * @return string
  267. */
  268. public function getLicense() {
  269. // license vs licence. Use license.
  270. $en_us = $this->parser->getAttribute('license');
  271. if ($en_us) {
  272. return $en_us;
  273. } else {
  274. return $this->parser->getAttribute('licence');
  275. }
  276. }
  277. /**
  278. * Returns the repository url
  279. *
  280. * @return string
  281. */
  282. public function getRepositoryURL() {
  283. return $this->parser->getAttribute('repository');
  284. }
  285. /**
  286. * Returns the bug tracker page
  287. *
  288. * @return string
  289. */
  290. public function getBugTrackerURL() {
  291. return $this->parser->getAttribute('bugtracker');
  292. }
  293. /**
  294. * Returns the donations page
  295. *
  296. * @return string
  297. */
  298. public function getDonationsPageURL() {
  299. return $this->parser->getAttribute('donations');
  300. }
  301. /**
  302. * Returns the version of the plugin.
  303. *
  304. * @return float
  305. */
  306. public function getVersion() {
  307. return $this->parser->getAttribute('version');
  308. }
  309. /**
  310. * Returns the plugin author.
  311. *
  312. * @return string
  313. */
  314. public function getAuthor() {
  315. return $this->parser->getAttribute('author');
  316. }
  317. /**
  318. * Return the copyright
  319. *
  320. * @return string
  321. */
  322. public function getCopyright() {
  323. return $this->parser->getAttribute('copyright');
  324. }
  325. /**
  326. * Return the website
  327. *
  328. * @return string
  329. */
  330. public function getWebsite() {
  331. return $this->parser->getAttribute('website');
  332. }
  333. /**
  334. * Return the categories listed for this plugin
  335. *
  336. * @return array
  337. */
  338. public function getCategories() {
  339. $bundled_plugins = array('blog', 'bookmarks', 'categories', 'ckeditor',
  340. 'custom_index', 'dashboard', 'developers', 'diagnostics',
  341. 'embed', 'externalpages', 'file', 'garbagecollector',
  342. 'groups', 'htmlawed', 'invitefriends', 'likes',
  343. 'logbrowser', 'logrotate', 'members', 'messageboard',
  344. 'messages', 'notifications', 'oauth_api', 'pages', 'profile',
  345. 'reportedcontent', 'search', 'tagcloud', 'thewire',
  346. 'twitter', 'twitter_api', 'uservalidationbyemail', 'web_services', 'zaudio',
  347. );
  348. $cats = $this->parser->getAttribute('category');
  349. if (!$cats) {
  350. $cats = array();
  351. }
  352. if (in_array('bundled', $cats) && !in_array($this->getPluginID(), $bundled_plugins)) {
  353. unset($cats[array_search('bundled', $cats)]);
  354. }
  355. return $cats;
  356. }
  357. /**
  358. * Return the screenshots listed.
  359. *
  360. * @return array
  361. */
  362. public function getScreenshots() {
  363. $ss = $this->parser->getAttribute('screenshot');
  364. if (!$ss) {
  365. $ss = array();
  366. }
  367. $normalized = array();
  368. foreach ($ss as $s) {
  369. $normalized[] = $this->buildStruct($this->screenshotStruct, $s);
  370. }
  371. return $normalized;
  372. }
  373. /**
  374. * Return the contributors listed.
  375. *
  376. * @return array
  377. */
  378. public function getContributors() {
  379. $ss = $this->parser->getAttribute('contributor');
  380. if (!$ss) {
  381. $ss = array();
  382. }
  383. $normalized = array();
  384. foreach ($ss as $s) {
  385. $normalized[] = $this->buildStruct($this->contributorStruct, $s);
  386. }
  387. return $normalized;
  388. }
  389. /**
  390. * Return the list of provides by this plugin.
  391. *
  392. * @return array
  393. */
  394. public function getProvides() {
  395. // normalize for 1.7
  396. if ($this->getApiVersion() < 1.8) {
  397. $provides = array();
  398. } else {
  399. $provides = $this->parser->getAttribute('provides');
  400. }
  401. if (!$provides) {
  402. $provides = array();
  403. }
  404. // always provide ourself if we can
  405. if ($this->pluginID) {
  406. $provides[] = array(
  407. 'type' => 'plugin',
  408. 'name' => $this->getPluginID(),
  409. 'version' => $this->getVersion()
  410. );
  411. }
  412. $normalized = array();
  413. foreach ($provides as $provide) {
  414. $normalized[] = $this->buildStruct($this->depsProvidesStruct, $provide);
  415. }
  416. return $normalized;
  417. }
  418. /**
  419. * Returns the dependencies listed.
  420. *
  421. * @return array
  422. */
  423. public function getRequires() {
  424. // rewrite the 1.7 style elgg_version as a real requires.
  425. if ($this->apiVersion < 1.8) {
  426. $elgg_version = $this->parser->getAttribute('elgg_version');
  427. if ($elgg_version) {
  428. $reqs = array(
  429. array(
  430. 'type' => 'elgg_version',
  431. 'version' => $elgg_version,
  432. 'comparison' => 'ge'
  433. )
  434. );
  435. } else {
  436. $reqs = array();
  437. }
  438. } else {
  439. $reqs = $this->parser->getAttribute('requires');
  440. }
  441. if (!$reqs) {
  442. $reqs = array();
  443. }
  444. $normalized = array();
  445. foreach ($reqs as $req) {
  446. $normalized[] = $this->normalizeDep($req);
  447. }
  448. return $normalized;
  449. }
  450. /**
  451. * Returns the suggests elements.
  452. *
  453. * @return array
  454. */
  455. public function getSuggests() {
  456. $suggests = $this->parser->getAttribute('suggests');
  457. if (!$suggests) {
  458. $suggests = array();
  459. }
  460. $normalized = array();
  461. foreach ($suggests as $suggest) {
  462. $normalized[] = $this->normalizeDep($suggest);
  463. }
  464. return $normalized;
  465. }
  466. /**
  467. * Normalizes a dependency array using the defined structs.
  468. * Can be used with either requires or suggests.
  469. *
  470. * @param array $dep A dependency array.
  471. * @return array The normalized deps array.
  472. */
  473. private function normalizeDep($dep) {
  474. switch ($dep['type']) {
  475. case 'elgg_version':
  476. case 'elgg_release':
  477. $struct = $this->depsStructElgg;
  478. break;
  479. case 'plugin':
  480. $struct = $this->depsStructPlugin;
  481. break;
  482. case 'priority':
  483. $struct = $this->depsStructPriority;
  484. break;
  485. case 'php_version':
  486. $struct = $this->depsStructPhpVersion;
  487. break;
  488. case 'php_extension':
  489. $struct = $this->depsStructPhpExtension;
  490. break;
  491. case 'php_ini':
  492. $struct = $this->depsStructPhpIni;
  493. // also normalize boolean values
  494. if (isset($dep['value'])) {
  495. switch (strtolower($dep['value'])) {
  496. case 'yes':
  497. case 'true':
  498. case 'on':
  499. case 1:
  500. $dep['value'] = 1;
  501. break;
  502. case 'no':
  503. case 'false':
  504. case 'off':
  505. case 0:
  506. case '':
  507. $dep['value'] = 0;
  508. break;
  509. }
  510. }
  511. break;
  512. default:
  513. // unrecognized so we just return the raw dependency
  514. return $dep;
  515. }
  516. // @todo $struct may not have been defined...
  517. $normalized_dep = $this->buildStruct($struct, $dep);
  518. // normalize comparison operators
  519. if (isset($normalized_dep['comparison'])) {
  520. switch ($normalized_dep['comparison']) {
  521. case '<':
  522. $normalized_dep['comparison'] = 'lt';
  523. break;
  524. case '<=':
  525. $normalized_dep['comparison'] = 'le';
  526. break;
  527. case '>':
  528. $normalized_dep['comparison'] = 'gt';
  529. break;
  530. case '>=':
  531. $normalized_dep['comparison'] = 'ge';
  532. break;
  533. case '==':
  534. case 'eq':
  535. $normalized_dep['comparison'] = '=';
  536. break;
  537. case '<>':
  538. case 'ne':
  539. $normalized_dep['comparison'] = '!=';
  540. break;
  541. }
  542. }
  543. return $normalized_dep;
  544. }
  545. /**
  546. * Returns the conflicts listed
  547. *
  548. * @return array
  549. */
  550. public function getConflicts() {
  551. // normalize for 1.7
  552. if ($this->getApiVersion() < 1.8) {
  553. $conflicts = array();
  554. } else {
  555. $conflicts = $this->parser->getAttribute('conflicts');
  556. }
  557. if (!$conflicts) {
  558. $conflicts = array();
  559. }
  560. $normalized = array();
  561. foreach ($conflicts as $conflict) {
  562. $normalized[] = $this->buildStruct($this->depsConflictsStruct, $conflict);
  563. }
  564. return $normalized;
  565. }
  566. /**
  567. * Should this plugin be activated when Elgg is installed
  568. *
  569. * @return bool
  570. */
  571. public function getActivateOnInstall() {
  572. $activate = $this->parser->getAttribute('activate_on_install');
  573. switch (strtolower($activate)) {
  574. case 'yes':
  575. case 'true':
  576. case 'on':
  577. case 1:
  578. return true;
  579. case 'no':
  580. case 'false':
  581. case 'off':
  582. case 0:
  583. case '':
  584. return false;
  585. }
  586. }
  587. /**
  588. * Normalizes an array into the structure specified
  589. *
  590. * @param array $struct The struct to normalize $element to.
  591. * @param array $array The array
  592. *
  593. * @return array
  594. */
  595. protected function buildStruct(array $struct, array $array) {
  596. $return = array();
  597. foreach ($struct as $index => $default) {
  598. $return[$index] = elgg_extract($index, $array, $default);
  599. }
  600. return $return;
  601. }
  602. /**
  603. * Returns a category's friendly name. This can be localized by
  604. * defining the string 'admin:plugins:category:<category>'. If no
  605. * localization is found, returns the category with _ and - converted to ' '
  606. * and then ucwords()'d.
  607. *
  608. * @param string $category The category as defined in the manifest.
  609. * @return string A human-readable category
  610. */
  611. static public function getFriendlyCategory($category) {
  612. $cat_raw_string = "admin:plugins:category:$category";
  613. $cat_display_string = elgg_echo($cat_raw_string);
  614. if ($cat_display_string == $cat_raw_string) {
  615. $category = str_replace(array('-', '_'), ' ', $category);
  616. $cat_display_string = ucwords($category);
  617. }
  618. return $cat_display_string;
  619. }
  620. }