PageRenderTime 31ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Mage/Connect/Package.php

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