PageRenderTime 64ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/downloader/lib/Mage/Connect/Package.php

https://bitbucket.org/andrewjleavitt/magestudy
PHP | 1499 lines | 1098 code | 66 blank | 335 comment | 64 complexity | 2170dc85d1a1b5fd9ce21213367a9c4f MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0, WTFPL
  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category Mage
  22. * @package Mage_Connect
  23. * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com)
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. */
  26. /**
  27. * Class to work with Magento Connect packages
  28. *
  29. * @category Mage
  30. * @package Mage_Connect
  31. * @author Magento Core Team <core@magentocommerce.com>
  32. */
  33. class Mage_Connect_Package
  34. {
  35. const PACKAGE_XML_DIR = 'var/package';
  36. /**
  37. * Contain SimpleXMLElement for composing document.
  38. *
  39. * @var SimpleXMLElement
  40. */
  41. protected $_packageXml;
  42. /**
  43. * Internal cache
  44. *
  45. * @var array
  46. */
  47. protected $_authors;
  48. /**
  49. * Internal cache
  50. *
  51. * @var array
  52. */
  53. protected $_contents;
  54. /**
  55. * Internal cache
  56. *
  57. * @var array
  58. */
  59. protected $_hashContents;
  60. /**
  61. * Internal cache
  62. *
  63. * @var array
  64. */
  65. protected $_compatible;
  66. /**
  67. * Internal cache
  68. *
  69. * @var array
  70. */
  71. protected $_dependencyPhpExtensions;
  72. /**
  73. * Internal cache
  74. *
  75. * @var array
  76. */
  77. protected $_dependencyPackages;
  78. /**
  79. * A helper object that can read from a package archive
  80. *
  81. * @var Mage_Connect_Package_Reader
  82. */
  83. protected $_reader;
  84. /**
  85. * A helper object that can create and write to a package archive
  86. *
  87. * @var Mage_Connect_Package_Writer
  88. */
  89. protected $_writer;
  90. /**
  91. * Validator object
  92. *
  93. * @var Mage_Connect_Validator
  94. */
  95. protected $_validator = null;
  96. /**
  97. * Validation errors
  98. *
  99. * @var array
  100. */
  101. protected $_validationErrors = array();
  102. /**
  103. * Object with target
  104. *
  105. * @var Mage_Connect_Package_Target
  106. */
  107. protected $_target = null;
  108. /**
  109. * Config object
  110. *
  111. * @var Mage_Connect_Config
  112. */
  113. protected $_config = null;
  114. /**
  115. * Creates a package object (empty, or from existing archive, or from package definition xml)
  116. *
  117. * @param null|string|resource $source
  118. */
  119. public function __construct($source=null)
  120. {
  121. libxml_use_internal_errors(true);
  122. if (is_string($source)) {
  123. // check what's in the string (a package definition or a package filename)
  124. if (0 === strpos($source, "<?xml")) {
  125. // package definition xml
  126. $this->_init($source);
  127. } elseif (is_file($source) && is_readable($source)) {
  128. // package archive filename
  129. $this->_loadFile($source);
  130. } else {
  131. throw new Mage_Exception('Invalid package source');
  132. }
  133. } elseif (is_resource($source)) {
  134. $this->_loadResource($source);
  135. } elseif (is_null($source)) {
  136. $this->_init();
  137. } else {
  138. throw new Mage_Exception('Invalid package source');
  139. }
  140. }
  141. /**
  142. * Initializes an empty package object
  143. *
  144. * @param null|string $definition optional package definition xml
  145. * @return Mage_Connect_Package
  146. */
  147. protected function _init($definition=null)
  148. {
  149. if (!is_null($definition)) {
  150. $this->_packageXml = simplexml_load_string($definition);
  151. } else {
  152. $packageXmlStub = <<<END
  153. <?xml version="1.0"?>
  154. <package>
  155. <name />
  156. <version />
  157. <stability />
  158. <license />
  159. <channel />
  160. <extends />
  161. <summary />
  162. <description />
  163. <notes />
  164. <authors />
  165. <date />
  166. <time />
  167. <contents />
  168. <compatible />
  169. <dependencies />
  170. </package>
  171. END;
  172. $this->_packageXml = simplexml_load_string($packageXmlStub);
  173. }
  174. return $this;
  175. }
  176. /**
  177. * Loads a package from specified file
  178. *
  179. * @param string $filename
  180. * @return Mage_Connect_Package
  181. */
  182. protected function _loadFile($filename='')
  183. {
  184. if (is_null($this->_reader)) {
  185. $this->_reader = new Mage_Connect_Package_Reader($filename);
  186. }
  187. $content = $this->_reader->load();
  188. $this->_packageXml = simplexml_load_string($content);
  189. return $this;
  190. }
  191. /**
  192. * Creates a package and saves it
  193. *
  194. * @param string $path
  195. * @return Mage_Connect_Package
  196. */
  197. public function save($path)
  198. {
  199. $this->validate();
  200. $path = rtrim($path, "\\/") . DS;
  201. $this->_savePackage($path);
  202. return $this;
  203. }
  204. /**
  205. * Creates a package archive and saves it to specified path
  206. *
  207. * @param string $path
  208. * @return Mage_Connect_Package
  209. */
  210. protected function _savePackage($path)
  211. {
  212. $fileName = $this->getReleaseFilename();
  213. if (is_null($this->_writer)) {
  214. $this->_writer = new Mage_Connect_Package_Writer($this->getContents(), $path.$fileName);
  215. }
  216. $this->_writer
  217. ->composePackage()
  218. ->addPackageXml($this->getPackageXml())
  219. ->archivePackage();
  220. return $this;
  221. }
  222. /**
  223. * Retrieve Target object
  224. *
  225. * @return Mage_Connect_Package_Target
  226. */
  227. protected function getTarget()
  228. {
  229. if (!$this->_target instanceof Mage_Connect_Package_Target) {
  230. $this->_target = new Mage_Connect_Package_Target();
  231. }
  232. return $this->_target;
  233. }
  234. public function setTarget($arg)
  235. {
  236. if ($arg instanceof Mage_Connect_Package_Target) {
  237. $this->_target = $arg;
  238. }
  239. }
  240. /* Mutators */
  241. /**
  242. * Puts value to name
  243. *
  244. * @param string $name
  245. * @return Mage_Connect_Package
  246. */
  247. public function setName($name)
  248. {
  249. $this->_packageXml->name = $name;
  250. return $this;
  251. }
  252. /**
  253. * Puts value to <channel />
  254. *
  255. * @param string $channel
  256. * @return Mage_Connect_Package
  257. */
  258. public function setChannel($channel)
  259. {
  260. $this->_packageXml->channel = $channel;
  261. return $this;
  262. }
  263. /**
  264. * Puts value to <summary />
  265. *
  266. * @param string $summary
  267. * @return Mage_Connect_Package
  268. */
  269. public function setSummary($summary)
  270. {
  271. $this->_packageXml->summary = $summary;
  272. return $this;
  273. }
  274. /**
  275. * Puts value to <description />
  276. *
  277. * @param string $description
  278. * @return Mage_Connect_Package
  279. */
  280. public function setDescription($description)
  281. {
  282. $this->_packageXml->description = $description;
  283. return $this;
  284. }
  285. /**
  286. * Puts value to <authors />
  287. *
  288. * array(
  289. * array('name'=>'Name1', 'user'=>'User1', 'email'=>'email1@email.com'),
  290. * array('name'=>'Name2', 'user'=>'User2', 'email'=>'email2@email.com'),
  291. * );
  292. *
  293. * @param array $authors
  294. * @return Mage_Connect_Package
  295. */
  296. public function setAuthors($authors)
  297. {
  298. $this->_authors = $authors;
  299. foreach ($authors as $_author) {
  300. $this->addAuthor($_author['name'], $_author['user'], $_author['email']);
  301. }
  302. return $this;
  303. }
  304. /**
  305. * Add author to <authors/>
  306. *
  307. * @param string $name
  308. * @param string $user
  309. * @param string $email
  310. * @return Mage_Connect_Package
  311. */
  312. public function addAuthor($name=null, $user=null, $email=null)
  313. {
  314. $this->_authors[] = array(
  315. 'name' =>$name,
  316. 'user' =>$user,
  317. 'email'=>$email
  318. );
  319. $author = $this->_packageXml->authors->addChild('author');
  320. $author->addChild('name', $name);
  321. $author->addChild('user', $user);
  322. $author->addChild('email', $email);
  323. return $this;
  324. }
  325. /**
  326. * Puts value to <date/>. Format should be Y-M-D.
  327. *
  328. * @param string $date
  329. * @return Mage_Connect_Package
  330. */
  331. public function setDate($date)
  332. {
  333. $this->_packageXml->date = $date;
  334. return $this;
  335. }
  336. /**
  337. * Puts value to <time />. Format should be H:i:s.
  338. *
  339. * @param string $time
  340. * @return Mage_Connect_Package
  341. */
  342. public function setTime($time)
  343. {
  344. $this->_packageXml->time = $time;
  345. return $this;
  346. }
  347. /**
  348. * Puts value to <version/>. Format should be X.Y.Z.
  349. *
  350. * @param string $version
  351. * @return Mage_Connect_Package
  352. */
  353. public function setVersion($version)
  354. {
  355. $this->_packageXml->version = $version;
  356. return $this;
  357. }
  358. /**
  359. * Puts value to <stability/>. It can be alpha, beta, devel and stable.
  360. *
  361. * @param string $stability
  362. * @return Mage_Connect_Package
  363. */
  364. public function setStability($stability)
  365. {
  366. $this->_packageXml->stability = $stability;
  367. return $this;
  368. }
  369. /**
  370. * Puts value to <license/>, also method can used for set attribute URI.
  371. *
  372. * @param string $license
  373. * @param string $uri
  374. * @return Mage_Connect_Package
  375. */
  376. public function setLicense($license, $uri=null)
  377. {
  378. $this->_packageXml->license = $license;
  379. if ($uri) {
  380. $this->_packageXml->license['uri'] = $uri;
  381. }
  382. return $this;
  383. }
  384. /**
  385. * Puts value to <notes/>.
  386. *
  387. * @param string $notes
  388. * @return Mage_Connect_Package
  389. */
  390. public function setNotes($notes)
  391. {
  392. $this->_packageXml->notes = $notes;
  393. return $this;
  394. }
  395. /**
  396. * Retrieve SimpleXMLElement node by xpath. If it absent, create new.
  397. * For comparing nodes method uses attribute "name" in each nodes.
  398. * If attribute "name" is same for both nodes, nodes are same.
  399. *
  400. * @param string $tag
  401. * @param SimpleXMLElement $parent
  402. * @param string $name
  403. * @return SimpleXMLElement
  404. */
  405. protected function _getNode($tag, $parent, $name='')
  406. {
  407. $found = false;
  408. foreach ($parent->xpath($tag) as $_node) {
  409. if ($_node['name'] == $name) {
  410. $node = $_node;
  411. $found = true;
  412. break;
  413. }
  414. }
  415. if (!$found) {
  416. $node = $parent->addChild($tag);
  417. if ($name) {
  418. $node->addAttribute('name', $name);
  419. }
  420. }
  421. return $node;
  422. }
  423. /**
  424. * Add directory or file to <contents />.
  425. *
  426. * @param string $path Path to directory or file
  427. * @param string $targetName Target name.
  428. * @param string $hash MD5 hash of the file
  429. * @return Mage_Connect_Package
  430. */
  431. public function addContent($path, $targetName)
  432. {
  433. $found = false;
  434. $parent = $this->_getNode('target', $this->_packageXml->contents, $targetName);
  435. $source = str_replace('\\', '/', $path);
  436. $directories = explode('/', dirname($source));
  437. foreach ($directories as $directory) {
  438. $parent = $this->_getNode('dir', $parent, $directory);
  439. }
  440. $fileName = basename($source);
  441. if ($fileName!='') {
  442. $fileNode = $parent->addChild('file');
  443. $fileNode->addAttribute('name', $fileName);
  444. $targetDir = $this->getTarget()->getTargetUri($targetName);
  445. $hash = md5_file($targetDir.DS.$path);
  446. $fileNode->addAttribute('hash', $hash);
  447. }
  448. return $this;
  449. }
  450. /**
  451. * Add directory recursively (with subdirectory and file).
  452. * Exclude and Include can be add using Regular Expression.
  453. *
  454. * @param string $targetName Target name
  455. * @param string $targetDir Path for target name
  456. * @param string $path Path to directory
  457. * @param string $exclude Exclude
  458. * @param string $include Include
  459. * @return Mage_Connect_Package
  460. */
  461. public function addContentDir($targetName, $path, $exclude=null, $include=null)
  462. {
  463. $targetDir = $this->getTarget()->getTargetUri($targetName);
  464. $targetDirLen = strlen($targetDir . DS);
  465. //get all subdirectories and files.
  466. $entries = @glob($targetDir. DS . $path . DS . "{,.}*", GLOB_BRACE);
  467. if (!empty($entries)) {
  468. foreach ($entries as $entry) {
  469. $filePath = substr($entry, $targetDirLen);
  470. if (!empty($include) && !preg_match($include, $filePath)) {
  471. continue;
  472. }
  473. if (!empty($exclude) && preg_match($exclude, $filePath)) {
  474. continue;
  475. }
  476. if (is_dir($entry)) {
  477. $baseName = basename($entry);
  478. if ('.'===$baseName || '..'===$baseName) {
  479. continue;
  480. }
  481. //for subdirectory call method recursively
  482. $this->addContentDir($targetName, $filePath, $exclude, $include);
  483. } elseif (is_file($entry)) {
  484. $this->addContent($filePath, $targetName);
  485. }
  486. }
  487. }
  488. return $this;
  489. }
  490. /**
  491. * Add value to <compatible />.
  492. *
  493. * @param string $packageName
  494. * @param string $channel
  495. * @param string $minVersion
  496. * @param string $maxVersion
  497. * @return Mage_Connect_Package
  498. */
  499. public function addCompatible($packageName, $channel, $minVersion, $maxVersion)
  500. {
  501. $package = $this->_packageXml->compatible->addChild('package');
  502. $package->addChild('name', $packageName);
  503. $package->addChild('channel', $channel);
  504. $package->addChild('min', $minVersion);
  505. $package->addChild('max', $maxVersion);
  506. return $this;
  507. }
  508. /**
  509. * Set dependency from php version.
  510. *
  511. * @param string $minVersion
  512. * @param string $maxVersion
  513. * @return Mage_Connect_Package
  514. */
  515. public function setDependencyPhpVersion($minVersion, $maxVersion)
  516. {
  517. $parent = $this->_packageXml->dependencies;
  518. $parent = $this->_getNode('required', $parent);
  519. $parent = $this->_getNode('php', $parent);
  520. $parent->addChild('min', $minVersion);
  521. $parent->addChild('max', $maxVersion);
  522. return $this;
  523. }
  524. /**
  525. * Check PHP version restriction
  526. * @param $phpVersion PHP_VERSION by default
  527. * @return true | string
  528. */
  529. public function checkPhpVersion()
  530. {
  531. $min = $this->getDependencyPhpVersionMin();
  532. $max = $this->getDependencyPhpVersionMax();
  533. $minOk = $min? version_compare(PHP_VERSION, $min, ">=") : true;
  534. $maxOk = $max? version_compare(PHP_VERSION, $max, "<=") : true;
  535. if(!$minOk || !$maxOk) {
  536. $err = "requires PHP version ";
  537. if($min && $max) {
  538. $err .= " >= $min and <= $max ";
  539. } elseif($min) {
  540. $err .= " >= $min ";
  541. } elseif($max) {
  542. $err .= " <= $max ";
  543. }
  544. $err .= " current is: ".PHP_VERSION;
  545. return $err;
  546. }
  547. return true;
  548. }
  549. /**
  550. * Check PHP extensions availability
  551. * @throws Exceptiom on failure
  552. * @return true | array
  553. */
  554. public function checkPhpDependencies()
  555. {
  556. $errors = array();
  557. foreach($this->getDependencyPhpExtensions() as $dep)
  558. {
  559. if(!extension_loaded($dep['name'])) {
  560. $errors[] = $dep;
  561. }
  562. }
  563. if(count($errors)) {
  564. return $errors;
  565. }
  566. return true;
  567. }
  568. /**
  569. * Set dependency from php extensions.
  570. *
  571. * $extension has next view:
  572. * array('curl', 'mysql')
  573. *
  574. * @param array|string $extensions
  575. * @return Mage_Connect_Package
  576. */
  577. public function setDependencyPhpExtensions($extensions)
  578. {
  579. foreach($extensions as $_extension) {
  580. $this->addDependencyExtension(
  581. $_extension['name'],
  582. $_extension['min_version'],
  583. $_extension['max_version']
  584. );
  585. }
  586. return $this;
  587. }
  588. /**
  589. * Set dependency from another packages.
  590. *
  591. * $packages should contain:
  592. * array(
  593. * array('name'=>'test1', 'channel'=>'test1', 'min_version'=>'0.0.1', 'max_version'=>'0.1.0'),
  594. * array('name'=>'test2', 'channel'=>'test2', 'min_version'=>'0.0.1', 'max_version'=>'0.1.0'),
  595. * )
  596. *
  597. * @param array $packages
  598. * @param bool $clear
  599. * @return Mage_Connect_Package
  600. */
  601. public function setDependencyPackages($packages, $clear = false)
  602. {
  603. if($clear) {
  604. unset($this->_packageXml->dependencies->required->package);
  605. }
  606. foreach($packages as $_package) {
  607. $filesArrayCondition = isset($_package['files']) && is_array($_package['files']);
  608. $filesArray = $filesArrayCondition ? $_package['files'] : array();
  609. $this->addDependencyPackage(
  610. $_package['name'],
  611. $_package['channel'],
  612. $_package['min_version'],
  613. $_package['max_version'],
  614. $filesArray
  615. );
  616. }
  617. return $this;
  618. }
  619. /**
  620. * Add package to dependency packages.
  621. *
  622. * @param string $package
  623. * @param string $channel
  624. * @param string $minVersion
  625. * @param string $maxVersion
  626. * @return Mage_Connect_Package
  627. */
  628. public function addDependencyPackage($name, $channel, $minVersion, $maxVersion, $files = array())
  629. {
  630. $parent = $this->_packageXml->dependencies;
  631. $parent = $this->_getNode('required', $parent);
  632. $parent = $parent->addChild('package');
  633. $parent->addChild('name', $name);
  634. $parent->addChild('channel', $channel);
  635. $parent->addChild('min', $minVersion);
  636. $parent->addChild('max', $maxVersion);
  637. if(count($files)) {
  638. $parent = $parent->addChild('files');
  639. foreach($files as $row) {
  640. if(!empty($row['target']) && !empty($row['path'])) {
  641. $node = $parent->addChild("file");
  642. $node["target"] = $row['target'];
  643. $node["path"] = $row['path'];
  644. }
  645. }
  646. }
  647. return $this;
  648. }
  649. /**
  650. * Add package to dependency extension.
  651. *
  652. * @param string $package
  653. * @param string $minVersion
  654. * @param string $maxVersion
  655. * @return Mage_Connect_Package
  656. */
  657. public function addDependencyExtension($name, $minVersion, $maxVersion)
  658. {
  659. $parent = $this->_packageXml->dependencies;
  660. $parent = $this->_getNode('required', $parent);
  661. $parent = $parent->addChild('extension');
  662. $parent->addChild('name', $name);
  663. $parent->addChild('min', $minVersion);
  664. $parent->addChild('max', $maxVersion);
  665. return $this;
  666. }
  667. /* Accessors */
  668. /**
  669. * Getter
  670. *
  671. * @return string
  672. */
  673. public function getName()
  674. {
  675. return (string)$this->_packageXml->name;
  676. }
  677. /**
  678. * Getter
  679. *
  680. * @return string
  681. */
  682. public function getChannel()
  683. {
  684. return (string)$this->_packageXml->channel;
  685. }
  686. /**
  687. * Getter
  688. *
  689. * @return string
  690. */
  691. public function getSummary()
  692. {
  693. return (string)$this->_packageXml->summary;
  694. }
  695. /**
  696. * Getter
  697. *
  698. * @return string
  699. */
  700. public function getDescription()
  701. {
  702. return (string)$this->_packageXml->description;
  703. }
  704. /**
  705. * Get list of authors in associative array.
  706. *
  707. * @return array
  708. */
  709. public function getAuthors()
  710. {
  711. if (is_array($this->_authors)) return $this->_authors;
  712. $this->_authors = array();
  713. if(!isset($this->_packageXml->authors->author)) {
  714. return array();
  715. }
  716. foreach ($this->_packageXml->authors->author as $_author) {
  717. $this->_authors[] = array(
  718. 'name' => (string)$_author->name,
  719. 'user' => (string)$_author->user,
  720. 'email'=> (string)$_author->email
  721. );
  722. }
  723. return $this->_authors;
  724. }
  725. /**
  726. * Getter
  727. *
  728. * @return string
  729. */
  730. public function getDate()
  731. {
  732. return (string)$this->_packageXml->date;
  733. }
  734. /**
  735. * Getter
  736. *
  737. * @return string
  738. */
  739. public function getTime()
  740. {
  741. return (string)$this->_packageXml->time;
  742. }
  743. /**
  744. * Getter
  745. *
  746. * @return string
  747. */
  748. public function getVersion()
  749. {
  750. return (string)$this->_packageXml->version;
  751. }
  752. /**
  753. * Getter
  754. *
  755. * @return string
  756. */
  757. public function getStability()
  758. {
  759. return (string)$this->_packageXml->stability;
  760. }
  761. /**
  762. * Getter
  763. *
  764. * @return string
  765. */
  766. public function getLicense()
  767. {
  768. return (string)$this->_packageXml->license;
  769. }
  770. /**
  771. * Getter
  772. *
  773. * @return string
  774. */
  775. public function getLicenseUri()
  776. {
  777. return (string)$this->_packageXml->license['uri'];
  778. }
  779. /**
  780. * Getter
  781. *
  782. * @return string
  783. */
  784. public function getNotes()
  785. {
  786. return (string)$this->_packageXml->notes;
  787. }
  788. /**
  789. * Create list of all files from package.xml
  790. *
  791. * @return array
  792. */
  793. public function getContents()
  794. {
  795. if (is_array($this->_contents)) return $this->_contents;
  796. $this->_contents = array();
  797. if(!isset($this->_packageXml->contents->target)) {
  798. return $this->_contents;
  799. }
  800. foreach($this->_packageXml->contents->target as $target) {
  801. $targetUri = $this->getTarget()->getTargetUri($target['name']);
  802. $this->_getList($target, $targetUri);
  803. }
  804. return $this->_contents;
  805. }
  806. /**
  807. * Helper for getContents(). Create recursively list.
  808. *
  809. * @param SimpleXMLElement $parent
  810. * @param string $path
  811. */
  812. protected function _getList($parent, $path)
  813. {
  814. if (count($parent) == 0) {
  815. $this->_contents[] = $path;
  816. } else {
  817. foreach($parent as $_content) {
  818. $this->_getList($_content, ($path ? $path . DS : '') . $_content['name']);
  819. }
  820. }
  821. }
  822. /**
  823. * Create list of all files from package.xml with hash
  824. *
  825. * @return array
  826. */
  827. public function getHashContents()
  828. {
  829. if (is_array($this->_hashContents)) return $this->_hashContents;
  830. $this->_hashContents = array();
  831. if(!isset($this->_packageXml->contents->target)) {
  832. return $this->_hashContents;
  833. }
  834. foreach($this->_packageXml->contents->target as $target) {
  835. $targetUri = $this->getTarget()->getTargetUri($target['name']);
  836. $this->_getHashList($target, $targetUri);
  837. }
  838. return $this->_hashContents;
  839. }
  840. /**
  841. * Helper for getHashContents(). Create recursively list.
  842. *
  843. * @param SimpleXMLElement $parent
  844. * @param string $path
  845. */
  846. protected function _getHashList($parent, $path, $hash='')
  847. {
  848. if (count($parent) == 0) {
  849. $this->_hashContents[$path] = $hash;
  850. } else {
  851. foreach($parent as $_content) {
  852. $contentHash = '';
  853. if (isset($_content['hash'])) {
  854. $contentHash = (string)$_content['hash'];
  855. }
  856. $this->_getHashList($_content, ($path ? $path . DS : '') . $_content['name'], $contentHash);
  857. }
  858. }
  859. }
  860. /**
  861. * Get compatible packages.
  862. *
  863. * @return array
  864. */
  865. public function getCompatible()
  866. {
  867. if (is_array($this->_compatible)) return $this->_compatible;
  868. $this->_compatible = array();
  869. if(!isset($this->_packageXml->compatible->package)) {
  870. return array();
  871. }
  872. foreach ($this->_packageXml->compatible->package as $_package) {
  873. $this->_compatible[] = array(
  874. 'name' => (string)$_package->name,
  875. 'channel' => (string)$_package->channel,
  876. 'min' => (string)$_package->min,
  877. 'max' => (string)$_package->max
  878. );
  879. }
  880. return $this->_compatible;
  881. }
  882. /**
  883. * Getter
  884. *
  885. * @return string
  886. */
  887. public function getDependencyPhpVersionMin()
  888. {
  889. if(!isset($this->_packageXml->dependencies->required->php->min)) {
  890. return false;
  891. }
  892. return (string)$this->_packageXml->dependencies->required->php->min;
  893. }
  894. /**
  895. * Getter
  896. *
  897. * @return string
  898. */
  899. public function getDependencyPhpVersionMax()
  900. {
  901. if(!isset($this->_packageXml->dependencies->required->php->max)) {
  902. return false;
  903. }
  904. return (string)$this->_packageXml->dependencies->required->php->max;
  905. }
  906. /**
  907. * Get list of php extensions.
  908. *
  909. * @return array
  910. */
  911. public function getDependencyPhpExtensions()
  912. {
  913. if (is_array($this->_dependencyPhpExtensions)) return $this->_dependencyPhpExtensions;
  914. $this->_dependencyPhpExtensions = array();
  915. if (!isset($this->_packageXml->dependencies->required->extension)) {
  916. return $this->_dependencyPhpExtensions;
  917. }
  918. foreach($this->_packageXml->dependencies->required->extension as $_package) {
  919. $this->_dependencyPhpExtensions[] = array(
  920. 'name' => (string)$_package->name,
  921. 'min' => (string)$_package->min,
  922. 'max' => (string)$_package->max,
  923. );
  924. }
  925. return $this->_dependencyPhpExtensions;
  926. }
  927. /**
  928. * Get list of dependency packages.
  929. *
  930. * @return array
  931. */
  932. public function getDependencyPackages()
  933. {
  934. $this->_dependencyPackages = array();
  935. if (!isset($this->_packageXml->dependencies->required->package)) {
  936. return $this->_dependencyPackages;
  937. }
  938. foreach($this->_packageXml->dependencies->required->package as $_package) {
  939. $add = array(
  940. 'name' => (string)$_package->name,
  941. 'channel' => (string)$_package->channel,
  942. 'min' => (string)$_package->min,
  943. 'max' => (string)$_package->max,
  944. );
  945. if(isset($_package->files)) {
  946. $add['files'] = array();
  947. foreach($_package->files as $node) {
  948. if(isset($node->file)) {
  949. $add['files'][] = array('target' => (string) $node->file['target'], 'path'=> (string) $node->file['path']);
  950. }
  951. }
  952. }
  953. $this->_dependencyPackages[] = $add;
  954. }
  955. return $this->_dependencyPackages;
  956. }
  957. /**
  958. * Get string with XML content.
  959. *
  960. * @return string
  961. */
  962. public function getPackageXml()
  963. {
  964. return $this->_packageXml->asXml();
  965. }
  966. /**
  967. * Validator instance (single)
  968. *
  969. * @return Mage_Connect_Validator
  970. */
  971. protected function validator()
  972. {
  973. if(is_null($this->_validator)) {
  974. $this->_validator = new Mage_Connect_Validator();
  975. }
  976. return $this->_validator;
  977. }
  978. /**
  979. * Get validation error strings
  980. *
  981. * @return array
  982. */
  983. public function getErrors()
  984. {
  985. return $this->_validationErrors;
  986. }
  987. /**
  988. * Setter for validation errors
  989. *
  990. * @param array $errors
  991. * @return
  992. */
  993. protected function setErrors(array $errors)
  994. {
  995. $this->_validationErrors = $errors;
  996. }
  997. /**
  998. * Check validation result.
  999. * Returns true if package data is invalid.
  1000. *
  1001. * @return bool
  1002. */
  1003. public function hasErrors()
  1004. {
  1005. return count($this->_validationErrors) != 0;
  1006. }
  1007. /**
  1008. * Validate package. Errors can be
  1009. * retreived by calling getErrors();
  1010. *
  1011. * @return bool
  1012. */
  1013. public function validate()
  1014. {
  1015. $v = $this->validator();
  1016. /**
  1017. * Validation map
  1018. *
  1019. * Format:
  1020. *
  1021. * 'key' => array(
  1022. * 'method' => this class method name to call, string, required
  1023. * 'method_args' => optional args for 'method' call, array, optional
  1024. * 'v_method' => validator method to call, string, required
  1025. * 'error' => custom error string when validation fails, optional
  1026. * if not set, error string fprmatted as "Invalid '$key' specified"
  1027. * 'v_error_method' => validator method - when called returned error string
  1028. * prepared by validator, optional,
  1029. * if not set => see 'error'
  1030. * 'optional' => optional value, if it's empty validation result ignored
  1031. *
  1032. */
  1033. $validateMap = array(
  1034. 'name' => array('method' => 'getName',
  1035. 'v_method' => 'validatePackageName',
  1036. 'error'=>"Invalid package name, allowed: [a-zA-Z0-9_-] chars"),
  1037. 'version' => array('method' => 'getVersion',
  1038. 'v_method' => 'validateVersion',
  1039. 'error'=>"Invalid version, should be like: x.x.x"),
  1040. 'stability' => array('method' => 'getStability',
  1041. 'v_method' => 'validateStability',
  1042. 'error'=>"Invalid stability"),
  1043. 'date' => array('method' => 'getDate',
  1044. 'v_method' => 'validateDate',
  1045. 'error'=>"Invalid date, should be YYYY-DD-MM"),
  1046. 'license_uri' => array('method' => 'getLicenseUri',
  1047. 'v_method' => 'validateLicenseUrl',
  1048. 'error'=>"Invalid license URL"),
  1049. 'channel' => array('method' => 'getChannel',
  1050. 'v_method' => 'validateChannelNameOrUri',
  1051. 'error'=>"Invalid channel URL"),
  1052. 'authors' => array('method' => 'getAuthors',
  1053. 'v_method' => 'validateAuthors',
  1054. 'v_error_method' => 'getErrors'),
  1055. 'php_min' => array('method' => 'getDependencyPhpVersionMin',
  1056. 'v_method' => 'validateVersion',
  1057. 'error' => 'PHP minimum version invalid',
  1058. 'optional' => true ),
  1059. 'php_max' => array('method' => 'getDependencyPhpVersionMax',
  1060. 'v_method' => 'validateVersion',
  1061. 'error' => 'PHP maximum version invalid',
  1062. 'optional' => true ),
  1063. 'compatible' => array('method' => 'getCompatible',
  1064. 'v_method' => 'validateCompatible',
  1065. 'v_error_method' => 'getErrors'),
  1066. 'content' => array('method' => 'getContents',
  1067. 'v_method' => 'validateContents',
  1068. 'v_args' => array('config' => $this->getConfig()),
  1069. 'v_error_method' => 'getErrors'),
  1070. );
  1071. $errors = array();
  1072. /**
  1073. * Iterate validation map
  1074. */
  1075. foreach($validateMap as $name=>$data) {
  1076. /**
  1077. * Check mandatory rules fields
  1078. */
  1079. if(!isset($data['method'], $data['v_method'])) {
  1080. throw new Mage_Exception("Invalid rules specified!");
  1081. }
  1082. $method = $data['method'];
  1083. $validatorMethod = $data['v_method'];
  1084. /**
  1085. * If $optional === false, value is mandatory
  1086. */
  1087. $optional = isset($data['optional']) ? (bool) $data['optional'] : false;
  1088. /**
  1089. * Check for method availability, package
  1090. */
  1091. if(!method_exists($this, $method)) {
  1092. throw new Mage_Exception("Invalid method specified for Package : $method");
  1093. }
  1094. /**
  1095. * Check for method availability, validator
  1096. */
  1097. if(!method_exists($v, $validatorMethod)) {
  1098. throw new Mage_Exception("Invalid method specified for Validator : $validatorMethod");
  1099. }
  1100. /**
  1101. * If $data['error'] => get error string from $data['error']
  1102. * Else concatenate "Invalid '{$name}' specified"
  1103. */
  1104. $errorString = isset($data['error']) ? $data['error'] : "Invalid '{$name}' specified";
  1105. /**
  1106. * Additional method args check
  1107. * array() by default
  1108. */
  1109. $methodArgs = isset($data['method_args']) ? $data['method_args'] : array();
  1110. /**
  1111. * Call package method
  1112. */
  1113. $out = @call_user_func_array(array($this, $method), $methodArgs);
  1114. /**
  1115. * Skip if result is empty and value is optional
  1116. */
  1117. if(empty($out) && $optional) {
  1118. continue;
  1119. }
  1120. /**
  1121. * Additional validator arguments, merged with array($out)
  1122. */
  1123. $validatorArgs = isset($data['v_args']) ? array_merge(array($out), $data['v_args']) : array($out);
  1124. /**
  1125. * Get validation result
  1126. */
  1127. $result = call_user_func_array(array($v, $validatorMethod), $validatorArgs);
  1128. /**
  1129. * Skip if validation success
  1130. */
  1131. if($result) {
  1132. continue;
  1133. }
  1134. /**
  1135. * From where to get error string?
  1136. * If validator callback method specified, call it to get errors array
  1137. * Else get it from $errorString - local error string
  1138. */
  1139. $validatorFetchErrorsMethod = isset($data['v_error_method']) ? $data['v_error_method'] : false;
  1140. if (false !== $validatorFetchErrorsMethod) {
  1141. $errorString = call_user_func_array(array($v, $validatorFetchErrorsMethod), array());
  1142. }
  1143. /**
  1144. * If errors is array => merge
  1145. * Else append
  1146. */
  1147. if(is_array($errorString)) {
  1148. $errors = array_merge($errors, $errorString);
  1149. } else {
  1150. $errors[] = $errorString;
  1151. }
  1152. }
  1153. /**
  1154. * Set local errors
  1155. */
  1156. $this->setErrors($errors);
  1157. /**
  1158. * Return true if there's no errors :)
  1159. */
  1160. return ! $this->hasErrors();
  1161. }
  1162. /**
  1163. * Return package release filename w/o extension
  1164. * @return string
  1165. */
  1166. public function getReleaseFilename()
  1167. {
  1168. return $this->getName()."-".$this->getVersion();
  1169. }
  1170. /**
  1171. * Return release filepath w/o extension
  1172. * @return string
  1173. */
  1174. public function getRelaseDirFilename()
  1175. {
  1176. return $this->getName() . DS . $this->getVersion() . DS . $this->getReleaseFilename();
  1177. }
  1178. /**
  1179. * Clear dependencies
  1180. *
  1181. * @return Mage_Connect_Package
  1182. */
  1183. public function clearDependencies()
  1184. {
  1185. $this->_packageXml->dependencies = null;
  1186. return $this;
  1187. }
  1188. /**
  1189. * Clear contents
  1190. *
  1191. * @return Mage_Connect_Package
  1192. */
  1193. public function clearContents()
  1194. {
  1195. $this->_packageXml->contents = null;
  1196. return $this;
  1197. }
  1198. /**
  1199. * Get config
  1200. *
  1201. * @return Mage_Connect_Config
  1202. */
  1203. public function getConfig()
  1204. {
  1205. return $this->_config;
  1206. }
  1207. /**
  1208. * Set config
  1209. *
  1210. * @param Mage_Connect_Config $config
  1211. */
  1212. public function setConfig($config)
  1213. {
  1214. $this->_config = $config;
  1215. }
  1216. /**
  1217. * Import package information from previous version of Magento Connect Manager
  1218. *
  1219. * @param array $data
  1220. *
  1221. * @return Mage_Connect_Package
  1222. */
  1223. public function importDataV1x(array $data)
  1224. {
  1225. $this->_packageXml = null;
  1226. $this->_init();
  1227. // Import simple data
  1228. if (isset($data['name'])) {
  1229. $this->setName($data['name']);
  1230. }
  1231. if (isset($data['summary'])) {
  1232. $this->setSummary($data['summary']);
  1233. }
  1234. if (isset($data['description'])) {
  1235. $this->setDescription($data['description']);
  1236. }
  1237. if (isset($data['channel'])) {
  1238. $this->setChannel($this->convertChannelFromV1x($data['channel']));
  1239. }
  1240. if (isset($data['license'])) {
  1241. if (is_array($data['license'])) {
  1242. $this->setLicense($data['license']['_content'], $data['license']['attribs']['uri']);
  1243. } else {
  1244. $this->setLicense($data['license']);
  1245. }
  1246. }
  1247. if (isset($data['version'])) {
  1248. $this->setVersion($data['version']['release']);
  1249. }
  1250. if (isset($data['stability'])) {
  1251. $this->setStability($data['stability']['release']);
  1252. }
  1253. if (isset($data['notes'])) {
  1254. $this->setNotes($data['notes']);
  1255. }
  1256. if (isset($data['date'])) {
  1257. $this->setDate($data['date']);
  1258. }
  1259. if (isset($data['time'])) {
  1260. $this->setTime($data['time']);
  1261. }
  1262. // Import authors
  1263. $authors = array();
  1264. $authorRoles = array('lead', 'developer', 'contributor', 'helper');
  1265. foreach ($authorRoles as $authorRole) {
  1266. if (isset($data[$authorRole])) {
  1267. $authorList = $data[$authorRole];
  1268. if (!is_array($authorList) || isset($authorList['name'])) {
  1269. $authorList = array($authorList);
  1270. }
  1271. foreach ($authorList as $authorRawData) {
  1272. $author = array();
  1273. $author['name'] = $authorRawData['name'];
  1274. $author['user'] = $authorRawData['user'];
  1275. $author['email'] = $authorRawData['email'];
  1276. array_push($authors, $author);
  1277. }
  1278. }
  1279. }
  1280. $this->setAuthors($authors);
  1281. // Import dependencies
  1282. $packages = array();
  1283. $extensions = array();
  1284. if (isset($data['dependencies']) && is_array($data['dependencies'])) {
  1285. $dependencySections = array('required', 'optional');
  1286. $elementTypes = array('package', 'extension');
  1287. foreach ($dependencySections as $dependencySection) {
  1288. if (isset($data['dependencies'][$dependencySection])) {
  1289. // Handle required PHP version
  1290. if ($dependencySection == 'required' && isset($data['dependencies']['required']['php'])) {
  1291. $this->setDependencyPhpVersion($data['dependencies']['required']['php']['min'], $data['dependencies']['required']['php']['max']);
  1292. }
  1293. // Handle extensions
  1294. if (isset($data['dependencies'][$dependencySection]['extension'])) {
  1295. $extensionList = $data['dependencies'][$dependencySection]['extension'];
  1296. if (!is_array($extensionList) || isset($extensionList['name'])) {
  1297. $extensionList = array($extensionList);
  1298. }
  1299. foreach ($extensionList as $extensionRawData) {
  1300. $extension = array();
  1301. $extension['name'] = $extensionRawData['name'];
  1302. $extension['min_version'] = isset($extensionRawData['min']) ? $extensionRawData['min'] : null;
  1303. $extension['max_version'] = isset($extensionRawData['max']) ? $extensionRawData['max'] : null;
  1304. array_push($extensions, $extension);
  1305. }
  1306. }
  1307. // Handle packages
  1308. if (isset($data['dependencies'][$dependencySection]['package'])) {
  1309. $packageList = $data['dependencies'][$dependencySection]['package'];
  1310. if (!is_array($packageList) || isset($packageList['name'])) {
  1311. $packageList = array($packageList);
  1312. }
  1313. foreach ($packageList as $packageRawData) {
  1314. $package = array();
  1315. $package['name'] = $packageRawData['name'];
  1316. $package['channel'] = $this->convertChannelFromV1x($packageRawData['channel']);
  1317. $package['min_version'] = isset($packageRawData['min']) ? $packageRawData['min'] : null;
  1318. $package['max_version'] = isset($packageRawData['max']) ? $packageRawData['max'] : null;
  1319. array_push($packages, $package);
  1320. }
  1321. }
  1322. }
  1323. }
  1324. }
  1325. $this->setDependencyPackages($packages);
  1326. $this->setDependencyPhpExtensions($extensions);
  1327. // Import contents
  1328. if (isset($data['contents']) && is_array($data['contents']) && is_array($data['contents']['dir'])) {
  1329. // Handle files
  1330. $root = $data['contents']['dir'];
  1331. if (isset($data['contents']['dir']['file'])) {
  1332. $fileList = $data['contents']['dir']['file'];
  1333. if (!is_array($fileList) || isset($fileList['attribs'])) {
  1334. $fileList = array($fileList);
  1335. }
  1336. foreach ($fileList as $fileRawData) {
  1337. $targetName = $fileRawData['attribs']['role'];
  1338. $parentTargetNode = $this->_getNode('target', $this->_packageXml->contents, $targetName);
  1339. $filePath = $fileRawData['attribs']['name'];
  1340. $filePathParts = explode('/', $filePath);
  1341. $fileName = array_pop($filePathParts);
  1342. $parentDirNode = null;
  1343. if (!empty($filePathParts)) {
  1344. $parentDirNode = $parentTargetNode;
  1345. foreach ($filePathParts as $directoryName) {
  1346. $parentDirNode = $this->_getNode('dir', $parentDirNode, $directoryName);
  1347. }
  1348. } else {
  1349. $parentDirNode = $this->_getNode('dir', $parentTargetNode, '.');
  1350. }
  1351. $fileNode = $parentDirNode->addChild('file');
  1352. $fileNode->addAttribute('name', $fileName);
  1353. $fileNode->addAttribute('hash', $fileRawData['attribs']['md5sum']);
  1354. }
  1355. }
  1356. }
  1357. return $this;
  1358. }
  1359. /**
  1360. * Convert package channel in order for it to be compatible with current version of Magento Connect Manager
  1361. *
  1362. * @param string $channel
  1363. *
  1364. * @return string
  1365. */
  1366. public function convertChannelFromV1x($channel)
  1367. {
  1368. $channelMap = array(
  1369. 'connect.magentocommerce.com/community' => 'community',
  1370. 'connect.magentocommerce.com/core' => 'community'
  1371. );
  1372. if (!empty($channel) && isset($channelMap[$channel])) {
  1373. $channel = $channelMap[$channel];
  1374. }
  1375. return $channel;
  1376. }
  1377. }