ElggPluginManifest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  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 = new \ElggXMLElement($raw_xml);
  154. } else {
  155. $manifest_obj = null;
  156. }
  157. }
  158. if (!$manifest_obj) {
  159. throw new \PluginException(_elgg_services()->translator->translate('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_services()->translator->translate('PluginException:NoAvailableParser',
  181. array($this->apiVersion, $this->getPluginID())));
  182. }
  183. if (!$this->parser->parse()) {
  184. throw new \PluginException(_elgg_services()->translator->translate('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_services()->translator->translate('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(
  340. 'aalborg_theme',
  341. 'blog',
  342. 'bookmarks',
  343. 'categories',
  344. 'ckeditor',
  345. 'custom_index',
  346. 'dashboard',
  347. 'developers',
  348. 'diagnostics',
  349. 'embed',
  350. 'externalpages',
  351. 'file',
  352. 'garbagecollector',
  353. 'groups',
  354. 'htmlawed',
  355. 'invitefriends',
  356. 'legacy_urls',
  357. 'likes',
  358. 'logbrowser',
  359. 'login_as',
  360. 'logrotate',
  361. 'members',
  362. 'messageboard',
  363. 'messages',
  364. 'notifications',
  365. 'pages',
  366. 'profile',
  367. 'reportedcontent',
  368. 'search',
  369. 'site_notifications',
  370. 'tagcloud',
  371. 'thewire',
  372. 'twitter_api',
  373. 'uservalidationbyemail',
  374. 'web_services',
  375. 'zaudio',
  376. );
  377. $cats = $this->parser->getAttribute('category');
  378. if (!$cats) {
  379. $cats = array();
  380. }
  381. if (in_array('bundled', $cats) && !in_array($this->getPluginID(), $bundled_plugins)) {
  382. unset($cats[array_search('bundled', $cats)]);
  383. }
  384. return $cats;
  385. }
  386. /**
  387. * Return the screenshots listed.
  388. *
  389. * @return array
  390. */
  391. public function getScreenshots() {
  392. $ss = $this->parser->getAttribute('screenshot');
  393. if (!$ss) {
  394. $ss = array();
  395. }
  396. $normalized = array();
  397. foreach ($ss as $s) {
  398. $normalized[] = $this->buildStruct($this->screenshotStruct, $s);
  399. }
  400. return $normalized;
  401. }
  402. /**
  403. * Return the contributors listed.
  404. *
  405. * @return array
  406. */
  407. public function getContributors() {
  408. $ss = $this->parser->getAttribute('contributor');
  409. if (!$ss) {
  410. $ss = array();
  411. }
  412. $normalized = array();
  413. foreach ($ss as $s) {
  414. $normalized[] = $this->buildStruct($this->contributorStruct, $s);
  415. }
  416. return $normalized;
  417. }
  418. /**
  419. * Return the list of provides by this plugin.
  420. *
  421. * @return array
  422. */
  423. public function getProvides() {
  424. // normalize for 1.7
  425. if ($this->getApiVersion() < 1.8) {
  426. $provides = array();
  427. } else {
  428. $provides = $this->parser->getAttribute('provides');
  429. }
  430. if (!$provides) {
  431. $provides = array();
  432. }
  433. // always provide ourself if we can
  434. if ($this->pluginID) {
  435. $provides[] = array(
  436. 'type' => 'plugin',
  437. 'name' => $this->getPluginID(),
  438. 'version' => $this->getVersion()
  439. );
  440. }
  441. $normalized = array();
  442. foreach ($provides as $provide) {
  443. $normalized[] = $this->buildStruct($this->depsProvidesStruct, $provide);
  444. }
  445. return $normalized;
  446. }
  447. /**
  448. * Returns the dependencies listed.
  449. *
  450. * @return array
  451. */
  452. public function getRequires() {
  453. // rewrite the 1.7 style elgg_version as a real requires.
  454. if ($this->apiVersion < 1.8) {
  455. $elgg_version = $this->parser->getAttribute('elgg_version');
  456. if ($elgg_version) {
  457. $reqs = array(
  458. array(
  459. 'type' => 'elgg_version',
  460. 'version' => $elgg_version,
  461. 'comparison' => 'ge'
  462. )
  463. );
  464. } else {
  465. $reqs = array();
  466. }
  467. } else {
  468. $reqs = $this->parser->getAttribute('requires');
  469. }
  470. if (!$reqs) {
  471. $reqs = array();
  472. }
  473. $normalized = array();
  474. foreach ($reqs as $req) {
  475. $normalized[] = $this->normalizeDep($req);
  476. }
  477. return $normalized;
  478. }
  479. /**
  480. * Returns the suggests elements.
  481. *
  482. * @return array
  483. */
  484. public function getSuggests() {
  485. $suggests = $this->parser->getAttribute('suggests');
  486. if (!$suggests) {
  487. $suggests = array();
  488. }
  489. $normalized = array();
  490. foreach ($suggests as $suggest) {
  491. $normalized[] = $this->normalizeDep($suggest);
  492. }
  493. return $normalized;
  494. }
  495. /**
  496. * Normalizes a dependency array using the defined structs.
  497. * Can be used with either requires or suggests.
  498. *
  499. * @param array $dep A dependency array.
  500. * @return array The normalized deps array.
  501. */
  502. private function normalizeDep($dep) {
  503. switch ($dep['type']) {
  504. case 'elgg_version':
  505. case 'elgg_release':
  506. $struct = $this->depsStructElgg;
  507. break;
  508. case 'plugin':
  509. $struct = $this->depsStructPlugin;
  510. break;
  511. case 'priority':
  512. $struct = $this->depsStructPriority;
  513. break;
  514. case 'php_version':
  515. $struct = $this->depsStructPhpVersion;
  516. break;
  517. case 'php_extension':
  518. $struct = $this->depsStructPhpExtension;
  519. break;
  520. case 'php_ini':
  521. $struct = $this->depsStructPhpIni;
  522. // also normalize boolean values
  523. if (isset($dep['value'])) {
  524. switch (strtolower($dep['value'])) {
  525. case 'yes':
  526. case 'true':
  527. case 'on':
  528. case 1:
  529. $dep['value'] = 1;
  530. break;
  531. case 'no':
  532. case 'false':
  533. case 'off':
  534. case 0:
  535. case '':
  536. $dep['value'] = 0;
  537. break;
  538. }
  539. }
  540. break;
  541. default:
  542. // unrecognized so we just return the raw dependency
  543. return $dep;
  544. }
  545. // @todo $struct may not have been defined...
  546. $normalized_dep = $this->buildStruct($struct, $dep);
  547. // normalize comparison operators
  548. if (isset($normalized_dep['comparison'])) {
  549. switch ($normalized_dep['comparison']) {
  550. case '<':
  551. $normalized_dep['comparison'] = 'lt';
  552. break;
  553. case '<=':
  554. $normalized_dep['comparison'] = 'le';
  555. break;
  556. case '>':
  557. $normalized_dep['comparison'] = 'gt';
  558. break;
  559. case '>=':
  560. $normalized_dep['comparison'] = 'ge';
  561. break;
  562. case '==':
  563. case 'eq':
  564. $normalized_dep['comparison'] = '=';
  565. break;
  566. case '<>':
  567. case 'ne':
  568. $normalized_dep['comparison'] = '!=';
  569. break;
  570. }
  571. }
  572. return $normalized_dep;
  573. }
  574. /**
  575. * Returns the conflicts listed
  576. *
  577. * @return array
  578. */
  579. public function getConflicts() {
  580. // normalize for 1.7
  581. if ($this->getApiVersion() < 1.8) {
  582. $conflicts = array();
  583. } else {
  584. $conflicts = $this->parser->getAttribute('conflicts');
  585. }
  586. if (!$conflicts) {
  587. $conflicts = array();
  588. }
  589. $normalized = array();
  590. foreach ($conflicts as $conflict) {
  591. $normalized[] = $this->buildStruct($this->depsConflictsStruct, $conflict);
  592. }
  593. return $normalized;
  594. }
  595. /**
  596. * Should this plugin be activated when Elgg is installed
  597. *
  598. * @return bool
  599. */
  600. public function getActivateOnInstall() {
  601. $activate = $this->parser->getAttribute('activate_on_install');
  602. switch (strtolower($activate)) {
  603. case 'yes':
  604. case 'true':
  605. case 'on':
  606. case 1:
  607. return true;
  608. case 'no':
  609. case 'false':
  610. case 'off':
  611. case 0:
  612. case '':
  613. return false;
  614. }
  615. }
  616. /**
  617. * Normalizes an array into the structure specified
  618. *
  619. * @param array $struct The struct to normalize $element to.
  620. * @param array $array The array
  621. *
  622. * @return array
  623. */
  624. protected function buildStruct(array $struct, array $array) {
  625. $return = array();
  626. foreach ($struct as $index => $default) {
  627. $return[$index] = elgg_extract($index, $array, $default);
  628. }
  629. return $return;
  630. }
  631. /**
  632. * Returns a category's friendly name. This can be localized by
  633. * defining the string 'admin:plugins:category:<category>'. If no
  634. * localization is found, returns the category with _ and - converted to ' '
  635. * and then ucwords()'d.
  636. *
  637. * @param string $category The category as defined in the manifest.
  638. * @return string A human-readable category
  639. */
  640. static public function getFriendlyCategory($category) {
  641. $cat_raw_string = "admin:plugins:category:$category";
  642. $cat_display_string = _elgg_services()->translator->translate($cat_raw_string);
  643. if ($cat_display_string == $cat_raw_string) {
  644. $category = str_replace(array('-', '_'), ' ', $category);
  645. $cat_display_string = ucwords($category);
  646. }
  647. return $cat_display_string;
  648. }
  649. }