PearRepository.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Repository;
  12. use Composer\Package\Loader\ArrayLoader;
  13. /**
  14. * @author Benjamin Eberlei <kontakt@beberlei.de>
  15. * @author Jordi Boggiano <j.boggiano@seld.be>
  16. */
  17. class PearRepository extends ArrayRepository
  18. {
  19. protected $url;
  20. public function __construct(array $config)
  21. {
  22. if (!preg_match('{^https?://}', $config['url'])) {
  23. $config['url'] = 'http://'.$config['url'];
  24. }
  25. if (!filter_var($config['url'], FILTER_VALIDATE_URL)) {
  26. throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$config['url']);
  27. }
  28. $this->url = rtrim($config['url'], '/');
  29. }
  30. protected function initialize()
  31. {
  32. parent::initialize();
  33. set_error_handler(function($severity, $message, $file, $line) {
  34. throw new \ErrorException($message, $severity, $severity, $file, $line);
  35. });
  36. $this->fetchFromServer();
  37. restore_error_handler();
  38. }
  39. protected function fetchFromServer()
  40. {
  41. $categoryXML = $this->requestXml($this->url . "/rest/c/categories.xml");
  42. $categories = $categoryXML->getElementsByTagName("c");
  43. foreach ($categories as $category) {
  44. $link = $category->getAttribute("xlink:href");
  45. try {
  46. $packagesLink = str_replace("info.xml", "packagesinfo.xml", $link);
  47. $this->fetchPear2Packages($this->url . '/' . $packagesLink);
  48. } catch (\ErrorException $e) {
  49. if (false === strpos($e->getMessage(), '404')) {
  50. throw $e;
  51. }
  52. $categoryLink = str_replace("info.xml", "packages.xml", $link);
  53. $this->fetchPearPackages($this->url . '/' . $categoryLink);
  54. }
  55. }
  56. }
  57. /**
  58. * @param string $categoryLink
  59. * @throws ErrorException
  60. * @throws InvalidArgumentException
  61. */
  62. private function fetchPearPackages($categoryLink)
  63. {
  64. $packagesXML = $this->requestXml($categoryLink);
  65. $packages = $packagesXML->getElementsByTagName('p');
  66. $loader = new ArrayLoader();
  67. foreach ($packages as $package) {
  68. $packageName = $package->nodeValue;
  69. $packageLink = $package->getAttribute('xlink:href');
  70. $releaseLink = $this->url . str_replace("/rest/p/", "/rest/r/", $packageLink);
  71. $allReleasesLink = $releaseLink . "/allreleases2.xml";
  72. try {
  73. $releasesXML = $this->requestXml($allReleasesLink);
  74. } catch (\ErrorException $e) {
  75. if (strpos($e->getMessage(), '404')) {
  76. continue;
  77. }
  78. throw $e;
  79. }
  80. $releases = $releasesXML->getElementsByTagName('r');
  81. foreach ($releases as $release) {
  82. /* @var $release \DOMElement */
  83. $pearVersion = $release->getElementsByTagName('v')->item(0)->nodeValue;
  84. $packageData = array(
  85. 'name' => $packageName,
  86. 'type' => 'library',
  87. 'dist' => array('type' => 'pear', 'url' => $this->url.'/get/'.$packageName.'-'.$pearVersion.".tgz"),
  88. 'version' => $pearVersion,
  89. );
  90. try {
  91. $deps = file_get_contents($releaseLink . "/deps.".$pearVersion.".txt");
  92. } catch (\ErrorException $e) {
  93. if (strpos($e->getMessage(), '404')) {
  94. continue;
  95. }
  96. throw $e;
  97. }
  98. $packageData += $this->parseDependencies($deps);
  99. try {
  100. $this->addPackage($loader->load($packageData));
  101. } catch (\UnexpectedValueException $e) {
  102. continue;
  103. }
  104. }
  105. }
  106. }
  107. /**
  108. * @param array $data
  109. * @return string
  110. */
  111. private function parseVersion(array $data)
  112. {
  113. if (!isset($data['min']) && !isset($data['max'])) {
  114. return '*';
  115. }
  116. $versions = array();
  117. if (isset($data['min'])) {
  118. $versions[] = '>=' . $data['min'];
  119. }
  120. if (isset($data['max'])) {
  121. $versions[] = '<=' . $data['max'];
  122. }
  123. return implode(',', $versions);
  124. }
  125. /**
  126. * @todo Improve dependencies resolution of pear packages.
  127. * @param array $options
  128. * @return array
  129. */
  130. private function parseDependenciesOptions(array $depsOptions)
  131. {
  132. $data = array();
  133. foreach ($depsOptions as $name => $options) {
  134. if ('php' == $name) {
  135. $data[$name] = $this->parseVersion($options);
  136. } elseif ('package' == $name) {
  137. foreach ($options as $key => $value) {
  138. $dataKey = $value['name'];
  139. $data[$dataKey] = $this->parseVersion($value);
  140. }
  141. } elseif ('extension' == $name) {
  142. foreach ($options as $key => $value) {
  143. $dataKey = 'ext-' . $value['name'];
  144. $data[$dataKey] = $this->parseVersion($value);
  145. }
  146. }
  147. }
  148. return $data;
  149. }
  150. /**
  151. * @param string $deps
  152. * @return array
  153. * @throws InvalidArgumentException
  154. */
  155. private function parseDependencies($deps)
  156. {
  157. if (preg_match('((O:([0-9])+:"([^"]+)"))', $deps, $matches)) {
  158. if (strlen($matches[3]) == $matches[2]) {
  159. throw new \InvalidArgumentException("Invalid dependency data, it contains serialized objects.");
  160. }
  161. }
  162. $deps = (array) @unserialize($deps);
  163. unset($deps['required']['pearinstaller']);
  164. $depsData = array();
  165. if (isset($deps['required'])) {
  166. $depsData['require'] = $this->parseDependenciesOptions($deps['required']);
  167. }
  168. if (isset($depsData['optional'])) {
  169. $depsData['recommend'] = $this->parseDependenciesOptions($depsData['optional']);
  170. }
  171. return $depsData;
  172. }
  173. /**
  174. * @param string $packagesLink
  175. * @return void
  176. * @throws InvalidArgumentException
  177. */
  178. private function fetchPear2Packages($packagesLink)
  179. {
  180. $loader = new ArrayLoader();
  181. $packagesXml = $this->requestXml($packagesLink);
  182. $informations = $packagesXml->getElementsByTagName('pi');
  183. foreach ($informations as $information) {
  184. $package = $information->getElementsByTagName('p')->item(0);
  185. $packageName = $package->getElementsByTagName('n')->item(0)->nodeValue;
  186. $packageData = array(
  187. 'name' => $packageName,
  188. 'type' => 'library'
  189. );
  190. $packageKeys = array('l' => 'license', 'd' => 'description');
  191. foreach ($packageKeys as $pear => $composer) {
  192. if ($package->getElementsByTagName($pear)->length > 0
  193. && ($pear = $package->getElementsByTagName($pear)->item(0)->nodeValue)) {
  194. $packageData[$composer] = $pear;
  195. }
  196. }
  197. $depsData = $information->getElementsByTagName('deps')->item(0);
  198. $depsData = $depsData->getElementsByTagName('d')->item(0);
  199. $depsData = $this->parseDependencies($depsData->nodeValue);
  200. $revisions = $information->getElementsByTagName('a')->item(0);
  201. $revisions = $revisions->getElementsByTagName('r');
  202. $packageUrl = $this->url . '/get/' . $packageName;
  203. foreach ($revisions as $revision) {
  204. $version = $revision->getElementsByTagName('v')->item(0)->nodeValue;
  205. $revisionData = array(
  206. 'dist' => array(
  207. 'type' => 'pear',
  208. 'url' => $packageUrl . '-' . $version . '.tgz'
  209. ),
  210. 'version' => $version
  211. );
  212. try {
  213. $this->addPackage(
  214. $loader->load($packageData + $revisionData + $depsData)
  215. );
  216. } catch (\UnexpectedValueException $e) {
  217. continue;
  218. }
  219. }
  220. }
  221. }
  222. /**
  223. * @param string $url
  224. * @return DOMDocument
  225. */
  226. private function requestXml($url)
  227. {
  228. $content = file_get_contents($url);
  229. if (!$content) {
  230. throw new \UnexpectedValueException('The PEAR channel at '.$url.' did not respond.');
  231. }
  232. $dom = new \DOMDocument('1.0', 'UTF-8');
  233. $dom->loadXML($content);
  234. return $dom;
  235. }
  236. }