PageRenderTime 53ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Mage/Connect/Package.php

https://bitbucket.org/andrewjleavitt/magestudy
PHP | 1486 lines | 1077 code | 65 blank | 344 comment | 37 complexity | 2df5f5a222c9e45bb9e4d8c0e5e6d170 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) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.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. if (!empty($include) && !preg_match($include, $filePath)) {
  626. continue;
  627. }
  628. if (!empty($exclude) && preg_match($exclude, $filePath)) {
  629. continue;
  630. }
  631. if (is_dir($entry)) {
  632. $baseName = basename($entry);
  633. if (in_array($baseName, array('.', '..', '.svn'))) {
  634. continue;
  635. }
  636. //for subdirectory call method recursively
  637. $this->addContentDir($targetName, $filePath, $exclude, $include);
  638. } elseif (is_file($entry)) {
  639. $this->addContent($filePath, $targetName);
  640. }
  641. }
  642. }
  643. return $this;
  644. }
  645. /**
  646. * Add value to <compatible />.
  647. *
  648. * @param string $packageName
  649. * @param string $channel
  650. * @param string $minVersion
  651. * @param string $maxVersion
  652. * @return Mage_Connect_Package
  653. */
  654. public function addCompatible($packageName, $channel, $minVersion, $maxVersion)
  655. {
  656. $package = $this->_packageXml->compatible->addChild('package');
  657. $package->addChild('name', $packageName);
  658. $package->addChild('channel', $channel);
  659. $package->addChild('min', $minVersion);
  660. $package->addChild('max', $maxVersion);
  661. return $this;
  662. }
  663. /**
  664. * Set dependency from php version.
  665. *
  666. * @param string $minVersion
  667. * @param string $maxVersion
  668. * @return Mage_Connect_Package
  669. */
  670. public function setDependencyPhpVersion($minVersion, $maxVersion)
  671. {
  672. $parent = $this->_packageXml->dependencies;
  673. $parent = $this->_getNode('required', $parent);
  674. $parent = $this->_getNode('php', $parent);
  675. $parent->addChild('min', $minVersion);
  676. $parent->addChild('max', $maxVersion);
  677. return $this;
  678. }
  679. /**
  680. * Check PHP version restriction
  681. * @param $phpVersion PHP_VERSION by default
  682. * @return true | string
  683. */
  684. public function checkPhpVersion()
  685. {
  686. $min = $this->getDependencyPhpVersionMin();
  687. $max = $this->getDependencyPhpVersionMax();
  688. $minOk = $min? version_compare(PHP_VERSION, $min, ">=") : true;
  689. $maxOk = $max? version_compare(PHP_VERSION, $max, "<=") : true;
  690. if(!$minOk || !$maxOk) {
  691. $err = "requires PHP version ";
  692. if($min && $max) {
  693. $err .= " >= $min and <= $max ";
  694. } elseif($min) {
  695. $err .= " >= $min ";
  696. } elseif($max) {
  697. $err .= " <= $max ";
  698. }
  699. $err .= " current is: ".PHP_VERSION;
  700. return $err;
  701. }
  702. return true;
  703. }
  704. /**
  705. * Check PHP extensions availability
  706. * @throws Exceptiom on failure
  707. * @return true | array
  708. */
  709. public function checkPhpDependencies()
  710. {
  711. $errors = array();
  712. foreach($this->getDependencyPhpExtensions() as $dep)
  713. {
  714. if(!extension_loaded($dep['name'])) {
  715. $errors[] = $dep;
  716. }
  717. }
  718. if(count($errors)) {
  719. return $errors;
  720. }
  721. return true;
  722. }
  723. /**
  724. * Set dependency from php extensions.
  725. *
  726. * $extension has next view:
  727. * array('curl', 'mysql')
  728. *
  729. * @param array|string $extensions
  730. * @return Mage_Connect_Package
  731. */
  732. public function setDependencyPhpExtensions($extensions)
  733. {
  734. foreach($extensions as $_extension) {
  735. $this->addDependencyExtension(
  736. $_extension['name'],
  737. $_extension['min_version'],
  738. $_extension['max_version']
  739. );
  740. }
  741. return $this;
  742. }
  743. /**
  744. * Set dependency from another packages.
  745. *
  746. * $packages should contain:
  747. * array(
  748. * array('name'=>'test1', 'channel'=>'test1', 'min_version'=>'0.0.1', 'max_version'=>'0.1.0'),
  749. * array('name'=>'test2', 'channel'=>'test2', 'min_version'=>'0.0.1', 'max_version'=>'0.1.0'),
  750. * )
  751. *
  752. * @param array $packages
  753. * @param bool $clear
  754. * @return Mage_Connect_Package
  755. */
  756. public function setDependencyPackages($packages, $clear = false)
  757. {
  758. if($clear) {
  759. unset($this->_packageXml->dependencies->required->package);
  760. }
  761. foreach($packages as $_package) {
  762. $filesArrayCondition = isset($_package['files']) && is_array($_package['files']);
  763. $filesArray = $filesArrayCondition ? $_package['files'] : array();
  764. $this->addDependencyPackage(
  765. $_package['name'],
  766. $_package['channel'],
  767. $_package['min_version'],
  768. $_package['max_version'],
  769. $filesArray
  770. );
  771. }
  772. return $this;
  773. }
  774. /**
  775. * Add package to dependency packages.
  776. *
  777. * @param string $package
  778. * @param string $channel
  779. * @param string $minVersion
  780. * @param string $maxVersion
  781. * @return Mage_Connect_Package
  782. */
  783. public function addDependencyPackage($name, $channel, $minVersion, $maxVersion, $files = array())
  784. {
  785. $parent = $this->_packageXml->dependencies;
  786. $parent = $this->_getNode('required', $parent);
  787. $parent = $parent->addChild('package');
  788. $parent->addChild('name', $name);
  789. $parent->addChild('channel', $channel);
  790. $parent->addChild('min', $minVersion);
  791. $parent->addChild('max', $maxVersion);
  792. if(count($files)) {
  793. $parent = $parent->addChild('files');
  794. foreach($files as $row) {
  795. if(!empty($row['target']) && !empty($row['path'])) {
  796. $node = $parent->addChild("file");
  797. $node["target"] = $row['target'];
  798. $node["path"] = $row['path'];
  799. }
  800. }
  801. }
  802. return $this;
  803. }
  804. /**
  805. * Add package to dependency extension.
  806. *
  807. * @param string $package
  808. * @param string $minVersion
  809. * @param string $maxVersion
  810. * @return Mage_Connect_Package
  811. */
  812. public function addDependencyExtension($name, $minVersion, $maxVersion)
  813. {
  814. $parent = $this->_packageXml->dependencies;
  815. $parent = $this->_getNode('required', $parent);
  816. $parent = $parent->addChild('extension');
  817. $parent->addChild('name', $name);
  818. $parent->addChild('min', $minVersion);
  819. $parent->addChild('max', $maxVersion);
  820. return $this;
  821. }
  822. /* Accessors */
  823. /**
  824. * Getter
  825. *
  826. * @return string
  827. */
  828. public function getName()
  829. {
  830. return (string)$this->_packageXml->name;
  831. }
  832. /**
  833. * Getter
  834. *
  835. * @return string
  836. */
  837. public function getChannel()
  838. {
  839. return (string)$this->_packageXml->channel;
  840. }
  841. /**
  842. * Getter
  843. *
  844. * @return string
  845. */
  846. public function getSummary()
  847. {
  848. return (string)$this->_packageXml->summary;
  849. }
  850. /**
  851. * Getter
  852. *
  853. * @return string
  854. */
  855. public function getDescription()
  856. {
  857. return (string)$this->_packageXml->description;
  858. }
  859. /**
  860. * Get list of authors in associative array.
  861. *
  862. * @return array
  863. */
  864. public function getAuthors()
  865. {
  866. if (is_array($this->_authors)) return $this->_authors;
  867. $this->_authors = array();
  868. if(!isset($this->_packageXml->authors->author)) {
  869. return array();
  870. }
  871. foreach ($this->_packageXml->authors->author as $_author) {
  872. $this->_authors[] = array(
  873. 'name' => (string)$_author->name,
  874. 'user' => (string)$_author->user,
  875. 'email'=> (string)$_author->email
  876. );
  877. }
  878. return $this->_authors;
  879. }
  880. /**
  881. * Getter
  882. *
  883. * @return string
  884. */
  885. public function getDate()
  886. {
  887. return (string)$this->_packageXml->date;
  888. }
  889. /**
  890. * Getter
  891. *
  892. * @return string
  893. */
  894. public function getTime()
  895. {
  896. return (string)$this->_packageXml->time;
  897. }
  898. /**
  899. * Getter
  900. *
  901. * @return string
  902. */
  903. public function getVersion()
  904. {
  905. return (string)$this->_packageXml->version;
  906. }
  907. /**
  908. * Getter
  909. *
  910. * @return string
  911. */
  912. public function getStability()
  913. {
  914. return (string)$this->_packageXml->stability;
  915. }
  916. /**
  917. * Getter
  918. *
  919. * @return string
  920. */
  921. public function getLicense()
  922. {
  923. return (string)$this->_packageXml->license;
  924. }
  925. /**
  926. * Getter
  927. *
  928. * @return string
  929. */
  930. public function getLicenseUri()
  931. {
  932. return (string)$this->_packageXml->license['uri'];
  933. }
  934. /**
  935. * Getter
  936. *
  937. * @return string
  938. */
  939. public function getNotes()
  940. {
  941. return (string)$this->_packageXml->notes;
  942. }
  943. /**
  944. * Create list of all files from package.xml
  945. *
  946. * @return array
  947. */
  948. public function getContents()
  949. {
  950. if (is_array($this->_contents)) return $this->_contents;
  951. $this->_contents = array();
  952. if(!isset($this->_packageXml->contents->target)) {
  953. return $this->_contents;
  954. }
  955. foreach($this->_packageXml->contents->target as $target) {
  956. $targetUri = $this->getTarget()->getTargetUri($target['name']);
  957. $this->_getList($target, $targetUri);
  958. }
  959. return $this->_contents;
  960. }
  961. /**
  962. * Create list of all files from package.xml compatible with previous version of Magento Connect Manager
  963. *
  964. * @return array
  965. */
  966. public function getContentsV1x()
  967. {
  968. $currentContents = $this->_contents;
  969. $this->_contents = array();
  970. if(!isset($this->_packageXml->contents->target)) {
  971. return $this->_contents;
  972. }
  973. foreach($this->_packageXml->contents->target as $target) {
  974. $this->_getList($target, '');
  975. }
  976. $contents = $this->_contents;
  977. $this->_contents = $currentContents;
  978. return $contents;
  979. }
  980. /**
  981. * Helper for getContents(). Create recursively list.
  982. *
  983. * @param SimpleXMLElement $parent
  984. * @param string $path
  985. */
  986. protected function _getList($parent, $path)
  987. {
  988. if (count($parent) == 0) {
  989. $this->_contents[] = $path;
  990. } else {
  991. foreach($parent as $_content) {
  992. $this->_getList($_content, ($path ? $path . DS : '') . $_content['name']);
  993. }
  994. }
  995. }
  996. /**
  997. * Create list of all files from package.xml with hash
  998. *
  999. * @return array
  1000. */
  1001. public function getHashContents()
  1002. {
  1003. if (is_array($this->_hashContents)) return $this->_hashContents;
  1004. $this->_hashContents = array();
  1005. if(!isset($this->_packageXml->contents->target)) {
  1006. return $this->_hashContents;
  1007. }
  1008. foreach($this->_packageXml->contents->target as $target) {
  1009. $targetUri = $this->getTarget()->getTargetUri($target['name']);
  1010. $this->_getHashList($target, $targetUri);
  1011. }
  1012. return $this->_hashContents;
  1013. }
  1014. /**
  1015. * Helper for getHashContents(). Create recursively list.
  1016. *
  1017. * @param SimpleXMLElement $parent
  1018. * @param string $path
  1019. */
  1020. protected function _getHashList($parent, $path, $hash='')
  1021. {
  1022. if (count($parent) == 0) {
  1023. $this->_hashContents[$path] = $hash;
  1024. } else {
  1025. foreach($parent as $_content) {
  1026. $contentHash = '';
  1027. if (isset($_content['hash'])) {
  1028. $contentHash = (string)$_content['hash'];
  1029. }
  1030. $this->_getHashList($_content, ($path ? $path . DS : '') . $_content['name'], $contentHash);
  1031. }
  1032. }
  1033. }
  1034. /**
  1035. * Get compatible packages.
  1036. *
  1037. * @return array
  1038. */
  1039. public function getCompatible()
  1040. {
  1041. if (is_array($this->_compatible)) return $this->_compatible;
  1042. $this->_compatible = array();
  1043. if(!isset($this->_packageXml->compatible->package)) {
  1044. return array();
  1045. }
  1046. foreach ($this->_packageXml->compatible->package as $_package) {
  1047. $this->_compatible[] = array(
  1048. 'name' => (string)$_package->name,
  1049. 'channel' => (string)$_package->channel,
  1050. 'min' => (string)$_package->min,
  1051. 'max' => (string)$_package->max
  1052. );
  1053. }
  1054. return $this->_compatible;
  1055. }
  1056. /**
  1057. * Getter
  1058. *
  1059. * @return string
  1060. */
  1061. public function getDependencyPhpVersionMin()
  1062. {
  1063. if(!isset($this->_packageXml->dependencies->required->php->min)) {
  1064. return false;
  1065. }
  1066. return (string)$this->_packageXml->dependencies->required->php->min;
  1067. }
  1068. /**
  1069. * Getter
  1070. *
  1071. * @return string
  1072. */
  1073. public function getDependencyPhpVersionMax()
  1074. {
  1075. if(!isset($this->_packageXml->dependencies->required->php->max)) {
  1076. return false;
  1077. }
  1078. return (string)$this->_packageXml->dependencies->required->php->max;
  1079. }
  1080. /**
  1081. * Get list of php extensions.
  1082. *
  1083. * @return array
  1084. */
  1085. public function getDependencyPhpExtensions()
  1086. {
  1087. if (is_array($this->_dependencyPhpExtensions)) return $this->_dependencyPhpExtensions;
  1088. $this->_dependencyPhpExtensions = array();
  1089. foreach($this->_packageXml->dependencies->required->extension as $_package) {
  1090. $this->_dependencyPhpExtensions[] = array(
  1091. 'name' => (string)$_package->name,
  1092. 'min' => (string)$_package->min,
  1093. 'max' => (string)$_package->max,
  1094. );
  1095. }
  1096. return $this->_dependencyPhpExtensions;
  1097. }
  1098. /**
  1099. * Get list of dependency packages.
  1100. *
  1101. * @return array
  1102. */
  1103. public function getDependencyPackages()
  1104. {
  1105. $this->_dependencyPackages = array();
  1106. foreach($this->_packageXml->dependencies->required->package as $_package) {
  1107. $add = array(
  1108. 'name' => (string)$_package->name,
  1109. 'channel' => (string)$_package->channel,
  1110. 'min' => (string)$_package->min,
  1111. 'max' => (string)$_package->max,
  1112. );
  1113. if(isset($_package->files)) {
  1114. $add['files'] = array();
  1115. foreach($_package->files as $node) {
  1116. if(isset($node->file)) {
  1117. $add['files'][] = array('target' => (string) $node->file['target'], 'path'=> (string) $node->file['path']);
  1118. }
  1119. }
  1120. }
  1121. $this->_dependencyPackages[] = $add;
  1122. }
  1123. return $this->_dependencyPackages;
  1124. }
  1125. /**
  1126. * Get string with XML content.
  1127. *
  1128. * @return string
  1129. */
  1130. public function getPackageXml()
  1131. {
  1132. return $this->_packageXml->asXml();
  1133. }
  1134. /**
  1135. * Validator instance (single)
  1136. *
  1137. * @return Mage_Connect_Validator
  1138. */
  1139. protected function validator()
  1140. {
  1141. if(is_null($this->_validator)) {
  1142. $this->_validator = new Mage_Connect_Validator();
  1143. }
  1144. return $this->_validator;
  1145. }
  1146. /**
  1147. * Get validation error strings
  1148. *
  1149. * @return array
  1150. */
  1151. public function getErrors()
  1152. {
  1153. return $this->_validationErrors;
  1154. }
  1155. /**
  1156. * Setter for validation errors
  1157. *
  1158. * @param array $errors
  1159. * @return
  1160. */
  1161. protected function setErrors(array $errors)
  1162. {
  1163. $this->_validationErrors = $errors;
  1164. }
  1165. /**
  1166. * Check validation result.
  1167. * Returns true if package data is invalid.
  1168. *
  1169. * @return bool
  1170. */
  1171. public function hasErrors()
  1172. {
  1173. return count($this->_validationErrors) != 0;
  1174. }
  1175. /**
  1176. * Validate package. Errors can be
  1177. * retreived by calling getErrors();
  1178. *
  1179. * @return bool
  1180. */
  1181. public function validate()
  1182. {
  1183. $v = $this->validator();
  1184. /**
  1185. * Validation map
  1186. *
  1187. * Format:
  1188. *
  1189. * 'key' => array(
  1190. * 'method' => this class method name to call, string, required
  1191. * 'method_args' => optional args for 'method' call, array, optional
  1192. * 'v_method' => validator method to call, string, required
  1193. * 'error' => custom error string when validation fails, optional
  1194. * if not set, error string fprmatted as "Invalid '$key' specified"
  1195. * 'v_error_method' => validator method - when called returned error string
  1196. * prepared by validator, optional,
  1197. * if not set => see 'error'
  1198. * 'optional' => optional value, if it's empty validation result ignored
  1199. *
  1200. */
  1201. $validateMap = array(
  1202. 'name' => array('method' => 'getName',
  1203. 'v_method' => 'validatePackageName',
  1204. 'error'=>"Invalid package name, allowed: [a-zA-Z0-9_-] chars"),
  1205. 'version' => array('method' => 'getVersion',
  1206. 'v_method' => 'validateVersion',
  1207. 'error'=>"Invalid version, should be like: x.x.x"),
  1208. 'stability' => array('method' => 'getStability',
  1209. 'v_method' => 'validateStability',
  1210. 'error'=>"Invalid stability"),
  1211. 'date' => array('method' => 'getDate',
  1212. 'v_method' => 'validateDate',
  1213. 'error'=>"Invalid date, should be YYYY-DD-MM"),
  1214. 'license_uri' => array('method' => 'getLicenseUri',
  1215. 'v_method' => 'validateLicenseUrl',
  1216. 'error'=>"Invalid license URL"),
  1217. 'channel' => array('method' => 'getChannel',
  1218. 'v_method' => 'validateChannelNameOrUri',
  1219. 'error'=>"Invalid channel URL"),
  1220. 'authors' => array('method' => 'getAuthors',
  1221. 'v_method' => 'validateAuthors',
  1222. 'v_error_method' => 'getErrors'),
  1223. 'php_min' => array('method' => 'getDependencyPhpVersionMin',
  1224. 'v_method' => 'validateVersion',
  1225. 'error' => 'PHP minimum version invalid',
  1226. 'optional' => true ),
  1227. 'php_max' => array('method' => 'getDependencyPhpVersionMax',
  1228. 'v_method' => 'validateVersion',
  1229. 'error' => 'PHP maximum version invalid',
  1230. 'optional' => true ),
  1231. 'compatible' => array('method' => 'getCompatible',
  1232. 'v_method' => 'validateCompatible',
  1233. 'v_error_method' => 'getErrors'),
  1234. );
  1235. $errors = array();
  1236. /**
  1237. * Iterate validation map
  1238. */
  1239. foreach($validateMap as $name=>$data) {
  1240. /**
  1241. * Check mandatory rules fields
  1242. */
  1243. if(!isset($data['method'], $data['v_method'])) {
  1244. throw new Mage_Exception("Invalid rules specified!");
  1245. }
  1246. $method = $data['method'];
  1247. $validatorMethod = $data['v_method'];
  1248. /**
  1249. * If $optional === false, value is mandatory
  1250. */
  1251. $optional = isset($data['optional']) ? (bool) $data['optional'] : false;
  1252. /**
  1253. * Check for method availability, package
  1254. */
  1255. if(!method_exists($this, $method)) {
  1256. throw new Mage_Exception("Invalid method specified for Package : $method");
  1257. }
  1258. /**
  1259. * Check for method availability, validator
  1260. */
  1261. if(!method_exists($v, $validatorMethod)) {
  1262. throw new Mage_Exception("Invalid method specified for Validator : $validatorMethod");
  1263. }
  1264. /**
  1265. * If $data['error'] => get error string from $data['error']
  1266. * Else concatenate "Invalid '{$name}' specified"
  1267. */
  1268. $errorString = isset($data['error']) ? $data['error'] : "Invalid '{$name}' specified";
  1269. /**
  1270. * Additional method args check
  1271. * array() by default
  1272. */
  1273. $methodArgs = isset($data['method_args']) ? $data['method_args'] : array();
  1274. /**
  1275. * Call package method
  1276. */
  1277. $out = @call_user_func_array(array($this, $method), $methodArgs);
  1278. /**
  1279. * Skip if result is empty and value is optional
  1280. */
  1281. if(empty($out) && $optional) {
  1282. continue;
  1283. }
  1284. /**
  1285. * Additional validator arguments, merged with array($out)
  1286. */
  1287. $validatorArgs = isset($data['v_args']) ? array_merge(array($out), $data['v_args']) : array($out);
  1288. /**
  1289. * Get validation result
  1290. */
  1291. $result = call_user_func_array(array($v, $validatorMethod), $validatorArgs);
  1292. /**
  1293. * Skip if validation success
  1294. */
  1295. if($result) {
  1296. continue;
  1297. }
  1298. /**
  1299. * From where to get error string?
  1300. * If validator callback method specified, call it to get errors array
  1301. * Else get it from $errorString - local error string
  1302. */
  1303. $validatorFetchErrorsMethod = isset($data['v_error_method']) ? $data['v_error_method'] : false;
  1304. if (false !== $validatorFetchErrorsMethod) {
  1305. $errorString = call_user_func_array(array($v, $validatorFetchErrorsMethod), array());
  1306. }
  1307. /**
  1308. * If errors is array => merge
  1309. * Else append
  1310. */
  1311. if(is_array($errorString)) {
  1312. $errors = array_merge($errors, $errorString);
  1313. } else {
  1314. $errors[] = $errorString;
  1315. }
  1316. }
  1317. /**
  1318. * Set local errors
  1319. */
  1320. $this->setErrors($errors);
  1321. /**
  1322. * Return true if there's no errors :)
  1323. */
  1324. return ! $this->hasErrors();
  1325. }
  1326. /**
  1327. * Return package release filename w/o extension
  1328. * @return string
  1329. */
  1330. public function getReleaseFilename()
  1331. {
  1332. return $this->getName()."-".$this->getVersion();
  1333. }
  1334. /**
  1335. * Return release filepath w/o extension
  1336. * @return string
  1337. */
  1338. public function getRelaseDirFilename()
  1339. {
  1340. return $this->getName() . DS . $this->getVersion() . DS . $this->getReleaseFilename();
  1341. }
  1342. /**
  1343. * Clear dependencies
  1344. *
  1345. * @return Mage_Connect_Package
  1346. */
  1347. public function clearDependencies()
  1348. {
  1349. $this->_packageXml->dependencies = null;
  1350. return $this;
  1351. }
  1352. /**
  1353. * Clear contents
  1354. *
  1355. * @return Mage_Connect_Package
  1356. */
  1357. public function clearContents()
  1358. {
  1359. $this->_packageXml->contents = null;
  1360. return $this;
  1361. }
  1362. }