PageRenderTime 52ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/script/lib/PEAR/Installer.php

https://bitbucket.org/renaatdemuynck/chamilo
PHP | 2151 lines | 1713 code | 198 blank | 240 comment | 289 complexity | 265b0b0c48bed2dc775fc3f9f294511e MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1, LGPL-3.0, GPL-3.0, MIT, GPL-2.0
  1. <?php
  2. /**
  3. * PEAR_Installer
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * @category pear
  8. * @package PEAR
  9. * @author Stig Bakken <ssb@php.net>
  10. * @author Tomas V.V. Cox <cox@idecnet.com>
  11. * @author Martin Jansen <mj@php.net>
  12. * @author Greg Beaver <cellog@php.net>
  13. * @copyright 1997-2009 The Authors
  14. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  15. * @version CVS: $Id: Installer.php 287446 2009-08-18 11:45:05Z dufuz $
  16. * @link http://pear.php.net/package/PEAR
  17. * @since File available since Release 0.1
  18. */
  19. /**
  20. * Used for installation groups in package.xml 2.0 and platform exceptions
  21. */
  22. require_once 'OS/Guess.php';
  23. require_once 'PEAR/Downloader.php';
  24. define('PEAR_INSTALLER_NOBINARY', - 240);
  25. /**
  26. * Administration class used to install PEAR packages and maintain the
  27. * installed package database.
  28. *
  29. * @category pear
  30. * @package PEAR
  31. * @author Stig Bakken <ssb@php.net>
  32. * @author Tomas V.V. Cox <cox@idecnet.com>
  33. * @author Martin Jansen <mj@php.net>
  34. * @author Greg Beaver <cellog@php.net>
  35. * @copyright 1997-2009 The Authors
  36. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  37. * @version Release: 1.9.1
  38. * @link http://pear.php.net/package/PEAR
  39. * @since Class available since Release 0.1
  40. */
  41. class PEAR_Installer extends PEAR_Downloader
  42. {
  43. // {{{ properties
  44. /** name of the package directory, for example Foo-1.0
  45. * @var string
  46. */
  47. var $pkgdir;
  48. /** directory where PHP code files go
  49. * @var string
  50. */
  51. var $phpdir;
  52. /** directory where PHP extension files go
  53. * @var string
  54. */
  55. var $extdir;
  56. /** directory where documentation goes
  57. * @var string
  58. */
  59. var $docdir;
  60. /** installation root directory (ala PHP's INSTALL_ROOT or
  61. * automake's DESTDIR
  62. * @var string
  63. */
  64. var $installroot = '';
  65. /** debug level
  66. * @var int
  67. */
  68. var $debug = 1;
  69. /** temporary directory
  70. * @var string
  71. */
  72. var $tmpdir;
  73. /**
  74. * PEAR_Registry object used by the installer
  75. * @var PEAR_Registry
  76. */
  77. var $registry;
  78. /**
  79. * array of PEAR_Downloader_Packages
  80. * @var array
  81. */
  82. var $_downloadedPackages;
  83. /** List of file transactions queued for an install/upgrade/uninstall.
  84. *
  85. * Format:
  86. * array(
  87. * 0 => array("rename => array("from-file", "to-file")),
  88. * 1 => array("delete" => array("file-to-delete")),
  89. * ...
  90. * )
  91. *
  92. * @var array
  93. */
  94. var $file_operations = array();
  95. // }}}
  96. // {{{ constructor
  97. /**
  98. * PEAR_Installer constructor.
  99. *
  100. * @param object $ui user interface object (instance of PEAR_Frontend_*)
  101. *
  102. * @access public
  103. */
  104. function PEAR_Installer(&$ui)
  105. {
  106. parent :: PEAR_Common();
  107. $this->setFrontendObject($ui);
  108. $this->debug = $this->config->get('verbose');
  109. }
  110. function setOptions($options)
  111. {
  112. $this->_options = $options;
  113. }
  114. function setConfig(&$config)
  115. {
  116. $this->config = &$config;
  117. $this->_registry = &$config->getRegistry();
  118. }
  119. // }}}
  120. function _removeBackups($files)
  121. {
  122. foreach ($files as $path)
  123. {
  124. $this->addFileOperation('removebackup', array($path));
  125. }
  126. }
  127. // {{{ _deletePackageFiles()
  128. /**
  129. * Delete a package's installed files, does not remove empty directories.
  130. *
  131. * @param string package name
  132. * @param string channel name
  133. * @param bool if true, then files are backed up first
  134. * @return bool TRUE on success, or a PEAR error on failure
  135. * @access protected
  136. */
  137. function _deletePackageFiles($package, $channel = false, $backup = false)
  138. {
  139. if (! $channel)
  140. {
  141. $channel = 'pear.php.net';
  142. }
  143. if (! strlen($package))
  144. {
  145. return $this->raiseError("No package to uninstall given");
  146. }
  147. if (strtolower($package) == 'pear' && $channel == 'pear.php.net')
  148. {
  149. // to avoid race conditions, include all possible needed files
  150. require_once 'PEAR/Task/Common.php';
  151. require_once 'PEAR/Task/Replace.php';
  152. require_once 'PEAR/Task/Unixeol.php';
  153. require_once 'PEAR/Task/Windowseol.php';
  154. require_once 'PEAR/PackageFile/v1.php';
  155. require_once 'PEAR/PackageFile/v2.php';
  156. require_once 'PEAR/PackageFile/Generator/v1.php';
  157. require_once 'PEAR/PackageFile/Generator/v2.php';
  158. }
  159. $filelist = $this->_registry->packageInfo($package, 'filelist', $channel);
  160. if ($filelist == null)
  161. {
  162. return $this->raiseError("$channel/$package not installed");
  163. }
  164. $ret = array();
  165. foreach ($filelist as $file => $props)
  166. {
  167. if (empty($props['installed_as']))
  168. {
  169. continue;
  170. }
  171. $path = $props['installed_as'];
  172. if ($backup)
  173. {
  174. $this->addFileOperation('backup', array($path));
  175. $ret[] = $path;
  176. }
  177. $this->addFileOperation('delete', array($path));
  178. }
  179. if ($backup)
  180. {
  181. return $ret;
  182. }
  183. return true;
  184. }
  185. // }}}
  186. // {{{ _installFile()
  187. /**
  188. * @param string filename
  189. * @param array attributes from <file> tag in package.xml
  190. * @param string path to install the file in
  191. * @param array options from command-line
  192. * @access private
  193. */
  194. function _installFile($file, $atts, $tmp_path, $options)
  195. {
  196. // {{{ return if this file is meant for another platform
  197. static $os;
  198. if (! isset($this->_registry))
  199. {
  200. $this->_registry = &$this->config->getRegistry();
  201. }
  202. if (isset($atts['platform']))
  203. {
  204. if (empty($os))
  205. {
  206. $os = new OS_Guess();
  207. }
  208. if (strlen($atts['platform']) && $atts['platform']{0} == '!')
  209. {
  210. $negate = true;
  211. $platform = substr($atts['platform'], 1);
  212. }
  213. else
  214. {
  215. $negate = false;
  216. $platform = $atts['platform'];
  217. }
  218. if ((bool) $os->matchSignature($platform) === $negate)
  219. {
  220. $this->log(3, "skipped $file (meant for $atts[platform], we are " . $os->getSignature() . ")");
  221. return PEAR_INSTALLER_SKIPPED;
  222. }
  223. }
  224. // }}}
  225. $channel = $this->pkginfo->getChannel();
  226. // {{{ assemble the destination paths
  227. switch ($atts['role'])
  228. {
  229. case 'src' :
  230. case 'extsrc' :
  231. $this->source_files ++;
  232. return;
  233. case 'doc' :
  234. case 'data' :
  235. case 'test' :
  236. $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) . DIRECTORY_SEPARATOR . $this->pkginfo->getPackage();
  237. unset($atts['baseinstalldir']);
  238. break;
  239. case 'ext' :
  240. case 'php' :
  241. $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel);
  242. break;
  243. case 'script' :
  244. $dest_dir = $this->config->get('bin_dir', null, $channel);
  245. break;
  246. default :
  247. return $this->raiseError("Invalid role `$atts[role]' for file $file");
  248. }
  249. $save_destdir = $dest_dir;
  250. if (! empty($atts['baseinstalldir']))
  251. {
  252. $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
  253. }
  254. if (dirname($file) != '.' && empty($atts['install-as']))
  255. {
  256. $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
  257. }
  258. if (empty($atts['install-as']))
  259. {
  260. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
  261. }
  262. else
  263. {
  264. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
  265. }
  266. $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
  267. // Clean up the DIRECTORY_SEPARATOR mess
  268. $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
  269. list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), array(DIRECTORY_SEPARATOR,
  270. DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), array($dest_file, $orig_file));
  271. $final_dest_file = $installed_as = $dest_file;
  272. if (isset($this->_options['packagingroot']))
  273. {
  274. $installedas_dest_dir = dirname($final_dest_file);
  275. $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  276. $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']);
  277. }
  278. else
  279. {
  280. $installedas_dest_dir = dirname($final_dest_file);
  281. $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  282. }
  283. $dest_dir = dirname($final_dest_file);
  284. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  285. if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file)))
  286. {
  287. return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
  288. }
  289. // }}}
  290. if (empty($this->_options['register-only']) && (! file_exists($dest_dir) || ! is_dir($dest_dir)))
  291. {
  292. if (! $this->mkDirHier($dest_dir))
  293. {
  294. return $this->raiseError("failed to mkdir $dest_dir", PEAR_INSTALLER_FAILED);
  295. }
  296. $this->log(3, "+ mkdir $dest_dir");
  297. }
  298. // pretty much nothing happens if we are only registering the install
  299. if (empty($this->_options['register-only']))
  300. {
  301. if (empty($atts['replacements']))
  302. {
  303. if (! file_exists($orig_file))
  304. {
  305. return $this->raiseError("file $orig_file does not exist", PEAR_INSTALLER_FAILED);
  306. }
  307. if (! @copy($orig_file, $dest_file))
  308. {
  309. return $this->raiseError("failed to write $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  310. }
  311. $this->log(3, "+ cp $orig_file $dest_file");
  312. if (isset($atts['md5sum']))
  313. {
  314. $md5sum = md5_file($dest_file);
  315. }
  316. }
  317. else
  318. {
  319. // {{{ file with replacements
  320. if (! file_exists($orig_file))
  321. {
  322. return $this->raiseError("file does not exist", PEAR_INSTALLER_FAILED);
  323. }
  324. $contents = file_get_contents($orig_file);
  325. if ($contents === false)
  326. {
  327. $contents = '';
  328. }
  329. if (isset($atts['md5sum']))
  330. {
  331. $md5sum = md5($contents);
  332. }
  333. $subst_from = $subst_to = array();
  334. foreach ($atts['replacements'] as $a)
  335. {
  336. $to = '';
  337. if ($a['type'] == 'php-const')
  338. {
  339. if (preg_match('/^[a-z0-9_]+\\z/i', $a['to']))
  340. {
  341. eval("\$to = $a[to];");
  342. }
  343. else
  344. {
  345. if (! isset($options['soft']))
  346. {
  347. $this->log(0, "invalid php-const replacement: $a[to]");
  348. }
  349. continue;
  350. }
  351. }
  352. elseif ($a['type'] == 'pear-config')
  353. {
  354. if ($a['to'] == 'master_server')
  355. {
  356. $chan = $this->_registry->getChannel($channel);
  357. if (! PEAR :: isError($chan))
  358. {
  359. $to = $chan->getServer();
  360. }
  361. else
  362. {
  363. $to = $this->config->get($a['to'], null, $channel);
  364. }
  365. }
  366. else
  367. {
  368. $to = $this->config->get($a['to'], null, $channel);
  369. }
  370. if (is_null($to))
  371. {
  372. if (! isset($options['soft']))
  373. {
  374. $this->log(0, "invalid pear-config replacement: $a[to]");
  375. }
  376. continue;
  377. }
  378. }
  379. elseif ($a['type'] == 'package-info')
  380. {
  381. if ($t = $this->pkginfo->packageInfo($a['to']))
  382. {
  383. $to = $t;
  384. }
  385. else
  386. {
  387. if (! isset($options['soft']))
  388. {
  389. $this->log(0, "invalid package-info replacement: $a[to]");
  390. }
  391. continue;
  392. }
  393. }
  394. if (! is_null($to))
  395. {
  396. $subst_from[] = $a['from'];
  397. $subst_to[] = $to;
  398. }
  399. }
  400. $this->log(3, "doing " . sizeof($subst_from) . " substitution(s) for $final_dest_file");
  401. if (sizeof($subst_from))
  402. {
  403. $contents = str_replace($subst_from, $subst_to, $contents);
  404. }
  405. $wp = @fopen($dest_file, "wb");
  406. if (! is_resource($wp))
  407. {
  408. return $this->raiseError("failed to create $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  409. }
  410. if (@fwrite($wp, $contents) === false)
  411. {
  412. return $this->raiseError("failed writing to $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  413. }
  414. fclose($wp);
  415. // }}}
  416. }
  417. // {{{ check the md5
  418. if (isset($md5sum))
  419. {
  420. if (strtolower($md5sum) === strtolower($atts['md5sum']))
  421. {
  422. $this->log(2, "md5sum ok: $final_dest_file");
  423. }
  424. else
  425. {
  426. if (empty($options['force']))
  427. {
  428. // delete the file
  429. if (file_exists($dest_file))
  430. {
  431. unlink($dest_file);
  432. }
  433. if (! isset($options['ignore-errors']))
  434. {
  435. return $this->raiseError("bad md5sum for file $final_dest_file", PEAR_INSTALLER_FAILED);
  436. }
  437. if (! isset($options['soft']))
  438. {
  439. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  440. }
  441. }
  442. else
  443. {
  444. if (! isset($options['soft']))
  445. {
  446. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  447. }
  448. }
  449. }
  450. }
  451. // }}}
  452. // {{{ set file permissions
  453. if (! OS_WINDOWS)
  454. {
  455. if ($atts['role'] == 'script')
  456. {
  457. $mode = 0777 & ~ (int) octdec($this->config->get('umask'));
  458. $this->log(3, "+ chmod +x $dest_file");
  459. }
  460. else
  461. {
  462. $mode = 0666 & ~ (int) octdec($this->config->get('umask'));
  463. }
  464. if ($atts['role'] != 'src')
  465. {
  466. $this->addFileOperation("chmod", array($mode, $dest_file));
  467. if (! @chmod($dest_file, $mode))
  468. {
  469. if (! isset($options['soft']))
  470. {
  471. $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
  472. }
  473. }
  474. }
  475. }
  476. // }}}
  477. if ($atts['role'] == 'src')
  478. {
  479. rename($dest_file, $final_dest_file);
  480. $this->log(2, "renamed source file $dest_file to $final_dest_file");
  481. }
  482. else
  483. {
  484. $this->addFileOperation("rename", array($dest_file, $final_dest_file, $atts['role'] == 'ext'));
  485. }
  486. }
  487. // Store the full path where the file was installed for easy unistall
  488. if ($atts['role'] != 'script')
  489. {
  490. $loc = $this->config->get($atts['role'] . '_dir');
  491. }
  492. else
  493. {
  494. $loc = $this->config->get('bin_dir');
  495. }
  496. if ($atts['role'] != 'src')
  497. {
  498. $this->addFileOperation("installed_as", array($file, $installed_as, $loc,
  499. dirname(substr($installedas_dest_file, strlen($loc)))));
  500. }
  501. //$this->log(2, "installed: $dest_file");
  502. return PEAR_INSTALLER_OK;
  503. }
  504. // }}}
  505. // {{{ _installFile2()
  506. /**
  507. * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
  508. * @param string filename
  509. * @param array attributes from <file> tag in package.xml
  510. * @param string path to install the file in
  511. * @param array options from command-line
  512. * @access private
  513. */
  514. function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options)
  515. {
  516. $atts = $real_atts;
  517. if (! isset($this->_registry))
  518. {
  519. $this->_registry = &$this->config->getRegistry();
  520. }
  521. $channel = $pkg->getChannel();
  522. // {{{ assemble the destination paths
  523. if (! in_array($atts['attribs']['role'], PEAR_Installer_Role :: getValidRoles($pkg->getPackageType())))
  524. {
  525. return $this->raiseError('Invalid role `' . $atts['attribs']['role'] . "' for file $file");
  526. }
  527. $role = &PEAR_Installer_Role :: factory($pkg, $atts['attribs']['role'], $this->config);
  528. $err = $role->setup($this, $pkg, $atts['attribs'], $file);
  529. if (PEAR :: isError($err))
  530. {
  531. return $err;
  532. }
  533. if (! $role->isInstallable())
  534. {
  535. return;
  536. }
  537. $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path);
  538. if (PEAR :: isError($info))
  539. {
  540. return $info;
  541. }
  542. list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info;
  543. if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file)))
  544. {
  545. return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
  546. }
  547. $final_dest_file = $installed_as = $dest_file;
  548. if (isset($this->_options['packagingroot']))
  549. {
  550. $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']);
  551. }
  552. $dest_dir = dirname($final_dest_file);
  553. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  554. // }}}
  555. if (empty($this->_options['register-only']))
  556. {
  557. if (! file_exists($dest_dir) || ! is_dir($dest_dir))
  558. {
  559. if (! $this->mkDirHier($dest_dir))
  560. {
  561. return $this->raiseError("failed to mkdir $dest_dir", PEAR_INSTALLER_FAILED);
  562. }
  563. $this->log(3, "+ mkdir $dest_dir");
  564. }
  565. }
  566. $attribs = $atts['attribs'];
  567. unset($atts['attribs']);
  568. // pretty much nothing happens if we are only registering the install
  569. if (empty($this->_options['register-only']))
  570. {
  571. if (! count($atts))
  572. { // no tasks
  573. if (! file_exists($orig_file))
  574. {
  575. return $this->raiseError("file $orig_file does not exist", PEAR_INSTALLER_FAILED);
  576. }
  577. if (! @copy($orig_file, $dest_file))
  578. {
  579. return $this->raiseError("failed to write $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  580. }
  581. $this->log(3, "+ cp $orig_file $dest_file");
  582. if (isset($attribs['md5sum']))
  583. {
  584. $md5sum = md5_file($dest_file);
  585. }
  586. }
  587. else
  588. { // file with tasks
  589. if (! file_exists($orig_file))
  590. {
  591. return $this->raiseError("file $orig_file does not exist", PEAR_INSTALLER_FAILED);
  592. }
  593. $contents = file_get_contents($orig_file);
  594. if ($contents === false)
  595. {
  596. $contents = '';
  597. }
  598. if (isset($attribs['md5sum']))
  599. {
  600. $md5sum = md5($contents);
  601. }
  602. foreach ($atts as $tag => $raw)
  603. {
  604. $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag);
  605. $task = "PEAR_Task_$tag";
  606. $task = &new $task($this->config, $this, PEAR_TASK_INSTALL);
  607. if (! $task->isScript())
  608. { // scripts are only handled after installation
  609. $task->init($raw, $attribs, $pkg->getLastInstalledVersion());
  610. $res = $task->startSession($pkg, $contents, $final_dest_file);
  611. if ($res === false)
  612. {
  613. continue; // skip this file
  614. }
  615. if (PEAR :: isError($res))
  616. {
  617. return $res;
  618. }
  619. $contents = $res; // save changes
  620. }
  621. $wp = @fopen($dest_file, "wb");
  622. if (! is_resource($wp))
  623. {
  624. return $this->raiseError("failed to create $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  625. }
  626. if (fwrite($wp, $contents) === false)
  627. {
  628. return $this->raiseError("failed writing to $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  629. }
  630. fclose($wp);
  631. }
  632. }
  633. // {{{ check the md5
  634. if (isset($md5sum))
  635. {
  636. // Make sure the original md5 sum matches with expected
  637. if (strtolower($md5sum) === strtolower($attribs['md5sum']))
  638. {
  639. $this->log(2, "md5sum ok: $final_dest_file");
  640. if (isset($contents))
  641. {
  642. // set md5 sum based on $content in case any tasks were run.
  643. $real_atts['attribs']['md5sum'] = md5($contents);
  644. }
  645. }
  646. else
  647. {
  648. if (empty($options['force']))
  649. {
  650. // delete the file
  651. if (file_exists($dest_file))
  652. {
  653. unlink($dest_file);
  654. }
  655. if (! isset($options['ignore-errors']))
  656. {
  657. return $this->raiseError("bad md5sum for file $final_dest_file", PEAR_INSTALLER_FAILED);
  658. }
  659. if (! isset($options['soft']))
  660. {
  661. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  662. }
  663. }
  664. else
  665. {
  666. if (! isset($options['soft']))
  667. {
  668. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  669. }
  670. }
  671. }
  672. }
  673. else
  674. {
  675. $real_atts['attribs']['md5sum'] = md5_file($dest_file);
  676. }
  677. // }}}
  678. // {{{ set file permissions
  679. if (! OS_WINDOWS)
  680. {
  681. if ($role->isExecutable())
  682. {
  683. $mode = 0777 & ~ (int) octdec($this->config->get('umask'));
  684. $this->log(3, "+ chmod +x $dest_file");
  685. }
  686. else
  687. {
  688. $mode = 0666 & ~ (int) octdec($this->config->get('umask'));
  689. }
  690. if ($attribs['role'] != 'src')
  691. {
  692. $this->addFileOperation("chmod", array($mode, $dest_file));
  693. if (! @chmod($dest_file, $mode))
  694. {
  695. if (! isset($options['soft']))
  696. {
  697. $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
  698. }
  699. }
  700. }
  701. }
  702. // }}}
  703. if ($attribs['role'] == 'src')
  704. {
  705. rename($dest_file, $final_dest_file);
  706. $this->log(2, "renamed source file $dest_file to $final_dest_file");
  707. }
  708. else
  709. {
  710. $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension()));
  711. }
  712. }
  713. // Store the full path where the file was installed for easy uninstall
  714. if ($attribs['role'] != 'src')
  715. {
  716. $loc = $this->config->get($role->getLocationConfig(), null, $channel);
  717. $this->addFileOperation('installed_as', array($file, $installed_as, $loc,
  718. dirname(substr($installed_as, strlen($loc)))));
  719. }
  720. //$this->log(2, "installed: $dest_file");
  721. return PEAR_INSTALLER_OK;
  722. }
  723. // }}}
  724. // {{{ addFileOperation()
  725. /**
  726. * Add a file operation to the current file transaction.
  727. *
  728. * @see startFileTransaction()
  729. * @param string $type This can be one of:
  730. * - rename: rename a file ($data has 3 values)
  731. * - backup: backup an existing file ($data has 1 value)
  732. * - removebackup: clean up backups created during install ($data has 1 value)
  733. * - chmod: change permissions on a file ($data has 2 values)
  734. * - delete: delete a file ($data has 1 value)
  735. * - rmdir: delete a directory if empty ($data has 1 value)
  736. * - installed_as: mark a file as installed ($data has 4 values).
  737. * @param array $data For all file operations, this array must contain the
  738. * full path to the file or directory that is being operated on. For
  739. * the rename command, the first parameter must be the file to rename,
  740. * the second its new name, the third whether this is a PHP extension.
  741. *
  742. * The installed_as operation contains 4 elements in this order:
  743. * 1. Filename as listed in the filelist element from package.xml
  744. * 2. Full path to the installed file
  745. * 3. Full path from the php_dir configuration variable used in this
  746. * installation
  747. * 4. Relative path from the php_dir that this file is installed in
  748. */
  749. function addFileOperation($type, $data)
  750. {
  751. if (! is_array($data))
  752. {
  753. return $this->raiseError('Internal Error: $data in addFileOperation' . ' must be an array, was ' . gettype($data));
  754. }
  755. if ($type == 'chmod')
  756. {
  757. $octmode = decoct($data[0]);
  758. $this->log(3, "adding to transaction: $type $octmode $data[1]");
  759. }
  760. else
  761. {
  762. $this->log(3, "adding to transaction: $type " . implode(" ", $data));
  763. }
  764. $this->file_operations[] = array($type, $data);
  765. }
  766. // }}}
  767. // {{{ startFileTransaction()
  768. function startFileTransaction($rollback_in_case = false)
  769. {
  770. if (count($this->file_operations) && $rollback_in_case)
  771. {
  772. $this->rollbackFileTransaction();
  773. }
  774. $this->file_operations = array();
  775. }
  776. // }}}
  777. // {{{ commitFileTransaction()
  778. function commitFileTransaction()
  779. {
  780. $n = count($this->file_operations);
  781. $this->log(2, "about to commit $n file operations");
  782. // {{{ first, check permissions and such manually
  783. $errors = array();
  784. foreach ($this->file_operations as $tr)
  785. {
  786. list($type, $data) = $tr;
  787. switch ($type)
  788. {
  789. case 'rename' :
  790. if (! file_exists($data[0]))
  791. {
  792. $errors[] = "cannot rename file $data[0], doesn't exist";
  793. }
  794. // check that dest dir. is writable
  795. if (! is_writable(dirname($data[1])))
  796. {
  797. $errors[] = "permission denied ($type): $data[1]";
  798. }
  799. break;
  800. case 'chmod' :
  801. // check that file is writable
  802. if (! is_writable($data[1]))
  803. {
  804. $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
  805. }
  806. break;
  807. case 'delete' :
  808. if (! file_exists($data[0]))
  809. {
  810. $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
  811. }
  812. // check that directory is writable
  813. if (file_exists($data[0]))
  814. {
  815. if (! is_writable(dirname($data[0])))
  816. {
  817. $errors[] = "permission denied ($type): $data[0]";
  818. }
  819. else
  820. {
  821. // make sure the file to be deleted can be opened for writing
  822. $fp = false;
  823. if (! is_dir($data[0]) && (! is_writable($data[0]) || ! ($fp = @fopen($data[0], 'a'))))
  824. {
  825. $errors[] = "permission denied ($type): $data[0]";
  826. }
  827. elseif ($fp)
  828. {
  829. fclose($fp);
  830. }
  831. }
  832. }
  833. break;
  834. }
  835. }
  836. // }}}
  837. $m = count($errors);
  838. if ($m > 0)
  839. {
  840. foreach ($errors as $error)
  841. {
  842. if (! isset($this->_options['soft']))
  843. {
  844. $this->log(1, $error);
  845. }
  846. }
  847. if (! isset($this->_options['ignore-errors']))
  848. {
  849. return false;
  850. }
  851. }
  852. $this->_dirtree = array();
  853. // {{{ really commit the transaction
  854. foreach ($this->file_operations as $i => $tr)
  855. {
  856. if (! $tr)
  857. {
  858. // support removal of non-existing backups
  859. continue;
  860. }
  861. list($type, $data) = $tr;
  862. switch ($type)
  863. {
  864. case 'backup' :
  865. if (! file_exists($data[0]))
  866. {
  867. $this->file_operations[$i] = false;
  868. break;
  869. }
  870. if (! @copy($data[0], $data[0] . '.bak'))
  871. {
  872. $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] . '.bak ' . $php_errormsg);
  873. return false;
  874. }
  875. $this->log(3, "+ backup $data[0] to $data[0].bak");
  876. break;
  877. case 'removebackup' :
  878. if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak'))
  879. {
  880. unlink($data[0] . '.bak');
  881. $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
  882. }
  883. break;
  884. case 'rename' :
  885. $test = file_exists($data[1]) ? @unlink($data[1]) : null;
  886. if (! $test && file_exists($data[1]))
  887. {
  888. if ($data[2])
  889. {
  890. $extra = ', this extension must be installed manually. Rename to "' . basename($data[1]) . '"';
  891. }
  892. else
  893. {
  894. $extra = '';
  895. }
  896. if (! isset($this->_options['soft']))
  897. {
  898. $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' . $data[0] . $extra);
  899. }
  900. if (! isset($this->_options['ignore-errors']))
  901. {
  902. return false;
  903. }
  904. }
  905. // permissions issues with rename - copy() is far superior
  906. $perms = @fileperms($data[0]);
  907. if (! @copy($data[0], $data[1]))
  908. {
  909. $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] . ' ' . $php_errormsg);
  910. return false;
  911. }
  912. // copy over permissions, otherwise they are lost
  913. @chmod($data[1], $perms);
  914. @unlink($data[0]);
  915. $this->log(3, "+ mv $data[0] $data[1]");
  916. break;
  917. case 'chmod' :
  918. if (! @chmod($data[1], $data[0]))
  919. {
  920. $this->log(1, 'Could not chmod ' . $data[1] . ' to ' . decoct($data[0]) . ' ' . $php_errormsg);
  921. return false;
  922. }
  923. $octmode = decoct($data[0]);
  924. $this->log(3, "+ chmod $octmode $data[1]");
  925. break;
  926. case 'delete' :
  927. if (file_exists($data[0]))
  928. {
  929. if (! @unlink($data[0]))
  930. {
  931. $this->log(1, 'Could not delete ' . $data[0] . ' ' . $php_errormsg);
  932. return false;
  933. }
  934. $this->log(3, "+ rm $data[0]");
  935. }
  936. break;
  937. case 'rmdir' :
  938. if (file_exists($data[0]))
  939. {
  940. do
  941. {
  942. $testme = opendir($data[0]);
  943. while (false !== ($entry = readdir($testme)))
  944. {
  945. if ($entry == '.' || $entry == '..')
  946. {
  947. continue;
  948. }
  949. closedir($testme);
  950. break 2; // this directory is not empty and can't be
  951. // deleted
  952. }
  953. closedir($testme);
  954. if (! @rmdir($data[0]))
  955. {
  956. $this->log(1, 'Could not rmdir ' . $data[0] . ' ' . $php_errormsg);
  957. return false;
  958. }
  959. $this->log(3, "+ rmdir $data[0]");
  960. }
  961. while (false);
  962. }
  963. break;
  964. case 'installed_as' :
  965. $this->pkginfo->setInstalledAs($data[0], $data[1]);
  966. if (! isset($this->_dirtree[dirname($data[1])]))
  967. {
  968. $this->_dirtree[dirname($data[1])] = true;
  969. $this->pkginfo->setDirtree(dirname($data[1]));
  970. while (! empty($data[3]) && dirname($data[3]) != $data[3] && $data[3] != '/' && $data[3] != '\\')
  971. {
  972. $this->pkginfo->setDirtree($pp = $this->_prependPath($data[3], $data[2]));
  973. $this->_dirtree[$pp] = true;
  974. $data[3] = dirname($data[3]);
  975. }
  976. }
  977. break;
  978. }
  979. }
  980. // }}}
  981. $this->log(2, "successfully committed $n file operations");
  982. $this->file_operations = array();
  983. return true;
  984. }
  985. // }}}
  986. // {{{ rollbackFileTransaction()
  987. function rollbackFileTransaction()
  988. {
  989. $n = count($this->file_operations);
  990. $this->log(2, "rolling back $n file operations");
  991. foreach ($this->file_operations as $tr)
  992. {
  993. list($type, $data) = $tr;
  994. switch ($type)
  995. {
  996. case 'backup' :
  997. if (file_exists($data[0] . '.bak'))
  998. {
  999. if (file_exists($data[0] && is_writable($data[0])))
  1000. {
  1001. unlink($data[0]);
  1002. }
  1003. @copy($data[0] . '.bak', $data[0]);
  1004. $this->log(3, "+ restore $data[0] from $data[0].bak");
  1005. }
  1006. break;
  1007. case 'removebackup' :
  1008. if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak'))
  1009. {
  1010. unlink($data[0] . '.bak');
  1011. $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
  1012. }
  1013. break;
  1014. case 'rename' :
  1015. @unlink($data[0]);
  1016. $this->log(3, "+ rm $data[0]");
  1017. break;
  1018. case 'mkdir' :
  1019. @rmdir($data[0]);
  1020. $this->log(3, "+ rmdir $data[0]");
  1021. break;
  1022. case 'chmod' :
  1023. break;
  1024. case 'delete' :
  1025. break;
  1026. case 'installed_as' :
  1027. $this->pkginfo->setInstalledAs($data[0], false);
  1028. break;
  1029. }
  1030. }
  1031. $this->pkginfo->resetDirtree();
  1032. $this->file_operations = array();
  1033. }
  1034. // }}}
  1035. // {{{ mkDirHier($dir)
  1036. function mkDirHier($dir)
  1037. {
  1038. $this->addFileOperation('mkdir', array($dir));
  1039. return parent :: mkDirHier($dir);
  1040. }
  1041. // }}}
  1042. // {{{ download()
  1043. /**
  1044. * Download any files and their dependencies, if necessary
  1045. *
  1046. * @param array a mixed list of package names, local files, or package.xml
  1047. * @param PEAR_Config
  1048. * @param array options from the command line
  1049. * @param array this is the array that will be populated with packages to
  1050. * install. Format of each entry:
  1051. *
  1052. * <code>
  1053. * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
  1054. * 'info' => array() // parsed package.xml
  1055. * );
  1056. * </code>
  1057. * @param array this will be populated with any error messages
  1058. * @param false private recursion variable
  1059. * @param false private recursion variable
  1060. * @param false private recursion variable
  1061. * @deprecated in favor of PEAR_Downloader
  1062. */
  1063. function download($packages, $options, &$config, &$installpackages, &$errors, $installed = false, $willinstall = false, $state = false)
  1064. {
  1065. // trickiness: initialize here
  1066. parent :: PEAR_Downloader($this->ui, $options, $config);
  1067. $ret = parent :: download($packages);
  1068. $errors = $this->getErrorMsgs();
  1069. $installpackages = $this->getDownloadedPackages();
  1070. trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " . "in favor of PEAR_Downloader class", E_USER_WARNING);
  1071. return $ret;
  1072. }
  1073. // }}}
  1074. // {{{ _parsePackageXml()
  1075. function _parsePackageXml(&$descfile, &$tmpdir)
  1076. {
  1077. if (substr($descfile, - 4) == '.xml')
  1078. {
  1079. $tmpdir = false;
  1080. }
  1081. else
  1082. {
  1083. // {{{ Decompress pack in tmp dir -------------------------------------
  1084. // To allow relative package file names
  1085. $descfile = realpath($descfile);
  1086. if (PEAR :: isError($tmpdir = System :: mktemp('-d')))
  1087. {
  1088. return $tmpdir;
  1089. }
  1090. $this->log(3, '+ tmp dir created at ' . $tmpdir);
  1091. // }}}
  1092. }
  1093. // Parse xml file -----------------------------------------------
  1094. $pkg = new PEAR_PackageFile($this->config, $this->debug, $tmpdir);
  1095. PEAR :: staticPushErrorHandling(PEAR_ERROR_RETURN);
  1096. $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING);
  1097. PEAR :: staticPopErrorHandling();
  1098. if (PEAR :: isError($p))
  1099. {
  1100. if (is_array($p->getUserInfo()))
  1101. {
  1102. foreach ($p->getUserInfo() as $err)
  1103. {
  1104. $loglevel = $err['level'] == 'error' ? 0 : 1;
  1105. if (! isset($this->_options['soft']))
  1106. {
  1107. $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']);
  1108. }
  1109. }
  1110. }
  1111. return $this->raiseError('Installation failed: invalid package file');
  1112. }
  1113. $descfile = $p->getPackageFile();
  1114. return $p;
  1115. }
  1116. // }}}
  1117. /**
  1118. * Set the list of PEAR_Downloader_Package objects to allow more sane
  1119. * dependency validation
  1120. * @param array
  1121. */
  1122. function setDownloadedPackages(&$pkgs)
  1123. {
  1124. PEAR :: pushErrorHandling(PEAR_ERROR_RETURN);
  1125. $err = $this->analyzeDependencies($pkgs);
  1126. PEAR :: popErrorHandling();
  1127. if (PEAR :: isError($err))
  1128. {
  1129. return $err;
  1130. }
  1131. $this->_downloadedPackages = &$pkgs;
  1132. }
  1133. /**
  1134. * Set the list of PEAR_Downloader_Package objects to allow more sane
  1135. * dependency validation
  1136. * @param array
  1137. */
  1138. function setUninstallPackages(&$pkgs)
  1139. {
  1140. $this->_downloadedPackages = &$pkgs;
  1141. }
  1142. function getInstallPackages()
  1143. {
  1144. return $this->_downloadedPackages;
  1145. }
  1146. // {{{ install()
  1147. /**
  1148. * Installs the files within the package file specified.
  1149. *
  1150. * @param string|PEAR_Downloader_Package $pkgfile path to the package file,
  1151. * or a pre-initialized packagefile object
  1152. * @param array $options
  1153. * recognized options:
  1154. * - installroot : optional prefix directory for installation
  1155. * - force : force installation
  1156. * - register-only : update registry but don't install files
  1157. * - upgrade : upgrade existing install
  1158. * - soft : fail silently
  1159. * - nodeps : ignore dependency conflicts/missing dependencies
  1160. * - alldeps : install all dependencies
  1161. * - onlyreqdeps : install only required dependencies
  1162. *
  1163. * @return array|PEAR_Error package info if successful
  1164. */
  1165. function install($pkgfile, $options = array())
  1166. {
  1167. $this->_options = $options;
  1168. $this->_registry = &$this->config->getRegistry();
  1169. if (is_object($pkgfile))
  1170. {
  1171. $dlpkg = &$pkgfile;
  1172. $pkg = $pkgfile->getPackageFile();
  1173. $pkgfile = $pkg->getArchiveFile();
  1174. $descfile = $pkg->getPackageFile();
  1175. $tmpdir = dirname($descfile);
  1176. }
  1177. else
  1178. {
  1179. $descfile = $pkgfile;
  1180. $tmpdir = '';
  1181. $pkg = $this->_parsePackageXml($descfile, $tmpdir);
  1182. if (PEAR :: isError($pkg))
  1183. {
  1184. return $pkg;
  1185. }
  1186. }
  1187. if (realpath($descfile) != realpath($pkgfile))
  1188. {
  1189. $tar = new Archive_Tar($pkgfile);
  1190. if (! $tar->extract($tmpdir))
  1191. {
  1192. return $this->raiseError("unable to unpack $pkgfile");
  1193. }
  1194. }
  1195. $pkgname = $pkg->getName();
  1196. $channel = $pkg->getChannel();
  1197. if (isset($this->_options['packagingroot']))
  1198. {
  1199. $regdir = $this->_prependPath($this->config->get('php_dir', null, 'pear.php.net'), $this->_options['packagingroot']);
  1200. $packrootphp_dir = $this->_prependPath($this->config->get('php_dir', null, $channel), $this->_options['packagingroot']);
  1201. }
  1202. if (isset($options['installroot']))
  1203. {
  1204. $this->config->setInstallRoot($options['installroot']);
  1205. $this->_registry = &$this->config->getRegistry();
  1206. $installregistry = &$this->_registry;
  1207. $this->installroot = ''; // all done automagically now
  1208. $php_dir = $this->config->get('php_dir', null, $channel);
  1209. }
  1210. else
  1211. {
  1212. $this->config->setInstallRoot(false);
  1213. $this->_registry = &$this->config->getRegistry();
  1214. if (isset($this->_options['packagingroot']))
  1215. {
  1216. $installregistry = &new PEAR_Registry($regdir);
  1217. if (! $installregistry->channelExists($channel, true))
  1218. {
  1219. // we need to fake a channel-discover of this channel
  1220. $chanobj = $this->_registry->getChannel($channel, true);
  1221. $installregistry->addChannel($chanobj);
  1222. }
  1223. $php_dir = $packrootphp_dir;
  1224. }
  1225. else
  1226. {
  1227. $installregistry = &$this->_registry;
  1228. $php_dir = $this->config->get('php_dir', null, $channel);
  1229. }
  1230. $this->installroot = '';
  1231. }
  1232. // {{{ checks to do when not in "force" mode
  1233. if (empty($options['force']) && (file_exists($this->config->get('php_dir')) && is_dir($this->config->get('php_dir'))))
  1234. {
  1235. $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname);
  1236. $instfilelist = $pkg->getInstallationFileList(true);
  1237. if (PEAR :: isError($instfilelist))
  1238. {
  1239. return $instfilelist;
  1240. }
  1241. // ensure we have the most accurate registry
  1242. $installregistry->flushFileMap();
  1243. $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1');
  1244. if (PEAR :: isError($test))
  1245. {
  1246. return $test;
  1247. }
  1248. if (sizeof($test))
  1249. {
  1250. $pkgs = $this->getInstallPackages();
  1251. $found = false;
  1252. foreach ($pkgs as $param)
  1253. {
  1254. if ($pkg->isSubpackageOf($param))
  1255. {
  1256. $found = true;
  1257. break;
  1258. }
  1259. }
  1260. if ($found)
  1261. {
  1262. // subpackages can conflict with earlier versions of parent packages
  1263. $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel());
  1264. $tmp = $test;
  1265. foreach ($tmp as $file => $info)
  1266. {
  1267. if (is_array($info))
  1268. {
  1269. if (strtolower($info[1]) == strtolower($param->getPackage()) && strtolower($info[0]) == strtolower($param->getChannel()))
  1270. {
  1271. if (isset($parentreg['filelist'][$file]))
  1272. {
  1273. unset($parentreg['filelist'][$file]);
  1274. }
  1275. else
  1276. {
  1277. $pos = strpos($file, '/');
  1278. $basedir = substr($file, 0, $pos);
  1279. $file2 = substr($file, $pos + 1);
  1280. if (isset($parentreg['filelist'][$file2]['baseinstalldir']) && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir)
  1281. {
  1282. unset($parentreg['filelist'][$file2]);
  1283. }
  1284. }
  1285. unset($test[$file]);
  1286. }
  1287. }
  1288. else
  1289. {
  1290. if (strtolower($param->getChannel()) != 'pear.php.net')
  1291. {
  1292. continue;
  1293. }
  1294. if (strtolower($info) == strtolower($param->getPackage()))
  1295. {
  1296. if (isset($parentreg['filelist'][$file]))
  1297. {
  1298. unset($parentreg['filelist'][$file]);
  1299. }
  1300. else
  1301. {
  1302. $pos = strpos($file, '/');
  1303. $basedir = substr($file, 0, $pos);
  1304. $file2 = substr($file, $pos + 1);
  1305. if (isset($parentreg['filelist'][$file2]['baseinstalldir']) && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir)
  1306. {
  1307. unset($parentreg['filelist'][$file2]);
  1308. }
  1309. }
  1310. unset($test[$file]);
  1311. }
  1312. }
  1313. }
  1314. $pfk = &new PEAR_PackageFile($this->config);
  1315. $parentpkg = &$pfk->fromArray($parentreg);
  1316. $installregistry->updatePackage2($parentpkg);
  1317. }
  1318. if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade']))
  1319. {
  1320. $tmp = $test;
  1321. foreach ($tmp as $file => $info)
  1322. {
  1323. if (is_string($info))
  1324. {
  1325. // pear.php.net packages are always stored as strings
  1326. if (strtolower($info) == strtolower($param->getPackage()))
  1327. {
  1328. // upgrading existing package
  1329. unset($test[$file]);
  1330. }
  1331. }
  1332. }
  1333. }
  1334. if (count($test))
  1335. {
  1336. $msg = "$channel/$pkgname: conflicting files found:\n";
  1337. $longest = max(array_map("strlen", array_keys($test)));
  1338. $fmt = "%${longest}s (%s)\n";
  1339. foreach ($test as $file => $info)
  1340. {
  1341. if (! is_array($info))
  1342. {
  1343. $info = array('pear.php.net', $info);
  1344. }
  1345. $info = $info[0] . '/' . $info[1];
  1346. $msg .= sprintf($fmt, $file, $info);
  1347. }
  1348. if (! isset($options['ignore-errors']))
  1349. {
  1350. return $this->raiseError($msg);
  1351. }
  1352. if (! isset($options['soft']))
  1353. {
  1354. $this->log(0, "WARNING: $msg");
  1355. }
  1356. }
  1357. }
  1358. }
  1359. // }}}
  1360. $this->startFileTransaction();
  1361. if (empty($options['upgrade']) && empty($options['soft']))
  1362. {
  1363. // checks to do only when installing new packages
  1364. if ($channel == 'pecl.php.net')
  1365. {
  1366. $test = $installregistry->packageExists($pkgname, $channel);
  1367. if (! $test)
  1368. {
  1369. $test = $installregistry->packageExists($pkgname, 'pear.php.net');
  1370. }
  1371. }
  1372. else
  1373. {
  1374. $test = $installregistry->packageExists($pkgname, $channel);
  1375. }
  1376. if (empty($options['force']) && $test)
  1377. {
  1378. return $this->raiseError("$channel/$pkgname is already installed");
  1379. }
  1380. }
  1381. else
  1382. {
  1383. $usechannel = $channel;
  1384. if ($channel == 'pecl.php.net')
  1385. {
  1386. $test = $installregistry->packageExists($pkgname, $channel);
  1387. if (! $test)
  1388. {
  1389. $test = $installregistry->packageExists($pkgname, 'pear.php.net');
  1390. $usechannel = 'pear.php.net';
  1391. }
  1392. }
  1393. else
  1394. {
  1395. $test = $installregistry->packageExists($pkgname, $channel);
  1396. }
  1397. if ($test)
  1398. {
  1399. $v1 = $installregistry->packageInfo($pkgname, 'version', $usechannel);
  1400. $v2 = $pkg->getVersion();
  1401. $cmp = version_compare("$v1", "$v2", 'gt');
  1402. if (empty($options['force']) && ! version_compare("$v2", "$v1", 'gt'))
  1403. {
  1404. return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)");
  1405. }
  1406. if (empty($options['register-only']))
  1407. {
  1408. // when upgrading, remove old release's files first:
  1409. if (PEAR :: isError($err = $this->_deletePackageFiles($pkgname, $usechannel, true)))
  1410. {
  1411. if (! isset($options['ignore-errors']))
  1412. {
  1413. return $this->raiseError($err);
  1414. }
  1415. if (! isset($options['soft']))
  1416. {
  1417. $this->log(0, 'WARNING: ' . $err->getMessage());
  1418. }
  1419. }
  1420. else
  1421. {
  1422. $backedup = $err;
  1423. }
  1424. }
  1425. }
  1426. }
  1427. // {{{ Copy files to dest dir ---------------------------------------
  1428. // info from the package it self we want to access from _installFile
  1429. $this->pkginfo = &$pkg;
  1430. // used to determine whether we should build any C code
  1431. $this->source_files = 0;
  1432. $savechannel = $this->config->get('default_channel');
  1433. if (empty($options['register-only']) && ! is_dir($php_dir))
  1434. {
  1435. if (PEAR :: isError(System :: mkdir(array('-p'), $php_dir)))
  1436. {
  1437. return $this->raiseError("no installation destination directory '$php_dir'\n");
  1438. }
  1439. }
  1440. $tmp_path = dirname($descfile);
  1441. if (substr($pkgfile, - 4) != '.xml')
  1442. {
  1443. $tmp_path .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion();
  1444. }
  1445. $this->configSet('default_channel', $channel);
  1446. // {{{ install files
  1447. $ver = $pkg->getPackagexmlVersion();
  1448. if (version_compare($ver, '2.0', '>='))
  1449. {
  1450. $filelist = $pkg->getInstallationFilelist();
  1451. }
  1452. else
  1453. {
  1454. $filelist = $pkg->getFileList();
  1455. }
  1456. if (PEAR :: isError($filelist))
  1457. {
  1458. return $filelist;
  1459. }
  1460. $p = &$installregistry->getPackage($pkgname, $channel);
  1461. $dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false;
  1462. $pkg->resetFilelist();
  1463. $pkg->setLastInstalledVersion($installregistry->packageInfo($pkg->getPackage(), 'version', $pkg->getChannel()));
  1464. foreach ($filelist as $file => $atts)
  1465. {
  1466. $this->expectError(PEAR_INSTALLER_FAILED);
  1467. if ($pkg->getPackagexmlVersion() == '1.0')
  1468. {
  1469. $res = $this->_installFile($file, $atts, $tmp_path, $options);
  1470. }
  1471. else
  1472. {
  1473. $res = $this->_installFile2($pkg, $file, $atts, $tmp_path, $options);
  1474. }
  1475. $this->popExpect();
  1476. if (PEAR :: isError($res))
  1477. {
  1478. if (empty($options['ignore-errors']))
  1479. {
  1480. $this->rollbackFileTransaction();
  1481. if ($res->getMessage() == "file does not exist")
  1482. {
  1483. $this->raiseError("file $file in package.xml does not exist");
  1484. }
  1485. return $this->raiseError($res);
  1486. }
  1487. if (! isset($options['soft']))
  1488. {
  1489. $this->log(0, "Warning: " . $res->getMessage());
  1490. }
  1491. }
  1492. $real = isset($atts['attribs']) ? $atts['attribs'] : $atts;
  1493. if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src')
  1494. {
  1495. // Register files that were installed
  1496. $pkg->installedFile($file, $atts);
  1497. }
  1498. }
  1499. // }}}
  1500. // {{{ compile and install source files
  1501. if ($this->source_files > 0 && empty($options['nobuild']))
  1502. {
  1503. if (PEAR :: isError($err = $this->_compileSourceFiles($savechannel, $pkg)))
  1504. {
  1505. return $err;
  1506. }
  1507. }
  1508. // }}}
  1509. if (isset($backedup))
  1510. {
  1511. $this->_removeBackups($backedup);
  1512. }
  1513. if (! $this->commitFileTransaction())
  1514. {
  1515. $this->rollbackFileTransaction();
  1516. $this->configSet('default_channel', $savechannel);
  1517. return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
  1518. }
  1519. // }}}
  1520. $ret = false;
  1521. $installphase = 'install';
  1522. $oldversion = false;
  1523. // {{{ Register that the package is installed -----------------------
  1524. if (empty($options['upgrade']))
  1525. {
  1526. // if 'force' is used, replace the info in registry
  1527. $usechannel = $channel;
  1528. if ($channel == 'pecl.php.net')
  1529. {
  1530. $test = $installregistry->packageExists($pkgname, $channel);
  1531. if (! $test)
  1532. {
  1533. $test = $installregistry->packageExists($pkgname, 'pear.php.net');
  1534. $usechannel = 'pear.php.net';
  1535. }
  1536. }
  1537. else
  1538. {
  1539. $test = $installregistry->packageExists($pkgname, $channel);
  1540. }
  1541. if (! empty($options['force']) && $test)
  1542. {
  1543. $oldversion = $installregistry->packageInfo($pkgname, 'version', $usechannel);
  1544. $installregistry->deletePackage($pkgname, $usechannel);
  1545. }
  1546. $ret = $installregistry->addPackage2($pkg);
  1547. }
  1548. else
  1549. {
  1550. if ($dirtree)
  1551. {
  1552. $this->startFileTransaction();
  1553. // attempt to delete empty directories
  1554. uksort($dirtree, array($this, '_sortDirs'));
  1555. foreach ($dirtree as $dir => $notused)
  1556. {
  1557. $this->addFileOperation('rmdir', array($dir));
  1558. }
  1559. $this->commitFileTransaction();
  1560. }
  1561. $usechannel = $channel;
  1562. if ($channel == 'pecl.php.net')
  1563. {
  1564. $test = $installregistry->packageExists($pkgname, $channel);
  1565. if (! $test)
  1566. {
  1567. $test = $installregistry->packageExists($pkgname, 'pear.php.net');
  1568. $usechannel = 'pear.php.net';
  1569. }
  1570. }
  1571. else
  1572. {
  1573. $test = $installregistry->packageExists($pkgname, $channel);
  1574. }
  1575. // new: upgrade installs a package if it isn't installed
  1576. if (! $test)
  1577. {
  1578. $ret = $installregistry->addPackage2($pkg);
  1579. }
  1580. else
  1581. {
  1582. if ($usechannel != $channel)
  1583. {
  1584. $installregistry->deletePackage($pkgname, $usechannel);
  1585. $ret = $installregistry->addPackage2($pkg);
  1586. }
  1587. else
  1588. {
  1589. $ret = $installregistry->updatePackage2($pkg);
  1590. }
  1591. $installphase = 'upgrade';
  1592. }
  1593. }
  1594. if (! $ret)
  1595. {
  1596. $this->configSet('default_channel', $savechannel);
  1597. return $this->raiseError("Adding package $channel/$pkgname to registry failed");
  1598. }
  1599. // }}}
  1600. $this->configSet('default_channel', $savechannel);
  1601. if (class_exists('PEAR_Task_Common'))
  1602. { // this is auto-included if any tasks exist
  1603. if (PEAR_Task_Common :: hasPostinstallTasks())
  1604. {
  1605. PEAR_Task_Common :: runPostinstallTasks($installphase);
  1606. }
  1607. }
  1608. return $pkg->toArray(true);
  1609. }
  1610. // }}}
  1611. // {{{ _compileSourceFiles()
  1612. /**
  1613. * @param string
  1614. * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
  1615. */
  1616. function _compileSourceFiles($savechannel, &$filelist)
  1617. {
  1618. require_once 'PEAR/Builder.php';
  1619. $this->log(1, "$this->source_files source files, building");
  1620. $bob = &new PEAR_Builder($this->ui);
  1621. $bob->debug = $this->debug;
  1622. $built = $bob->build($filelist, array(&$this, '_buildCallback'));
  1623. if (PEAR :: isError($built))
  1624. {
  1625. $this->rollbackFileTransaction();
  1626. $this->configSet('default_channel', $savechannel);
  1627. return $built;
  1628. }
  1629. $this->log(1, "\nBuild process completed successfully");
  1630. foreach ($built as $ext)
  1631. {
  1632. $bn = basename($ext['file']);
  1633. list($_ext_name, $_ext_suff) = explode('.', $bn);
  1634. if ($_ext_suff == '.so' || $_ext_suff == '.dll')
  1635. {
  1636. if (extension_loaded($_ext_name))
  1637. {
  1638. $this->raiseError("Extension '$_ext_name' already loaded. " . 'Please unload it in your php.ini file ' . 'prior to install or upgrade');
  1639. }
  1640. $role = 'ext';
  1641. }
  1642. else
  1643. {
  1644. $role = 'src';
  1645. }
  1646. $dest = $ext['dest'];
  1647. $packagingroot = '';
  1648. if (isset($this->_options['packagingroot']))
  1649. {
  1650. $packagingroot = $this->_options['packagingroot'];
  1651. }
  1652. $copyto = $this->_prependPath($dest, $packagingroot);
  1653. $extra = $copyto != $dest ? " as '$copyto'" : '';
  1654. $this->log(1, "Installing '$dest'$extra");
  1655. $copydir = dirname($copyto);
  1656. // pretty much nothing happens if we are only registering the install
  1657. if (empty($this->_options['register-only']))
  1658. {
  1659. if (! file_exists($copydir) || ! is_dir($copydir))
  1660. {
  1661. if (! $this->mkDirHier($copydir))
  1662. {
  1663. return $this->raiseError("failed to mkdir $copydir", PEAR_INSTALLER_FAILED);
  1664. }
  1665. $this->log(3, "+ mkdir $copydir");
  1666. }
  1667. if (! @copy($ext['file'], $copyto))
  1668. {
  1669. return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED);
  1670. }
  1671. $this->log(3, "+ cp $ext[file] $copyto");
  1672. $this->addFileOperation('rename', array($ext['file'], $copyto));
  1673. if (! OS_WINDOWS)
  1674. {
  1675. $mode = 0666 & ~ (int) octdec($this->config->get('umask'));
  1676. $this->addFileOperation('chmod', array($mode, $copyto));
  1677. if (! @chmod($copyto, $mode))
  1678. {
  1679. $this->log(0, "failed to change mode of $copyto ($php_errormsg)");
  1680. }
  1681. }
  1682. }
  1683. $data = array('role' => $role, 'name' => $bn, 'installed_as' => $dest, 'php_api' => $ext['php_api'],
  1684. 'zend_mod_api' => $ext['zend_mod_api'], 'zend_ext_api' => $ext['zend_ext_api']);
  1685. if ($filelist->getPackageXmlVersion() == '1.0')
  1686. {
  1687. $filelist->installedFile($bn, $data);
  1688. }
  1689. else
  1690. {
  1691. $filelist->installedFile($bn, array('attribs' => $data));
  1692. }
  1693. }
  1694. }
  1695. // }}}
  1696. function &getUninstallPackages()
  1697. {
  1698. return $this->_downloadedPackages;
  1699. }
  1700. // {{{ uninstall()
  1701. /**
  1702. * Uninstall a package
  1703. *
  1704. * This method removes all files installed by the application, and then
  1705. * removes any empty directories.
  1706. * @param string package name
  1707. * @param array Command-line options. Possibilities include:
  1708. *
  1709. * - installroot: base installation dir, if not the default
  1710. * - register-only : update registry but don't remove files
  1711. * - nodeps: do not process dependencies of other packages to ensure
  1712. * uninstallation does not break things
  1713. */
  1714. function uninstall($package, $options = array())
  1715. {
  1716. $installRoot = isset($options['installroot']) ? $options['installroot'] : '';
  1717. $this->config->setInstallRoot($installRoot);
  1718. $this->installroot = '';
  1719. $this->_registry = &$this->config->getRegistry();
  1720. if (is_object($package))
  1721. {
  1722. $channel = $package->getChannel();
  1723. $pkg = $package;
  1724. $package = $pkg->getPackage();
  1725. }
  1726. else
  1727. {
  1728. $pkg = false;
  1729. $info = $this->_registry->parsePackageName($package, $this->config->get('default_channel'));
  1730. $channel = $info['channel'];
  1731. $package = $info['package'];
  1732. }
  1733. $savechannel = $this->config->get('default_channel');
  1734. $this->configSet('default_channel', $channel);
  1735. if (! is_object($pkg))
  1736. {
  1737. $pkg = $this->_registry->getPackage($package, $channel);
  1738. }
  1739. if (! $pkg)
  1740. {
  1741. $this->configSet('default_channel', $savechannel);
  1742. return $this->raiseError($this->_registry->parsedPackageNameToString(array('channel' => $channel,
  1743. 'package' => $package), true) . ' not installed');
  1744. }
  1745. if ($pkg->getInstalledBinary())
  1746. {
  1747. // this is just an alias for a binary package
  1748. return $this->_registry->deletePackage($package, $channel);
  1749. }
  1750. $filelist = $pkg->getFilelist();
  1751. PEAR :: staticPushErrorHandling(PEAR_ERROR_RETURN);
  1752. if (! class_exists('PEAR_Dependency2'))
  1753. {
  1754. require_once 'PEAR/Dependency2.php';
  1755. }
  1756. $depchecker = &new PEAR_Dependency2($this->config, $options, array('channel' => $channel, 'package' => $package), PEAR_VALIDATE_UNINSTALLING);
  1757. $e = $depchecker->validatePackageUninstall($this);
  1758. PEAR :: staticPopErrorHandling();
  1759. if (PEAR :: isError($e))
  1760. {
  1761. if (! isset($options['ignore-errors']))
  1762. {
  1763. return $this->raiseError($e);
  1764. }
  1765. if (! isset($options['soft']))
  1766. {
  1767. $this->log(0, 'WARNING: ' . $e->getMessage());
  1768. }
  1769. }
  1770. elseif (is_array($e))
  1771. {
  1772. if (! isset($options['soft']))
  1773. {
  1774. $this->log(0, $e[0]);
  1775. }
  1776. }
  1777. $this->pkginfo = &$pkg;
  1778. // pretty much nothing happens if we are only registering the uninstall
  1779. if (empty($options['register-only']))
  1780. {
  1781. // {{{ Delete the files
  1782. $this->startFileTransaction();
  1783. PEAR :: pushErrorHandling(PEAR_ERROR_RETURN);
  1784. if (PEAR :: isError($err = $this->_deletePackageFiles($package, $channel)))
  1785. {
  1786. PEAR :: popErrorHandling();
  1787. $this->rollbackFileTransaction();
  1788. $this->configSet('default_channel', $savechannel);
  1789. if (! isset($options['ignore-errors']))
  1790. {
  1791. return $this->raiseError($err);
  1792. }
  1793. if (! isset($options['soft']))
  1794. {
  1795. $this->log(0, 'WARNING: ' . $err->getMessage());
  1796. }
  1797. }
  1798. else
  1799. {
  1800. PEAR :: popErrorHandling();
  1801. }
  1802. if (! $this->commitFileTransaction())
  1803. {
  1804. $this->rollbackFileTransaction();
  1805. if (! isset($options['ignore-errors']))
  1806. {
  1807. return $this->raiseError("uninstall failed");
  1808. }
  1809. if (! isset($options['soft']))
  1810. {
  1811. $this->log(0, 'WARNING: uninstall failed');
  1812. }
  1813. }
  1814. else
  1815. {
  1816. $this->startFileTransaction();
  1817. $dirtree = $pkg->getDirTree();
  1818. if ($dirtree === false)
  1819. {
  1820. $this->configSet('default_channel', $savechannel);
  1821. return $this->_registry->deletePackage($package, $channel);
  1822. }
  1823. // attempt to delete empty directories
  1824. uksort($dirtree, array($this, '_sortDirs'));
  1825. foreach ($dirtree as $dir => $notused)
  1826. {
  1827. $this->addFileOperation('rmdir', array($dir));
  1828. }
  1829. if (! $this->commitFileTransaction())
  1830. {
  1831. $this->rollbackFileTransaction();
  1832. if (! isset($options['ignore-errors']))
  1833. {
  1834. return $this->raiseError("uninstall failed");
  1835. }
  1836. if (! isset($options['soft']))
  1837. {
  1838. $this->log(0, 'WARNING: uninstall failed');
  1839. }
  1840. }
  1841. }
  1842. // }}}
  1843. }
  1844. $this->configSet('default_channel', $savechannel);
  1845. // Register that the package is no longer installed
  1846. return $this->_registry->deletePackage($package, $channel);
  1847. }
  1848. /**
  1849. * Sort a list of arrays of array(downloaded packagefilename) by dependency.
  1850. *
  1851. * It also removes duplicate dependencies
  1852. * @param array an array of PEAR_PackageFile_v[1/2] objects
  1853. * @return array|PEAR_Error array of array(packagefilename, package.xml contents)
  1854. */
  1855. function sortPackagesForUninstall(&$packages)
  1856. {
  1857. $this->_dependencyDB = &PEAR_DependencyDB :: singleton($this->config);
  1858. if (PEAR :: isError($this->_dependencyDB))
  1859. {
  1860. return $this->_dependencyDB;
  1861. }
  1862. usort($packages, array(&$this, '_sortUninstall'));
  1863. }
  1864. function _sortUninstall($a, $b)
  1865. {
  1866. if (! $a->getDeps() && ! $b->getDeps())
  1867. {
  1868. return 0; // neither package has dependencies, order is insignificant
  1869. }
  1870. if ($a->getDeps() && ! $b->getDeps())
  1871. {
  1872. return - 1; // $a must be installed after $b because $a has dependencies
  1873. }
  1874. if (! $a->getDeps() && $b->getDeps())
  1875. {
  1876. return 1; // $b must be installed after $a because $b has dependencies
  1877. }
  1878. // both packages have dependencies
  1879. if ($this->_dependencyDB->dependsOn($a, $b))
  1880. {
  1881. return - 1;
  1882. }
  1883. if ($this->_dependencyDB->dependsOn($b, $a))
  1884. {
  1885. return 1;
  1886. }
  1887. return 0;
  1888. }
  1889. // }}}
  1890. // {{{ _sortDirs()
  1891. function _sortDirs($a, $b)
  1892. {
  1893. if (strnatcmp($a, $b) == - 1)
  1894. return 1;
  1895. if (strnatcmp($a, $b) == 1)
  1896. return - 1;
  1897. return 0;
  1898. }
  1899. // }}}
  1900. // {{{ _buildCallback()
  1901. function _buildCallback($what, $data)
  1902. {
  1903. if (($what == 'cmdoutput' && $this->debug > 1) || ($what == 'output' && $this->debug > 0))
  1904. {
  1905. $this->ui->outputData(rtrim($data), 'build');
  1906. }
  1907. }
  1908. // }}}
  1909. }