PageRenderTime 62ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/common/libraries/plugin/pear/PEAR/Installer.php

https://bitbucket.org/cbenelug/chamilo
PHP | 2079 lines | 1736 code | 97 blank | 246 comment | 285 complexity | 17d55bc8cd9ae1257d4265f620a19e0a MD5 | raw file
Possible License(s): GPL-3.0, MIT, GPL-2.0, BSD-3-Clause, LGPL-2.1, LGPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * PEAR_Installer
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * LICENSE: This source file is subject to version 3.0 of the PHP license
  8. * that is available through the world-wide-web at the following URI:
  9. * http://www.php.net/license/3_0.txt. If you did not receive a copy of
  10. * the PHP License and are unable to obtain it through the web, please
  11. * send a note to license@php.net so we can mail you a copy immediately.
  12. *
  13. * @category pear
  14. * @package PEAR
  15. * @author Stig Bakken <ssb@php.net>
  16. * @author Tomas V.V. Cox <cox@idecnet.com>
  17. * @author Martin Jansen <mj@php.net>
  18. * @author Greg Beaver <cellog@php.net>
  19. * @copyright 1997-2008 The PHP Group
  20. * @license http://www.php.net/license/3_0.txt PHP License 3.0
  21. * @version CVS: $Id: Installer.php 137 2009-11-09 13:24:37Z vanpouckesven $
  22. * @link http://pear.php.net/package/PEAR
  23. * @since File available since Release 0.1
  24. */
  25. /**
  26. * Used for installation groups in package.xml 2.0 and platform exceptions
  27. */
  28. require_once 'OS/Guess.php';
  29. require_once 'PEAR/Downloader.php';
  30. define('PEAR_INSTALLER_NOBINARY', - 240);
  31. /**
  32. * Administration class used to install PEAR packages and maintain the
  33. * installed package database.
  34. *
  35. * @category pear
  36. * @package PEAR
  37. * @author Stig Bakken <ssb@php.net>
  38. * @author Tomas V.V. Cox <cox@idecnet.com>
  39. * @author Martin Jansen <mj@php.net>
  40. * @author Greg Beaver <cellog@php.net>
  41. * @copyright 1997-2008 The PHP Group
  42. * @license http://www.php.net/license/3_0.txt PHP License 3.0
  43. * @version Release: 1.7.2
  44. * @link http://pear.php.net/package/PEAR
  45. * @since Class available since Release 0.1
  46. */
  47. class PEAR_Installer extends PEAR_Downloader
  48. {
  49. // {{{ properties
  50. /** name of the package directory, for example Foo-1.0
  51. * @var string
  52. */
  53. var $pkgdir;
  54. /** directory where PHP code files go
  55. * @var string
  56. */
  57. var $phpdir;
  58. /** directory where PHP extension files go
  59. * @var string
  60. */
  61. var $extdir;
  62. /** directory where documentation goes
  63. * @var string
  64. */
  65. var $docdir;
  66. /** installation root directory (ala PHP's INSTALL_ROOT or
  67. * automake's DESTDIR
  68. * @var string
  69. */
  70. var $installroot = '';
  71. /** debug level
  72. * @var int
  73. */
  74. var $debug = 1;
  75. /** temporary directory
  76. * @var string
  77. */
  78. var $tmpdir;
  79. /**
  80. * PEAR_Registry object used by the installer
  81. * @var PEAR_Registry
  82. */
  83. var $registry;
  84. /**
  85. * array of PEAR_Downloader_Packages
  86. * @var array
  87. */
  88. var $_downloadedPackages;
  89. /** List of file transactions queued for an install/upgrade/uninstall.
  90. *
  91. * Format:
  92. * array(
  93. * 0 => array("rename => array("from-file", "to-file")),
  94. * 1 => array("delete" => array("file-to-delete")),
  95. * ...
  96. * )
  97. *
  98. * @var array
  99. */
  100. var $file_operations = array();
  101. // }}}
  102. // {{{ constructor
  103. /**
  104. * PEAR_Installer constructor.
  105. *
  106. * @param object $ui user interface object (instance of PEAR_Frontend_*)
  107. *
  108. * @access public
  109. */
  110. function __construct(&$ui)
  111. {
  112. parent :: __construct();
  113. $this->setFrontendObject($ui);
  114. $this->debug = $this->config->get('verbose');
  115. }
  116. function setOptions($options)
  117. {
  118. $this->_options = $options;
  119. }
  120. function setConfig(&$config)
  121. {
  122. $this->config = &$config;
  123. $this->_registry = &$config->getRegistry();
  124. }
  125. // }}}
  126. function _removeBackups($files)
  127. {
  128. foreach ($files as $path)
  129. {
  130. $this->addFileOperation('removebackup', array($path));
  131. }
  132. }
  133. // {{{ _deletePackageFiles()
  134. /**
  135. * Delete a package's installed files, does not remove empty directories.
  136. *
  137. * @param string package name
  138. * @param string channel name
  139. * @param bool if true, then files are backed up first
  140. * @return bool TRUE on success, or a PEAR error on failure
  141. * @access protected
  142. */
  143. function _deletePackageFiles($package, $channel = false, $backup = false)
  144. {
  145. if (! $channel)
  146. {
  147. $channel = 'pear.php.net';
  148. }
  149. if (! strlen($package))
  150. {
  151. return $this->raiseError("No package to uninstall given");
  152. }
  153. if (strtolower($package) == 'pear' && $channel == 'pear.php.net')
  154. {
  155. // to avoid race conditions, include all possible needed files
  156. require_once 'PEAR/Task/Common.php';
  157. require_once 'PEAR/Task/Replace.php';
  158. require_once 'PEAR/Task/Unixeol.php';
  159. require_once 'PEAR/Task/Windowseol.php';
  160. require_once 'PEAR/PackageFile/v1.php';
  161. require_once 'PEAR/PackageFile/v2.php';
  162. require_once 'PEAR/PackageFile/Generator/v1.php';
  163. require_once 'PEAR/PackageFile/Generator/v2.php';
  164. }
  165. $filelist = $this->_registry->packageInfo($package, 'filelist', $channel);
  166. if ($filelist == null)
  167. {
  168. return $this->raiseError("$channel/$package not installed");
  169. }
  170. $ret = array();
  171. foreach ($filelist as $file => $props)
  172. {
  173. if (empty($props['installed_as']))
  174. {
  175. continue;
  176. }
  177. $path = $props['installed_as'];
  178. if ($backup)
  179. {
  180. $this->addFileOperation('backup', array($path));
  181. $ret[] = $path;
  182. }
  183. $this->addFileOperation('delete', array($path));
  184. }
  185. if ($backup)
  186. {
  187. return $ret;
  188. }
  189. return true;
  190. }
  191. // }}}
  192. // {{{ _installFile()
  193. /**
  194. * @param string filename
  195. * @param array attributes from <file> tag in package.xml
  196. * @param string path to install the file in
  197. * @param array options from command-line
  198. * @access private
  199. */
  200. function _installFile($file, $atts, $tmp_path, $options)
  201. {
  202. // {{{ return if this file is meant for another platform
  203. static $os;
  204. if (! isset($this->_registry))
  205. {
  206. $this->_registry = &$this->config->getRegistry();
  207. }
  208. if (isset($atts['platform']))
  209. {
  210. if (empty($os))
  211. {
  212. $os = new OS_Guess();
  213. }
  214. if (strlen($atts['platform']) && $atts['platform']{0} == '!')
  215. {
  216. $negate = true;
  217. $platform = substr($atts['platform'], 1);
  218. }
  219. else
  220. {
  221. $negate = false;
  222. $platform = $atts['platform'];
  223. }
  224. if ((bool) $os->matchSignature($platform) === $negate)
  225. {
  226. $this->log(3, "skipped $file (meant for $atts[platform], we are " . $os->getSignature() . ")");
  227. return PEAR_INSTALLER_SKIPPED;
  228. }
  229. }
  230. // }}}
  231. $channel = $this->pkginfo->getChannel();
  232. // {{{ assemble the destination paths
  233. switch ($atts['role'])
  234. {
  235. case 'src' :
  236. case 'extsrc' :
  237. $this->source_files ++;
  238. return;
  239. case 'doc' :
  240. case 'data' :
  241. case 'test' :
  242. $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) . DIRECTORY_SEPARATOR . $this->pkginfo->getPackage();
  243. unset($atts['baseinstalldir']);
  244. break;
  245. case 'ext' :
  246. case 'php' :
  247. $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel);
  248. break;
  249. case 'script' :
  250. $dest_dir = $this->config->get('bin_dir', null, $channel);
  251. break;
  252. default :
  253. return $this->raiseError("Invalid role `$atts[role]' for file $file");
  254. }
  255. $save_destdir = $dest_dir;
  256. if (! empty($atts['baseinstalldir']))
  257. {
  258. $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
  259. }
  260. if (dirname($file) != '.' && empty($atts['install-as']))
  261. {
  262. $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
  263. }
  264. if (empty($atts['install-as']))
  265. {
  266. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
  267. }
  268. else
  269. {
  270. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
  271. }
  272. $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
  273. // Clean up the DIRECTORY_SEPARATOR mess
  274. $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
  275. list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), array(DIRECTORY_SEPARATOR,
  276. DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), array($dest_file, $orig_file));
  277. $final_dest_file = $installed_as = $dest_file;
  278. if (isset($this->_options['packagingroot']))
  279. {
  280. $installedas_dest_dir = dirname($final_dest_file);
  281. $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  282. $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']);
  283. }
  284. else
  285. {
  286. $installedas_dest_dir = dirname($final_dest_file);
  287. $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  288. }
  289. $dest_dir = dirname($final_dest_file);
  290. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  291. if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file)))
  292. {
  293. return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
  294. }
  295. // }}}
  296. if (empty($this->_options['register-only']) && (! file_exists($dest_dir) || ! is_dir($dest_dir)))
  297. {
  298. if (! $this->mkDirHier($dest_dir))
  299. {
  300. return $this->raiseError("failed to mkdir $dest_dir", PEAR_INSTALLER_FAILED);
  301. }
  302. $this->log(3, "+ mkdir $dest_dir");
  303. }
  304. // pretty much nothing happens if we are only registering the install
  305. if (empty($this->_options['register-only']))
  306. {
  307. if (empty($atts['replacements']))
  308. {
  309. if (! file_exists($orig_file))
  310. {
  311. return $this->raiseError("file $orig_file does not exist", PEAR_INSTALLER_FAILED);
  312. }
  313. if (! @copy($orig_file, $dest_file))
  314. {
  315. return $this->raiseError("failed to write $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  316. }
  317. $this->log(3, "+ cp $orig_file $dest_file");
  318. if (isset($atts['md5sum']))
  319. {
  320. $md5sum = md5_file($dest_file);
  321. }
  322. }
  323. else
  324. {
  325. // {{{ file with replacements
  326. if (! file_exists($orig_file))
  327. {
  328. return $this->raiseError("file does not exist", PEAR_INSTALLER_FAILED);
  329. }
  330. $contents = file_get_contents($orig_file);
  331. if ($contents === false)
  332. {
  333. $contents = '';
  334. }
  335. if (isset($atts['md5sum']))
  336. {
  337. $md5sum = md5($contents);
  338. }
  339. $subst_from = $subst_to = array();
  340. foreach ($atts['replacements'] as $a)
  341. {
  342. $to = '';
  343. if ($a['type'] == 'php-const')
  344. {
  345. if (preg_match('/^[a-z0-9_]+\\z/i', $a['to']))
  346. {
  347. eval("\$to = $a[to];");
  348. }
  349. else
  350. {
  351. if (! isset($options['soft']))
  352. {
  353. $this->log(0, "invalid php-const replacement: $a[to]");
  354. }
  355. continue;
  356. }
  357. }
  358. elseif ($a['type'] == 'pear-config')
  359. {
  360. if ($a['to'] == 'master_server')
  361. {
  362. $chan = $this->_registry->getChannel($channel);
  363. if (! PEAR :: isError($chan))
  364. {
  365. $to = $chan->getServer();
  366. }
  367. else
  368. {
  369. $to = $this->config->get($a['to'], null, $channel);
  370. }
  371. }
  372. else
  373. {
  374. $to = $this->config->get($a['to'], null, $channel);
  375. }
  376. if (is_null($to))
  377. {
  378. if (! isset($options['soft']))
  379. {
  380. $this->log(0, "invalid pear-config replacement: $a[to]");
  381. }
  382. continue;
  383. }
  384. }
  385. elseif ($a['type'] == 'package-info')
  386. {
  387. if ($t = $this->pkginfo->packageInfo($a['to']))
  388. {
  389. $to = $t;
  390. }
  391. else
  392. {
  393. if (! isset($options['soft']))
  394. {
  395. $this->log(0, "invalid package-info replacement: $a[to]");
  396. }
  397. continue;
  398. }
  399. }
  400. if (! is_null($to))
  401. {
  402. $subst_from[] = $a['from'];
  403. $subst_to[] = $to;
  404. }
  405. }
  406. $this->log(3, "doing " . sizeof($subst_from) . " substitution(s) for $final_dest_file");
  407. if (sizeof($subst_from))
  408. {
  409. $contents = str_replace($subst_from, $subst_to, $contents);
  410. }
  411. $wp = @fopen($dest_file, "wb");
  412. if (! is_resource($wp))
  413. {
  414. return $this->raiseError("failed to create $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  415. }
  416. if (@fwrite($wp, $contents) === false)
  417. {
  418. return $this->raiseError("failed writing to $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  419. }
  420. fclose($wp);
  421. // }}}
  422. }
  423. // {{{ check the md5
  424. if (isset($md5sum))
  425. {
  426. if (strtolower($md5sum) === strtolower($atts['md5sum']))
  427. {
  428. $this->log(2, "md5sum ok: $final_dest_file");
  429. }
  430. else
  431. {
  432. if (empty($options['force']))
  433. {
  434. // delete the file
  435. if (file_exists($dest_file))
  436. {
  437. unlink($dest_file);
  438. }
  439. if (! isset($options['ignore-errors']))
  440. {
  441. return $this->raiseError("bad md5sum for file $final_dest_file", PEAR_INSTALLER_FAILED);
  442. }
  443. else
  444. {
  445. if (! isset($options['soft']))
  446. {
  447. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  448. }
  449. }
  450. }
  451. else
  452. {
  453. if (! isset($options['soft']))
  454. {
  455. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  456. }
  457. }
  458. }
  459. }
  460. // }}}
  461. // {{{ set file permissions
  462. if (! OS_WINDOWS)
  463. {
  464. if ($atts['role'] == 'script')
  465. {
  466. $mode = 0777 & ~ (int) octdec($this->config->get('umask'));
  467. $this->log(3, "+ chmod +x $dest_file");
  468. }
  469. else
  470. {
  471. $mode = 0666 & ~ (int) octdec($this->config->get('umask'));
  472. }
  473. if ($atts['role'] != 'src')
  474. {
  475. $this->addFileOperation("chmod", array($mode, $dest_file));
  476. if (! @chmod($dest_file, $mode))
  477. {
  478. if (! isset($options['soft']))
  479. {
  480. $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
  481. }
  482. }
  483. }
  484. }
  485. // }}}
  486. if ($atts['role'] == 'src')
  487. {
  488. rename($dest_file, $final_dest_file);
  489. $this->log(2, "renamed source file $dest_file to $final_dest_file");
  490. }
  491. else
  492. {
  493. $this->addFileOperation("rename", array($dest_file, $final_dest_file, $atts['role'] == 'ext'));
  494. }
  495. }
  496. // Store the full path where the file was installed for easy unistall
  497. if ($atts['role'] != 'script')
  498. {
  499. $loc = $this->config->get($atts['role'] . '_dir');
  500. }
  501. else
  502. {
  503. $loc = $this->config->get('bin_dir');
  504. }
  505. if ($atts['role'] != 'src')
  506. {
  507. $this->addFileOperation("installed_as", array($file, $installed_as, $loc,
  508. dirname(substr($installedas_dest_file, strlen($loc)))));
  509. }
  510. //$this->log(2, "installed: $dest_file");
  511. return PEAR_INSTALLER_OK;
  512. }
  513. // }}}
  514. // {{{ _installFile2()
  515. /**
  516. * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
  517. * @param string filename
  518. * @param array attributes from <file> tag in package.xml
  519. * @param string path to install the file in
  520. * @param array options from command-line
  521. * @access private
  522. */
  523. function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options)
  524. {
  525. $atts = $real_atts;
  526. if (! isset($this->_registry))
  527. {
  528. $this->_registry = &$this->config->getRegistry();
  529. }
  530. $channel = $pkg->getChannel();
  531. // {{{ assemble the destination paths
  532. if (! in_array($atts['attribs']['role'], PEAR_Installer_Role :: getValidRoles($pkg->getPackageType())))
  533. {
  534. return $this->raiseError('Invalid role `' . $atts['attribs']['role'] . "' for file $file");
  535. }
  536. $role = &PEAR_Installer_Role :: factory($pkg, $atts['attribs']['role'], $this->config);
  537. $err = $role->setup($this, $pkg, $atts['attribs'], $file);
  538. if (PEAR :: isError($err))
  539. {
  540. return $err;
  541. }
  542. if (! $role->isInstallable())
  543. {
  544. return;
  545. }
  546. $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path);
  547. if (PEAR :: isError($info))
  548. {
  549. return $info;
  550. }
  551. else
  552. {
  553. list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info;
  554. }
  555. if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file)))
  556. {
  557. return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
  558. }
  559. $final_dest_file = $installed_as = $dest_file;
  560. if (isset($this->_options['packagingroot']))
  561. {
  562. $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']);
  563. }
  564. $dest_dir = dirname($final_dest_file);
  565. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  566. // }}}
  567. if (empty($this->_options['register-only']))
  568. {
  569. if (! file_exists($dest_dir) || ! is_dir($dest_dir))
  570. {
  571. if (! $this->mkDirHier($dest_dir))
  572. {
  573. return $this->raiseError("failed to mkdir $dest_dir", PEAR_INSTALLER_FAILED);
  574. }
  575. $this->log(3, "+ mkdir $dest_dir");
  576. }
  577. }
  578. $attribs = $atts['attribs'];
  579. unset($atts['attribs']);
  580. // pretty much nothing happens if we are only registering the install
  581. if (empty($this->_options['register-only']))
  582. {
  583. if (! count($atts))
  584. { // no tasks
  585. if (! file_exists($orig_file))
  586. {
  587. return $this->raiseError("file $orig_file does not exist", PEAR_INSTALLER_FAILED);
  588. }
  589. if (! @copy($orig_file, $dest_file))
  590. {
  591. return $this->raiseError("failed to write $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  592. }
  593. $this->log(3, "+ cp $orig_file $dest_file");
  594. if (isset($attribs['md5sum']))
  595. {
  596. $md5sum = md5_file($dest_file);
  597. }
  598. }
  599. else
  600. { // file with tasks
  601. if (! file_exists($orig_file))
  602. {
  603. return $this->raiseError("file $orig_file does not exist", PEAR_INSTALLER_FAILED);
  604. }
  605. $contents = file_get_contents($orig_file);
  606. if ($contents === false)
  607. {
  608. $contents = '';
  609. }
  610. if (isset($attribs['md5sum']))
  611. {
  612. $md5sum = md5($contents);
  613. }
  614. foreach ($atts as $tag => $raw)
  615. {
  616. $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag);
  617. $task = "PEAR_Task_$tag";
  618. $task = &new $task($this->config, $this, PEAR_TASK_INSTALL);
  619. if (! $task->isScript())
  620. { // scripts are only handled after installation
  621. $task->init($raw, $attribs, $pkg->getLastInstalledVersion());
  622. $res = $task->startSession($pkg, $contents, $final_dest_file);
  623. if ($res === false)
  624. {
  625. continue; // skip this file
  626. }
  627. if (PEAR :: isError($res))
  628. {
  629. return $res;
  630. }
  631. $contents = $res; // save changes
  632. }
  633. $wp = @fopen($dest_file, "wb");
  634. if (! is_resource($wp))
  635. {
  636. return $this->raiseError("failed to create $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  637. }
  638. if (fwrite($wp, $contents) === false)
  639. {
  640. return $this->raiseError("failed writing to $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  641. }
  642. fclose($wp);
  643. }
  644. }
  645. // {{{ check the md5
  646. if (isset($md5sum))
  647. {
  648. if (strtolower($md5sum) === strtolower($attribs['md5sum']))
  649. {
  650. $this->log(2, "md5sum ok: $final_dest_file");
  651. }
  652. else
  653. {
  654. if (empty($options['force']))
  655. {
  656. // delete the file
  657. if (file_exists($dest_file))
  658. {
  659. unlink($dest_file);
  660. }
  661. if (! isset($options['ignore-errors']))
  662. {
  663. return $this->raiseError("bad md5sum for file $final_dest_file", PEAR_INSTALLER_FAILED);
  664. }
  665. else
  666. {
  667. if (! isset($options['soft']))
  668. {
  669. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  670. }
  671. }
  672. }
  673. else
  674. {
  675. if (! isset($options['soft']))
  676. {
  677. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  678. }
  679. }
  680. }
  681. }
  682. else
  683. {
  684. $real_atts['attribs']['md5sum'] = md5_file($dest_file);
  685. }
  686. // }}}
  687. // {{{ set file permissions
  688. if (! OS_WINDOWS)
  689. {
  690. if ($role->isExecutable())
  691. {
  692. $mode = 0777 & ~ (int) octdec($this->config->get('umask'));
  693. $this->log(3, "+ chmod +x $dest_file");
  694. }
  695. else
  696. {
  697. $mode = 0666 & ~ (int) octdec($this->config->get('umask'));
  698. }
  699. if ($attribs['role'] != 'src')
  700. {
  701. $this->addFileOperation("chmod", array($mode, $dest_file));
  702. if (! @chmod($dest_file, $mode))
  703. {
  704. if (! isset($options['soft']))
  705. {
  706. $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
  707. }
  708. }
  709. }
  710. }
  711. // }}}
  712. if ($attribs['role'] == 'src')
  713. {
  714. rename($dest_file, $final_dest_file);
  715. $this->log(2, "renamed source file $dest_file to $final_dest_file");
  716. }
  717. else
  718. {
  719. $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension()));
  720. }
  721. }
  722. // Store the full path where the file was installed for easy uninstall
  723. if ($attribs['role'] != 'src')
  724. {
  725. $loc = $this->config->get($role->getLocationConfig(), null, $channel);
  726. $this->addFileOperation("installed_as", array($file, $installed_as, $loc,
  727. dirname(substr($installed_as, strlen($loc)))));
  728. }
  729. //$this->log(2, "installed: $dest_file");
  730. return PEAR_INSTALLER_OK;
  731. }
  732. // }}}
  733. // {{{ addFileOperation()
  734. /**
  735. * Add a file operation to the current file transaction.
  736. *
  737. * @see startFileTransaction()
  738. * @param string $type This can be one of:
  739. * - rename: rename a file ($data has 3 values)
  740. * - backup: backup an existing file ($data has 1 value)
  741. * - removebackup: clean up backups created during install ($data has 1 value)
  742. * - chmod: change permissions on a file ($data has 2 values)
  743. * - delete: delete a file ($data has 1 value)
  744. * - rmdir: delete a directory if empty ($data has 1 value)
  745. * - installed_as: mark a file as installed ($data has 4 values).
  746. * @param array $data For all file operations, this array must contain the
  747. * full path to the file or directory that is being operated on. For
  748. * the rename command, the first parameter must be the file to rename,
  749. * the second its new name, the third whether this is a PHP extension.
  750. *
  751. * The installed_as operation contains 4 elements in this order:
  752. * 1. Filename as listed in the filelist element from package.xml
  753. * 2. Full path to the installed file
  754. * 3. Full path from the php_dir configuration variable used in this
  755. * installation
  756. * 4. Relative path from the php_dir that this file is installed in
  757. */
  758. function addFileOperation($type, $data)
  759. {
  760. if (! is_array($data))
  761. {
  762. return $this->raiseError('Internal Error: $data in addFileOperation' . ' must be an array, was ' . gettype($data));
  763. }
  764. if ($type == 'chmod')
  765. {
  766. $octmode = decoct($data[0]);
  767. $this->log(3, "adding to transaction: $type $octmode $data[1]");
  768. }
  769. else
  770. {
  771. $this->log(3, "adding to transaction: $type " . implode(" ", $data));
  772. }
  773. $this->file_operations[] = array($type, $data);
  774. }
  775. // }}}
  776. // {{{ startFileTransaction()
  777. function startFileTransaction($rollback_in_case = false)
  778. {
  779. if (count($this->file_operations) && $rollback_in_case)
  780. {
  781. $this->rollbackFileTransaction();
  782. }
  783. $this->file_operations = array();
  784. }
  785. // }}}
  786. // {{{ commitFileTransaction()
  787. function commitFileTransaction()
  788. {
  789. $n = count($this->file_operations);
  790. $this->log(2, "about to commit $n file operations");
  791. // {{{ first, check permissions and such manually
  792. $errors = array();
  793. foreach ($this->file_operations as $tr)
  794. {
  795. list($type, $data) = $tr;
  796. switch ($type)
  797. {
  798. case 'rename' :
  799. if (! file_exists($data[0]))
  800. {
  801. $errors[] = "cannot rename file $data[0], doesn't exist";
  802. }
  803. // check that dest dir. is writable
  804. if (! is_writable(dirname($data[1])))
  805. {
  806. $errors[] = "permission denied ($type): $data[1]";
  807. }
  808. break;
  809. case 'chmod' :
  810. // check that file is writable
  811. if (! is_writable($data[1]))
  812. {
  813. $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
  814. }
  815. break;
  816. case 'delete' :
  817. if (! file_exists($data[0]))
  818. {
  819. $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
  820. }
  821. // check that directory is writable
  822. if (file_exists($data[0]))
  823. {
  824. if (! is_writable(dirname($data[0])))
  825. {
  826. $errors[] = "permission denied ($type): $data[0]";
  827. }
  828. else
  829. {
  830. // make sure the file to be deleted can be opened for writing
  831. $fp = false;
  832. if (! is_dir($data[0]) && (! is_writable($data[0]) || ! ($fp = @fopen($data[0], 'a'))))
  833. {
  834. $errors[] = "permission denied ($type): $data[0]";
  835. }
  836. elseif ($fp)
  837. {
  838. fclose($fp);
  839. }
  840. }
  841. }
  842. break;
  843. }
  844. }
  845. // }}}
  846. $m = sizeof($errors);
  847. if ($m > 0)
  848. {
  849. foreach ($errors as $error)
  850. {
  851. if (! isset($this->_options['soft']))
  852. {
  853. $this->log(1, $error);
  854. }
  855. }
  856. if (! isset($this->_options['ignore-errors']))
  857. {
  858. return false;
  859. }
  860. }
  861. $this->_dirtree = array();
  862. // {{{ really commit the transaction
  863. foreach ($this->file_operations as $i => $tr)
  864. {
  865. if (! $tr)
  866. {
  867. // support removal of non-existing backups
  868. continue;
  869. }
  870. list($type, $data) = $tr;
  871. switch ($type)
  872. {
  873. case 'backup' :
  874. if (! file_exists($data[0]))
  875. {
  876. $this->file_operations[$i] = false;
  877. break;
  878. }
  879. if (! @copy($data[0], $data[0] . '.bak'))
  880. {
  881. $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] . '.bak ' . $php_errormsg);
  882. return false;
  883. }
  884. $this->log(3, "+ backup $data[0] to $data[0].bak");
  885. break;
  886. case 'removebackup' :
  887. if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak'))
  888. {
  889. unlink($data[0] . '.bak');
  890. $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
  891. }
  892. break;
  893. case 'rename' :
  894. if (file_exists($data[1]))
  895. {
  896. $test = @unlink($data[1]);
  897. }
  898. else
  899. {
  900. $test = null;
  901. }
  902. if (! $test && file_exists($data[1]))
  903. {
  904. if ($data[2])
  905. {
  906. $extra = ', this extension must be installed manually. Rename to "' . basename($data[1]) . '"';
  907. }
  908. else
  909. {
  910. $extra = '';
  911. }
  912. if (! isset($this->_options['soft']))
  913. {
  914. $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' . $data[0] . $extra);
  915. }
  916. if (! isset($this->_options['ignore-errors']))
  917. {
  918. return false;
  919. }
  920. }
  921. // permissions issues with rename - copy() is far superior
  922. $perms = @fileperms($data[0]);
  923. if (! @copy($data[0], $data[1]))
  924. {
  925. $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] . ' ' . $php_errormsg);
  926. return false;
  927. }
  928. // copy over permissions, otherwise they are lost
  929. @chmod($data[1], $perms);
  930. @unlink($data[0]);
  931. $this->log(3, "+ mv $data[0] $data[1]");
  932. break;
  933. case 'chmod' :
  934. if (! @chmod($data[1], $data[0]))
  935. {
  936. $this->log(1, 'Could not chmod ' . $data[1] . ' to ' . decoct($data[0]) . ' ' . $php_errormsg);
  937. return false;
  938. }
  939. $octmode = decoct($data[0]);
  940. $this->log(3, "+ chmod $octmode $data[1]");
  941. break;
  942. case 'delete' :
  943. if (file_exists($data[0]))
  944. {
  945. if (! @unlink($data[0]))
  946. {
  947. $this->log(1, 'Could not delete ' . $data[0] . ' ' . $php_errormsg);
  948. return false;
  949. }
  950. $this->log(3, "+ rm $data[0]");
  951. }
  952. break;
  953. case 'rmdir' :
  954. if (file_exists($data[0]))
  955. {
  956. do
  957. {
  958. $testme = opendir($data[0]);
  959. while (false !== ($entry = readdir($testme)))
  960. {
  961. if ($entry == '.' || $entry == '..')
  962. {
  963. continue;
  964. }
  965. closedir($testme);
  966. break 2; // this directory is not empty and can't be
  967. // deleted
  968. }
  969. closedir($testme);
  970. if (! @rmdir($data[0]))
  971. {
  972. $this->log(1, 'Could not rmdir ' . $data[0] . ' ' . $php_errormsg);
  973. return false;
  974. }
  975. $this->log(3, "+ rmdir $data[0]");
  976. }
  977. while (false);
  978. }
  979. break;
  980. case 'installed_as' :
  981. $this->pkginfo->setInstalledAs($data[0], $data[1]);
  982. if (! isset($this->_dirtree[dirname($data[1])]))
  983. {
  984. $this->_dirtree[dirname($data[1])] = true;
  985. $this->pkginfo->setDirtree(dirname($data[1]));
  986. while (! empty($data[3]) && dirname($data[3]) != $data[3] && $data[3] != '/' && $data[3] != '\\')
  987. {
  988. $this->pkginfo->setDirtree($pp = $this->_prependPath($data[3], $data[2]));
  989. $this->_dirtree[$pp] = true;
  990. $data[3] = dirname($data[3]);
  991. }
  992. }
  993. break;
  994. }
  995. }
  996. // }}}
  997. $this->log(2, "successfully committed $n file operations");
  998. $this->file_operations = array();
  999. return true;
  1000. }
  1001. // }}}
  1002. // {{{ rollbackFileTransaction()
  1003. function rollbackFileTransaction()
  1004. {
  1005. $n = count($this->file_operations);
  1006. $this->log(2, "rolling back $n file operations");
  1007. foreach ($this->file_operations as $tr)
  1008. {
  1009. list($type, $data) = $tr;
  1010. switch ($type)
  1011. {
  1012. case 'backup' :
  1013. if (file_exists($data[0] . '.bak'))
  1014. {
  1015. if (file_exists($data[0] && is_writable($data[0])))
  1016. {
  1017. unlink($data[0]);
  1018. }
  1019. @copy($data[0] . '.bak', $data[0]);
  1020. $this->log(3, "+ restore $data[0] from $data[0].bak");
  1021. }
  1022. break;
  1023. case 'removebackup' :
  1024. if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak'))
  1025. {
  1026. unlink($data[0] . '.bak');
  1027. $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
  1028. }
  1029. break;
  1030. case 'rename' :
  1031. @unlink($data[0]);
  1032. $this->log(3, "+ rm $data[0]");
  1033. break;
  1034. case 'mkdir' :
  1035. @rmdir($data[0]);
  1036. $this->log(3, "+ rmdir $data[0]");
  1037. break;
  1038. case 'chmod' :
  1039. break;
  1040. case 'delete' :
  1041. break;
  1042. case 'installed_as' :
  1043. $this->pkginfo->setInstalledAs($data[0], false);
  1044. break;
  1045. }
  1046. }
  1047. $this->pkginfo->resetDirtree();
  1048. $this->file_operations = array();
  1049. }
  1050. // }}}
  1051. // {{{ mkDirHier($dir)
  1052. function mkDirHier($dir)
  1053. {
  1054. $this->addFileOperation('mkdir', array($dir));
  1055. return parent :: mkDirHier($dir);
  1056. }
  1057. // }}}
  1058. // {{{ download()
  1059. /**
  1060. * Download any files and their dependencies, if necessary
  1061. *
  1062. * @param array a mixed list of package names, local files, or package.xml
  1063. * @param PEAR_Config
  1064. * @param array options from the command line
  1065. * @param array this is the array that will be populated with packages to
  1066. * install. Format of each entry:
  1067. *
  1068. * <code>
  1069. * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
  1070. * 'info' => array() // parsed package.xml
  1071. * );
  1072. * </code>
  1073. * @param array this will be populated with any error messages
  1074. * @param false private recursion variable
  1075. * @param false private recursion variable
  1076. * @param false private recursion variable
  1077. * @deprecated in favor of PEAR_Downloader
  1078. */
  1079. function download($packages, $options, &$config, &$installpackages, &$errors, $installed = false, $willinstall = false, $state = false)
  1080. {
  1081. // trickiness: initialize here
  1082. parent :: PEAR_Downloader($this->ui, $options, $config);
  1083. $ret = parent :: download($packages);
  1084. $errors = $this->getErrorMsgs();
  1085. $installpackages = $this->getDownloadedPackages();
  1086. trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " . "in favor of PEAR_Downloader class", E_USER_WARNING);
  1087. return $ret;
  1088. }
  1089. // }}}
  1090. // {{{ _parsePackageXml()
  1091. function _parsePackageXml(&$descfile, &$tmpdir)
  1092. {
  1093. if (substr($descfile, - 4) == '.xml')
  1094. {
  1095. $tmpdir = false;
  1096. }
  1097. else
  1098. {
  1099. // {{{ Decompress pack in tmp dir -------------------------------------
  1100. // To allow relative package file names
  1101. $descfile = realpath($descfile);
  1102. if (PEAR :: isError($tmpdir = System :: mktemp('-d')))
  1103. {
  1104. return $tmpdir;
  1105. }
  1106. $this->log(3, '+ tmp dir created at ' . $tmpdir);
  1107. // }}}
  1108. }
  1109. // Parse xml file -----------------------------------------------
  1110. $pkg = new PEAR_PackageFile($this->config, $this->debug, $tmpdir);
  1111. PEAR :: staticPushErrorHandling(PEAR_ERROR_RETURN);
  1112. $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING);
  1113. PEAR :: staticPopErrorHandling();
  1114. if (PEAR :: isError($p))
  1115. {
  1116. if (is_array($p->getUserInfo()))
  1117. {
  1118. foreach ($p->getUserInfo() as $err)
  1119. {
  1120. $loglevel = $err['level'] == 'error' ? 0 : 1;
  1121. if (! isset($this->_options['soft']))
  1122. {
  1123. $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']);
  1124. }
  1125. }
  1126. }
  1127. return $this->raiseError('Installation failed: invalid package file');
  1128. }
  1129. else
  1130. {
  1131. $descfile = $p->getPackageFile();
  1132. }
  1133. return $p;
  1134. }
  1135. // }}}
  1136. /**
  1137. * Set the list of PEAR_Downloader_Package objects to allow more sane
  1138. * dependency validation
  1139. * @param array
  1140. */
  1141. function setDownloadedPackages(&$pkgs)
  1142. {
  1143. PEAR :: pushErrorHandling(PEAR_ERROR_RETURN);
  1144. $err = $this->analyzeDependencies($pkgs);
  1145. PEAR :: popErrorHandling();
  1146. if (PEAR :: isError($err))
  1147. {
  1148. return $err;
  1149. }
  1150. $this->_downloadedPackages = &$pkgs;
  1151. }
  1152. /**
  1153. * Set the list of PEAR_Downloader_Package objects to allow more sane
  1154. * dependency validation
  1155. * @param array
  1156. */
  1157. function setUninstallPackages(&$pkgs)
  1158. {
  1159. $this->_downloadedPackages = &$pkgs;
  1160. }
  1161. function getInstallPackages()
  1162. {
  1163. return $this->_downloadedPackages;
  1164. }
  1165. // {{{ install()
  1166. /**
  1167. * Installs the files within the package file specified.
  1168. *
  1169. * @param string|PEAR_Downloader_Package $pkgfile path to the package file,
  1170. * or a pre-initialized packagefile object
  1171. * @param array $options
  1172. * recognized options:
  1173. * - installroot : optional prefix directory for installation
  1174. * - force : force installation
  1175. * - register-only : update registry but don't install files
  1176. * - upgrade : upgrade existing install
  1177. * - soft : fail silently
  1178. * - nodeps : ignore dependency conflicts/missing dependencies
  1179. * - alldeps : install all dependencies
  1180. * - onlyreqdeps : install only required dependencies
  1181. *
  1182. * @return array|PEAR_Error package info if successful
  1183. */
  1184. function install($pkgfile, $options = array())
  1185. {
  1186. $this->_options = $options;
  1187. $this->_registry = &$this->config->getRegistry();
  1188. if (is_object($pkgfile))
  1189. {
  1190. $dlpkg = &$pkgfile;
  1191. $pkg = $pkgfile->getPackageFile();
  1192. $pkgfile = $pkg->getArchiveFile();
  1193. $descfile = $pkg->getPackageFile();
  1194. $tmpdir = dirname($descfile);
  1195. }
  1196. else
  1197. {
  1198. $descfile = $pkgfile;
  1199. $tmpdir = '';
  1200. if (PEAR :: isError($pkg = &$this->_parsePackageXml($descfile, $tmpdir)))
  1201. {
  1202. return $pkg;
  1203. }
  1204. }
  1205. if (realpath($descfile) != realpath($pkgfile))
  1206. {
  1207. $tar = new Archive_Tar($pkgfile);
  1208. if (! $tar->extract($tmpdir))
  1209. {
  1210. return $this->raiseError("unable to unpack $pkgfile");
  1211. }
  1212. }
  1213. $pkgname = $pkg->getName();
  1214. $channel = $pkg->getChannel();
  1215. if (isset($this->_options['packagingroot']))
  1216. {
  1217. $regdir = $this->_prependPath($this->config->get('php_dir', null, 'pear.php.net'), $this->_options['packagingroot']);
  1218. $packrootphp_dir = $this->_prependPath($this->config->get('php_dir', null, $channel), $this->_options['packagingroot']);
  1219. }
  1220. if (isset($options['installroot']))
  1221. {
  1222. $this->config->setInstallRoot($options['installroot']);
  1223. $this->_registry = &$this->config->getRegistry();
  1224. $installregistry = &$this->_registry;
  1225. $this->installroot = ''; // all done automagically now
  1226. $php_dir = $this->config->get('php_dir', null, $channel);
  1227. }
  1228. else
  1229. {
  1230. $this->config->setInstallRoot(false);
  1231. $this->_registry = &$this->config->getRegistry();
  1232. if (isset($this->_options['packagingroot']))
  1233. {
  1234. $installregistry = &new PEAR_Registry($regdir);
  1235. if (! $installregistry->channelExists($channel, true))
  1236. {
  1237. // we need to fake a channel-discover of this channel
  1238. $chanobj = $this->_registry->getChannel($channel, true);
  1239. $installregistry->addChannel($chanobj);
  1240. }
  1241. $php_dir = $packrootphp_dir;
  1242. }
  1243. else
  1244. {
  1245. $installregistry = &$this->_registry;
  1246. $php_dir = $this->config->get('php_dir', null, $channel);
  1247. }
  1248. $this->installroot = '';
  1249. }
  1250. // {{{ checks to do when not in "force" mode
  1251. if (empty($options['force']) && (file_exists($this->config->get('php_dir')) && is_dir($this->config->get('php_dir'))))
  1252. {
  1253. $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname);
  1254. $instfilelist = $pkg->getInstallationFileList(true);
  1255. if (PEAR :: isError($instfilelist))
  1256. {
  1257. return $instfilelist;
  1258. }
  1259. // ensure we have the most accurate registry
  1260. $installregistry->flushFileMap();
  1261. $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1');
  1262. if (PEAR :: isError($test))
  1263. {
  1264. return $test;
  1265. }
  1266. if (sizeof($test))
  1267. {
  1268. $pkgs = $this->getInstallPackages();
  1269. $found = false;
  1270. foreach ($pkgs as $param)
  1271. {
  1272. if ($pkg->isSubpackageOf($param))
  1273. {
  1274. $found = true;
  1275. break;

Large files files are truncated, but you can click here to view the full file