PageRenderTime 64ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/inc/app/siteinvoice/lib/PEAR/PEAR/Installer.php

https://github.com/lux/sitellite
PHP | 1660 lines | 1290 code | 82 blank | 288 comment | 262 complexity | ec095157f58b890c8ea812a4112b103d MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, Apache-2.0, GPL-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-2005 The PHP Group
  20. * @license http://www.php.net/license/3_0.txt PHP License 3.0
  21. * @version CVS: $Id: Installer.php,v 1.1 2005/07/02 21:12:30 lux Exp $
  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-2005 The PHP Group
  42. * @license http://www.php.net/license/3_0.txt PHP License 3.0
  43. * @version Release: 1.4.0a12
  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 PEAR_Installer(&$ui)
  111. {
  112. parent::PEAR_Common();
  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. $this->addFileOperation('removebackup', array($path));
  130. }
  131. }
  132. // {{{ _deletePackageFiles()
  133. /**
  134. * Delete a package's installed files, does not remove empty directories.
  135. *
  136. * @param string package name
  137. * @param string channel name
  138. * @param bool if true, then files are backed up first
  139. * @return bool TRUE on success, or a PEAR error on failure
  140. * @access private
  141. */
  142. function _deletePackageFiles($package, $channel = false, $backup = false)
  143. {
  144. if (!$channel) {
  145. $channel = 'pear.php.net';
  146. }
  147. if (!strlen($package)) {
  148. return $this->raiseError("No package to uninstall given");
  149. }
  150. if (strtolower($package) == 'pear' && $channel == 'pear.php.net') {
  151. // to avoid race conditions, include all possible needed files
  152. require_once 'PEAR/Task/Common.php';
  153. require_once 'PEAR/Task/Replace.php';
  154. require_once 'PEAR/Task/Unixeol.php';
  155. require_once 'PEAR/Task/Windowseol.php';
  156. require_once 'PEAR/PackageFile/v1.php';
  157. require_once 'PEAR/PackageFile/v2.php';
  158. require_once 'PEAR/PackageFile/Generator/v1.php';
  159. require_once 'PEAR/PackageFile/Generator/v2.php';
  160. }
  161. $filelist = $this->_registry->packageInfo($package, 'filelist', $channel);
  162. if ($filelist == null) {
  163. return $this->raiseError("$channel/$package not installed");
  164. }
  165. $ret = array();
  166. if ($this->config->isDefinedLayer('ftp') && isset($this->_options['upgrade'])) {
  167. $pkg = $this->_registry->getPackage($package, $channel);
  168. $this->ftpUninstall($pkg); // no error checking
  169. }
  170. foreach ($filelist as $file => $props) {
  171. if (empty($props['installed_as'])) {
  172. continue;
  173. }
  174. $path = $props['installed_as'];
  175. if ($backup) {
  176. $this->addFileOperation('backup', array($path));
  177. $ret[] = $path;
  178. }
  179. $this->addFileOperation('delete', array($path));
  180. }
  181. if ($backup) {
  182. return $ret;
  183. }
  184. return true;
  185. }
  186. // }}}
  187. // {{{ _installFile()
  188. /**
  189. * @param string filename
  190. * @param array attributes from <file> tag in package.xml
  191. * @param string path to install the file in
  192. * @param array options from command-line
  193. * @access private
  194. */
  195. function _installFile($file, $atts, $tmp_path, $options)
  196. {
  197. // {{{ return if this file is meant for another platform
  198. static $os;
  199. if (!isset($this->_registry)) {
  200. $this->_registry = &$this->config->getRegistry();
  201. }
  202. if (isset($atts['platform'])) {
  203. if (empty($os)) {
  204. $os = new OS_Guess();
  205. }
  206. if (strlen($atts['platform']) && $atts['platform']{0} == '!') {
  207. $negate = true;
  208. $platform = substr($atts['platform'], 1);
  209. } else {
  210. $negate = false;
  211. $platform = $atts['platform'];
  212. }
  213. if ((bool) $os->matchSignature($platform) === $negate) {
  214. $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")");
  215. return PEAR_INSTALLER_SKIPPED;
  216. }
  217. }
  218. // }}}
  219. $channel = $this->pkginfo->getChannel();
  220. // {{{ assemble the destination paths
  221. switch ($atts['role']) {
  222. case 'doc':
  223. case 'data':
  224. case 'test':
  225. $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) .
  226. DIRECTORY_SEPARATOR . $this->pkginfo->getPackage();
  227. unset($atts['baseinstalldir']);
  228. break;
  229. case 'ext':
  230. case 'php':
  231. $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel);
  232. break;
  233. case 'script':
  234. $dest_dir = $this->config->get('bin_dir', null, $channel);
  235. break;
  236. case 'src':
  237. case 'extsrc':
  238. $this->source_files++;
  239. return;
  240. default:
  241. return $this->raiseError("Invalid role `$atts[role]' for file $file");
  242. }
  243. $save_destdir = $dest_dir;
  244. if (!empty($atts['baseinstalldir'])) {
  245. $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
  246. }
  247. if (dirname($file) != '.' && empty($atts['install-as'])) {
  248. $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
  249. }
  250. if (empty($atts['install-as'])) {
  251. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
  252. } else {
  253. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
  254. }
  255. $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
  256. // Clean up the DIRECTORY_SEPARATOR mess
  257. $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
  258. list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
  259. array(DIRECTORY_SEPARATOR,
  260. DIRECTORY_SEPARATOR,
  261. DIRECTORY_SEPARATOR),
  262. array($dest_file, $orig_file));
  263. $final_dest_file = $installed_as = $dest_file;
  264. $dest_dir = dirname($final_dest_file);
  265. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  266. // }}}
  267. if (!@is_dir($dest_dir)) {
  268. if (!$this->mkDirHier($dest_dir)) {
  269. return $this->raiseError("failed to mkdir $dest_dir",
  270. PEAR_INSTALLER_FAILED);
  271. }
  272. $this->log(3, "+ mkdir $dest_dir");
  273. }
  274. if (empty($atts['replacements'])) {
  275. if (!file_exists($orig_file)) {
  276. return $this->raiseError("file $orig_file does not exist",
  277. PEAR_INSTALLER_FAILED);
  278. }
  279. if (!@copy($orig_file, $dest_file)) {
  280. return $this->raiseError("failed to write $dest_file",
  281. PEAR_INSTALLER_FAILED);
  282. }
  283. $this->log(3, "+ cp $orig_file $dest_file");
  284. if (isset($atts['md5sum'])) {
  285. $md5sum = md5_file($dest_file);
  286. }
  287. } else {
  288. // {{{ file with replacements
  289. if (!file_exists($orig_file)) {
  290. return $this->raiseError("file does not exist",
  291. PEAR_INSTALLER_FAILED);
  292. }
  293. $fp = fopen($orig_file, "r");
  294. $contents = @fread($fp, filesize($orig_file));
  295. if ($contents === false) {
  296. $contents = '';
  297. }
  298. fclose($fp);
  299. if (isset($atts['md5sum'])) {
  300. $md5sum = md5($contents);
  301. }
  302. $subst_from = $subst_to = array();
  303. foreach ($atts['replacements'] as $a) {
  304. $to = '';
  305. if ($a['type'] == 'php-const') {
  306. if (preg_match('/^[a-z0-9_]+$/i', $a['to'])) {
  307. eval("\$to = $a[to];");
  308. } else {
  309. if (!isset($options['soft'])) {
  310. $this->log(0, "invalid php-const replacement: $a[to]");
  311. }
  312. continue;
  313. }
  314. } elseif ($a['type'] == 'pear-config') {
  315. if ($a['to'] == 'master_server') {
  316. $chan = $this->_registry->getChannel($channel);
  317. if ($chan) {
  318. $to = $chan->getServer();
  319. } else {
  320. $to = $this->config->get($a['to'], null, $channel);
  321. }
  322. } else {
  323. $to = $this->config->get($a['to'], null, $channel);
  324. }
  325. if (is_null($to)) {
  326. if (!isset($options['soft'])) {
  327. $this->log(0, "invalid pear-config replacement: $a[to]");
  328. }
  329. continue;
  330. }
  331. } elseif ($a['type'] == 'package-info') {
  332. if ($t = $this->pkginfo->packageInfo($a['to'])) {
  333. $to = $t;
  334. } else {
  335. if (!isset($options['soft'])) {
  336. $this->log(0, "invalid package-info replacement: $a[to]");
  337. }
  338. continue;
  339. }
  340. }
  341. if (!is_null($to)) {
  342. $subst_from[] = $a['from'];
  343. $subst_to[] = $to;
  344. }
  345. }
  346. $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file");
  347. if (sizeof($subst_from)) {
  348. $contents = str_replace($subst_from, $subst_to, $contents);
  349. }
  350. $wp = @fopen($dest_file, "wb");
  351. if (!is_resource($wp)) {
  352. return $this->raiseError("failed to create $dest_file: $php_errormsg",
  353. PEAR_INSTALLER_FAILED);
  354. }
  355. if (fwrite($wp, $contents) === false) {
  356. return $this->raiseError("failed writing to $dest_file: $php_errormsg",
  357. PEAR_INSTALLER_FAILED);
  358. }
  359. fclose($wp);
  360. // }}}
  361. }
  362. // {{{ check the md5
  363. if (isset($md5sum)) {
  364. if (strtolower($md5sum) == strtolower($atts['md5sum'])) {
  365. $this->log(2, "md5sum ok: $final_dest_file");
  366. } else {
  367. if (empty($options['force'])) {
  368. // delete the file
  369. @unlink($dest_file);
  370. if (!isset($options['ignore-errors'])) {
  371. return $this->raiseError("bad md5sum for file $final_dest_file",
  372. PEAR_INSTALLER_FAILED);
  373. } else {
  374. if (!isset($options['soft'])) {
  375. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  376. }
  377. }
  378. } else {
  379. if (!isset($options['soft'])) {
  380. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  381. }
  382. }
  383. }
  384. }
  385. // }}}
  386. // {{{ set file permissions
  387. if (!OS_WINDOWS) {
  388. if ($atts['role'] == 'script') {
  389. $mode = 0777 & ~(int)octdec($this->config->get('umask'));
  390. $this->log(3, "+ chmod +x $dest_file");
  391. } else {
  392. $mode = 0666 & ~(int)octdec($this->config->get('umask'));
  393. }
  394. $this->addFileOperation("chmod", array($mode, $dest_file));
  395. if (!@chmod($dest_file, $mode)) {
  396. if (!isset($options['soft'])) {
  397. $this->log(0, "failed to change mode of $dest_file");
  398. }
  399. }
  400. }
  401. // }}}
  402. $this->addFileOperation("rename", array($dest_file, $final_dest_file,
  403. $atts['role'] == 'ext'));
  404. // Store the full path where the file was installed for easy unistall
  405. $this->addFileOperation("installed_as", array($file, $installed_as,
  406. $save_destdir, dirname(substr($dest_file, strlen($save_destdir)))));
  407. //$this->log(2, "installed: $dest_file");
  408. return PEAR_INSTALLER_OK;
  409. }
  410. // }}}
  411. // {{{ _installFile2()
  412. /**
  413. * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
  414. * @param string filename
  415. * @param array attributes from <file> tag in package.xml
  416. * @param string path to install the file in
  417. * @param array options from command-line
  418. * @access private
  419. */
  420. function _installFile2(&$pkg, $file, $atts, $tmp_path, $options)
  421. {
  422. if (!isset($this->_registry)) {
  423. $this->_registry = &$this->config->getRegistry();
  424. }
  425. $channel = $pkg->getChannel();
  426. // {{{ assemble the destination paths
  427. if (!in_array($atts['attribs']['role'],
  428. PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) {
  429. return $this->raiseError('Invalid role `' . $atts['attribs']['role'] .
  430. "' for file $file");
  431. }
  432. $role = &PEAR_Installer_Role::factory($pkg, $atts['attribs']['role'], $this->config);
  433. $role->setup($this, $pkg, $atts['attribs'], $file);
  434. if (!$role->isInstallable()) {
  435. return;
  436. }
  437. list($save_destdir, $dest_dir, $dest_file, $orig_file) =
  438. $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path);
  439. $final_dest_file = $installed_as = $dest_file;
  440. $dest_dir = dirname($final_dest_file);
  441. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  442. // }}}
  443. if (!@is_dir($dest_dir)) {
  444. if (!$this->mkDirHier($dest_dir)) {
  445. return $this->raiseError("failed to mkdir $dest_dir",
  446. PEAR_INSTALLER_FAILED);
  447. }
  448. $this->log(3, "+ mkdir $dest_dir");
  449. }
  450. $attribs = $atts['attribs'];
  451. unset($atts['attribs']);
  452. if (!count($atts)) { // no tasks
  453. if (!file_exists($orig_file)) {
  454. return $this->raiseError("file $orig_file does not exist",
  455. PEAR_INSTALLER_FAILED);
  456. }
  457. if (!@copy($orig_file, $dest_file)) {
  458. return $this->raiseError("failed to write $dest_file",
  459. PEAR_INSTALLER_FAILED);
  460. }
  461. $this->log(3, "+ cp $orig_file $dest_file");
  462. if (isset($attribs['md5sum'])) {
  463. $md5sum = md5_file($dest_file);
  464. }
  465. } else { // file with tasks
  466. if (!file_exists($orig_file)) {
  467. return $this->raiseError("file $orig_file does not exist",
  468. PEAR_INSTALLER_FAILED);
  469. }
  470. $fp = fopen($orig_file, "r");
  471. $contents = @fread($fp, filesize($orig_file)); // filesize can be 0
  472. if ($contents === false) {
  473. $contents = '';
  474. }
  475. fclose($fp);
  476. if (isset($attribs['md5sum'])) {
  477. $md5sum = md5($contents);
  478. }
  479. foreach ($atts as $tag => $raw) {
  480. $tag = str_replace($pkg->getTasksNs() . ':', '', $tag);
  481. $task = "PEAR_Task_$tag";
  482. $task = &new $task($this->config, $this, PEAR_TASK_INSTALL);
  483. if (!$task->isScript()) { // scripts are only handled after installation
  484. $task->init($raw, $attribs, $pkg->getLastInstalledVersion());
  485. $res = $task->startSession($pkg, $contents, $final_dest_file);
  486. if ($res === false) {
  487. continue; // skip this file
  488. }
  489. if (PEAR::isError($res)) {
  490. return $res;
  491. }
  492. $contents = $res; // save changes
  493. }
  494. $wp = @fopen($dest_file, "wb");
  495. if (!is_resource($wp)) {
  496. return $this->raiseError("failed to create $dest_file: $php_errormsg",
  497. PEAR_INSTALLER_FAILED);
  498. }
  499. if (fwrite($wp, $contents) === false) {
  500. return $this->raiseError("failed writing to $dest_file: $php_errormsg",
  501. PEAR_INSTALLER_FAILED);
  502. }
  503. fclose($wp);
  504. }
  505. }
  506. // {{{ check the md5
  507. if (isset($md5sum)) {
  508. if (strtolower($md5sum) == strtolower($attribs['md5sum'])) {
  509. $this->log(2, "md5sum ok: $final_dest_file");
  510. } else {
  511. if (empty($options['force'])) {
  512. // delete the file
  513. @unlink($dest_file);
  514. if (!isset($options['ignore-errors'])) {
  515. return $this->raiseError("bad md5sum for file $final_dest_file",
  516. PEAR_INSTALLER_FAILED);
  517. } else {
  518. if (!isset($options['soft'])) {
  519. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  520. }
  521. }
  522. } else {
  523. if (!isset($options['soft'])) {
  524. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  525. }
  526. }
  527. }
  528. }
  529. // }}}
  530. // {{{ set file permissions
  531. if (!OS_WINDOWS) {
  532. if ($role->isExecutable()) {
  533. $mode = 0777 & ~(int)octdec($this->config->get('umask'));
  534. $this->log(3, "+ chmod +x $dest_file");
  535. } else {
  536. $mode = 0666 & ~(int)octdec($this->config->get('umask'));
  537. }
  538. $this->addFileOperation("chmod", array($mode, $dest_file));
  539. if (!@chmod($dest_file, $mode)) {
  540. if (!isset($options['soft'])) {
  541. $this->log(0, "failed to change mode of $dest_file");
  542. }
  543. }
  544. }
  545. // }}}
  546. $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension()));
  547. // Store the full path where the file was installed for easy unistall
  548. $this->addFileOperation("installed_as", array($file, $installed_as,
  549. $save_destdir, dirname(substr($dest_file, strlen($save_destdir)))));
  550. //$this->log(2, "installed: $dest_file");
  551. return PEAR_INSTALLER_OK;
  552. }
  553. // }}}
  554. // {{{ addFileOperation()
  555. /**
  556. * Add a file operation to the current file transaction.
  557. *
  558. * @see startFileTransaction()
  559. * @param string $type This can be one of:
  560. * - rename: rename a file ($data has 3 values)
  561. * - backup: backup an existing file ($data has 1 value)
  562. * - removebackup: clean up backups created during install ($data has 1 value)
  563. * - chmod: change permissions on a file ($data has 2 values)
  564. * - delete: delete a file ($data has 1 value)
  565. * - rmdir: delete a directory if empty ($data has 1 value)
  566. * - installed_as: mark a file as installed ($data has 4 values).
  567. * @param array $data For all file operations, this array must contain the
  568. * full path to the file or directory that is being operated on. For
  569. * the rename command, the first parameter must be the file to rename,
  570. * the second its new name, the third whether this is a PHP extension.
  571. *
  572. * The installed_as operation contains 4 elements in this order:
  573. * 1. Filename as listed in the filelist element from package.xml
  574. * 2. Full path to the installed file
  575. * 3. Full path from the php_dir configuration variable used in this
  576. * installation
  577. * 4. Relative path from the php_dir that this file is installed in
  578. */
  579. function addFileOperation($type, $data)
  580. {
  581. if (!is_array($data)) {
  582. return $this->raiseError('Internal Error: $data in addFileOperation'
  583. . ' must be an array, was ' . gettype($data));
  584. }
  585. if ($type == 'chmod') {
  586. $octmode = decoct($data[0]);
  587. $this->log(3, "adding to transaction: $type $octmode $data[1]");
  588. } else {
  589. $this->log(3, "adding to transaction: $type " . implode(" ", $data));
  590. }
  591. $this->file_operations[] = array($type, $data);
  592. }
  593. // }}}
  594. // {{{ startFileTransaction()
  595. function startFileTransaction($rollback_in_case = false)
  596. {
  597. if (count($this->file_operations) && $rollback_in_case) {
  598. $this->rollbackFileTransaction();
  599. }
  600. $this->file_operations = array();
  601. }
  602. // }}}
  603. // {{{ commitFileTransaction()
  604. function commitFileTransaction()
  605. {
  606. $n = count($this->file_operations);
  607. $this->log(2, "about to commit $n file operations");
  608. // {{{ first, check permissions and such manually
  609. $errors = array();
  610. foreach ($this->file_operations as $tr) {
  611. list($type, $data) = $tr;
  612. switch ($type) {
  613. case 'rename':
  614. if (!file_exists($data[0])) {
  615. $errors[] = "cannot rename file $data[0], doesn't exist";
  616. }
  617. // check that dest dir. is writable
  618. if (!is_writable(dirname($data[1]))) {
  619. $errors[] = "permission denied ($type): $data[1]";
  620. }
  621. break;
  622. case 'chmod':
  623. // check that file is writable
  624. if (!is_writable($data[1])) {
  625. $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
  626. }
  627. break;
  628. case 'delete':
  629. if (!file_exists($data[0])) {
  630. $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
  631. }
  632. // check that directory is writable
  633. if (file_exists($data[0])) {
  634. if (!is_writable(dirname($data[0]))) {
  635. $errors[] = "permission denied ($type): $data[0]";
  636. } else {
  637. // make sure the file to be deleted can be opened for writing
  638. $fp = false;
  639. if (!is_dir($data[0]) && !($fp = @fopen($data[0], 'a'))) {
  640. $errors[] = "permission denied ($type): $data[0]";
  641. } elseif ($fp) {
  642. fclose($fp);
  643. }
  644. }
  645. }
  646. break;
  647. }
  648. }
  649. // }}}
  650. $m = sizeof($errors);
  651. if ($m > 0) {
  652. foreach ($errors as $error) {
  653. if (!isset($this->_options['soft'])) {
  654. $this->log(1, $error);
  655. }
  656. }
  657. if (!isset($this->_options['ignore-errors'])) {
  658. return false;
  659. }
  660. }
  661. $this->_dirtree = array();
  662. // {{{ really commit the transaction
  663. foreach ($this->file_operations as $tr) {
  664. list($type, $data) = $tr;
  665. switch ($type) {
  666. case 'backup':
  667. @copy($data[0], $data[0] . '.bak');
  668. $this->log(3, "+ backup $data[0] to $data[0].bak");
  669. break;
  670. case 'removebackup':
  671. @unlink($data[0] . '.bak');
  672. $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
  673. break;
  674. case 'rename':
  675. $test = @unlink($data[1]);
  676. if (!$test && file_exists($data[1])) {
  677. if ($data[2]) {
  678. $extra = ', this extension must be installed manually. Rename to "' .
  679. basename($data[1]) . '"';
  680. } else {
  681. $extra = '';
  682. }
  683. if (!isset($this->_options['soft'])) {
  684. $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' .
  685. $data[0] . $extra);
  686. }
  687. if (!isset($this->_options['ignore-errors'])) {
  688. return false;
  689. }
  690. }
  691. @rename($data[0], $data[1]);
  692. $this->log(3, "+ mv $data[0] $data[1]");
  693. break;
  694. case 'chmod':
  695. @chmod($data[1], $data[0]);
  696. $octmode = decoct($data[0]);
  697. $this->log(3, "+ chmod $octmode $data[1]");
  698. break;
  699. case 'delete':
  700. @unlink($data[0]);
  701. $this->log(3, "+ rm $data[0]");
  702. break;
  703. case 'rmdir':
  704. @rmdir($data[0]);
  705. $this->log(3, "+ rmdir $data[0]");
  706. break;
  707. case 'installed_as':
  708. $this->pkginfo->setInstalledAs($data[0], $data[1]);
  709. if (!isset($this->_dirtree[dirname($data[1])])) {
  710. $this->_dirtree[dirname($data[1])] = true;
  711. $this->pkginfo->setDirtree(dirname($data[1]));
  712. while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\'
  713. && $data[3] != '.') {
  714. $this->pkginfo->setDirtree($pp =
  715. $this->_prependPath($data[3], $data[2]));
  716. $this->_dirtree[$pp] = true;
  717. $data[3] = dirname($data[3]);
  718. }
  719. }
  720. break;
  721. }
  722. }
  723. // }}}
  724. $this->log(2, "successfully committed $n file operations");
  725. $this->file_operations = array();
  726. return true;
  727. }
  728. // }}}
  729. // {{{ rollbackFileTransaction()
  730. function rollbackFileTransaction()
  731. {
  732. $n = count($this->file_operations);
  733. $this->log(2, "rolling back $n file operations");
  734. foreach ($this->file_operations as $tr) {
  735. list($type, $data) = $tr;
  736. switch ($type) {
  737. case 'backup':
  738. if (file_exists($data[0] . '.bak')) {
  739. @unlink($data[0]);
  740. @copy($data[0] . '.bak', $data[0]);
  741. $this->log(3, "+ restore $data[0] from $data[0].bak");
  742. }
  743. break;
  744. case 'rename':
  745. @unlink($data[0]);
  746. $this->log(3, "+ rm $data[0]");
  747. break;
  748. case 'mkdir':
  749. @rmdir($data[0]);
  750. $this->log(3, "+ rmdir $data[0]");
  751. break;
  752. case 'chmod':
  753. break;
  754. case 'delete':
  755. break;
  756. case 'installed_as':
  757. $this->pkginfo->setInstalledAs($data[0], false);
  758. break;
  759. }
  760. }
  761. $this->pkginfo->resetDirtree();
  762. $this->file_operations = array();
  763. }
  764. // }}}
  765. // {{{ mkDirHier($dir)
  766. function mkDirHier($dir)
  767. {
  768. $this->addFileOperation('mkdir', array($dir));
  769. return parent::mkDirHier($dir);
  770. }
  771. // }}}
  772. // {{{ download()
  773. /**
  774. * Download any files and their dependencies, if necessary
  775. *
  776. * @param array a mixed list of package names, local files, or package.xml
  777. * @param PEAR_Config
  778. * @param array options from the command line
  779. * @param array this is the array that will be populated with packages to
  780. * install. Format of each entry:
  781. *
  782. * <code>
  783. * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
  784. * 'info' => array() // parsed package.xml
  785. * );
  786. * </code>
  787. * @param array this will be populated with any error messages
  788. * @param false private recursion variable
  789. * @param false private recursion variable
  790. * @param false private recursion variable
  791. * @deprecated in favor of PEAR_Downloader
  792. */
  793. function download($packages, $options, &$config, &$installpackages,
  794. &$errors, $installed = false, $willinstall = false, $state = false)
  795. {
  796. // trickiness: initialize here
  797. parent::PEAR_Downloader($this->ui, $options, $config);
  798. $ret = parent::download($packages);
  799. $errors = $this->getErrorMsgs();
  800. $installpackages = $this->getDownloadedPackages();
  801. trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " .
  802. "in favor of PEAR_Downloader class", E_USER_WARNING);
  803. return $ret;
  804. }
  805. // }}}
  806. // {{{ _parsePackageXml()
  807. function _parsePackageXml(&$descfile, &$tmpdir)
  808. {
  809. if (substr($descfile, -4) == '.xml') {
  810. $tmpdir = false;
  811. } else {
  812. // {{{ Decompress pack in tmp dir -------------------------------------
  813. // To allow relative package file names
  814. $descfile = realpath($descfile);
  815. if (PEAR::isError($tmpdir = System::mktemp('-d'))) {
  816. return $tmpdir;
  817. }
  818. $this->log(3, '+ tmp dir created at ' . $tmpdir);
  819. // }}}
  820. }
  821. // Parse xml file -----------------------------------------------
  822. $pkg = new PEAR_PackageFile($this->config, $this->debug, $tmpdir);
  823. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  824. $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING);
  825. PEAR::staticPopErrorHandling();
  826. if (PEAR::isError($p)) {
  827. foreach ($pkg->getValidationWarnings(true) as $err) {
  828. $loglevel = $err['level'] == 'error' ? 0 : 1;
  829. if (!isset($this->_options['soft'])) {
  830. $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']);
  831. }
  832. }
  833. return $this->raiseError('Installation failed: invalid package file');
  834. } else {
  835. $descfile = $p->getPackageFile();
  836. }
  837. return $p;
  838. }
  839. // }}}
  840. /**
  841. * Set the list of PEAR_Downloader_Package objects to allow more sane
  842. * dependency validation
  843. * @param array
  844. */
  845. function setDownloadedPackages(&$pkgs)
  846. {
  847. PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  848. $err = $this->analyzeDependencies($pkgs);
  849. PEAR::popErrorHandling();
  850. if (PEAR::isError($err)) {
  851. return $err;
  852. }
  853. $this->_downloadedPackages = &$pkgs;
  854. }
  855. /**
  856. * Set the list of PEAR_Downloader_Package objects to allow more sane
  857. * dependency validation
  858. * @param array
  859. */
  860. function setUninstallPackages(&$pkgs)
  861. {
  862. $this->_downloadedPackages = &$pkgs;
  863. }
  864. function getInstallPackages()
  865. {
  866. return $this->_downloadedPackages;
  867. }
  868. // {{{ install()
  869. /**
  870. * Installs the files within the package file specified.
  871. *
  872. * @param string|PEAR_Downloader_Package $pkgfile path to the package file,
  873. * or a pre-initialized packagefile object
  874. * @param array $options
  875. * recognized options:
  876. * - installroot : optional prefix directory for installation
  877. * - force : force installation
  878. * - register-only : update registry but don't install files
  879. * - upgrade : upgrade existing install
  880. * - soft : fail silently
  881. * - nodeps : ignore dependency conflicts/missing dependencies
  882. * - alldeps : install all dependencies
  883. * - onlyreqdeps : install only required dependencies
  884. *
  885. * @return array|PEAR_Error package info if successful
  886. */
  887. function install($pkgfile, $options = array())
  888. {
  889. $this->_options = $options;
  890. $this->_registry = &$this->config->getRegistry();
  891. if (is_object($pkgfile)) {
  892. $dlpkg = &$pkgfile;
  893. $pkg = $pkgfile->getPackageFile();
  894. $pkgfile = $pkg->getArchiveFile();
  895. $descfile = $pkg->getPackageFile();
  896. $tmpdir = dirname($descfile);
  897. } else {
  898. $descfile = $pkgfile;
  899. $tmpdir = '';
  900. if (PEAR::isError($pkg = &$this->_parsePackageXml($descfile, $tmpdir))) {
  901. return $pkg;
  902. }
  903. }
  904. if (realpath($descfile) != realpath($pkgfile)) {
  905. $tar = new Archive_Tar($pkgfile);
  906. if (!@$tar->extract($tmpdir)) {
  907. return $this->raiseError("unable to unpack $pkgfile");
  908. }
  909. }
  910. $pkgname = $pkg->getName();
  911. $channel = $pkg->getChannel();
  912. if (isset($options['installroot'])) {
  913. $this->config->setInstallRoot($options['installroot']);
  914. $this->_registry = &$this->config->getRegistry();
  915. $this->installroot = ''; // all done automagically now
  916. } else {
  917. $this->config->setInstallRoot(false);
  918. $this->_registry = &$this->config->getRegistry();
  919. $this->installroot = '';
  920. }
  921. $php_dir = $this->config->get('php_dir', null, $channel);
  922. // {{{ checks to do when not in "force" mode
  923. if (empty($options['force'])) {
  924. $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname);
  925. $test = $this->_registry->checkFileMap($pkg->getInstallationFileList(true), $testp, '1.1');
  926. if (sizeof($test)) {
  927. $pkgs = $this->getInstallPackages();
  928. $found = false;
  929. foreach ($pkgs as $param) {
  930. if ($pkg->isSubpackageOf($param)) {
  931. $found = true;
  932. break;
  933. }
  934. }
  935. if ($found) {
  936. // subpackages can conflict with earlier versions of parent packages
  937. $parentreg = $this->_registry->packageInfo($param->getPackage(), null, $param->getChannel());
  938. $tmp = $test;
  939. foreach ($tmp as $file => $info) {
  940. if (is_array($info)) {
  941. if (strtolower($info[1]) == strtolower($param->getPackage()) &&
  942. strtolower($info[0]) == strtolower($param->getChannel())) {
  943. unset($test[$file]);
  944. unset($parentreg['filelist'][$file]);
  945. }
  946. } else {
  947. if (strtolower($param->getChannel()) != 'pear.php.net') {
  948. continue;
  949. }
  950. if (strtolower($info) == strtolower($param->getPackage())) {
  951. unset($test[$file]);
  952. unset($parentreg['filelist'][$file]);
  953. }
  954. }
  955. }
  956. $pfk = &new PEAR_PackageFile($this->config);
  957. $parentpkg = &$pfk->fromArray($parentreg);
  958. $this->_registry->updatePackage2($parentpkg);
  959. }
  960. if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) {
  961. $tmp = $test;
  962. foreach ($tmp as $file => $info) {
  963. if (is_string($info)) {
  964. // pear.php.net packages are always stored as strings
  965. if (strtolower($info) == strtolower($param->getPackage())) {
  966. // upgrading existing package
  967. unset($test[$file]);
  968. }
  969. }
  970. }
  971. }
  972. if (sizeof($test)) {
  973. $msg = "$channel/$pkgname: conflicting files found:\n";
  974. $longest = max(array_map("strlen", array_keys($test)));
  975. $fmt = "%${longest}s (%s)\n";
  976. foreach ($test as $file => $info) {
  977. if (!is_array($info)) {
  978. $info = array('pear.php.net', $info);
  979. }
  980. $info = $info[0] . '/' . $info[1];
  981. $msg .= sprintf($fmt, $file, $info);
  982. }
  983. if (!isset($options['ignore-errors'])) {
  984. return $this->raiseError($msg);
  985. } else {
  986. if (!isset($options['soft'])) {
  987. $this->log(0, "WARNING: $msg");
  988. }
  989. }
  990. }
  991. }
  992. }
  993. // }}}
  994. $this->startFileTransaction();
  995. if (empty($options['upgrade']) && empty($options['soft'])) {
  996. // checks to do only when installing new packages
  997. if ($channel == 'pecl.php.net') {
  998. $test = $this->_registry->packageExists($pkgname, $channel);
  999. if (!$test) {
  1000. $test = $this->_registry->packageExists($pkgname, 'pear.php.net');
  1001. }
  1002. } else {
  1003. $test = $this->_registry->packageExists($pkgname, $channel);
  1004. }
  1005. if (empty($options['force']) && $test) {
  1006. return $this->raiseError("$channel/$pkgname is already installed");
  1007. }
  1008. } else {
  1009. $usechannel = $channel;
  1010. if ($channel == 'pecl.php.net') {
  1011. $test = $this->_registry->packageExists($pkgname, $channel);
  1012. if (!$test) {
  1013. $test = $this->_registry->packageExists($pkgname, 'pear.php.net');
  1014. $usechannel = 'pear.php.net';
  1015. }
  1016. } else {
  1017. $test = $this->_registry->packageExists($pkgname, $channel);
  1018. }
  1019. if ($test) {
  1020. $v1 = $this->_registry->packageInfo($pkgname, 'version', $usechannel);
  1021. $v2 = $pkg->getVersion();
  1022. $cmp = version_compare("$v1", "$v2", 'gt');
  1023. if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) {
  1024. return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)");
  1025. }
  1026. if (empty($options['register-only'])) {
  1027. // when upgrading, remove old release's files first:
  1028. if (PEAR::isError($err = $this->_deletePackageFiles($pkgname, $usechannel,
  1029. true))) {
  1030. if (!isset($options['ignore-errors'])) {
  1031. return $this->raiseError($err);
  1032. } else {
  1033. if (!isset($options['soft'])) {
  1034. $this->log(0, 'WARNING: ' . $err->getMessage());
  1035. }
  1036. }
  1037. } else {
  1038. $backedup = $err;
  1039. }
  1040. }
  1041. }
  1042. }
  1043. // {{{ Copy files to dest dir ---------------------------------------
  1044. // info from the package it self we want to access from _installFile
  1045. $this->pkginfo = &$pkg;
  1046. // used to determine whether we should build any C code
  1047. $this->source_files = 0;
  1048. $savechannel = $this->config->get('default_channel');
  1049. if (empty($options['register-only'])) {
  1050. if (!is_dir($php_dir)) {
  1051. if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) {
  1052. return $this->raiseError("no installation destination directory '$php_dir'\n");
  1053. }
  1054. }
  1055. $tmp_path = dirname($descfile);
  1056. if (substr($pkgfile, -4) != '.xml') {
  1057. $tmp_path .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion();
  1058. }
  1059. $this->configSet('default_channel', $channel);
  1060. // {{{ install files
  1061. if ($pkg->getPackagexmlVersion() == '2.0') {
  1062. $filelist = $pkg->getInstallationFilelist();
  1063. } else {
  1064. $filelist = $pkg->getFileList();
  1065. }
  1066. if (PEAR::isError($filelist)) {
  1067. return $filelist;
  1068. }
  1069. $pkg->resetFilelist();
  1070. $pkg->setLastInstalledVersion($this->_registry->packageInfo($pkg->getPackage(),
  1071. 'version', $pkg->getChannel()));
  1072. foreach ($filelist as $file => $atts) {
  1073. if ($pkg->getPackagexmlVersion() == '1.0') {
  1074. $this->expectError(PEAR_INSTALLER_FAILED);
  1075. $res = $this->_installFile($file, $atts, $tmp_path, $options);
  1076. $this->popExpect();
  1077. } else {
  1078. $this->expectError(PEAR_INSTALLER_FAILED);
  1079. $res = $this->_installFile2($pkg, $file, $atts, $tmp_path, $options);
  1080. $this->popExpect();
  1081. }
  1082. if (PEAR::isError($res)) {
  1083. if (empty($options['ignore-errors'])) {
  1084. $this->rollbackFileTransaction();
  1085. if ($res->getMessage() == "file does not exist") {
  1086. $this->raiseError("file $file in package.xml does not exist");
  1087. }
  1088. return $this->raiseError($res);
  1089. } else {
  1090. if (!isset($options['soft'])) {
  1091. $this->log(0, "Warning: " . $res->getMessage());
  1092. }
  1093. }
  1094. }
  1095. if ($res == PEAR_INSTALLER_OK) {
  1096. // Register files that were installed
  1097. $pkg->installedFile($file, $atts);
  1098. }
  1099. }
  1100. // }}}
  1101. // {{{ compile and install source files
  1102. if ($this->source_files > 0 && empty($options['nobuild'])) {
  1103. if (PEAR::isError($err =
  1104. $this->_compileSourceFiles($savechannel, $pkg))) {
  1105. return $err;
  1106. }
  1107. }
  1108. // }}}
  1109. }
  1110. if (isset($backedup)) {
  1111. $this->_removeBackups($backedup);
  1112. }
  1113. if (!$this->commitFileTransaction()) {
  1114. $this->rollbackFileTransaction();
  1115. $this->configSet('default_channel', $savechannel);
  1116. return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
  1117. }
  1118. // }}}
  1119. $ret = false;
  1120. $installphase = 'install';
  1121. $oldversion = false;
  1122. // {{{ Register that the package is installed -----------------------
  1123. if (empty($options['upgrade'])) {
  1124. // if 'force' is used, replace the info in registry
  1125. $usechannel = $channel;
  1126. if ($channel == 'pecl.php.net') {
  1127. $test = $this->_registry->packageExists($pkgname, $channel);
  1128. if (!$test) {
  1129. $test = $this->_registry->packageExists($pkgname, 'pear.php.net');
  1130. $usechannel = 'pear.php.net';
  1131. }
  1132. } else {
  1133. $test = $this->_registry->packageExists($pkgname, $channel);
  1134. }
  1135. if (!empty($options['force']) && $test) {
  1136. $oldversion = $this->_registry->packageInfo($pkgname, 'version', $usechannel);
  1137. $this->_registry->deletePackage($pkgname, $usechannel);
  1138. }
  1139. $ret = $this->_registry->addPackage2($pkg);
  1140. } else {
  1141. $usechannel = $channel;
  1142. if ($channel == 'pecl.php.net') {
  1143. $test = $this->_registry->packageExists($pkgname, $channel);
  1144. if (!$test) {
  1145. $test = $this->_registry->packageExists($pkgname, 'pear.php.net');
  1146. $usechannel = 'pear.php.net';
  1147. }
  1148. } else {
  1149. $test = $this->_registry->packageExists($pkgname, $channel);
  1150. }
  1151. // new: upgrade installs a package if it isn't installed
  1152. if (!$test) {
  1153. $ret = $this->_registry->addPackage2($pkg);
  1154. } else {
  1155. if ($usechannel != $channel) {
  1156. $this->_registry->deletePackage($pkgname, $usechannel);
  1157. $ret = $this->_registry->addPackage2($pkg);
  1158. } else {
  1159. $ret = $this->_registry->updatePackage2($pkg);
  1160. }
  1161. $installphase = 'upgrade';
  1162. }
  1163. }
  1164. if (!$ret) {
  1165. $this->configSet('default_channel', $savechannel);
  1166. return $this->raiseError("Adding package $channel/$pkgname to registry failed");
  1167. }
  1168. // }}}
  1169. $this->configSet('default_channel', $savechannel);
  1170. if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist
  1171. if (PEAR_Task_Common::hasPostinstallTasks()) {
  1172. PEAR_Task_Common::runPostinstallTasks($installphase);
  1173. }
  1174. }
  1175. return $pkg->toArray(true);
  1176. }
  1177. // }}}
  1178. /**
  1179. * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
  1180. */
  1181. function ftpUninstall($pkg)
  1182. {
  1183. $ftp = &$this->config->getFTP();
  1184. if (!$ftp) {
  1185. return $this->raiseError('FTP client not initialized');
  1186. }
  1187. $this->log(2, 'Connect to FTP server');
  1188. $e = $ftp->init();
  1189. if (PEAR::isError($e)) {
  1190. return $e;
  1191. }
  1192. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  1193. foreach ($pkg->getFilelist() as $file => $atts) {
  1194. if ($pkg->getPackagexmlVersion() == '1.0') {
  1195. $channel = 'pear.php.net';
  1196. switch ($atts['role']) {
  1197. case 'doc':
  1198. case 'data':
  1199. case 'test':
  1200. $dest_dir = $this->config->get($atts['role'] . '_dir', 'ftp', $channel) .
  1201. DIRECTORY_SEPARATOR . $pkg->getPackage();
  1202. unset($atts['baseinstalldir']);

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