commiting ownCloud 1.0 beta 1

This commit is contained in:
Frank Karlitschek 2010-03-10 13:03:40 +01:00
parent ef20281486
commit 16f3bd4e23
136 changed files with 54754 additions and 0 deletions

43
config/config.php Executable file
View File

@ -0,0 +1,43 @@
<?php
// Owner
$CONFIG_FOOTEROWNERNAME = 'Frank Karlitschek';
$CONFIG_FOOTEROWNEREMAIL = 'karlitschek@kde.org';
// ADMIN ACCOUNT
$CONFIG_ADMINLOGIN = 'frank';
$CONFIG_ADMINPASSWORD = '123';
// DB Config
$CONFIG_DBHOST = 'localhost';
$CONFIG_DBNAME = 'owncloud';
$CONFIG_DBUSER = 'owncloud';
$CONFIG_DBPWD = 'owncloud12345';
// directories
$CONFIG_DATADIRECTORY = '/www/testy';
$CONFIG_DOCUMENTROOT = '/www/owncloud/htdocs';
// force SSL
$CONFIG_HTTPFORCESSL = false;
// other
$CONFIG_DATEFORMAT = 'j M Y G:i';
// plugins
//$CONFIG_LOADPLUGINS = 'music test';
$CONFIG_LOADPLUGINS = '';
// set the right include path
// don´t change unless you know what you are doing
set_include_path(get_include_path().PATH_SEPARATOR.$CONFIG_DOCUMENTROOT.PATH_SEPARATOR.$CONFIG_DOCUMENTROOT.'/inc');
require_once('lib_base.php');
?>

80
css/default.css Executable file
View File

@ -0,0 +1,80 @@
body,th,td,ul,li,a,div,p,pre {color:#333333; font-family:Verdana,"Bitstream Vera Sans",Arial,Helvetica,Sans,"Bitstream Vera Serif"; font-size:9.0pt;}
.nametext a {color:#333333; font-size:8pt; font-weight:bold; text-decoration:none;}
.highlighttext {color:#333333; font-size:9pt; font-weight:bold; text-decoration:none;}
.datetext {color:#333333; font-size:7pt;}
.sizetext {color:#333333; font-size:7pt;}
.footer {color:#999999; text-align:center; font-size:9pt;}
.hint {color:#AAAAAA; text-align:center; font-size:8pt;}
.hint a{color:#AAAAAA; text-align:center; font-size:8pt;}
.formstyle {
font-weight:normal;
font-size: 8.0pt;
color: #555555;
background-color: #FFFFFF;
border: 1px solid #DDDDDD;
padding:0px;
margin:0px;
}
.loginform {
background-color: #EEEEEE;
}
.browser {
background-color: #EEEEEE;
}
.browserline {
background-color: #EEEEEE;
}
.browserline:hover {
background-color: #DDDDDD;
}
.navigationitem1 {
background-color: #EEEEEE;
color:#555555;
font-size:9pt;
font-weight:bold;
}
.navigationitem1 a{
text-decoration:none;
}
.navigationitem1:hover {
background-color: #EEEEEE;
}
.navigationitem {
background-color: #EEEEEE;
}
.navigationitem:hover {
background-color: #DDDDDD;
}
.navigationselected td {
background-color: #DDDDDD;
}
.navigationitem a {
text-decoration:none;
color: #333333;
font-size: 8.0pt;
}
.navigationitemselected a {
text-decoration:none;
color: #333333;
font-size: 8.0pt;
font-weight:bold;
}

4
docs/INSTALL Executable file
View File

@ -0,0 +1,4 @@
nothing here yet

29
docs/TODO Executable file
View File

@ -0,0 +1,29 @@
- remove dependency on mysql. replace with sqlite to make it easier to install
- simplify installation
- write installation documentation
- better ajax web gui
- internationalizing of the web gui
- write support for web gui
- themed webgui to match plasma theme
- plugin system
- store kde settings
- store my music collection
- integration with kolab server
- create a versioning backend
- create an automatic backup system to store files an a second device/server
- create a usermanagement to share data

39
docs/owncloud.sql Executable file
View File

@ -0,0 +1,39 @@
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
CREATE TABLE IF NOT EXISTS `locks` (
`token` varchar(255) NOT NULL DEFAULT '',
`path` varchar(200) NOT NULL DEFAULT '',
`expires` int(11) NOT NULL DEFAULT '0',
`owner` varchar(200) DEFAULT NULL,
`recursive` int(11) DEFAULT '0',
`writelock` int(11) DEFAULT '0',
`exclusivelock` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`token`),
UNIQUE KEY `token` (`token`),
KEY `path` (`path`),
KEY `path_2` (`path`),
KEY `path_3` (`path`,`token`),
KEY `expires` (`expires`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`timestamp` int(11) NOT NULL,
`user` varchar(250) NOT NULL,
`type` int(11) NOT NULL,
`message` varchar(250) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=15 ;
CREATE TABLE IF NOT EXISTS `properties` (
`path` varchar(255) NOT NULL DEFAULT '',
`name` varchar(120) NOT NULL DEFAULT '',
`ns` varchar(120) NOT NULL DEFAULT 'DAV:',
`value` text,
PRIMARY KEY (`path`,`name`,`ns`),
KEY `path` (`path`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

BIN
img/arrow.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

BIN
img/dots.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

BIN
img/icons/folder.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

BIN
img/icons/other.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

BIN
img/owncloud-logo-small.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
img/owncloud-logo.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

1762
inc/Archive/Tar.php Executable file

File diff suppressed because it is too large Load Diff

251
inc/Console/Getopt.php Executable file
View File

@ -0,0 +1,251 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Andrei Zmievski <andrei@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Getopt.php,v 1.21.4.7 2003/12/05 21:57:01 andrei Exp $
require_once 'PEAR.php';
/**
* Command-line options parsing class.
*
* @author Andrei Zmievski <andrei@php.net>
*
*/
class Console_Getopt {
/**
* Parses the command-line options.
*
* The first parameter to this function should be the list of command-line
* arguments without the leading reference to the running program.
*
* The second parameter is a string of allowed short options. Each of the
* option letters can be followed by a colon ':' to specify that the option
* requires an argument, or a double colon '::' to specify that the option
* takes an optional argument.
*
* The third argument is an optional array of allowed long options. The
* leading '--' should not be included in the option name. Options that
* require an argument should be followed by '=', and options that take an
* option argument should be followed by '=='.
*
* The return value is an array of two elements: the list of parsed
* options and the list of non-option command-line arguments. Each entry in
* the list of parsed options is a pair of elements - the first one
* specifies the option, and the second one specifies the option argument,
* if there was one.
*
* Long and short options can be mixed.
*
* Most of the semantics of this function are based on GNU getopt_long().
*
* @param array $args an array of command-line arguments
* @param string $short_options specifies the list of allowed short options
* @param array $long_options specifies the list of allowed long options
*
* @return array two-element array containing the list of parsed options and
* the non-option arguments
*
* @access public
*
*/
function getopt2($args, $short_options, $long_options = null)
{
return Console_Getopt::doGetopt(2, $args, $short_options, $long_options);
}
/**
* This function expects $args to start with the script name (POSIX-style).
* Preserved for backwards compatibility.
* @see getopt2()
*/
function getopt($args, $short_options, $long_options = null)
{
return Console_Getopt::doGetopt(1, $args, $short_options, $long_options);
}
/**
* The actual implementation of the argument parsing code.
*/
function doGetopt($version, $args, $short_options, $long_options = null)
{
// in case you pass directly readPHPArgv() as the first arg
if (PEAR::isError($args)) {
return $args;
}
if (empty($args)) {
return array(array(), array());
}
$opts = array();
$non_opts = array();
settype($args, 'array');
if ($long_options) {
sort($long_options);
}
/*
* Preserve backwards compatibility with callers that relied on
* erroneous POSIX fix.
*/
if ($version < 2) {
if (isset($args[0]{0}) && $args[0]{0} != '-') {
array_shift($args);
}
}
reset($args);
while (list($i, $arg) = each($args)) {
/* The special element '--' means explicit end of
options. Treat the rest of the arguments as non-options
and end the loop. */
if ($arg == '--') {
$non_opts = array_merge($non_opts, array_slice($args, $i + 1));
break;
}
if ($arg{0} != '-' || (strlen($arg) > 1 && $arg{1} == '-' && !$long_options)) {
$non_opts = array_merge($non_opts, array_slice($args, $i));
break;
} elseif (strlen($arg) > 1 && $arg{1} == '-') {
$error = Console_Getopt::_parseLongOption(substr($arg, 2), $long_options, $opts, $args);
if (PEAR::isError($error))
return $error;
} else {
$error = Console_Getopt::_parseShortOption(substr($arg, 1), $short_options, $opts, $args);
if (PEAR::isError($error))
return $error;
}
}
return array($opts, $non_opts);
}
/**
* @access private
*
*/
function _parseShortOption($arg, $short_options, &$opts, &$args)
{
for ($i = 0; $i < strlen($arg); $i++) {
$opt = $arg{$i};
$opt_arg = null;
/* Try to find the short option in the specifier string. */
if (($spec = strstr($short_options, $opt)) === false || $arg{$i} == ':')
{
return PEAR::raiseError("Console_Getopt: unrecognized option -- $opt");
}
if (strlen($spec) > 1 && $spec{1} == ':') {
if (strlen($spec) > 2 && $spec{2} == ':') {
if ($i + 1 < strlen($arg)) {
/* Option takes an optional argument. Use the remainder of
the arg string if there is anything left. */
$opts[] = array($opt, substr($arg, $i + 1));
break;
}
} else {
/* Option requires an argument. Use the remainder of the arg
string if there is anything left. */
if ($i + 1 < strlen($arg)) {
$opts[] = array($opt, substr($arg, $i + 1));
break;
} else if (list(, $opt_arg) = each($args))
/* Else use the next argument. */;
else
return PEAR::raiseError("Console_Getopt: option requires an argument -- $opt");
}
}
$opts[] = array($opt, $opt_arg);
}
}
/**
* @access private
*
*/
function _parseLongOption($arg, $long_options, &$opts, &$args)
{
@list($opt, $opt_arg) = explode('=', $arg);
$opt_len = strlen($opt);
for ($i = 0; $i < count($long_options); $i++) {
$long_opt = $long_options[$i];
$opt_start = substr($long_opt, 0, $opt_len);
/* Option doesn't match. Go on to the next one. */
if ($opt_start != $opt)
continue;
$opt_rest = substr($long_opt, $opt_len);
/* Check that the options uniquely matches one of the allowed
options. */
if ($opt_rest != '' && $opt{0} != '=' &&
$i + 1 < count($long_options) &&
$opt == substr($long_options[$i+1], 0, $opt_len)) {
return PEAR::raiseError("Console_Getopt: option --$opt is ambiguous");
}
if (substr($long_opt, -1) == '=') {
if (substr($long_opt, -2) != '==') {
/* Long option requires an argument.
Take the next argument if one wasn't specified. */;
if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args))) {
return PEAR::raiseError("Console_Getopt: option --$opt requires an argument");
}
}
} else if ($opt_arg) {
return PEAR::raiseError("Console_Getopt: option --$opt doesn't allow an argument");
}
$opts[] = array('--' . $opt, $opt_arg);
return;
}
return PEAR::raiseError("Console_Getopt: unrecognized option --$opt");
}
/**
* Safely read the $argv PHP array across different PHP configurations.
* Will take care on register_globals and register_argc_argv ini directives
*
* @access public
* @return mixed the $argv PHP array or PEAR error if not registered
*/
function readPHPArgv()
{
global $argv;
if (!is_array($argv)) {
if (!@is_array($_SERVER['argv'])) {
if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
return PEAR::raiseError("Console_Getopt: Could not read cmd args (register_argc_argv=Off?)");
}
return $GLOBALS['HTTP_SERVER_VARS']['argv'];
}
return $_SERVER['argv'];
}
return $argv;
}
}
?>

1388
inc/DB.php Executable file

File diff suppressed because it is too large Load Diff

2157
inc/DB/common.php Executable file

File diff suppressed because it is too large Load Diff

510
inc/DB/dbase.php Executable file
View File

@ -0,0 +1,510 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* The PEAR DB driver for PHP's dbase extension
* for interacting with dBase databases
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category Database
* @package DB
* @author Tomas V.V. Cox <cox@idecnet.com>
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: dbase.php,v 1.39 2005/02/19 23:25:25 danielc Exp $
* @link http://pear.php.net/package/DB
*/
/**
* Obtain the DB_common class so it can be extended from
*/
require_once 'DB/common.php';
/**
* The methods PEAR DB uses to interact with PHP's dbase extension
* for interacting with dBase databases
*
* These methods overload the ones declared in DB_common.
*
* @category Database
* @package DB
* @author Tomas V.V. Cox <cox@idecnet.com>
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: @package_version@
* @link http://pear.php.net/package/DB
*/
class DB_dbase extends DB_common
{
// {{{ properties
/**
* The DB driver type (mysql, oci8, odbc, etc.)
* @var string
*/
var $phptype = 'dbase';
/**
* The database syntax variant to be used (db2, access, etc.), if any
* @var string
*/
var $dbsyntax = 'dbase';
/**
* The capabilities of this DB implementation
*
* The 'new_link' element contains the PHP version that first provided
* new_link support for this DBMS. Contains false if it's unsupported.
*
* Meaning of the 'limit' element:
* + 'emulate' = emulate with fetch row by number
* + 'alter' = alter the query
* + false = skip rows
*
* @var array
*/
var $features = array(
'limit' => false,
'new_link' => false,
'numrows' => true,
'pconnect' => false,
'prepare' => false,
'ssl' => false,
'transactions' => false,
);
/**
* A mapping of native error codes to DB error codes
* @var array
*/
var $errorcode_map = array(
);
/**
* The raw database connection created by PHP
* @var resource
*/
var $connection;
/**
* The DSN information for connecting to a database
* @var array
*/
var $dsn = array();
/**
* A means of emulating result resources
* @var array
*/
var $res_row = array();
/**
* The quantity of results so far
*
* For emulating result resources.
*
* @var integer
*/
var $result = 0;
/**
* Maps dbase data type id's to human readable strings
*
* The human readable values are based on the output of PHP's
* dbase_get_header_info() function.
*
* @var array
* @since Property available since Release 1.7.0
*/
var $types = array(
'C' => 'character',
'D' => 'date',
'L' => 'boolean',
'M' => 'memo',
'N' => 'number',
);
// }}}
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
*
* @return void
*/
function DB_dbase()
{
$this->DB_common();
}
// }}}
// {{{ connect()
/**
* Connect to the database and create it if it doesn't exist
*
* Don't call this method directly. Use DB::connect() instead.
*
* PEAR DB's dbase driver supports the following extra DSN options:
* + mode An integer specifying the read/write mode to use
* (0 = read only, 1 = write only, 2 = read/write).
* Available since PEAR DB 1.7.0.
* + fields An array of arrays that PHP's dbase_create() function needs
* to create a new database. This information is used if the
* dBase file specified in the "database" segment of the DSN
* does not exist. For more info, see the PHP manual's
* {@link http://php.net/dbase_create dbase_create()} page.
* Available since PEAR DB 1.7.0.
*
* Example of how to connect and establish a new dBase file if necessary:
* <code>
* require_once 'DB.php';
*
* $dsn = array(
* 'phptype' => 'dbase',
* 'database' => '/path/and/name/of/dbase/file',
* 'mode' => 2,
* 'fields' => array(
* array('a', 'N', 5, 0),
* array('b', 'C', 40),
* array('c', 'C', 255),
* array('d', 'C', 20),
* ),
* );
* $options = array(
* 'debug' => 2,
* 'portability' => DB_PORTABILITY_ALL,
* );
*
* $db =& DB::connect($dsn, $options);
* if (PEAR::isError($db)) {
* die($db->getMessage());
* }
* </code>
*
* @param array $dsn the data source name
* @param bool $persistent should the connection be persistent?
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function connect($dsn, $persistent = false)
{
if (!PEAR::loadExtension('dbase')) {
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
}
$this->dsn = $dsn;
if ($dsn['dbsyntax']) {
$this->dbsyntax = $dsn['dbsyntax'];
}
/*
* Turn track_errors on for entire script since $php_errormsg
* is the only way to find errors from the dbase extension.
*/
ini_set('track_errors', 1);
$php_errormsg = '';
if (!file_exists($dsn['database'])) {
$this->dsn['mode'] = 2;
if (empty($dsn['fields']) || !is_array($dsn['fields'])) {
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
null, null, null,
'the dbase file does not exist and '
. 'it could not be created because '
. 'the "fields" element of the DSN '
. 'is not properly set');
}
$this->connection = @dbase_create($dsn['database'],
$dsn['fields']);
if (!$this->connection) {
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
null, null, null,
'the dbase file does not exist and '
. 'the attempt to create it failed: '
. $php_errormsg);
}
} else {
if (!isset($this->dsn['mode'])) {
$this->dsn['mode'] = 0;
}
$this->connection = @dbase_open($dsn['database'],
$this->dsn['mode']);
if (!$this->connection) {
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
null, null, null,
$php_errormsg);
}
}
return DB_OK;
}
// }}}
// {{{ disconnect()
/**
* Disconnects from the database server
*
* @return bool TRUE on success, FALSE on failure
*/
function disconnect()
{
$ret = @dbase_close($this->connection);
$this->connection = null;
return $ret;
}
// }}}
// {{{ &query()
function &query($query = null)
{
// emulate result resources
$this->res_row[(int)$this->result] = 0;
$tmp =& new DB_result($this, $this->result++);
return $tmp;
}
// }}}
// {{{ fetchInto()
/**
* Places a row from the result set into the given array
*
* Formating of the array and the data therein are configurable.
* See DB_result::fetchInto() for more information.
*
* This method is not meant to be called directly. Use
* DB_result::fetchInto() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result the query result resource
* @param array $arr the referenced array to put the data in
* @param int $fetchmode how the resulting array should be indexed
* @param int $rownum the row number to fetch (0 = first row)
*
* @return mixed DB_OK on success, NULL when the end of a result set is
* reached or on failure
*
* @see DB_result::fetchInto()
*/
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
{
if ($rownum === null) {
$rownum = $this->res_row[(int)$result]++;
}
if ($fetchmode & DB_FETCHMODE_ASSOC) {
$arr = @dbase_get_record_with_names($this->connection, $rownum);
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
$arr = array_change_key_case($arr, CASE_LOWER);
}
} else {
$arr = @dbase_get_record($this->connection, $rownum);
}
if (!$arr) {
return null;
}
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
$this->_rtrimArrayValues($arr);
}
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
$this->_convertNullArrayValuesToEmpty($arr);
}
return DB_OK;
}
// }}}
// {{{ numCols()
/**
* Gets the number of columns in a result set
*
* This method is not meant to be called directly. Use
* DB_result::numCols() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return int the number of columns. A DB_Error object on failure.
*
* @see DB_result::numCols()
*/
function numCols($foo)
{
return @dbase_numfields($this->connection);
}
// }}}
// {{{ numRows()
/**
* Gets the number of rows in a result set
*
* This method is not meant to be called directly. Use
* DB_result::numRows() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return int the number of rows. A DB_Error object on failure.
*
* @see DB_result::numRows()
*/
function numRows($foo)
{
return @dbase_numrecords($this->connection);
}
// }}}
// {{{ quoteSmart()
/**
* Formats input so it can be safely used in a query
*
* @param mixed $in the data to be formatted
*
* @return mixed the formatted data. The format depends on the input's
* PHP type:
* + null = the string <samp>NULL</samp>
* + boolean = <samp>T</samp> if true or
* <samp>F</samp> if false. Use the <kbd>Logical</kbd>
* data type.
* + integer or double = the unquoted number
* + other (including strings and numeric strings) =
* the data with single quotes escaped by preceeding
* single quotes then the whole string is encapsulated
* between single quotes
*
* @see DB_common::quoteSmart()
* @since Method available since Release 1.6.0
*/
function quoteSmart($in)
{
if (is_int($in) || is_double($in)) {
return $in;
} elseif (is_bool($in)) {
return $in ? 'T' : 'F';
} elseif (is_null($in)) {
return 'NULL';
} else {
return "'" . $this->escapeSimple($in) . "'";
}
}
// }}}
// {{{ tableInfo()
/**
* Returns information about the current database
*
* @param mixed $result THIS IS UNUSED IN DBASE. The current database
* is examined regardless of what is provided here.
* @param int $mode a valid tableInfo mode
*
* @return array an associative array with the information requested.
* A DB_Error object on failure.
*
* @see DB_common::tableInfo()
* @since Method available since Release 1.7.0
*/
function tableInfo($result = null, $mode = null)
{
if (function_exists('dbase_get_header_info')) {
$id = @dbase_get_header_info($this->connection);
if (!$id && $php_errormsg) {
return $this->raiseError(DB_ERROR,
null, null, null,
$php_errormsg);
}
} else {
/*
* This segment for PHP 4 is loosely based on code by
* Hadi Rusiah <deegos@yahoo.com> in the comments on
* the dBase reference page in the PHP manual.
*/
$db = @fopen($this->dsn['database'], 'r');
if (!$db) {
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
null, null, null,
$php_errormsg);
}
$id = array();
$i = 0;
$line = fread($db, 32);
while (!feof($db)) {
$line = fread($db, 32);
if (substr($line, 0, 1) == chr(13)) {
break;
} else {
$pos = strpos(substr($line, 0, 10), chr(0));
$pos = ($pos == 0 ? 10 : $pos);
$id[$i] = array(
'name' => substr($line, 0, $pos),
'type' => $this->types[substr($line, 11, 1)],
'length' => ord(substr($line, 16, 1)),
'precision' => ord(substr($line, 17, 1)),
);
}
$i++;
}
fclose($db);
}
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
$case_func = 'strtolower';
} else {
$case_func = 'strval';
}
$res = array();
$count = count($id);
if ($mode) {
$res['num_fields'] = $count;
}
for ($i = 0; $i < $count; $i++) {
$res[$i] = array(
'table' => $this->dsn['database'],
'name' => $case_func($id[$i]['name']),
'type' => $id[$i]['type'],
'len' => $id[$i]['length'],
'flags' => ''
);
if ($mode & DB_TABLEINFO_ORDER) {
$res['order'][$res[$i]['name']] = $i;
}
if ($mode & DB_TABLEINFO_ORDERTABLE) {
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
}
}
return $res;
}
// }}}
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
*/
?>

770
inc/DB/fbsql.php Executable file
View File

@ -0,0 +1,770 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* The PEAR DB driver for PHP's fbsql extension
* for interacting with FrontBase databases
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category Database
* @package DB
* @author Frank M. Kromann <frank@frontbase.com>
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: fbsql.php,v 1.82 2005/03/04 23:12:36 danielc Exp $
* @link http://pear.php.net/package/DB
*/
/**
* Obtain the DB_common class so it can be extended from
*/
require_once 'DB/common.php';
/**
* The methods PEAR DB uses to interact with PHP's fbsql extension
* for interacting with FrontBase databases
*
* These methods overload the ones declared in DB_common.
*
* @category Database
* @package DB
* @author Frank M. Kromann <frank@frontbase.com>
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: @package_version@
* @link http://pear.php.net/package/DB
* @since Class functional since Release 1.7.0
*/
class DB_fbsql extends DB_common
{
// {{{ properties
/**
* The DB driver type (mysql, oci8, odbc, etc.)
* @var string
*/
var $phptype = 'fbsql';
/**
* The database syntax variant to be used (db2, access, etc.), if any
* @var string
*/
var $dbsyntax = 'fbsql';
/**
* The capabilities of this DB implementation
*
* The 'new_link' element contains the PHP version that first provided
* new_link support for this DBMS. Contains false if it's unsupported.
*
* Meaning of the 'limit' element:
* + 'emulate' = emulate with fetch row by number
* + 'alter' = alter the query
* + false = skip rows
*
* @var array
*/
var $features = array(
'limit' => 'alter',
'new_link' => false,
'numrows' => true,
'pconnect' => true,
'prepare' => false,
'ssl' => false,
'transactions' => true,
);
/**
* A mapping of native error codes to DB error codes
* @var array
*/
var $errorcode_map = array(
22 => DB_ERROR_SYNTAX,
85 => DB_ERROR_ALREADY_EXISTS,
108 => DB_ERROR_SYNTAX,
116 => DB_ERROR_NOSUCHTABLE,
124 => DB_ERROR_VALUE_COUNT_ON_ROW,
215 => DB_ERROR_NOSUCHFIELD,
217 => DB_ERROR_INVALID_NUMBER,
226 => DB_ERROR_NOSUCHFIELD,
231 => DB_ERROR_INVALID,
239 => DB_ERROR_TRUNCATED,
251 => DB_ERROR_SYNTAX,
266 => DB_ERROR_NOT_FOUND,
357 => DB_ERROR_CONSTRAINT_NOT_NULL,
358 => DB_ERROR_CONSTRAINT,
360 => DB_ERROR_CONSTRAINT,
361 => DB_ERROR_CONSTRAINT,
);
/**
* The raw database connection created by PHP
* @var resource
*/
var $connection;
/**
* The DSN information for connecting to a database
* @var array
*/
var $dsn = array();
// }}}
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
*
* @return void
*/
function DB_fbsql()
{
$this->DB_common();
}
// }}}
// {{{ connect()
/**
* Connect to the database server, log in and open the database
*
* Don't call this method directly. Use DB::connect() instead.
*
* @param array $dsn the data source name
* @param bool $persistent should the connection be persistent?
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function connect($dsn, $persistent = false)
{
if (!PEAR::loadExtension('fbsql')) {
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
}
$this->dsn = $dsn;
if ($dsn['dbsyntax']) {
$this->dbsyntax = $dsn['dbsyntax'];
}
$params = array(
$dsn['hostspec'] ? $dsn['hostspec'] : 'localhost',
$dsn['username'] ? $dsn['username'] : null,
$dsn['password'] ? $dsn['password'] : null,
);
$connect_function = $persistent ? 'fbsql_pconnect' : 'fbsql_connect';
$ini = ini_get('track_errors');
$php_errormsg = '';
if ($ini) {
$this->connection = @call_user_func_array($connect_function,
$params);
} else {
ini_set('track_errors', 1);
$this->connection = @call_user_func_array($connect_function,
$params);
ini_set('track_errors', $ini);
}
if (!$this->connection) {
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
null, null, null,
$php_errormsg);
}
if ($dsn['database']) {
if (!@fbsql_select_db($dsn['database'], $this->connection)) {
return $this->fbsqlRaiseError();
}
}
return DB_OK;
}
// }}}
// {{{ disconnect()
/**
* Disconnects from the database server
*
* @return bool TRUE on success, FALSE on failure
*/
function disconnect()
{
$ret = @fbsql_close($this->connection);
$this->connection = null;
return $ret;
}
// }}}
// {{{ simpleQuery()
/**
* Sends a query to the database server
*
* @param string the SQL query string
*
* @return mixed + a PHP result resrouce for successful SELECT queries
* + the DB_OK constant for other successful queries
* + a DB_Error object on failure
*/
function simpleQuery($query)
{
$this->last_query = $query;
$query = $this->modifyQuery($query);
$result = @fbsql_query("$query;", $this->connection);
if (!$result) {
return $this->fbsqlRaiseError();
}
// Determine which queries that should return data, and which
// should return an error code only.
if (DB::isManip($query)) {
return DB_OK;
}
return $result;
}
// }}}
// {{{ nextResult()
/**
* Move the internal fbsql result pointer to the next available result
*
* @param a valid fbsql result resource
*
* @access public
*
* @return true if a result is available otherwise return false
*/
function nextResult($result)
{
return @fbsql_next_result($result);
}
// }}}
// {{{ fetchInto()
/**
* Places a row from the result set into the given array
*
* Formating of the array and the data therein are configurable.
* See DB_result::fetchInto() for more information.
*
* This method is not meant to be called directly. Use
* DB_result::fetchInto() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result the query result resource
* @param array $arr the referenced array to put the data in
* @param int $fetchmode how the resulting array should be indexed
* @param int $rownum the row number to fetch (0 = first row)
*
* @return mixed DB_OK on success, NULL when the end of a result set is
* reached or on failure
*
* @see DB_result::fetchInto()
*/
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
{
if ($rownum !== null) {
if (!@fbsql_data_seek($result, $rownum)) {
return null;
}
}
if ($fetchmode & DB_FETCHMODE_ASSOC) {
$arr = @fbsql_fetch_array($result, FBSQL_ASSOC);
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
$arr = array_change_key_case($arr, CASE_LOWER);
}
} else {
$arr = @fbsql_fetch_row($result);
}
if (!$arr) {
return null;
}
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
$this->_rtrimArrayValues($arr);
}
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
$this->_convertNullArrayValuesToEmpty($arr);
}
return DB_OK;
}
// }}}
// {{{ freeResult()
/**
* Deletes the result set and frees the memory occupied by the result set
*
* This method is not meant to be called directly. Use
* DB_result::free() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return bool TRUE on success, FALSE if $result is invalid
*
* @see DB_result::free()
*/
function freeResult($result)
{
return @fbsql_free_result($result);
}
// }}}
// {{{ autoCommit()
/**
* Enables or disables automatic commits
*
* @param bool $onoff true turns it on, false turns it off
*
* @return int DB_OK on success. A DB_Error object if the driver
* doesn't support auto-committing transactions.
*/
function autoCommit($onoff=false)
{
if ($onoff) {
$this->query("SET COMMIT TRUE");
} else {
$this->query("SET COMMIT FALSE");
}
}
// }}}
// {{{ commit()
/**
* Commits the current transaction
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function commit()
{
@fbsql_commit();
}
// }}}
// {{{ rollback()
/**
* Reverts the current transaction
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function rollback()
{
@fbsql_rollback();
}
// }}}
// {{{ numCols()
/**
* Gets the number of columns in a result set
*
* This method is not meant to be called directly. Use
* DB_result::numCols() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return int the number of columns. A DB_Error object on failure.
*
* @see DB_result::numCols()
*/
function numCols($result)
{
$cols = @fbsql_num_fields($result);
if (!$cols) {
return $this->fbsqlRaiseError();
}
return $cols;
}
// }}}
// {{{ numRows()
/**
* Gets the number of rows in a result set
*
* This method is not meant to be called directly. Use
* DB_result::numRows() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return int the number of rows. A DB_Error object on failure.
*
* @see DB_result::numRows()
*/
function numRows($result)
{
$rows = @fbsql_num_rows($result);
if ($rows === null) {
return $this->fbsqlRaiseError();
}
return $rows;
}
// }}}
// {{{ affectedRows()
/**
* Determines the number of rows affected by a data maniuplation query
*
* 0 is returned for queries that don't manipulate data.
*
* @return int the number of rows. A DB_Error object on failure.
*/
function affectedRows()
{
if (DB::isManip($this->last_query)) {
$result = @fbsql_affected_rows($this->connection);
} else {
$result = 0;
}
return $result;
}
// }}}
// {{{ nextId()
/**
* Returns the next free id in a sequence
*
* @param string $seq_name name of the sequence
* @param boolean $ondemand when true, the seqence is automatically
* created if it does not exist
*
* @return int the next id number in the sequence.
* A DB_Error object on failure.
*
* @see DB_common::nextID(), DB_common::getSequenceName(),
* DB_fbsql::createSequence(), DB_fbsql::dropSequence()
*/
function nextId($seq_name, $ondemand = true)
{
$seqname = $this->getSequenceName($seq_name);
do {
$repeat = 0;
$this->pushErrorHandling(PEAR_ERROR_RETURN);
$result = $this->query('SELECT UNIQUE FROM ' . $seqname);
$this->popErrorHandling();
if ($ondemand && DB::isError($result) &&
$result->getCode() == DB_ERROR_NOSUCHTABLE) {
$repeat = 1;
$result = $this->createSequence($seq_name);
if (DB::isError($result)) {
return $result;
}
} else {
$repeat = 0;
}
} while ($repeat);
if (DB::isError($result)) {
return $this->fbsqlRaiseError();
}
$result->fetchInto($tmp, DB_FETCHMODE_ORDERED);
return $tmp[0];
}
/**
* Creates a new sequence
*
* @param string $seq_name name of the new sequence
*
* @return int DB_OK on success. A DB_Error object on failure.
*
* @see DB_common::createSequence(), DB_common::getSequenceName(),
* DB_fbsql::nextID(), DB_fbsql::dropSequence()
*/
function createSequence($seq_name)
{
$seqname = $this->getSequenceName($seq_name);
$res = $this->query('CREATE TABLE ' . $seqname
. ' (id INTEGER NOT NULL,'
. ' PRIMARY KEY(id))');
if ($res) {
$res = $this->query('SET UNIQUE = 0 FOR ' . $seqname);
}
return $res;
}
// }}}
// {{{ dropSequence()
/**
* Deletes a sequence
*
* @param string $seq_name name of the sequence to be deleted
*
* @return int DB_OK on success. A DB_Error object on failure.
*
* @see DB_common::dropSequence(), DB_common::getSequenceName(),
* DB_fbsql::nextID(), DB_fbsql::createSequence()
*/
function dropSequence($seq_name)
{
return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)
. ' RESTRICT');
}
// }}}
// {{{ modifyLimitQuery()
/**
* Adds LIMIT clauses to a query string according to current DBMS standards
*
* @param string $query the query to modify
* @param int $from the row to start to fetching (0 = the first row)
* @param int $count the numbers of rows to fetch
* @param mixed $params array, string or numeric data to be used in
* execution of the statement. Quantity of items
* passed must match quantity of placeholders in
* query: meaning 1 placeholder for non-array
* parameters or 1 placeholder per array element.
*
* @return string the query string with LIMIT clauses added
*
* @access protected
*/
function modifyLimitQuery($query, $from, $count, $params = array())
{
if (DB::isManip($query)) {
return preg_replace('/^([\s(])*SELECT/i',
"\\1SELECT TOP($count)", $query);
} else {
return preg_replace('/([\s(])*SELECT/i',
"\\1SELECT TOP($from, $count)", $query);
}
}
// }}}
// {{{ quoteSmart()
/**
* Formats input so it can be safely used in a query
*
* @param mixed $in the data to be formatted
*
* @return mixed the formatted data. The format depends on the input's
* PHP type:
* + null = the string <samp>NULL</samp>
* + boolean = string <samp>TRUE</samp> or <samp>FALSE</samp>
* + integer or double = the unquoted number
* + other (including strings and numeric strings) =
* the data escaped according to FrontBase's settings
* then encapsulated between single quotes
*
* @see DB_common::quoteSmart()
* @since Method available since Release 1.6.0
*/
function quoteSmart($in)
{
if (is_int($in) || is_double($in)) {
return $in;
} elseif (is_bool($in)) {
return $in ? 'TRUE' : 'FALSE';
} elseif (is_null($in)) {
return 'NULL';
} else {
return "'" . $this->escapeSimple($in) . "'";
}
}
// }}}
// {{{ fbsqlRaiseError()
/**
* Produces a DB_Error object regarding the current problem
*
* @param int $errno if the error is being manually raised pass a
* DB_ERROR* constant here. If this isn't passed
* the error information gathered from the DBMS.
*
* @return object the DB_Error object
*
* @see DB_common::raiseError(),
* DB_fbsql::errorNative(), DB_common::errorCode()
*/
function fbsqlRaiseError($errno = null)
{
if ($errno === null) {
$errno = $this->errorCode(fbsql_errno($this->connection));
}
return $this->raiseError($errno, null, null, null,
@fbsql_error($this->connection));
}
// }}}
// {{{ errorNative()
/**
* Gets the DBMS' native error code produced by the last query
*
* @return int the DBMS' error code
*/
function errorNative()
{
return @fbsql_errno($this->connection);
}
// }}}
// {{{ tableInfo()
/**
* Returns information about a table or a result set
*
* @param object|string $result DB_result object from a query or a
* string containing the name of a table.
* While this also accepts a query result
* resource identifier, this behavior is
* deprecated.
* @param int $mode a valid tableInfo mode
*
* @return array an associative array with the information requested.
* A DB_Error object on failure.
*
* @see DB_common::tableInfo()
*/
function tableInfo($result, $mode = null)
{
if (is_string($result)) {
/*
* Probably received a table name.
* Create a result resource identifier.
*/
$id = @fbsql_list_fields($this->dsn['database'],
$result, $this->connection);
$got_string = true;
} elseif (isset($result->result)) {
/*
* Probably received a result object.
* Extract the result resource identifier.
*/
$id = $result->result;
$got_string = false;
} else {
/*
* Probably received a result resource identifier.
* Copy it.
* Deprecated. Here for compatibility only.
*/
$id = $result;
$got_string = false;
}
if (!is_resource($id)) {
return $this->fbsqlRaiseError(DB_ERROR_NEED_MORE_DATA);
}
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
$case_func = 'strtolower';
} else {
$case_func = 'strval';
}
$count = @fbsql_num_fields($id);
$res = array();
if ($mode) {
$res['num_fields'] = $count;
}
for ($i = 0; $i < $count; $i++) {
$res[$i] = array(
'table' => $case_func(@fbsql_field_table($id, $i)),
'name' => $case_func(@fbsql_field_name($id, $i)),
'type' => @fbsql_field_type($id, $i),
'len' => @fbsql_field_len($id, $i),
'flags' => @fbsql_field_flags($id, $i),
);
if ($mode & DB_TABLEINFO_ORDER) {
$res['order'][$res[$i]['name']] = $i;
}
if ($mode & DB_TABLEINFO_ORDERTABLE) {
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
}
}
// free the result only if we were called on a table
if ($got_string) {
@fbsql_free_result($id);
}
return $res;
}
// }}}
// {{{ getSpecialQuery()
/**
* Obtains the query string needed for listing a given type of objects
*
* @param string $type the kind of objects you want to retrieve
*
* @return string the SQL query string or null if the driver doesn't
* support the object type requested
*
* @access protected
* @see DB_common::getListOf()
*/
function getSpecialQuery($type)
{
switch ($type) {
case 'tables':
return 'SELECT "table_name" FROM information_schema.tables'
. ' t0, information_schema.schemata t1'
. ' WHERE t0.schema_pk=t1.schema_pk AND'
. ' "table_type" = \'BASE TABLE\''
. ' AND "schema_name" = current_schema';
case 'views':
return 'SELECT "table_name" FROM information_schema.tables'
. ' t0, information_schema.schemata t1'
. ' WHERE t0.schema_pk=t1.schema_pk AND'
. ' "table_type" = \'VIEW\''
. ' AND "schema_name" = current_schema';
case 'users':
return 'SELECT "user_name" from information_schema.users';
case 'functions':
return 'SELECT "routine_name" FROM'
. ' information_schema.psm_routines'
. ' t0, information_schema.schemata t1'
. ' WHERE t0.schema_pk=t1.schema_pk'
. ' AND "routine_kind"=\'FUNCTION\''
. ' AND "schema_name" = current_schema';
case 'procedures':
return 'SELECT "routine_name" FROM'
. ' information_schema.psm_routines'
. ' t0, information_schema.schemata t1'
. ' WHERE t0.schema_pk=t1.schema_pk'
. ' AND "routine_kind"=\'PROCEDURE\''
. ' AND "schema_name" = current_schema';
default:
return null;
}
}
// }}}
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
*/
?>

1071
inc/DB/ibase.php Executable file

File diff suppressed because it is too large Load Diff

681
inc/DB/ifx.php Executable file
View File

@ -0,0 +1,681 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* The PEAR DB driver for PHP's ifx extension
* for interacting with Informix databases
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category Database
* @package DB
* @author Tomas V.V.Cox <cox@idecnet.com>
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: ifx.php,v 1.70 2005/02/20 00:44:48 danielc Exp $
* @link http://pear.php.net/package/DB
*/
/**
* Obtain the DB_common class so it can be extended from
*/
require_once 'DB/common.php';
/**
* The methods PEAR DB uses to interact with PHP's ifx extension
* for interacting with Informix databases
*
* These methods overload the ones declared in DB_common.
*
* More info on Informix errors can be found at:
* http://www.informix.com/answers/english/ierrors.htm
*
* TODO:
* - set needed env Informix vars on connect
* - implement native prepare/execute
*
* @category Database
* @package DB
* @author Tomas V.V.Cox <cox@idecnet.com>
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: @package_version@
* @link http://pear.php.net/package/DB
*/
class DB_ifx extends DB_common
{
// {{{ properties
/**
* The DB driver type (mysql, oci8, odbc, etc.)
* @var string
*/
var $phptype = 'ifx';
/**
* The database syntax variant to be used (db2, access, etc.), if any
* @var string
*/
var $dbsyntax = 'ifx';
/**
* The capabilities of this DB implementation
*
* The 'new_link' element contains the PHP version that first provided
* new_link support for this DBMS. Contains false if it's unsupported.
*
* Meaning of the 'limit' element:
* + 'emulate' = emulate with fetch row by number
* + 'alter' = alter the query
* + false = skip rows
*
* @var array
*/
var $features = array(
'limit' => 'emulate',
'new_link' => false,
'numrows' => 'emulate',
'pconnect' => true,
'prepare' => false,
'ssl' => false,
'transactions' => true,
);
/**
* A mapping of native error codes to DB error codes
* @var array
*/
var $errorcode_map = array(
'-201' => DB_ERROR_SYNTAX,
'-206' => DB_ERROR_NOSUCHTABLE,
'-217' => DB_ERROR_NOSUCHFIELD,
'-236' => DB_ERROR_VALUE_COUNT_ON_ROW,
'-239' => DB_ERROR_CONSTRAINT,
'-253' => DB_ERROR_SYNTAX,
'-292' => DB_ERROR_CONSTRAINT_NOT_NULL,
'-310' => DB_ERROR_ALREADY_EXISTS,
'-316' => DB_ERROR_ALREADY_EXISTS,
'-319' => DB_ERROR_NOT_FOUND,
'-329' => DB_ERROR_NODBSELECTED,
'-346' => DB_ERROR_CONSTRAINT,
'-386' => DB_ERROR_CONSTRAINT_NOT_NULL,
'-391' => DB_ERROR_CONSTRAINT_NOT_NULL,
'-554' => DB_ERROR_SYNTAX,
'-691' => DB_ERROR_CONSTRAINT,
'-692' => DB_ERROR_CONSTRAINT,
'-703' => DB_ERROR_CONSTRAINT_NOT_NULL,
'-1204' => DB_ERROR_INVALID_DATE,
'-1205' => DB_ERROR_INVALID_DATE,
'-1206' => DB_ERROR_INVALID_DATE,
'-1209' => DB_ERROR_INVALID_DATE,
'-1210' => DB_ERROR_INVALID_DATE,
'-1212' => DB_ERROR_INVALID_DATE,
'-1213' => DB_ERROR_INVALID_NUMBER,
);
/**
* The raw database connection created by PHP
* @var resource
*/
var $connection;
/**
* The DSN information for connecting to a database
* @var array
*/
var $dsn = array();
/**
* Should data manipulation queries be committed automatically?
* @var bool
* @access private
*/
var $autocommit = true;
/**
* The quantity of transactions begun
*
* {@internal While this is private, it can't actually be designated
* private in PHP 5 because it is directly accessed in the test suite.}}
*
* @var integer
* @access private
*/
var $transaction_opcount = 0;
/**
* The number of rows affected by a data manipulation query
* @var integer
* @access private
*/
var $affected = 0;
// }}}
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
*
* @return void
*/
function DB_ifx()
{
$this->DB_common();
}
// }}}
// {{{ connect()
/**
* Connect to the database server, log in and open the database
*
* Don't call this method directly. Use DB::connect() instead.
*
* @param array $dsn the data source name
* @param bool $persistent should the connection be persistent?
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function connect($dsn, $persistent = false)
{
if (!PEAR::loadExtension('informix') &&
!PEAR::loadExtension('Informix'))
{
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
}
$this->dsn = $dsn;
if ($dsn['dbsyntax']) {
$this->dbsyntax = $dsn['dbsyntax'];
}
$dbhost = $dsn['hostspec'] ? '@' . $dsn['hostspec'] : '';
$dbname = $dsn['database'] ? $dsn['database'] . $dbhost : '';
$user = $dsn['username'] ? $dsn['username'] : '';
$pw = $dsn['password'] ? $dsn['password'] : '';
$connect_function = $persistent ? 'ifx_pconnect' : 'ifx_connect';
$this->connection = @$connect_function($dbname, $user, $pw);
if (!is_resource($this->connection)) {
return $this->ifxRaiseError(DB_ERROR_CONNECT_FAILED);
}
return DB_OK;
}
// }}}
// {{{ disconnect()
/**
* Disconnects from the database server
*
* @return bool TRUE on success, FALSE on failure
*/
function disconnect()
{
$ret = @ifx_close($this->connection);
$this->connection = null;
return $ret;
}
// }}}
// {{{ simpleQuery()
/**
* Sends a query to the database server
*
* @param string the SQL query string
*
* @return mixed + a PHP result resrouce for successful SELECT queries
* + the DB_OK constant for other successful queries
* + a DB_Error object on failure
*/
function simpleQuery($query)
{
$ismanip = DB::isManip($query);
$this->last_query = $query;
$this->affected = null;
if (preg_match('/(SELECT)/i', $query)) { //TESTME: Use !DB::isManip()?
// the scroll is needed for fetching absolute row numbers
// in a select query result
$result = @ifx_query($query, $this->connection, IFX_SCROLL);
} else {
if (!$this->autocommit && $ismanip) {
if ($this->transaction_opcount == 0) {
$result = @ifx_query('BEGIN WORK', $this->connection);
if (!$result) {
return $this->ifxRaiseError();
}
}
$this->transaction_opcount++;
}
$result = @ifx_query($query, $this->connection);
}
if (!$result) {
return $this->ifxRaiseError();
}
$this->affected = @ifx_affected_rows($result);
// Determine which queries should return data, and which
// should return an error code only.
if (preg_match('/(SELECT)/i', $query)) {
return $result;
}
// XXX Testme: free results inside a transaction
// may cause to stop it and commit the work?
// Result has to be freed even with a insert or update
@ifx_free_result($result);
return DB_OK;
}
// }}}
// {{{ nextResult()
/**
* Move the internal ifx result pointer to the next available result
*
* @param a valid fbsql result resource
*
* @access public
*
* @return true if a result is available otherwise return false
*/
function nextResult($result)
{
return false;
}
// }}}
// {{{ affectedRows()
/**
* Determines the number of rows affected by a data maniuplation query
*
* 0 is returned for queries that don't manipulate data.
*
* @return int the number of rows. A DB_Error object on failure.
*/
function affectedRows()
{
if (DB::isManip($this->last_query)) {
return $this->affected;
} else {
return 0;
}
}
// }}}
// {{{ fetchInto()
/**
* Places a row from the result set into the given array
*
* Formating of the array and the data therein are configurable.
* See DB_result::fetchInto() for more information.
*
* This method is not meant to be called directly. Use
* DB_result::fetchInto() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result the query result resource
* @param array $arr the referenced array to put the data in
* @param int $fetchmode how the resulting array should be indexed
* @param int $rownum the row number to fetch (0 = first row)
*
* @return mixed DB_OK on success, NULL when the end of a result set is
* reached or on failure
*
* @see DB_result::fetchInto()
*/
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
{
if (($rownum !== null) && ($rownum < 0)) {
return null;
}
if ($rownum === null) {
/*
* Even though fetch_row() should return the next row if
* $rownum is null, it doesn't in all cases. Bug 598.
*/
$rownum = 'NEXT';
} else {
// Index starts at row 1, unlike most DBMS's starting at 0.
$rownum++;
}
if (!$arr = @ifx_fetch_row($result, $rownum)) {
return null;
}
if ($fetchmode !== DB_FETCHMODE_ASSOC) {
$i=0;
$order = array();
foreach ($arr as $val) {
$order[$i++] = $val;
}
$arr = $order;
} elseif ($fetchmode == DB_FETCHMODE_ASSOC &&
$this->options['portability'] & DB_PORTABILITY_LOWERCASE)
{
$arr = array_change_key_case($arr, CASE_LOWER);
}
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
$this->_rtrimArrayValues($arr);
}
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
$this->_convertNullArrayValuesToEmpty($arr);
}
return DB_OK;
}
// }}}
// {{{ numCols()
/**
* Gets the number of columns in a result set
*
* This method is not meant to be called directly. Use
* DB_result::numCols() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return int the number of columns. A DB_Error object on failure.
*
* @see DB_result::numCols()
*/
function numCols($result)
{
if (!$cols = @ifx_num_fields($result)) {
return $this->ifxRaiseError();
}
return $cols;
}
// }}}
// {{{ freeResult()
/**
* Deletes the result set and frees the memory occupied by the result set
*
* This method is not meant to be called directly. Use
* DB_result::free() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return bool TRUE on success, FALSE if $result is invalid
*
* @see DB_result::free()
*/
function freeResult($result)
{
return @ifx_free_result($result);
}
// }}}
// {{{ autoCommit()
/**
* Enables or disables automatic commits
*
* @param bool $onoff true turns it on, false turns it off
*
* @return int DB_OK on success. A DB_Error object if the driver
* doesn't support auto-committing transactions.
*/
function autoCommit($onoff = true)
{
// XXX if $this->transaction_opcount > 0, we should probably
// issue a warning here.
$this->autocommit = $onoff ? true : false;
return DB_OK;
}
// }}}
// {{{ commit()
/**
* Commits the current transaction
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function commit()
{
if ($this->transaction_opcount > 0) {
$result = @ifx_query('COMMIT WORK', $this->connection);
$this->transaction_opcount = 0;
if (!$result) {
return $this->ifxRaiseError();
}
}
return DB_OK;
}
// }}}
// {{{ rollback()
/**
* Reverts the current transaction
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function rollback()
{
if ($this->transaction_opcount > 0) {
$result = @ifx_query('ROLLBACK WORK', $this->connection);
$this->transaction_opcount = 0;
if (!$result) {
return $this->ifxRaiseError();
}
}
return DB_OK;
}
// }}}
// {{{ ifxRaiseError()
/**
* Produces a DB_Error object regarding the current problem
*
* @param int $errno if the error is being manually raised pass a
* DB_ERROR* constant here. If this isn't passed
* the error information gathered from the DBMS.
*
* @return object the DB_Error object
*
* @see DB_common::raiseError(),
* DB_ifx::errorNative(), DB_ifx::errorCode()
*/
function ifxRaiseError($errno = null)
{
if ($errno === null) {
$errno = $this->errorCode(ifx_error());
}
return $this->raiseError($errno, null, null, null,
$this->errorNative());
}
// }}}
// {{{ errorNative()
/**
* Gets the DBMS' native error code and message produced by the last query
*
* @return string the DBMS' error code and message
*/
function errorNative()
{
return @ifx_error() . ' ' . @ifx_errormsg();
}
// }}}
// {{{ errorCode()
/**
* Maps native error codes to DB's portable ones.
*
* Requires that the DB implementation's constructor fills
* in the <var>$errorcode_map</var> property.
*
* @param string $nativecode error code returned by the database
* @return int a portable DB error code, or DB_ERROR if this DB
* implementation has no mapping for the given error code.
*/
function errorCode($nativecode)
{
if (ereg('SQLCODE=(.*)]', $nativecode, $match)) {
$code = $match[1];
if (isset($this->errorcode_map[$code])) {
return $this->errorcode_map[$code];
}
}
return DB_ERROR;
}
// }}}
// {{{ tableInfo()
/**
* Returns information about a table or a result set
*
* NOTE: only supports 'table' if <var>$result</var> is a table name.
*
* If analyzing a query result and the result has duplicate field names,
* an error will be raised saying
* <samp>can't distinguish duplicate field names</samp>.
*
* @param object|string $result DB_result object from a query or a
* string containing the name of a table.
* While this also accepts a query result
* resource identifier, this behavior is
* deprecated.
* @param int $mode a valid tableInfo mode
*
* @return array an associative array with the information requested.
* A DB_Error object on failure.
*
* @see DB_common::tableInfo()
* @since Method available since Release 1.6.0
*/
function tableInfo($result, $mode = null)
{
if (is_string($result)) {
/*
* Probably received a table name.
* Create a result resource identifier.
*/
$id = @ifx_query("SELECT * FROM $result WHERE 1=0",
$this->connection);
$got_string = true;
} elseif (isset($result->result)) {
/*
* Probably received a result object.
* Extract the result resource identifier.
*/
$id = $result->result;
$got_string = false;
} else {
/*
* Probably received a result resource identifier.
* Copy it.
*/
$id = $result;
$got_string = false;
}
if (!is_resource($id)) {
return $this->ifxRaiseError(DB_ERROR_NEED_MORE_DATA);
}
$flds = @ifx_fieldproperties($id);
$count = @ifx_num_fields($id);
if (count($flds) != $count) {
return $this->raiseError("can't distinguish duplicate field names");
}
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
$case_func = 'strtolower';
} else {
$case_func = 'strval';
}
$i = 0;
$res = array();
if ($mode) {
$res['num_fields'] = $count;
}
foreach ($flds as $key => $value) {
$props = explode(';', $value);
$res[$i] = array(
'table' => $got_string ? $case_func($result) : '',
'name' => $case_func($key),
'type' => $props[0],
'len' => $props[1],
'flags' => $props[4] == 'N' ? 'not_null' : '',
);
if ($mode & DB_TABLEINFO_ORDER) {
$res['order'][$res[$i]['name']] = $i;
}
if ($mode & DB_TABLEINFO_ORDERTABLE) {
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
}
$i++;
}
// free the result only if we were called on a table
if ($got_string) {
@ifx_free_result($id);
}
return $res;
}
// }}}
// {{{ getSpecialQuery()
/**
* Obtains the query string needed for listing a given type of objects
*
* @param string $type the kind of objects you want to retrieve
*
* @return string the SQL query string or null if the driver doesn't
* support the object type requested
*
* @access protected
* @see DB_common::getListOf()
*/
function getSpecialQuery($type)
{
switch ($type) {
case 'tables':
return 'SELECT tabname FROM systables WHERE tabid >= 100';
default:
return null;
}
}
// }}}
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
*/
?>

810
inc/DB/msql.php Executable file
View File

@ -0,0 +1,810 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* The PEAR DB driver for PHP's msql extension
* for interacting with Mini SQL databases
*
* PHP's mSQL extension did weird things with NULL values prior to PHP
* 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds
* those versions.
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category Database
* @package DB
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: msql.php,v 1.57 2005/02/22 07:26:46 danielc Exp $
* @link http://pear.php.net/package/DB
*/
/**
* Obtain the DB_common class so it can be extended from
*/
require_once 'DB/common.php';
/**
* The methods PEAR DB uses to interact with PHP's msql extension
* for interacting with Mini SQL databases
*
* These methods overload the ones declared in DB_common.
*
* PHP's mSQL extension did weird things with NULL values prior to PHP
* 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds
* those versions.
*
* @category Database
* @package DB
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: @package_version@
* @link http://pear.php.net/package/DB
* @since Class not functional until Release 1.7.0
*/
class DB_msql extends DB_common
{
// {{{ properties
/**
* The DB driver type (mysql, oci8, odbc, etc.)
* @var string
*/
var $phptype = 'msql';
/**
* The database syntax variant to be used (db2, access, etc.), if any
* @var string
*/
var $dbsyntax = 'msql';
/**
* The capabilities of this DB implementation
*
* The 'new_link' element contains the PHP version that first provided
* new_link support for this DBMS. Contains false if it's unsupported.
*
* Meaning of the 'limit' element:
* + 'emulate' = emulate with fetch row by number
* + 'alter' = alter the query
* + false = skip rows
*
* @var array
*/
var $features = array(
'limit' => 'emulate',
'new_link' => false,
'numrows' => true,
'pconnect' => true,
'prepare' => false,
'ssl' => false,
'transactions' => false,
);
/**
* A mapping of native error codes to DB error codes
* @var array
*/
var $errorcode_map = array(
);
/**
* The raw database connection created by PHP
* @var resource
*/
var $connection;
/**
* The DSN information for connecting to a database
* @var array
*/
var $dsn = array();
/**
* The query result resource created by PHP
*
* Used to make affectedRows() work. Only contains the result for
* data manipulation queries. Contains false for other queries.
*
* @var resource
* @access private
*/
var $_result;
// }}}
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
*
* @return void
*/
function DB_msql()
{
$this->DB_common();
}
// }}}
// {{{ connect()
/**
* Connect to the database server, log in and open the database
*
* Don't call this method directly. Use DB::connect() instead.
*
* Example of how to connect:
* <code>
* require_once 'DB.php';
*
* // $dsn = 'msql://hostname/dbname'; // use a TCP connection
* $dsn = 'msql:///dbname'; // use a socket
* $options = array(
* 'portability' => DB_PORTABILITY_ALL,
* );
*
* $db =& DB::connect($dsn, $options);
* if (PEAR::isError($db)) {
* die($db->getMessage());
* }
* </code>
*
* @param array $dsn the data source name
* @param bool $persistent should the connection be persistent?
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function connect($dsn, $persistent = false)
{
if (!PEAR::loadExtension('msql')) {
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
}
$this->dsn = $dsn;
if ($dsn['dbsyntax']) {
$this->dbsyntax = $dsn['dbsyntax'];
}
$params = array();
if ($dsn['hostspec']) {
$params[] = $dsn['port']
? $dsn['hostspec'] . ',' . $dsn['port']
: $dsn['hostspec'];
}
$connect_function = $persistent ? 'msql_pconnect' : 'msql_connect';
$ini = ini_get('track_errors');
$php_errormsg = '';
if ($ini) {
$this->connection = @call_user_func_array($connect_function,
$params);
} else {
ini_set('track_errors', 1);
$this->connection = @call_user_func_array($connect_function,
$params);
ini_set('track_errors', $ini);
}
if (!$this->connection) {
if (($err = @msql_error()) != '') {
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
null, null, null,
$err);
} else {
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
null, null, null,
$php_errormsg);
}
}
if (!@msql_select_db($dsn['database'], $this->connection)) {
return $this->msqlRaiseError();
}
return DB_OK;
}
// }}}
// {{{ disconnect()
/**
* Disconnects from the database server
*
* @return bool TRUE on success, FALSE on failure
*/
function disconnect()
{
$ret = @msql_close($this->connection);
$this->connection = null;
return $ret;
}
// }}}
// {{{ simpleQuery()
/**
* Sends a query to the database server
*
* @param string the SQL query string
*
* @return mixed + a PHP result resrouce for successful SELECT queries
* + the DB_OK constant for other successful queries
* + a DB_Error object on failure
*/
function simpleQuery($query)
{
$this->last_query = $query;
$query = $this->modifyQuery($query);
$result = @msql_query($query, $this->connection);
if (!$result) {
return $this->msqlRaiseError();
}
// Determine which queries that should return data, and which
// should return an error code only.
if (DB::isManip($query)) {
$this->_result = $result;
return DB_OK;
} else {
$this->_result = false;
return $result;
}
}
// }}}
// {{{ nextResult()
/**
* Move the internal msql result pointer to the next available result
*
* @param a valid fbsql result resource
*
* @access public
*
* @return true if a result is available otherwise return false
*/
function nextResult($result)
{
return false;
}
// }}}
// {{{ fetchInto()
/**
* Places a row from the result set into the given array
*
* Formating of the array and the data therein are configurable.
* See DB_result::fetchInto() for more information.
*
* This method is not meant to be called directly. Use
* DB_result::fetchInto() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* PHP's mSQL extension did weird things with NULL values prior to PHP
* 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds
* those versions.
*
* @param resource $result the query result resource
* @param array $arr the referenced array to put the data in
* @param int $fetchmode how the resulting array should be indexed
* @param int $rownum the row number to fetch (0 = first row)
*
* @return mixed DB_OK on success, NULL when the end of a result set is
* reached or on failure
*
* @see DB_result::fetchInto()
*/
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
{
if ($rownum !== null) {
if (!@msql_data_seek($result, $rownum)) {
return null;
}
}
if ($fetchmode & DB_FETCHMODE_ASSOC) {
$arr = @msql_fetch_array($result, MSQL_ASSOC);
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
$arr = array_change_key_case($arr, CASE_LOWER);
}
} else {
$arr = @msql_fetch_row($result);
}
if (!$arr) {
return null;
}
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
$this->_rtrimArrayValues($arr);
}
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
$this->_convertNullArrayValuesToEmpty($arr);
}
return DB_OK;
}
// }}}
// {{{ freeResult()
/**
* Deletes the result set and frees the memory occupied by the result set
*
* This method is not meant to be called directly. Use
* DB_result::free() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return bool TRUE on success, FALSE if $result is invalid
*
* @see DB_result::free()
*/
function freeResult($result)
{
return @msql_free_result($result);
}
// }}}
// {{{ numCols()
/**
* Gets the number of columns in a result set
*
* This method is not meant to be called directly. Use
* DB_result::numCols() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return int the number of columns. A DB_Error object on failure.
*
* @see DB_result::numCols()
*/
function numCols($result)
{
$cols = @msql_num_fields($result);
if (!$cols) {
return $this->msqlRaiseError();
}
return $cols;
}
// }}}
// {{{ numRows()
/**
* Gets the number of rows in a result set
*
* This method is not meant to be called directly. Use
* DB_result::numRows() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return int the number of rows. A DB_Error object on failure.
*
* @see DB_result::numRows()
*/
function numRows($result)
{
$rows = @msql_num_rows($result);
if ($rows === false) {
return $this->msqlRaiseError();
}
return $rows;
}
// }}}
// {{{ affected()
/**
* Determines the number of rows affected by a data maniuplation query
*
* 0 is returned for queries that don't manipulate data.
*
* @return int the number of rows. A DB_Error object on failure.
*/
function affectedRows()
{
if (!$this->_result) {
return 0;
}
return msql_affected_rows($this->_result);
}
// }}}
// {{{ nextId()
/**
* Returns the next free id in a sequence
*
* @param string $seq_name name of the sequence
* @param boolean $ondemand when true, the seqence is automatically
* created if it does not exist
*
* @return int the next id number in the sequence.
* A DB_Error object on failure.
*
* @see DB_common::nextID(), DB_common::getSequenceName(),
* DB_msql::createSequence(), DB_msql::dropSequence()
*/
function nextId($seq_name, $ondemand = true)
{
$seqname = $this->getSequenceName($seq_name);
$repeat = false;
do {
$this->pushErrorHandling(PEAR_ERROR_RETURN);
$result =& $this->query("SELECT _seq FROM ${seqname}");
$this->popErrorHandling();
if ($ondemand && DB::isError($result) &&
$result->getCode() == DB_ERROR_NOSUCHTABLE) {
$repeat = true;
$this->pushErrorHandling(PEAR_ERROR_RETURN);
$result = $this->createSequence($seq_name);
$this->popErrorHandling();
if (DB::isError($result)) {
return $this->raiseError($result);
}
} else {
$repeat = false;
}
} while ($repeat);
if (DB::isError($result)) {
return $this->raiseError($result);
}
$arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
$result->free();
return $arr[0];
}
// }}}
// {{{ createSequence()
/**
* Creates a new sequence
*
* Also creates a new table to associate the sequence with. Uses
* a separate table to ensure portability with other drivers.
*
* @param string $seq_name name of the new sequence
*
* @return int DB_OK on success. A DB_Error object on failure.
*
* @see DB_common::createSequence(), DB_common::getSequenceName(),
* DB_msql::nextID(), DB_msql::dropSequence()
*/
function createSequence($seq_name)
{
$seqname = $this->getSequenceName($seq_name);
$res = $this->query('CREATE TABLE ' . $seqname
. ' (id INTEGER NOT NULL)');
if (DB::isError($res)) {
return $res;
}
$res = $this->query("CREATE SEQUENCE ON ${seqname}");
return $res;
}
// }}}
// {{{ dropSequence()
/**
* Deletes a sequence
*
* @param string $seq_name name of the sequence to be deleted
*
* @return int DB_OK on success. A DB_Error object on failure.
*
* @see DB_common::dropSequence(), DB_common::getSequenceName(),
* DB_msql::nextID(), DB_msql::createSequence()
*/
function dropSequence($seq_name)
{
return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
}
// }}}
// {{{ quoteIdentifier()
/**
* mSQL does not support delimited identifiers
*
* @param string $str the identifier name to be quoted
*
* @return object a DB_Error object
*
* @see DB_common::quoteIdentifier()
* @since Method available since Release 1.7.0
*/
function quoteIdentifier($str)
{
return $this->raiseError(DB_ERROR_UNSUPPORTED);
}
// }}}
// {{{ escapeSimple()
/**
* Escapes a string according to the current DBMS's standards
*
* @param string $str the string to be escaped
*
* @return string the escaped string
*
* @see DB_common::quoteSmart()
* @since Method available since Release 1.7.0
*/
function escapeSimple($str)
{
return addslashes($str);
}
// }}}
// {{{ msqlRaiseError()
/**
* Produces a DB_Error object regarding the current problem
*
* @param int $errno if the error is being manually raised pass a
* DB_ERROR* constant here. If this isn't passed
* the error information gathered from the DBMS.
*
* @return object the DB_Error object
*
* @see DB_common::raiseError(),
* DB_msql::errorNative(), DB_msql::errorCode()
*/
function msqlRaiseError($errno = null)
{
$native = $this->errorNative();
if ($errno === null) {
$errno = $this->errorCode($native);
}
return $this->raiseError($errno, null, null, null, $native);
}
// }}}
// {{{ errorNative()
/**
* Gets the DBMS' native error message produced by the last query
*
* @return string the DBMS' error message
*/
function errorNative()
{
return @msql_error();
}
// }}}
// {{{ errorCode()
/**
* Determines PEAR::DB error code from the database's text error message
*
* @param string $errormsg the error message returned from the database
*
* @return integer the error number from a DB_ERROR* constant
*/
function errorCode($errormsg)
{
static $error_regexps;
if (!isset($error_regexps)) {
$error_regexps = array(
'/^Access to database denied/i'
=> DB_ERROR_ACCESS_VIOLATION,
'/^Bad index name/i'
=> DB_ERROR_ALREADY_EXISTS,
'/^Bad order field/i'
=> DB_ERROR_SYNTAX,
'/^Bad type for comparison/i'
=> DB_ERROR_SYNTAX,
'/^Can\'t perform LIKE on/i'
=> DB_ERROR_SYNTAX,
'/^Can\'t use TEXT fields in LIKE comparison/i'
=> DB_ERROR_SYNTAX,
'/^Couldn\'t create temporary table/i'
=> DB_ERROR_CANNOT_CREATE,
'/^Error creating table file/i'
=> DB_ERROR_CANNOT_CREATE,
'/^Field .* cannot be null$/i'
=> DB_ERROR_CONSTRAINT_NOT_NULL,
'/^Index (field|condition) .* cannot be null$/i'
=> DB_ERROR_SYNTAX,
'/^Invalid date format/i'
=> DB_ERROR_INVALID_DATE,
'/^Invalid time format/i'
=> DB_ERROR_INVALID,
'/^Literal value for .* is wrong type$/i'
=> DB_ERROR_INVALID_NUMBER,
'/^No Database Selected/i'
=> DB_ERROR_NODBSELECTED,
'/^No value specified for field/i'
=> DB_ERROR_VALUE_COUNT_ON_ROW,
'/^Non unique value for unique index/i'
=> DB_ERROR_CONSTRAINT,
'/^Out of memory for temporary table/i'
=> DB_ERROR_CANNOT_CREATE,
'/^Permission denied/i'
=> DB_ERROR_ACCESS_VIOLATION,
'/^Reference to un-selected table/i'
=> DB_ERROR_SYNTAX,
'/^syntax error/i'
=> DB_ERROR_SYNTAX,
'/^Table .* exists$/i'
=> DB_ERROR_ALREADY_EXISTS,
'/^Unknown database/i'
=> DB_ERROR_NOSUCHDB,
'/^Unknown field/i'
=> DB_ERROR_NOSUCHFIELD,
'/^Unknown (index|system variable)/i'
=> DB_ERROR_NOT_FOUND,
'/^Unknown table/i'
=> DB_ERROR_NOSUCHTABLE,
'/^Unqualified field/i'
=> DB_ERROR_SYNTAX,
);
}
foreach ($error_regexps as $regexp => $code) {
if (preg_match($regexp, $errormsg)) {
return $code;
}
}
return DB_ERROR;
}
// }}}
// {{{ tableInfo()
/**
* Returns information about a table or a result set
*
* @param object|string $result DB_result object from a query or a
* string containing the name of a table.
* While this also accepts a query result
* resource identifier, this behavior is
* deprecated.
* @param int $mode a valid tableInfo mode
*
* @return array an associative array with the information requested.
* A DB_Error object on failure.
*
* @see DB_common::setOption()
*/
function tableInfo($result, $mode = null)
{
if (is_string($result)) {
/*
* Probably received a table name.
* Create a result resource identifier.
*/
$id = @msql_query("SELECT * FROM $result",
$this->connection);
$got_string = true;
} elseif (isset($result->result)) {
/*
* Probably received a result object.
* Extract the result resource identifier.
*/
$id = $result->result;
$got_string = false;
} else {
/*
* Probably received a result resource identifier.
* Copy it.
* Deprecated. Here for compatibility only.
*/
$id = $result;
$got_string = false;
}
if (!is_resource($id)) {
return $this->raiseError(DB_ERROR_NEED_MORE_DATA);
}
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
$case_func = 'strtolower';
} else {
$case_func = 'strval';
}
$count = @msql_num_fields($id);
$res = array();
if ($mode) {
$res['num_fields'] = $count;
}
for ($i = 0; $i < $count; $i++) {
$tmp = @msql_fetch_field($id);
$flags = '';
if ($tmp->not_null) {
$flags .= 'not_null ';
}
if ($tmp->unique) {
$flags .= 'unique_key ';
}
$flags = trim($flags);
$res[$i] = array(
'table' => $case_func($tmp->table),
'name' => $case_func($tmp->name),
'type' => $tmp->type,
'len' => msql_field_len($id, $i),
'flags' => $flags,
);
if ($mode & DB_TABLEINFO_ORDER) {
$res['order'][$res[$i]['name']] = $i;
}
if ($mode & DB_TABLEINFO_ORDERTABLE) {
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
}
}
// free the result only if we were called on a table
if ($got_string) {
@msql_free_result($id);
}
return $res;
}
// }}}
// {{{ getSpecialQuery()
/**
* Obtain a list of a given type of objects
*
* @param string $type the kind of objects you want to retrieve
*
* @return array the array containing the list of objects requested
*
* @access protected
* @see DB_common::getListOf()
*/
function getSpecialQuery($type)
{
switch ($type) {
case 'databases':
$id = @msql_list_dbs($this->connection);
break;
case 'tables':
$id = @msql_list_tables($this->dsn['database'],
$this->connection);
break;
default:
return null;
}
if (!$id) {
return $this->msqlRaiseError();
}
$out = array();
while ($row = @msql_fetch_row($id)) {
$out[] = $row[0];
}
return $out;
}
// }}}
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
*/
?>

914
inc/DB/mssql.php Executable file
View File

@ -0,0 +1,914 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* The PEAR DB driver for PHP's mssql extension
* for interacting with Microsoft SQL Server databases
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category Database
* @package DB
* @author Sterling Hughes <sterling@php.net>
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: mssql.php,v 1.83 2005/03/07 18:24:51 danielc Exp $
* @link http://pear.php.net/package/DB
*/
/**
* Obtain the DB_common class so it can be extended from
*/
require_once 'DB/common.php';
/**
* The methods PEAR DB uses to interact with PHP's mssql extension
* for interacting with Microsoft SQL Server databases
*
* These methods overload the ones declared in DB_common.
*
* @category Database
* @package DB
* @author Sterling Hughes <sterling@php.net>
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: @package_version@
* @link http://pear.php.net/package/DB
*/
class DB_mssql extends DB_common
{
// {{{ properties
/**
* The DB driver type (mysql, oci8, odbc, etc.)
* @var string
*/
var $phptype = 'mssql';
/**
* The database syntax variant to be used (db2, access, etc.), if any
* @var string
*/
var $dbsyntax = 'mssql';
/**
* The capabilities of this DB implementation
*
* The 'new_link' element contains the PHP version that first provided
* new_link support for this DBMS. Contains false if it's unsupported.
*
* Meaning of the 'limit' element:
* + 'emulate' = emulate with fetch row by number
* + 'alter' = alter the query
* + false = skip rows
*
* @var array
*/
var $features = array(
'limit' => 'emulate',
'new_link' => false,
'numrows' => true,
'pconnect' => true,
'prepare' => false,
'ssl' => false,
'transactions' => true,
);
/**
* A mapping of native error codes to DB error codes
* @var array
*/
// XXX Add here error codes ie: 'S100E' => DB_ERROR_SYNTAX
var $errorcode_map = array(
110 => DB_ERROR_VALUE_COUNT_ON_ROW,
155 => DB_ERROR_NOSUCHFIELD,
170 => DB_ERROR_SYNTAX,
207 => DB_ERROR_NOSUCHFIELD,
208 => DB_ERROR_NOSUCHTABLE,
245 => DB_ERROR_INVALID_NUMBER,
515 => DB_ERROR_CONSTRAINT_NOT_NULL,
547 => DB_ERROR_CONSTRAINT,
1913 => DB_ERROR_ALREADY_EXISTS,
2627 => DB_ERROR_CONSTRAINT,
2714 => DB_ERROR_ALREADY_EXISTS,
3701 => DB_ERROR_NOSUCHTABLE,
8134 => DB_ERROR_DIVZERO,
);
/**
* The raw database connection created by PHP
* @var resource
*/
var $connection;
/**
* The DSN information for connecting to a database
* @var array
*/
var $dsn = array();
/**
* Should data manipulation queries be committed automatically?
* @var bool
* @access private
*/
var $autocommit = true;
/**
* The quantity of transactions begun
*
* {@internal While this is private, it can't actually be designated
* private in PHP 5 because it is directly accessed in the test suite.}}
*
* @var integer
* @access private
*/
var $transaction_opcount = 0;
/**
* The database specified in the DSN
*
* It's a fix to allow calls to different databases in the same script.
*
* @var string
* @access private
*/
var $_db = null;
// }}}
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
*
* @return void
*/
function DB_mssql()
{
$this->DB_common();
}
// }}}
// {{{ connect()
/**
* Connect to the database server, log in and open the database
*
* Don't call this method directly. Use DB::connect() instead.
*
* @param array $dsn the data source name
* @param bool $persistent should the connection be persistent?
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function connect($dsn, $persistent = false)
{
if (!PEAR::loadExtension('mssql') && !PEAR::loadExtension('sybase')
&& !PEAR::loadExtension('sybase_ct'))
{
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
}
$this->dsn = $dsn;
if ($dsn['dbsyntax']) {
$this->dbsyntax = $dsn['dbsyntax'];
}
$params = array(
$dsn['hostspec'] ? $dsn['hostspec'] : 'localhost',
$dsn['username'] ? $dsn['username'] : null,
$dsn['password'] ? $dsn['password'] : null,
);
if ($dsn['port']) {
$params[0] .= ((substr(PHP_OS, 0, 3) == 'WIN') ? ',' : ':')
. $dsn['port'];
}
$connect_function = $persistent ? 'mssql_pconnect' : 'mssql_connect';
$this->connection = @call_user_func_array($connect_function, $params);
if (!$this->connection) {
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
null, null, null,
@mssql_get_last_message());
}
if ($dsn['database']) {
if (!@mssql_select_db($dsn['database'], $this->connection)) {
return $this->raiseError(DB_ERROR_NODBSELECTED,
null, null, null,
@mssql_get_last_message());
}
$this->_db = $dsn['database'];
}
return DB_OK;
}
// }}}
// {{{ disconnect()
/**
* Disconnects from the database server
*
* @return bool TRUE on success, FALSE on failure
*/
function disconnect()
{
$ret = @mssql_close($this->connection);
$this->connection = null;
return $ret;
}
// }}}
// {{{ simpleQuery()
/**
* Sends a query to the database server
*
* @param string the SQL query string
*
* @return mixed + a PHP result resrouce for successful SELECT queries
* + the DB_OK constant for other successful queries
* + a DB_Error object on failure
*/
function simpleQuery($query)
{
$ismanip = DB::isManip($query);
$this->last_query = $query;
if (!@mssql_select_db($this->_db, $this->connection)) {
return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
}
$query = $this->modifyQuery($query);
if (!$this->autocommit && $ismanip) {
if ($this->transaction_opcount == 0) {
$result = @mssql_query('BEGIN TRAN', $this->connection);
if (!$result) {
return $this->mssqlRaiseError();
}
}
$this->transaction_opcount++;
}
$result = @mssql_query($query, $this->connection);
if (!$result) {
return $this->mssqlRaiseError();
}
// Determine which queries that should return data, and which
// should return an error code only.
return $ismanip ? DB_OK : $result;
}
// }}}
// {{{ nextResult()
/**
* Move the internal mssql result pointer to the next available result
*
* @param a valid fbsql result resource
*
* @access public
*
* @return true if a result is available otherwise return false
*/
function nextResult($result)
{
return @mssql_next_result($result);
}
// }}}
// {{{ fetchInto()
/**
* Places a row from the result set into the given array
*
* Formating of the array and the data therein are configurable.
* See DB_result::fetchInto() for more information.
*
* This method is not meant to be called directly. Use
* DB_result::fetchInto() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result the query result resource
* @param array $arr the referenced array to put the data in
* @param int $fetchmode how the resulting array should be indexed
* @param int $rownum the row number to fetch (0 = first row)
*
* @return mixed DB_OK on success, NULL when the end of a result set is
* reached or on failure
*
* @see DB_result::fetchInto()
*/
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
{
if ($rownum !== null) {
if (!@mssql_data_seek($result, $rownum)) {
return null;
}
}
if ($fetchmode & DB_FETCHMODE_ASSOC) {
$arr = @mssql_fetch_array($result, MSSQL_ASSOC);
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
$arr = array_change_key_case($arr, CASE_LOWER);
}
} else {
$arr = @mssql_fetch_row($result);
}
if (!$arr) {
return null;
}
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
$this->_rtrimArrayValues($arr);
}
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
$this->_convertNullArrayValuesToEmpty($arr);
}
return DB_OK;
}
// }}}
// {{{ freeResult()
/**
* Deletes the result set and frees the memory occupied by the result set
*
* This method is not meant to be called directly. Use
* DB_result::free() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return bool TRUE on success, FALSE if $result is invalid
*
* @see DB_result::free()
*/
function freeResult($result)
{
return @mssql_free_result($result);
}
// }}}
// {{{ numCols()
/**
* Gets the number of columns in a result set
*
* This method is not meant to be called directly. Use
* DB_result::numCols() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return int the number of columns. A DB_Error object on failure.
*
* @see DB_result::numCols()
*/
function numCols($result)
{
$cols = @mssql_num_fields($result);
if (!$cols) {
return $this->mssqlRaiseError();
}
return $cols;
}
// }}}
// {{{ numRows()
/**
* Gets the number of rows in a result set
*
* This method is not meant to be called directly. Use
* DB_result::numRows() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return int the number of rows. A DB_Error object on failure.
*
* @see DB_result::numRows()
*/
function numRows($result)
{
$rows = @mssql_num_rows($result);
if ($rows === false) {
return $this->mssqlRaiseError();
}
return $rows;
}
// }}}
// {{{ autoCommit()
/**
* Enables or disables automatic commits
*
* @param bool $onoff true turns it on, false turns it off
*
* @return int DB_OK on success. A DB_Error object if the driver
* doesn't support auto-committing transactions.
*/
function autoCommit($onoff = false)
{
// XXX if $this->transaction_opcount > 0, we should probably
// issue a warning here.
$this->autocommit = $onoff ? true : false;
return DB_OK;
}
// }}}
// {{{ commit()
/**
* Commits the current transaction
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function commit()
{
if ($this->transaction_opcount > 0) {
if (!@mssql_select_db($this->_db, $this->connection)) {
return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
}
$result = @mssql_query('COMMIT TRAN', $this->connection);
$this->transaction_opcount = 0;
if (!$result) {
return $this->mssqlRaiseError();
}
}
return DB_OK;
}
// }}}
// {{{ rollback()
/**
* Reverts the current transaction
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function rollback()
{
if ($this->transaction_opcount > 0) {
if (!@mssql_select_db($this->_db, $this->connection)) {
return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
}
$result = @mssql_query('ROLLBACK TRAN', $this->connection);
$this->transaction_opcount = 0;
if (!$result) {
return $this->mssqlRaiseError();
}
}
return DB_OK;
}
// }}}
// {{{ affectedRows()
/**
* Determines the number of rows affected by a data maniuplation query
*
* 0 is returned for queries that don't manipulate data.
*
* @return int the number of rows. A DB_Error object on failure.
*/
function affectedRows()
{
if (DB::isManip($this->last_query)) {
$res = @mssql_query('select @@rowcount', $this->connection);
if (!$res) {
return $this->mssqlRaiseError();
}
$ar = @mssql_fetch_row($res);
if (!$ar) {
$result = 0;
} else {
@mssql_free_result($res);
$result = $ar[0];
}
} else {
$result = 0;
}
return $result;
}
// }}}
// {{{ nextId()
/**
* Returns the next free id in a sequence
*
* @param string $seq_name name of the sequence
* @param boolean $ondemand when true, the seqence is automatically
* created if it does not exist
*
* @return int the next id number in the sequence.
* A DB_Error object on failure.
*
* @see DB_common::nextID(), DB_common::getSequenceName(),
* DB_mssql::createSequence(), DB_mssql::dropSequence()
*/
function nextId($seq_name, $ondemand = true)
{
$seqname = $this->getSequenceName($seq_name);
if (!@mssql_select_db($this->_db, $this->connection)) {
return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
}
$repeat = 0;
do {
$this->pushErrorHandling(PEAR_ERROR_RETURN);
$result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)");
$this->popErrorHandling();
if ($ondemand && DB::isError($result) &&
($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE))
{
$repeat = 1;
$result = $this->createSequence($seq_name);
if (DB::isError($result)) {
return $this->raiseError($result);
}
} elseif (!DB::isError($result)) {
$result =& $this->query("SELECT @@IDENTITY FROM $seqname");
$repeat = 0;
} else {
$repeat = false;
}
} while ($repeat);
if (DB::isError($result)) {
return $this->raiseError($result);
}
$result = $result->fetchRow(DB_FETCHMODE_ORDERED);
return $result[0];
}
/**
* Creates a new sequence
*
* @param string $seq_name name of the new sequence
*
* @return int DB_OK on success. A DB_Error object on failure.
*
* @see DB_common::createSequence(), DB_common::getSequenceName(),
* DB_mssql::nextID(), DB_mssql::dropSequence()
*/
function createSequence($seq_name)
{
return $this->query('CREATE TABLE '
. $this->getSequenceName($seq_name)
. ' ([id] [int] IDENTITY (1, 1) NOT NULL,'
. ' [vapor] [int] NULL)');
}
// }}}
// {{{ dropSequence()
/**
* Deletes a sequence
*
* @param string $seq_name name of the sequence to be deleted
*
* @return int DB_OK on success. A DB_Error object on failure.
*
* @see DB_common::dropSequence(), DB_common::getSequenceName(),
* DB_mssql::nextID(), DB_mssql::createSequence()
*/
function dropSequence($seq_name)
{
return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
}
// }}}
// {{{ quoteIdentifier()
/**
* Quotes a string so it can be safely used as a table or column name
*
* @param string $str identifier name to be quoted
*
* @return string quoted identifier string
*
* @see DB_common::quoteIdentifier()
* @since Method available since Release 1.6.0
*/
function quoteIdentifier($str)
{
return '[' . str_replace(']', ']]', $str) . ']';
}
// }}}
// {{{ mssqlRaiseError()
/**
* Produces a DB_Error object regarding the current problem
*
* @param int $errno if the error is being manually raised pass a
* DB_ERROR* constant here. If this isn't passed
* the error information gathered from the DBMS.
*
* @return object the DB_Error object
*
* @see DB_common::raiseError(),
* DB_mssql::errorNative(), DB_mssql::errorCode()
*/
function mssqlRaiseError($code = null)
{
$message = @mssql_get_last_message();
if (!$code) {
$code = $this->errorNative();
}
return $this->raiseError($this->errorCode($code, $message),
null, null, null, "$code - $message");
}
// }}}
// {{{ errorNative()
/**
* Gets the DBMS' native error code produced by the last query
*
* @return int the DBMS' error code
*/
function errorNative()
{
$res = @mssql_query('select @@ERROR as ErrorCode', $this->connection);
if (!$res) {
return DB_ERROR;
}
$row = @mssql_fetch_row($res);
return $row[0];
}
// }}}
// {{{ errorCode()
/**
* Determines PEAR::DB error code from mssql's native codes.
*
* If <var>$nativecode</var> isn't known yet, it will be looked up.
*
* @param mixed $nativecode mssql error code, if known
* @return integer an error number from a DB error constant
* @see errorNative()
*/
function errorCode($nativecode = null, $msg = '')
{
if (!$nativecode) {
$nativecode = $this->errorNative();
}
if (isset($this->errorcode_map[$nativecode])) {
if ($nativecode == 3701
&& preg_match('/Cannot drop the index/i', $msg))
{
return DB_ERROR_NOT_FOUND;
}
return $this->errorcode_map[$nativecode];
} else {
return DB_ERROR;
}
}
// }}}
// {{{ tableInfo()
/**
* Returns information about a table or a result set
*
* NOTE: only supports 'table' and 'flags' if <var>$result</var>
* is a table name.
*
* @param object|string $result DB_result object from a query or a
* string containing the name of a table.
* While this also accepts a query result
* resource identifier, this behavior is
* deprecated.
* @param int $mode a valid tableInfo mode
*
* @return array an associative array with the information requested.
* A DB_Error object on failure.
*
* @see DB_common::tableInfo()
*/
function tableInfo($result, $mode = null)
{
if (is_string($result)) {
/*
* Probably received a table name.
* Create a result resource identifier.
*/
if (!@mssql_select_db($this->_db, $this->connection)) {
return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
}
$id = @mssql_query("SELECT * FROM $result WHERE 1=0",
$this->connection);
$got_string = true;
} elseif (isset($result->result)) {
/*
* Probably received a result object.
* Extract the result resource identifier.
*/
$id = $result->result;
$got_string = false;
} else {
/*
* Probably received a result resource identifier.
* Copy it.
* Deprecated. Here for compatibility only.
*/
$id = $result;
$got_string = false;
}
if (!is_resource($id)) {
return $this->mssqlRaiseError(DB_ERROR_NEED_MORE_DATA);
}
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
$case_func = 'strtolower';
} else {
$case_func = 'strval';
}
$count = @mssql_num_fields($id);
$res = array();
if ($mode) {
$res['num_fields'] = $count;
}
for ($i = 0; $i < $count; $i++) {
$res[$i] = array(
'table' => $got_string ? $case_func($result) : '',
'name' => $case_func(@mssql_field_name($id, $i)),
'type' => @mssql_field_type($id, $i),
'len' => @mssql_field_length($id, $i),
// We only support flags for table
'flags' => $got_string
? $this->_mssql_field_flags($result,
@mssql_field_name($id, $i))
: '',
);
if ($mode & DB_TABLEINFO_ORDER) {
$res['order'][$res[$i]['name']] = $i;
}
if ($mode & DB_TABLEINFO_ORDERTABLE) {
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
}
}
// free the result only if we were called on a table
if ($got_string) {
@mssql_free_result($id);
}
return $res;
}
// }}}
// {{{ _mssql_field_flags()
/**
* Get a column's flags
*
* Supports "not_null", "primary_key",
* "auto_increment" (mssql identity), "timestamp" (mssql timestamp),
* "unique_key" (mssql unique index, unique check or primary_key) and
* "multiple_key" (multikey index)
*
* mssql timestamp is NOT similar to the mysql timestamp so this is maybe
* not useful at all - is the behaviour of mysql_field_flags that primary
* keys are alway unique? is the interpretation of multiple_key correct?
*
* @param string $table the table name
* @param string $column the field name
*
* @return string the flags
*
* @access private
* @author Joern Barthel <j_barthel@web.de>
*/
function _mssql_field_flags($table, $column)
{
static $tableName = null;
static $flags = array();
if ($table != $tableName) {
$flags = array();
$tableName = $table;
// get unique and primary keys
$res = $this->getAll("EXEC SP_HELPINDEX[$table]", DB_FETCHMODE_ASSOC);
foreach ($res as $val) {
$keys = explode(', ', $val['index_keys']);
if (sizeof($keys) > 1) {
foreach ($keys as $key) {
$this->_add_flag($flags[$key], 'multiple_key');
}
}
if (strpos($val['index_description'], 'primary key')) {
foreach ($keys as $key) {
$this->_add_flag($flags[$key], 'primary_key');
}
} elseif (strpos($val['index_description'], 'unique')) {
foreach ($keys as $key) {
$this->_add_flag($flags[$key], 'unique_key');
}
}
}
// get auto_increment, not_null and timestamp
$res = $this->getAll("EXEC SP_COLUMNS[$table]", DB_FETCHMODE_ASSOC);
foreach ($res as $val) {
$val = array_change_key_case($val, CASE_LOWER);
if ($val['nullable'] == '0') {
$this->_add_flag($flags[$val['column_name']], 'not_null');
}
if (strpos($val['type_name'], 'identity')) {
$this->_add_flag($flags[$val['column_name']], 'auto_increment');
}
if (strpos($val['type_name'], 'timestamp')) {
$this->_add_flag($flags[$val['column_name']], 'timestamp');
}
}
}
if (array_key_exists($column, $flags)) {
return(implode(' ', $flags[$column]));
}
return '';
}
// }}}
// {{{ _add_flag()
/**
* Adds a string to the flags array if the flag is not yet in there
* - if there is no flag present the array is created
*
* @param array &$array the reference to the flag-array
* @param string $value the flag value
*
* @return void
*
* @access private
* @author Joern Barthel <j_barthel@web.de>
*/
function _add_flag(&$array, $value)
{
if (!is_array($array)) {
$array = array($value);
} elseif (!in_array($value, $array)) {
array_push($array, $value);
}
}
// }}}
// {{{ getSpecialQuery()
/**
* Obtains the query string needed for listing a given type of objects
*
* @param string $type the kind of objects you want to retrieve
*
* @return string the SQL query string or null if the driver doesn't
* support the object type requested
*
* @access protected
* @see DB_common::getListOf()
*/
function getSpecialQuery($type)
{
switch ($type) {
case 'tables':
return "SELECT name FROM sysobjects WHERE type = 'U'"
. ' ORDER BY name';
case 'views':
return "SELECT name FROM sysobjects WHERE type = 'V'";
default:
return null;
}
}
// }}}
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
*/
?>

1034
inc/DB/mysql.php Executable file

File diff suppressed because it is too large Load Diff

1076
inc/DB/mysqli.php Executable file

File diff suppressed because it is too large Load Diff

1117
inc/DB/oci8.php Executable file

File diff suppressed because it is too large Load Diff

883
inc/DB/odbc.php Executable file
View File

@ -0,0 +1,883 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* The PEAR DB driver for PHP's odbc extension
* for interacting with databases via ODBC connections
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category Database
* @package DB
* @author Stig Bakken <ssb@php.net>
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: odbc.php,v 1.78 2005/02/28 01:42:17 danielc Exp $
* @link http://pear.php.net/package/DB
*/
/**
* Obtain the DB_common class so it can be extended from
*/
require_once 'DB/common.php';
/**
* The methods PEAR DB uses to interact with PHP's odbc extension
* for interacting with databases via ODBC connections
*
* These methods overload the ones declared in DB_common.
*
* More info on ODBC errors could be found here:
* http://msdn.microsoft.com/library/default.asp?url=/library/en-us/trblsql/tr_err_odbc_5stz.asp
*
* @category Database
* @package DB
* @author Stig Bakken <ssb@php.net>
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: @package_version@
* @link http://pear.php.net/package/DB
*/
class DB_odbc extends DB_common
{
// {{{ properties
/**
* The DB driver type (mysql, oci8, odbc, etc.)
* @var string
*/
var $phptype = 'odbc';
/**
* The database syntax variant to be used (db2, access, etc.), if any
* @var string
*/
var $dbsyntax = 'sql92';
/**
* The capabilities of this DB implementation
*
* The 'new_link' element contains the PHP version that first provided
* new_link support for this DBMS. Contains false if it's unsupported.
*
* Meaning of the 'limit' element:
* + 'emulate' = emulate with fetch row by number
* + 'alter' = alter the query
* + false = skip rows
*
* NOTE: The feature set of the following drivers are different than
* the default:
* + solid: 'transactions' = true
* + navision: 'limit' = false
*
* @var array
*/
var $features = array(
'limit' => 'emulate',
'new_link' => false,
'numrows' => true,
'pconnect' => true,
'prepare' => false,
'ssl' => false,
'transactions' => false,
);
/**
* A mapping of native error codes to DB error codes
* @var array
*/
var $errorcode_map = array(
'01004' => DB_ERROR_TRUNCATED,
'07001' => DB_ERROR_MISMATCH,
'21S01' => DB_ERROR_VALUE_COUNT_ON_ROW,
'21S02' => DB_ERROR_MISMATCH,
'22001' => DB_ERROR_INVALID,
'22003' => DB_ERROR_INVALID_NUMBER,
'22005' => DB_ERROR_INVALID_NUMBER,
'22008' => DB_ERROR_INVALID_DATE,
'22012' => DB_ERROR_DIVZERO,
'23000' => DB_ERROR_CONSTRAINT,
'23502' => DB_ERROR_CONSTRAINT_NOT_NULL,
'23503' => DB_ERROR_CONSTRAINT,
'23504' => DB_ERROR_CONSTRAINT,
'23505' => DB_ERROR_CONSTRAINT,
'24000' => DB_ERROR_INVALID,
'34000' => DB_ERROR_INVALID,
'37000' => DB_ERROR_SYNTAX,
'42000' => DB_ERROR_SYNTAX,
'42601' => DB_ERROR_SYNTAX,
'IM001' => DB_ERROR_UNSUPPORTED,
'S0000' => DB_ERROR_NOSUCHTABLE,
'S0001' => DB_ERROR_ALREADY_EXISTS,
'S0002' => DB_ERROR_NOSUCHTABLE,
'S0011' => DB_ERROR_ALREADY_EXISTS,
'S0012' => DB_ERROR_NOT_FOUND,
'S0021' => DB_ERROR_ALREADY_EXISTS,
'S0022' => DB_ERROR_NOSUCHFIELD,
'S1009' => DB_ERROR_INVALID,
'S1090' => DB_ERROR_INVALID,
'S1C00' => DB_ERROR_NOT_CAPABLE,
);
/**
* The raw database connection created by PHP
* @var resource
*/
var $connection;
/**
* The DSN information for connecting to a database
* @var array
*/
var $dsn = array();
/**
* The number of rows affected by a data manipulation query
* @var integer
* @access private
*/
var $affected = 0;
// }}}
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
*
* @return void
*/
function DB_odbc()
{
$this->DB_common();
}
// }}}
// {{{ connect()
/**
* Connect to the database server, log in and open the database
*
* Don't call this method directly. Use DB::connect() instead.
*
* PEAR DB's odbc driver supports the following extra DSN options:
* + cursor The type of cursor to be used for this connection.
*
* @param array $dsn the data source name
* @param bool $persistent should the connection be persistent?
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function connect($dsn, $persistent = false)
{
if (!PEAR::loadExtension('odbc')) {
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
}
$this->dsn = $dsn;
if ($dsn['dbsyntax']) {
$this->dbsyntax = $dsn['dbsyntax'];
}
switch ($this->dbsyntax) {
case 'access':
case 'db2':
case 'solid':
$this->features['transactions'] = true;
break;
case 'navision':
$this->features['limit'] = false;
}
/*
* This is hear for backwards compatibility. Should have been using
* 'database' all along, but prior to 1.6.0RC3 'hostspec' was used.
*/
if ($dsn['database']) {
$odbcdsn = $dsn['database'];
} elseif ($dsn['hostspec']) {
$odbcdsn = $dsn['hostspec'];
} else {
$odbcdsn = 'localhost';
}
$connect_function = $persistent ? 'odbc_pconnect' : 'odbc_connect';
if (empty($dsn['cursor'])) {
$this->connection = @$connect_function($odbcdsn, $dsn['username'],
$dsn['password']);
} else {
$this->connection = @$connect_function($odbcdsn, $dsn['username'],
$dsn['password'],
$dsn['cursor']);
}
if (!is_resource($this->connection)) {
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
null, null, null,
$this->errorNative());
}
return DB_OK;
}
// }}}
// {{{ disconnect()
/**
* Disconnects from the database server
*
* @return bool TRUE on success, FALSE on failure
*/
function disconnect()
{
$err = @odbc_close($this->connection);
$this->connection = null;
return $err;
}
// }}}
// {{{ simpleQuery()
/**
* Sends a query to the database server
*
* @param string the SQL query string
*
* @return mixed + a PHP result resrouce for successful SELECT queries
* + the DB_OK constant for other successful queries
* + a DB_Error object on failure
*/
function simpleQuery($query)
{
$this->last_query = $query;
$query = $this->modifyQuery($query);
$result = @odbc_exec($this->connection, $query);
if (!$result) {
return $this->odbcRaiseError(); // XXX ERRORMSG
}
// Determine which queries that should return data, and which
// should return an error code only.
if (DB::isManip($query)) {
$this->affected = $result; // For affectedRows()
return DB_OK;
}
$this->affected = 0;
return $result;
}
// }}}
// {{{ nextResult()
/**
* Move the internal odbc result pointer to the next available result
*
* @param a valid fbsql result resource
*
* @access public
*
* @return true if a result is available otherwise return false
*/
function nextResult($result)
{
return @odbc_next_result($result);
}
// }}}
// {{{ fetchInto()
/**
* Places a row from the result set into the given array
*
* Formating of the array and the data therein are configurable.
* See DB_result::fetchInto() for more information.
*
* This method is not meant to be called directly. Use
* DB_result::fetchInto() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result the query result resource
* @param array $arr the referenced array to put the data in
* @param int $fetchmode how the resulting array should be indexed
* @param int $rownum the row number to fetch (0 = first row)
*
* @return mixed DB_OK on success, NULL when the end of a result set is
* reached or on failure
*
* @see DB_result::fetchInto()
*/
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
{
$arr = array();
if ($rownum !== null) {
$rownum++; // ODBC first row is 1
if (version_compare(phpversion(), '4.2.0', 'ge')) {
$cols = @odbc_fetch_into($result, $arr, $rownum);
} else {
$cols = @odbc_fetch_into($result, $rownum, $arr);
}
} else {
$cols = @odbc_fetch_into($result, $arr);
}
if (!$cols) {
return null;
}
if ($fetchmode !== DB_FETCHMODE_ORDERED) {
for ($i = 0; $i < count($arr); $i++) {
$colName = @odbc_field_name($result, $i+1);
$a[$colName] = $arr[$i];
}
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
$a = array_change_key_case($a, CASE_LOWER);
}
$arr = $a;
}
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
$this->_rtrimArrayValues($arr);
}
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
$this->_convertNullArrayValuesToEmpty($arr);
}
return DB_OK;
}
// }}}
// {{{ freeResult()
/**
* Deletes the result set and frees the memory occupied by the result set
*
* This method is not meant to be called directly. Use
* DB_result::free() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return bool TRUE on success, FALSE if $result is invalid
*
* @see DB_result::free()
*/
function freeResult($result)
{
return @odbc_free_result($result);
}
// }}}
// {{{ numCols()
/**
* Gets the number of columns in a result set
*
* This method is not meant to be called directly. Use
* DB_result::numCols() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return int the number of columns. A DB_Error object on failure.
*
* @see DB_result::numCols()
*/
function numCols($result)
{
$cols = @odbc_num_fields($result);
if (!$cols) {
return $this->odbcRaiseError();
}
return $cols;
}
// }}}
// {{{ affectedRows()
/**
* Determines the number of rows affected by a data maniuplation query
*
* 0 is returned for queries that don't manipulate data.
*
* @return int the number of rows. A DB_Error object on failure.
*/
function affectedRows()
{
if (empty($this->affected)) { // In case of SELECT stms
return 0;
}
$nrows = @odbc_num_rows($this->affected);
if ($nrows == -1) {
return $this->odbcRaiseError();
}
return $nrows;
}
// }}}
// {{{ numRows()
/**
* Gets the number of rows in a result set
*
* Not all ODBC drivers support this functionality. If they don't
* a DB_Error object for DB_ERROR_UNSUPPORTED is returned.
*
* This method is not meant to be called directly. Use
* DB_result::numRows() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return int the number of rows. A DB_Error object on failure.
*
* @see DB_result::numRows()
*/
function numRows($result)
{
$nrows = @odbc_num_rows($result);
if ($nrows == -1) {
return $this->odbcRaiseError(DB_ERROR_UNSUPPORTED);
}
if ($nrows === false) {
return $this->odbcRaiseError();
}
return $nrows;
}
// }}}
// {{{ quoteIdentifier()
/**
* Quotes a string so it can be safely used as a table or column name
*
* Use 'mssql' as the dbsyntax in the DB DSN only if you've unchecked
* "Use ANSI quoted identifiers" when setting up the ODBC data source.
*
* @param string $str identifier name to be quoted
*
* @return string quoted identifier string
*
* @see DB_common::quoteIdentifier()
* @since Method available since Release 1.6.0
*/
function quoteIdentifier($str)
{
switch ($this->dsn['dbsyntax']) {
case 'access':
return '[' . $str . ']';
case 'mssql':
case 'sybase':
return '[' . str_replace(']', ']]', $str) . ']';
case 'mysql':
case 'mysqli':
return '`' . $str . '`';
default:
return '"' . str_replace('"', '""', $str) . '"';
}
}
// }}}
// {{{ quote()
/**
* @deprecated Deprecated in release 1.6.0
* @internal
*/
function quote($str)
{
return $this->quoteSmart($str);
}
// }}}
// {{{ nextId()
/**
* Returns the next free id in a sequence
*
* @param string $seq_name name of the sequence
* @param boolean $ondemand when true, the seqence is automatically
* created if it does not exist
*
* @return int the next id number in the sequence.
* A DB_Error object on failure.
*
* @see DB_common::nextID(), DB_common::getSequenceName(),
* DB_odbc::createSequence(), DB_odbc::dropSequence()
*/
function nextId($seq_name, $ondemand = true)
{
$seqname = $this->getSequenceName($seq_name);
$repeat = 0;
do {
$this->pushErrorHandling(PEAR_ERROR_RETURN);
$result = $this->query("update ${seqname} set id = id + 1");
$this->popErrorHandling();
if ($ondemand && DB::isError($result) &&
$result->getCode() == DB_ERROR_NOSUCHTABLE) {
$repeat = 1;
$this->pushErrorHandling(PEAR_ERROR_RETURN);
$result = $this->createSequence($seq_name);
$this->popErrorHandling();
if (DB::isError($result)) {
return $this->raiseError($result);
}
$result = $this->query("insert into ${seqname} (id) values(0)");
} else {
$repeat = 0;
}
} while ($repeat);
if (DB::isError($result)) {
return $this->raiseError($result);
}
$result = $this->query("select id from ${seqname}");
if (DB::isError($result)) {
return $result;
}
$row = $result->fetchRow(DB_FETCHMODE_ORDERED);
if (DB::isError($row || !$row)) {
return $row;
}
return $row[0];
}
/**
* Creates a new sequence
*
* @param string $seq_name name of the new sequence
*
* @return int DB_OK on success. A DB_Error object on failure.
*
* @see DB_common::createSequence(), DB_common::getSequenceName(),
* DB_odbc::nextID(), DB_odbc::dropSequence()
*/
function createSequence($seq_name)
{
return $this->query('CREATE TABLE '
. $this->getSequenceName($seq_name)
. ' (id integer NOT NULL,'
. ' PRIMARY KEY(id))');
}
// }}}
// {{{ dropSequence()
/**
* Deletes a sequence
*
* @param string $seq_name name of the sequence to be deleted
*
* @return int DB_OK on success. A DB_Error object on failure.
*
* @see DB_common::dropSequence(), DB_common::getSequenceName(),
* DB_odbc::nextID(), DB_odbc::createSequence()
*/
function dropSequence($seq_name)
{
return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
}
// }}}
// {{{ autoCommit()
/**
* Enables or disables automatic commits
*
* @param bool $onoff true turns it on, false turns it off
*
* @return int DB_OK on success. A DB_Error object if the driver
* doesn't support auto-committing transactions.
*/
function autoCommit($onoff = false)
{
if (!@odbc_autocommit($this->connection, $onoff)) {
return $this->odbcRaiseError();
}
return DB_OK;
}
// }}}
// {{{ commit()
/**
* Commits the current transaction
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function commit()
{
if (!@odbc_commit($this->connection)) {
return $this->odbcRaiseError();
}
return DB_OK;
}
// }}}
// {{{ rollback()
/**
* Reverts the current transaction
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function rollback()
{
if (!@odbc_rollback($this->connection)) {
return $this->odbcRaiseError();
}
return DB_OK;
}
// }}}
// {{{ odbcRaiseError()
/**
* Produces a DB_Error object regarding the current problem
*
* @param int $errno if the error is being manually raised pass a
* DB_ERROR* constant here. If this isn't passed
* the error information gathered from the DBMS.
*
* @return object the DB_Error object
*
* @see DB_common::raiseError(),
* DB_odbc::errorNative(), DB_common::errorCode()
*/
function odbcRaiseError($errno = null)
{
if ($errno === null) {
switch ($this->dbsyntax) {
case 'access':
if ($this->options['portability'] & DB_PORTABILITY_ERRORS) {
$this->errorcode_map['07001'] = DB_ERROR_NOSUCHFIELD;
} else {
// Doing this in case mode changes during runtime.
$this->errorcode_map['07001'] = DB_ERROR_MISMATCH;
}
$native_code = odbc_error($this->connection);
// S1000 is for "General Error." Let's be more specific.
if ($native_code == 'S1000') {
$errormsg = odbc_errormsg($this->connection);
static $error_regexps;
if (!isset($error_regexps)) {
$error_regexps = array(
'/includes related records.$/i' => DB_ERROR_CONSTRAINT,
'/cannot contain a Null value/i' => DB_ERROR_CONSTRAINT_NOT_NULL,
);
}
foreach ($error_regexps as $regexp => $code) {
if (preg_match($regexp, $errormsg)) {
return $this->raiseError($code,
null, null, null,
$native_code . ' ' . $errormsg);
}
}
$errno = DB_ERROR;
} else {
$errno = $this->errorCode($native_code);
}
break;
default:
$errno = $this->errorCode(odbc_error($this->connection));
}
}
return $this->raiseError($errno, null, null, null,
$this->errorNative());
}
// }}}
// {{{ errorNative()
/**
* Gets the DBMS' native error code and message produced by the last query
*
* @return string the DBMS' error code and message
*/
function errorNative()
{
if (!is_resource($this->connection)) {
return @odbc_error() . ' ' . @odbc_errormsg();
}
return @odbc_error($this->connection) . ' ' . @odbc_errormsg($this->connection);
}
// }}}
// {{{ tableInfo()
/**
* Returns information about a table or a result set
*
* @param object|string $result DB_result object from a query or a
* string containing the name of a table.
* While this also accepts a query result
* resource identifier, this behavior is
* deprecated.
* @param int $mode a valid tableInfo mode
*
* @return array an associative array with the information requested.
* A DB_Error object on failure.
*
* @see DB_common::tableInfo()
* @since Method available since Release 1.7.0
*/
function tableInfo($result, $mode = null)
{
if (is_string($result)) {
/*
* Probably received a table name.
* Create a result resource identifier.
*/
$id = @odbc_exec($this->connection, "SELECT * FROM $result");
if (!$id) {
return $this->odbcRaiseError();
}
$got_string = true;
} elseif (isset($result->result)) {
/*
* Probably received a result object.
* Extract the result resource identifier.
*/
$id = $result->result;
$got_string = false;
} else {
/*
* Probably received a result resource identifier.
* Copy it.
* Deprecated. Here for compatibility only.
*/
$id = $result;
$got_string = false;
}
if (!is_resource($id)) {
return $this->odbcRaiseError(DB_ERROR_NEED_MORE_DATA);
}
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
$case_func = 'strtolower';
} else {
$case_func = 'strval';
}
$count = @odbc_num_fields($id);
$res = array();
if ($mode) {
$res['num_fields'] = $count;
}
for ($i = 0; $i < $count; $i++) {
$col = $i + 1;
$res[$i] = array(
'table' => $got_string ? $case_func($result) : '',
'name' => $case_func(@odbc_field_name($id, $col)),
'type' => @odbc_field_type($id, $col),
'len' => @odbc_field_len($id, $col),
'flags' => '',
);
if ($mode & DB_TABLEINFO_ORDER) {
$res['order'][$res[$i]['name']] = $i;
}
if ($mode & DB_TABLEINFO_ORDERTABLE) {
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
}
}
// free the result only if we were called on a table
if ($got_string) {
@odbc_free_result($id);
}
return $res;
}
// }}}
// {{{ getSpecialQuery()
/**
* Obtains the query string needed for listing a given type of objects
*
* Thanks to symbol1@gmail.com and Philippe.Jausions@11abacus.com.
*
* @param string $type the kind of objects you want to retrieve
*
* @return string the list of objects requested
*
* @access protected
* @see DB_common::getListOf()
* @since Method available since Release 1.7.0
*/
function getSpecialQuery($type)
{
switch ($type) {
case 'databases':
if (!function_exists('odbc_data_source')) {
return null;
}
$res = @odbc_data_source($this->connection, SQL_FETCH_FIRST);
if (is_array($res)) {
$out = array($res['server']);
while($res = @odbc_data_source($this->connection,
SQL_FETCH_NEXT))
{
$out[] = $res['server'];
}
return $out;
} else {
return $this->odbcRaiseError();
}
break;
case 'tables':
case 'schema.tables':
$keep = 'TABLE';
break;
case 'views':
$keep = 'VIEW';
break;
default:
return null;
}
/*
* Removing non-conforming items in the while loop rather than
* in the odbc_tables() call because some backends choke on this:
* odbc_tables($this->connection, '', '', '', 'TABLE')
*/
$res = @odbc_tables($this->connection);
if (!$res) {
return $this->odbcRaiseError();
}
$out = array();
while ($row = odbc_fetch_array($res)) {
if ($row['TABLE_TYPE'] != $keep) {
continue;
}
if ($type == 'schema.tables') {
$out[] = $row['TABLE_SCHEM'] . '.' . $row['TABLE_NAME'];
} else {
$out[] = $row['TABLE_NAME'];
}
}
return $out;
}
// }}}
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
*/
?>

1097
inc/DB/pgsql.php Executable file

File diff suppressed because it is too large Load Diff

942
inc/DB/sqlite.php Executable file
View File

@ -0,0 +1,942 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* The PEAR DB driver for PHP's sqlite extension
* for interacting with SQLite databases
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category Database
* @package DB
* @author Urs Gehrig <urs@circle.ch>
* @author Mika Tuupola <tuupola@appelsiini.net>
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0
* @version CVS: $Id: sqlite.php,v 1.109 2005/03/10 01:22:48 danielc Exp $
* @link http://pear.php.net/package/DB
*/
/**
* Obtain the DB_common class so it can be extended from
*/
require_once 'DB/common.php';
/**
* The methods PEAR DB uses to interact with PHP's sqlite extension
* for interacting with SQLite databases
*
* These methods overload the ones declared in DB_common.
*
* NOTICE: This driver needs PHP's track_errors ini setting to be on.
* It is automatically turned on when connecting to the database.
* Make sure your scripts don't turn it off.
*
* @category Database
* @package DB
* @author Urs Gehrig <urs@circle.ch>
* @author Mika Tuupola <tuupola@appelsiini.net>
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0
* @version Release: @package_version@
* @link http://pear.php.net/package/DB
*/
class DB_sqlite extends DB_common
{
// {{{ properties
/**
* The DB driver type (mysql, oci8, odbc, etc.)
* @var string
*/
var $phptype = 'sqlite';
/**
* The database syntax variant to be used (db2, access, etc.), if any
* @var string
*/
var $dbsyntax = 'sqlite';
/**
* The capabilities of this DB implementation
*
* The 'new_link' element contains the PHP version that first provided
* new_link support for this DBMS. Contains false if it's unsupported.
*
* Meaning of the 'limit' element:
* + 'emulate' = emulate with fetch row by number
* + 'alter' = alter the query
* + false = skip rows
*
* @var array
*/
var $features = array(
'limit' => 'alter',
'new_link' => false,
'numrows' => true,
'pconnect' => true,
'prepare' => false,
'ssl' => false,
'transactions' => false,
);
/**
* A mapping of native error codes to DB error codes
*
* {@internal Error codes according to sqlite_exec. See the online
* manual at http://sqlite.org/c_interface.html for info.
* This error handling based on sqlite_exec is not yet implemented.}}
*
* @var array
*/
var $errorcode_map = array(
);
/**
* The raw database connection created by PHP
* @var resource
*/
var $connection;
/**
* The DSN information for connecting to a database
* @var array
*/
var $dsn = array();
/**
* SQLite data types
*
* @link http://www.sqlite.org/datatypes.html
*
* @var array
*/
var $keywords = array (
'BLOB' => '',
'BOOLEAN' => '',
'CHARACTER' => '',
'CLOB' => '',
'FLOAT' => '',
'INTEGER' => '',
'KEY' => '',
'NATIONAL' => '',
'NUMERIC' => '',
'NVARCHAR' => '',
'PRIMARY' => '',
'TEXT' => '',
'TIMESTAMP' => '',
'UNIQUE' => '',
'VARCHAR' => '',
'VARYING' => '',
);
/**
* The most recent error message from $php_errormsg
* @var string
* @access private
*/
var $_lasterror = '';
// }}}
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
*
* @return void
*/
function DB_sqlite()
{
$this->DB_common();
}
// }}}
// {{{ connect()
/**
* Connect to the database server, log in and open the database
*
* Don't call this method directly. Use DB::connect() instead.
*
* PEAR DB's sqlite driver supports the following extra DSN options:
* + mode The permissions for the database file, in four digit
* chmod octal format (eg "0600").
*
* Example of connecting to a database in read-only mode:
* <code>
* require_once 'DB.php';
*
* $dsn = 'sqlite:///path/and/name/of/db/file?mode=0400';
* $options = array(
* 'portability' => DB_PORTABILITY_ALL,
* );
*
* $db =& DB::connect($dsn, $options);
* if (PEAR::isError($db)) {
* die($db->getMessage());
* }
* </code>
*
* @param array $dsn the data source name
* @param bool $persistent should the connection be persistent?
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function connect($dsn, $persistent = false)
{
if (!PEAR::loadExtension('sqlite')) {
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
}
$this->dsn = $dsn;
if ($dsn['dbsyntax']) {
$this->dbsyntax = $dsn['dbsyntax'];
}
if ($dsn['database']) {
if (!file_exists($dsn['database'])) {
if (!touch($dsn['database'])) {
return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
}
if (!isset($dsn['mode']) ||
!is_numeric($dsn['mode']))
{
$mode = 0644;
} else {
$mode = octdec($dsn['mode']);
}
if (!chmod($dsn['database'], $mode)) {
return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
}
if (!file_exists($dsn['database'])) {
return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
}
}
if (!is_file($dsn['database'])) {
return $this->sqliteRaiseError(DB_ERROR_INVALID);
}
if (!is_readable($dsn['database'])) {
return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION);
}
} else {
return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION);
}
$connect_function = $persistent ? 'sqlite_popen' : 'sqlite_open';
// track_errors must remain on for simpleQuery()
ini_set('track_errors', 1);
$php_errormsg = '';
if (!$this->connection = @$connect_function($dsn['database'])) {
return $this->raiseError(DB_ERROR_NODBSELECTED,
null, null, null,
$php_errormsg);
}
return DB_OK;
}
// }}}
// {{{ disconnect()
/**
* Disconnects from the database server
*
* @return bool TRUE on success, FALSE on failure
*/
function disconnect()
{
$ret = @sqlite_close($this->connection);
$this->connection = null;
return $ret;
}
// }}}
// {{{ simpleQuery()
/**
* Sends a query to the database server
*
* NOTICE: This method needs PHP's track_errors ini setting to be on.
* It is automatically turned on when connecting to the database.
* Make sure your scripts don't turn it off.
*
* @param string the SQL query string
*
* @return mixed + a PHP result resrouce for successful SELECT queries
* + the DB_OK constant for other successful queries
* + a DB_Error object on failure
*/
function simpleQuery($query)
{
$ismanip = DB::isManip($query);
$this->last_query = $query;
$query = $this->modifyQuery($query);
$php_errormsg = '';
$result = @sqlite_query($query, $this->connection);
$this->_lasterror = $php_errormsg ? $php_errormsg : '';
$this->result = $result;
if (!$this->result) {
return $this->sqliteRaiseError(null);
}
// sqlite_query() seems to allways return a resource
// so cant use that. Using $ismanip instead
if (!$ismanip) {
$numRows = $this->numRows($result);
if (is_object($numRows)) {
// we've got PEAR_Error
return $numRows;
}
return $result;
}
return DB_OK;
}
// }}}
// {{{ nextResult()
/**
* Move the internal sqlite result pointer to the next available result
*
* @param resource $result the valid sqlite result resource
*
* @return bool true if a result is available otherwise return false
*/
function nextResult($result)
{
return false;
}
// }}}
// {{{ fetchInto()
/**
* Places a row from the result set into the given array
*
* Formating of the array and the data therein are configurable.
* See DB_result::fetchInto() for more information.
*
* This method is not meant to be called directly. Use
* DB_result::fetchInto() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result the query result resource
* @param array $arr the referenced array to put the data in
* @param int $fetchmode how the resulting array should be indexed
* @param int $rownum the row number to fetch (0 = first row)
*
* @return mixed DB_OK on success, NULL when the end of a result set is
* reached or on failure
*
* @see DB_result::fetchInto()
*/
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
{
if ($rownum !== null) {
if (!@sqlite_seek($this->result, $rownum)) {
return null;
}
}
if ($fetchmode & DB_FETCHMODE_ASSOC) {
$arr = @sqlite_fetch_array($result, SQLITE_ASSOC);
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
$arr = array_change_key_case($arr, CASE_LOWER);
}
} else {
$arr = @sqlite_fetch_array($result, SQLITE_NUM);
}
if (!$arr) {
return null;
}
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
/*
* Even though this DBMS already trims output, we do this because
* a field might have intentional whitespace at the end that
* gets removed by DB_PORTABILITY_RTRIM under another driver.
*/
$this->_rtrimArrayValues($arr);
}
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
$this->_convertNullArrayValuesToEmpty($arr);
}
return DB_OK;
}
// }}}
// {{{ freeResult()
/**
* Deletes the result set and frees the memory occupied by the result set
*
* This method is not meant to be called directly. Use
* DB_result::free() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return bool TRUE on success, FALSE if $result is invalid
*
* @see DB_result::free()
*/
function freeResult(&$result)
{
// XXX No native free?
if (!is_resource($result)) {
return false;
}
$result = null;
return true;
}
// }}}
// {{{ numCols()
/**
* Gets the number of columns in a result set
*
* This method is not meant to be called directly. Use
* DB_result::numCols() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return int the number of columns. A DB_Error object on failure.
*
* @see DB_result::numCols()
*/
function numCols($result)
{
$cols = @sqlite_num_fields($result);
if (!$cols) {
return $this->sqliteRaiseError();
}
return $cols;
}
// }}}
// {{{ numRows()
/**
* Gets the number of rows in a result set
*
* This method is not meant to be called directly. Use
* DB_result::numRows() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return int the number of rows. A DB_Error object on failure.
*
* @see DB_result::numRows()
*/
function numRows($result)
{
$rows = @sqlite_num_rows($result);
if ($rows === null) {
return $this->sqliteRaiseError();
}
return $rows;
}
// }}}
// {{{ affected()
/**
* Determines the number of rows affected by a data maniuplation query
*
* 0 is returned for queries that don't manipulate data.
*
* @return int the number of rows. A DB_Error object on failure.
*/
function affectedRows()
{
return @sqlite_changes($this->connection);
}
// }}}
// {{{ dropSequence()
/**
* Deletes a sequence
*
* @param string $seq_name name of the sequence to be deleted
*
* @return int DB_OK on success. A DB_Error object on failure.
*
* @see DB_common::dropSequence(), DB_common::getSequenceName(),
* DB_sqlite::nextID(), DB_sqlite::createSequence()
*/
function dropSequence($seq_name)
{
return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
}
/**
* Creates a new sequence
*
* @param string $seq_name name of the new sequence
*
* @return int DB_OK on success. A DB_Error object on failure.
*
* @see DB_common::createSequence(), DB_common::getSequenceName(),
* DB_sqlite::nextID(), DB_sqlite::dropSequence()
*/
function createSequence($seq_name)
{
$seqname = $this->getSequenceName($seq_name);
$query = 'CREATE TABLE ' . $seqname .
' (id INTEGER UNSIGNED PRIMARY KEY) ';
$result = $this->query($query);
if (DB::isError($result)) {
return($result);
}
$query = "CREATE TRIGGER ${seqname}_cleanup AFTER INSERT ON $seqname
BEGIN
DELETE FROM $seqname WHERE id<LAST_INSERT_ROWID();
END ";
$result = $this->query($query);
if (DB::isError($result)) {
return($result);
}
}
// }}}
// {{{ nextId()
/**
* Returns the next free id in a sequence
*
* @param string $seq_name name of the sequence
* @param boolean $ondemand when true, the seqence is automatically
* created if it does not exist
*
* @return int the next id number in the sequence.
* A DB_Error object on failure.
*
* @see DB_common::nextID(), DB_common::getSequenceName(),
* DB_sqlite::createSequence(), DB_sqlite::dropSequence()
*/
function nextId($seq_name, $ondemand = true)
{
$seqname = $this->getSequenceName($seq_name);
do {
$repeat = 0;
$this->pushErrorHandling(PEAR_ERROR_RETURN);
$result = $this->query("INSERT INTO $seqname (id) VALUES (NULL)");
$this->popErrorHandling();
if ($result === DB_OK) {
$id = @sqlite_last_insert_rowid($this->connection);
if ($id != 0) {
return $id;
}
} elseif ($ondemand && DB::isError($result) &&
$result->getCode() == DB_ERROR_NOSUCHTABLE)
{
$result = $this->createSequence($seq_name);
if (DB::isError($result)) {
return $this->raiseError($result);
} else {
$repeat = 1;
}
}
} while ($repeat);
return $this->raiseError($result);
}
// }}}
// {{{ getDbFileStats()
/**
* Get the file stats for the current database
*
* Possible arguments are dev, ino, mode, nlink, uid, gid, rdev, size,
* atime, mtime, ctime, blksize, blocks or a numeric key between
* 0 and 12.
*
* @param string $arg the array key for stats()
*
* @return mixed an array on an unspecified key, integer on a passed
* arg and false at a stats error
*/
function getDbFileStats($arg = '')
{
$stats = stat($this->dsn['database']);
if ($stats == false) {
return false;
}
if (is_array($stats)) {
if (is_numeric($arg)) {
if (((int)$arg <= 12) & ((int)$arg >= 0)) {
return false;
}
return $stats[$arg ];
}
if (array_key_exists(trim($arg), $stats)) {
return $stats[$arg ];
}
}
return $stats;
}
// }}}
// {{{ escapeSimple()
/**
* Escapes a string according to the current DBMS's standards
*
* In SQLite, this makes things safe for inserts/updates, but may
* cause problems when performing text comparisons against columns
* containing binary data. See the
* {@link http://php.net/sqlite_escape_string PHP manual} for more info.
*
* @param string $str the string to be escaped
*
* @return string the escaped string
*
* @since Method available since Release 1.6.1
* @see DB_common::escapeSimple()
*/
function escapeSimple($str)
{
return @sqlite_escape_string($str);
}
// }}}
// {{{ modifyLimitQuery()
/**
* Adds LIMIT clauses to a query string according to current DBMS standards
*
* @param string $query the query to modify
* @param int $from the row to start to fetching (0 = the first row)
* @param int $count the numbers of rows to fetch
* @param mixed $params array, string or numeric data to be used in
* execution of the statement. Quantity of items
* passed must match quantity of placeholders in
* query: meaning 1 placeholder for non-array
* parameters or 1 placeholder per array element.
*
* @return string the query string with LIMIT clauses added
*
* @access protected
*/
function modifyLimitQuery($query, $from, $count, $params = array())
{
return "$query LIMIT $count OFFSET $from";
}
// }}}
// {{{ modifyQuery()
/**
* Changes a query string for various DBMS specific reasons
*
* This little hack lets you know how many rows were deleted
* when running a "DELETE FROM table" query. Only implemented
* if the DB_PORTABILITY_DELETE_COUNT portability option is on.
*
* @param string $query the query string to modify
*
* @return string the modified query string
*
* @access protected
* @see DB_common::setOption()
*/
function modifyQuery($query)
{
if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) {
if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
$query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
'DELETE FROM \1 WHERE 1=1', $query);
}
}
return $query;
}
// }}}
// {{{ sqliteRaiseError()
/**
* Produces a DB_Error object regarding the current problem
*
* @param int $errno if the error is being manually raised pass a
* DB_ERROR* constant here. If this isn't passed
* the error information gathered from the DBMS.
*
* @return object the DB_Error object
*
* @see DB_common::raiseError(),
* DB_sqlite::errorNative(), DB_sqlite::errorCode()
*/
function sqliteRaiseError($errno = null)
{
$native = $this->errorNative();
if ($errno === null) {
$errno = $this->errorCode($native);
}
$errorcode = @sqlite_last_error($this->connection);
$userinfo = "$errorcode ** $this->last_query";
return $this->raiseError($errno, null, null, $userinfo, $native);
}
// }}}
// {{{ errorNative()
/**
* Gets the DBMS' native error message produced by the last query
*
* {@internal This is used to retrieve more meaningfull error messages
* because sqlite_last_error() does not provide adequate info.}}
*
* @return string the DBMS' error message
*/
function errorNative()
{
return $this->_lasterror;
}
// }}}
// {{{ errorCode()
/**
* Determines PEAR::DB error code from the database's text error message
*
* @param string $errormsg the error message returned from the database
*
* @return integer the DB error number
*/
function errorCode($errormsg)
{
static $error_regexps;
if (!isset($error_regexps)) {
$error_regexps = array(
'/^no such table:/' => DB_ERROR_NOSUCHTABLE,
'/^no such index:/' => DB_ERROR_NOT_FOUND,
'/^(table|index) .* already exists$/' => DB_ERROR_ALREADY_EXISTS,
'/PRIMARY KEY must be unique/i' => DB_ERROR_CONSTRAINT,
'/is not unique/' => DB_ERROR_CONSTRAINT,
'/columns .* are not unique/i' => DB_ERROR_CONSTRAINT,
'/uniqueness constraint failed/' => DB_ERROR_CONSTRAINT,
'/may not be NULL/' => DB_ERROR_CONSTRAINT_NOT_NULL,
'/^no such column:/' => DB_ERROR_NOSUCHFIELD,
'/column not present in both tables/i' => DB_ERROR_NOSUCHFIELD,
'/^near ".*": syntax error$/' => DB_ERROR_SYNTAX,
'/[0-9]+ values for [0-9]+ columns/i' => DB_ERROR_VALUE_COUNT_ON_ROW,
);
}
foreach ($error_regexps as $regexp => $code) {
if (preg_match($regexp, $errormsg)) {
return $code;
}
}
// Fall back to DB_ERROR if there was no mapping.
return DB_ERROR;
}
// }}}
// {{{ tableInfo()
/**
* Returns information about a table
*
* @param string $result a string containing the name of a table
* @param int $mode a valid tableInfo mode
*
* @return array an associative array with the information requested.
* A DB_Error object on failure.
*
* @see DB_common::tableInfo()
* @since Method available since Release 1.7.0
*/
function tableInfo($result, $mode = null)
{
if (is_string($result)) {
/*
* Probably received a table name.
* Create a result resource identifier.
*/
$id = @sqlite_array_query($this->connection,
"PRAGMA table_info('$result');",
SQLITE_ASSOC);
$got_string = true;
} else {
$this->last_query = '';
return $this->raiseError(DB_ERROR_NOT_CAPABLE, null, null, null,
'This DBMS can not obtain tableInfo' .
' from result sets');
}
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
$case_func = 'strtolower';
} else {
$case_func = 'strval';
}
$count = count($id);
$res = array();
if ($mode) {
$res['num_fields'] = $count;
}
for ($i = 0; $i < $count; $i++) {
if (strpos($id[$i]['type'], '(') !== false) {
$bits = explode('(', $id[$i]['type']);
$type = $bits[0];
$len = rtrim($bits[1],')');
} else {
$type = $id[$i]['type'];
$len = 0;
}
$flags = '';
if ($id[$i]['pk']) {
$flags .= 'primary_key ';
}
if ($id[$i]['notnull']) {
$flags .= 'not_null ';
}
if ($id[$i]['dflt_value'] !== null) {
$flags .= 'default_' . rawurlencode($id[$i]['dflt_value']);
}
$flags = trim($flags);
$res[$i] = array(
'table' => $case_func($result),
'name' => $case_func($id[$i]['name']),
'type' => $type,
'len' => $len,
'flags' => $flags,
);
if ($mode & DB_TABLEINFO_ORDER) {
$res['order'][$res[$i]['name']] = $i;
}
if ($mode & DB_TABLEINFO_ORDERTABLE) {
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
}
}
return $res;
}
// }}}
// {{{ getSpecialQuery()
/**
* Obtains the query string needed for listing a given type of objects
*
* @param string $type the kind of objects you want to retrieve
* @param array $args SQLITE DRIVER ONLY: a private array of arguments
* used by the getSpecialQuery(). Do not use
* this directly.
*
* @return string the SQL query string or null if the driver doesn't
* support the object type requested
*
* @access protected
* @see DB_common::getListOf()
*/
function getSpecialQuery($type, $args = array())
{
if (!is_array($args)) {
return $this->raiseError('no key specified', null, null, null,
'Argument has to be an array.');
}
switch ($type) {
case 'master':
return 'SELECT * FROM sqlite_master;';
case 'tables':
return "SELECT name FROM sqlite_master WHERE type='table' "
. 'UNION ALL SELECT name FROM sqlite_temp_master '
. "WHERE type='table' ORDER BY name;";
case 'schema':
return 'SELECT sql FROM (SELECT * FROM sqlite_master '
. 'UNION ALL SELECT * FROM sqlite_temp_master) '
. "WHERE type!='meta' "
. 'ORDER BY tbl_name, type DESC, name;';
case 'schemax':
case 'schema_x':
/*
* Use like:
* $res = $db->query($db->getSpecialQuery('schema_x',
* array('table' => 'table3')));
*/
return 'SELECT sql FROM (SELECT * FROM sqlite_master '
. 'UNION ALL SELECT * FROM sqlite_temp_master) '
. "WHERE tbl_name LIKE '{$args['table']}' "
. "AND type!='meta' "
. 'ORDER BY type DESC, name;';
case 'alter':
/*
* SQLite does not support ALTER TABLE; this is a helper query
* to handle this. 'table' represents the table name, 'rows'
* the news rows to create, 'save' the row(s) to keep _with_
* the data.
*
* Use like:
* $args = array(
* 'table' => $table,
* 'rows' => "id INTEGER PRIMARY KEY, firstname TEXT, surname TEXT, datetime TEXT",
* 'save' => "NULL, titel, content, datetime"
* );
* $res = $db->query( $db->getSpecialQuery('alter', $args));
*/
$rows = strtr($args['rows'], $this->keywords);
$q = array(
'BEGIN TRANSACTION',
"CREATE TEMPORARY TABLE {$args['table']}_backup ({$args['rows']})",
"INSERT INTO {$args['table']}_backup SELECT {$args['save']} FROM {$args['table']}",
"DROP TABLE {$args['table']}",
"CREATE TABLE {$args['table']} ({$args['rows']})",
"INSERT INTO {$args['table']} SELECT {$rows} FROM {$args['table']}_backup",
"DROP TABLE {$args['table']}_backup",
'COMMIT',
);
/*
* This is a dirty hack, since the above query will not get
* executed with a single query call so here the query method
* will be called directly and return a select instead.
*/
foreach ($q as $query) {
$this->query($query);
}
return "SELECT * FROM {$args['table']};";
default:
return null;
}
}
// }}}
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
*/
?>

504
inc/DB/storage.php Executable file
View File

@ -0,0 +1,504 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Provides an object interface to a table row
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category Database
* @package DB
* @author Stig Bakken <stig@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: storage.php,v 1.21 2005/02/02 02:54:51 danielc Exp $
* @link http://pear.php.net/package/DB
*/
/**
* Obtain the DB class so it can be extended from
*/
require_once 'DB.php';
/**
* Provides an object interface to a table row
*
* It lets you add, delete and change rows using objects rather than SQL
* statements.
*
* @category Database
* @package DB
* @author Stig Bakken <stig@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: @package_version@
* @link http://pear.php.net/package/DB
*/
class DB_storage extends PEAR
{
// {{{ properties
/** the name of the table (or view, if the backend database supports
updates in views) we hold data from */
var $_table = null;
/** which column(s) in the table contains primary keys, can be a
string for single-column primary keys, or an array of strings
for multiple-column primary keys */
var $_keycolumn = null;
/** DB connection handle used for all transactions */
var $_dbh = null;
/** an assoc with the names of database fields stored as properties
in this object */
var $_properties = array();
/** an assoc with the names of the properties in this object that
have been changed since they were fetched from the database */
var $_changes = array();
/** flag that decides if data in this object can be changed.
objects that don't have their table's key column in their
property lists will be flagged as read-only. */
var $_readonly = false;
/** function or method that implements a validator for fields that
are set, this validator function returns true if the field is
valid, false if not */
var $_validator = null;
// }}}
// {{{ constructor
/**
* Constructor
*
* @param $table string the name of the database table
*
* @param $keycolumn mixed string with name of key column, or array of
* strings if the table has a primary key of more than one column
*
* @param $dbh object database connection object
*
* @param $validator mixed function or method used to validate
* each new value, called with three parameters: the name of the
* field/column that is changing, a reference to the new value and
* a reference to this object
*
*/
function DB_storage($table, $keycolumn, &$dbh, $validator = null)
{
$this->PEAR('DB_Error');
$this->_table = $table;
$this->_keycolumn = $keycolumn;
$this->_dbh = $dbh;
$this->_readonly = false;
$this->_validator = $validator;
}
// }}}
// {{{ _makeWhere()
/**
* Utility method to build a "WHERE" clause to locate ourselves in
* the table.
*
* XXX future improvement: use rowids?
*
* @access private
*/
function _makeWhere($keyval = null)
{
if (is_array($this->_keycolumn)) {
if ($keyval === null) {
for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
$keyval[] = $this->{$this->_keycolumn[$i]};
}
}
$whereclause = '';
for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
if ($i > 0) {
$whereclause .= ' AND ';
}
$whereclause .= $this->_keycolumn[$i];
if (is_null($keyval[$i])) {
// there's not much point in having a NULL key,
// but we support it anyway
$whereclause .= ' IS NULL';
} else {
$whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]);
}
}
} else {
if ($keyval === null) {
$keyval = @$this->{$this->_keycolumn};
}
$whereclause = $this->_keycolumn;
if (is_null($keyval)) {
// there's not much point in having a NULL key,
// but we support it anyway
$whereclause .= ' IS NULL';
} else {
$whereclause .= ' = ' . $this->_dbh->quote($keyval);
}
}
return $whereclause;
}
// }}}
// {{{ setup()
/**
* Method used to initialize a DB_storage object from the
* configured table.
*
* @param $keyval mixed the key[s] of the row to fetch (string or array)
*
* @return int DB_OK on success, a DB error if not
*/
function setup($keyval)
{
$whereclause = $this->_makeWhere($keyval);
$query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause;
$sth = $this->_dbh->query($query);
if (DB::isError($sth)) {
return $sth;
}
$row = $sth->fetchRow(DB_FETCHMODE_ASSOC);
if (DB::isError($row)) {
return $row;
}
if (!$row) {
return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null,
$query, null, true);
}
foreach ($row as $key => $value) {
$this->_properties[$key] = true;
$this->$key = $value;
}
return DB_OK;
}
// }}}
// {{{ insert()
/**
* Create a new (empty) row in the configured table for this
* object.
*/
function insert($newpk)
{
if (is_array($this->_keycolumn)) {
$primarykey = $this->_keycolumn;
} else {
$primarykey = array($this->_keycolumn);
}
settype($newpk, "array");
for ($i = 0; $i < sizeof($primarykey); $i++) {
$pkvals[] = $this->_dbh->quote($newpk[$i]);
}
$sth = $this->_dbh->query("INSERT INTO $this->_table (" .
implode(",", $primarykey) . ") VALUES(" .
implode(",", $pkvals) . ")");
if (DB::isError($sth)) {
return $sth;
}
if (sizeof($newpk) == 1) {
$newpk = $newpk[0];
}
$this->setup($newpk);
}
// }}}
// {{{ toString()
/**
* Output a simple description of this DB_storage object.
* @return string object description
*/
function toString()
{
$info = strtolower(get_class($this));
$info .= " (table=";
$info .= $this->_table;
$info .= ", keycolumn=";
if (is_array($this->_keycolumn)) {
$info .= "(" . implode(",", $this->_keycolumn) . ")";
} else {
$info .= $this->_keycolumn;
}
$info .= ", dbh=";
if (is_object($this->_dbh)) {
$info .= $this->_dbh->toString();
} else {
$info .= "null";
}
$info .= ")";
if (sizeof($this->_properties)) {
$info .= " [loaded, key=";
$keyname = $this->_keycolumn;
if (is_array($keyname)) {
$info .= "(";
for ($i = 0; $i < sizeof($keyname); $i++) {
if ($i > 0) {
$info .= ",";
}
$info .= $this->$keyname[$i];
}
$info .= ")";
} else {
$info .= $this->$keyname;
}
$info .= "]";
}
if (sizeof($this->_changes)) {
$info .= " [modified]";
}
return $info;
}
// }}}
// {{{ dump()
/**
* Dump the contents of this object to "standard output".
*/
function dump()
{
foreach ($this->_properties as $prop => $foo) {
print "$prop = ";
print htmlentities($this->$prop);
print "<br />\n";
}
}
// }}}
// {{{ &create()
/**
* Static method used to create new DB storage objects.
* @param $data assoc. array where the keys are the names
* of properties/columns
* @return object a new instance of DB_storage or a subclass of it
*/
function &create($table, &$data)
{
$classname = strtolower(get_class($this));
$obj =& new $classname($table);
foreach ($data as $name => $value) {
$obj->_properties[$name] = true;
$obj->$name = &$value;
}
return $obj;
}
// }}}
// {{{ loadFromQuery()
/**
* Loads data into this object from the given query. If this
* object already contains table data, changes will be saved and
* the object re-initialized first.
*
* @param $query SQL query
*
* @param $params parameter list in case you want to use
* prepare/execute mode
*
* @return int DB_OK on success, DB_WARNING_READ_ONLY if the
* returned object is read-only (because the object's specified
* key column was not found among the columns returned by $query),
* or another DB error code in case of errors.
*/
// XXX commented out for now
/*
function loadFromQuery($query, $params = null)
{
if (sizeof($this->_properties)) {
if (sizeof($this->_changes)) {
$this->store();
$this->_changes = array();
}
$this->_properties = array();
}
$rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params);
if (DB::isError($rowdata)) {
return $rowdata;
}
reset($rowdata);
$found_keycolumn = false;
while (list($key, $value) = each($rowdata)) {
if ($key == $this->_keycolumn) {
$found_keycolumn = true;
}
$this->_properties[$key] = true;
$this->$key = &$value;
unset($value); // have to unset, or all properties will
// refer to the same value
}
if (!$found_keycolumn) {
$this->_readonly = true;
return DB_WARNING_READ_ONLY;
}
return DB_OK;
}
*/
// }}}
// {{{ set()
/**
* Modify an attriute value.
*/
function set($property, $newvalue)
{
// only change if $property is known and object is not
// read-only
if ($this->_readonly) {
return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
null, null, null, true);
}
if (@isset($this->_properties[$property])) {
if (empty($this->_validator)) {
$valid = true;
} else {
$valid = @call_user_func($this->_validator,
$this->_table,
$property,
$newvalue,
$this->$property,
$this);
}
if ($valid) {
$this->$property = $newvalue;
if (empty($this->_changes[$property])) {
$this->_changes[$property] = 0;
} else {
$this->_changes[$property]++;
}
} else {
return $this->raiseError(null, DB_ERROR_INVALID, null,
null, "invalid field: $property",
null, true);
}
return true;
}
return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null,
null, "unknown field: $property",
null, true);
}
// }}}
// {{{ &get()
/**
* Fetch an attribute value.
*
* @param string attribute name
*
* @return attribute contents, or null if the attribute name is
* unknown
*/
function &get($property)
{
// only return if $property is known
if (isset($this->_properties[$property])) {
return $this->$property;
}
$tmp = null;
return $tmp;
}
// }}}
// {{{ _DB_storage()
/**
* Destructor, calls DB_storage::store() if there are changes
* that are to be kept.
*/
function _DB_storage()
{
if (sizeof($this->_changes)) {
$this->store();
}
$this->_properties = array();
$this->_changes = array();
$this->_table = null;
}
// }}}
// {{{ store()
/**
* Stores changes to this object in the database.
*
* @return DB_OK or a DB error
*/
function store()
{
foreach ($this->_changes as $name => $foo) {
$params[] = &$this->$name;
$vars[] = $name . ' = ?';
}
if ($vars) {
$query = 'UPDATE ' . $this->_table . ' SET ' .
implode(', ', $vars) . ' WHERE ' .
$this->_makeWhere();
$stmt = $this->_dbh->prepare($query);
$res = $this->_dbh->execute($stmt, $params);
if (DB::isError($res)) {
return $res;
}
$this->_changes = array();
}
return DB_OK;
}
// }}}
// {{{ remove()
/**
* Remove the row represented by this object from the database.
*
* @return mixed DB_OK or a DB error
*/
function remove()
{
if ($this->_readonly) {
return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
null, null, null, true);
}
$query = 'DELETE FROM ' . $this->_table .' WHERE '.
$this->_makeWhere();
$res = $this->_dbh->query($query);
if (DB::isError($res)) {
return $res;
}
foreach ($this->_properties as $prop => $foo) {
unset($this->$prop);
}
$this->_properties = array();
$this->_changes = array();
return DB_OK;
}
// }}}
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
*/
?>

907
inc/DB/sybase.php Executable file
View File

@ -0,0 +1,907 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* The PEAR DB driver for PHP's sybase extension
* for interacting with Sybase databases
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category Database
* @package DB
* @author Sterling Hughes <sterling@php.net>
* @author Antônio Carlos Venâncio Júnior <floripa@php.net>
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: sybase.php,v 1.78 2005/02/20 00:44:48 danielc Exp $
* @link http://pear.php.net/package/DB
*/
/**
* Obtain the DB_common class so it can be extended from
*/
require_once 'DB/common.php';
/**
* The methods PEAR DB uses to interact with PHP's sybase extension
* for interacting with Sybase databases
*
* These methods overload the ones declared in DB_common.
*
* WARNING: This driver may fail with multiple connections under the
* same user/pass/host and different databases.
*
* @category Database
* @package DB
* @author Sterling Hughes <sterling@php.net>
* @author Antônio Carlos Venâncio Júnior <floripa@php.net>
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: @package_version@
* @link http://pear.php.net/package/DB
*/
class DB_sybase extends DB_common
{
// {{{ properties
/**
* The DB driver type (mysql, oci8, odbc, etc.)
* @var string
*/
var $phptype = 'sybase';
/**
* The database syntax variant to be used (db2, access, etc.), if any
* @var string
*/
var $dbsyntax = 'sybase';
/**
* The capabilities of this DB implementation
*
* The 'new_link' element contains the PHP version that first provided
* new_link support for this DBMS. Contains false if it's unsupported.
*
* Meaning of the 'limit' element:
* + 'emulate' = emulate with fetch row by number
* + 'alter' = alter the query
* + false = skip rows
*
* @var array
*/
var $features = array(
'limit' => 'emulate',
'new_link' => false,
'numrows' => true,
'pconnect' => true,
'prepare' => false,
'ssl' => false,
'transactions' => true,
);
/**
* A mapping of native error codes to DB error codes
* @var array
*/
var $errorcode_map = array(
);
/**
* The raw database connection created by PHP
* @var resource
*/
var $connection;
/**
* The DSN information for connecting to a database
* @var array
*/
var $dsn = array();
/**
* Should data manipulation queries be committed automatically?
* @var bool
* @access private
*/
var $autocommit = true;
/**
* The quantity of transactions begun
*
* {@internal While this is private, it can't actually be designated
* private in PHP 5 because it is directly accessed in the test suite.}}
*
* @var integer
* @access private
*/
var $transaction_opcount = 0;
/**
* The database specified in the DSN
*
* It's a fix to allow calls to different databases in the same script.
*
* @var string
* @access private
*/
var $_db = '';
// }}}
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
*
* @return void
*/
function DB_sybase()
{
$this->DB_common();
}
// }}}
// {{{ connect()
/**
* Connect to the database server, log in and open the database
*
* Don't call this method directly. Use DB::connect() instead.
*
* PEAR DB's sybase driver supports the following extra DSN options:
* + appname The application name to use on this connection.
* Available since PEAR DB 1.7.0.
* + charset The character set to use on this connection.
* Available since PEAR DB 1.7.0.
*
* @param array $dsn the data source name
* @param bool $persistent should the connection be persistent?
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function connect($dsn, $persistent = false)
{
if (!PEAR::loadExtension('sybase') &&
!PEAR::loadExtension('sybase_ct'))
{
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
}
$this->dsn = $dsn;
if ($dsn['dbsyntax']) {
$this->dbsyntax = $dsn['dbsyntax'];
}
$dsn['hostspec'] = $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost';
$dsn['password'] = !empty($dsn['password']) ? $dsn['password'] : false;
$dsn['charset'] = isset($dsn['charset']) ? $dsn['charset'] : false;
$dsn['appname'] = isset($dsn['appname']) ? $dsn['appname'] : false;
$connect_function = $persistent ? 'sybase_pconnect' : 'sybase_connect';
if ($dsn['username']) {
$this->connection = @$connect_function($dsn['hostspec'],
$dsn['username'],
$dsn['password'],
$dsn['charset'],
$dsn['appname']);
} else {
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
null, null, null,
'The DSN did not contain a username.');
}
if (!$this->connection) {
return $this->raiseError(DB_ERROR_CONNECT_FAILED,
null, null, null,
@sybase_get_last_message());
}
if ($dsn['database']) {
if (!@sybase_select_db($dsn['database'], $this->connection)) {
return $this->raiseError(DB_ERROR_NODBSELECTED,
null, null, null,
@sybase_get_last_message());
}
$this->_db = $dsn['database'];
}
return DB_OK;
}
// }}}
// {{{ disconnect()
/**
* Disconnects from the database server
*
* @return bool TRUE on success, FALSE on failure
*/
function disconnect()
{
$ret = @sybase_close($this->connection);
$this->connection = null;
return $ret;
}
// }}}
// {{{ simpleQuery()
/**
* Sends a query to the database server
*
* @param string the SQL query string
*
* @return mixed + a PHP result resrouce for successful SELECT queries
* + the DB_OK constant for other successful queries
* + a DB_Error object on failure
*/
function simpleQuery($query)
{
$ismanip = DB::isManip($query);
$this->last_query = $query;
if (!@sybase_select_db($this->_db, $this->connection)) {
return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
}
$query = $this->modifyQuery($query);
if (!$this->autocommit && $ismanip) {
if ($this->transaction_opcount == 0) {
$result = @sybase_query('BEGIN TRANSACTION', $this->connection);
if (!$result) {
return $this->sybaseRaiseError();
}
}
$this->transaction_opcount++;
}
$result = @sybase_query($query, $this->connection);
if (!$result) {
return $this->sybaseRaiseError();
}
if (is_resource($result)) {
return $result;
}
// Determine which queries that should return data, and which
// should return an error code only.
return $ismanip ? DB_OK : $result;
}
// }}}
// {{{ nextResult()
/**
* Move the internal sybase result pointer to the next available result
*
* @param a valid sybase result resource
*
* @access public
*
* @return true if a result is available otherwise return false
*/
function nextResult($result)
{
return false;
}
// }}}
// {{{ fetchInto()
/**
* Places a row from the result set into the given array
*
* Formating of the array and the data therein are configurable.
* See DB_result::fetchInto() for more information.
*
* This method is not meant to be called directly. Use
* DB_result::fetchInto() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result the query result resource
* @param array $arr the referenced array to put the data in
* @param int $fetchmode how the resulting array should be indexed
* @param int $rownum the row number to fetch (0 = first row)
*
* @return mixed DB_OK on success, NULL when the end of a result set is
* reached or on failure
*
* @see DB_result::fetchInto()
*/
function fetchInto($result, &$arr, $fetchmode, $rownum = null)
{
if ($rownum !== null) {
if (!@sybase_data_seek($result, $rownum)) {
return null;
}
}
if ($fetchmode & DB_FETCHMODE_ASSOC) {
if (function_exists('sybase_fetch_assoc')) {
$arr = @sybase_fetch_assoc($result);
} else {
if ($arr = @sybase_fetch_array($result)) {
foreach ($arr as $key => $value) {
if (is_int($key)) {
unset($arr[$key]);
}
}
}
}
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
$arr = array_change_key_case($arr, CASE_LOWER);
}
} else {
$arr = @sybase_fetch_row($result);
}
if (!$arr) {
return null;
}
if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
$this->_rtrimArrayValues($arr);
}
if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
$this->_convertNullArrayValuesToEmpty($arr);
}
return DB_OK;
}
// }}}
// {{{ freeResult()
/**
* Deletes the result set and frees the memory occupied by the result set
*
* This method is not meant to be called directly. Use
* DB_result::free() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return bool TRUE on success, FALSE if $result is invalid
*
* @see DB_result::free()
*/
function freeResult($result)
{
return @sybase_free_result($result);
}
// }}}
// {{{ numCols()
/**
* Gets the number of columns in a result set
*
* This method is not meant to be called directly. Use
* DB_result::numCols() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return int the number of columns. A DB_Error object on failure.
*
* @see DB_result::numCols()
*/
function numCols($result)
{
$cols = @sybase_num_fields($result);
if (!$cols) {
return $this->sybaseRaiseError();
}
return $cols;
}
// }}}
// {{{ numRows()
/**
* Gets the number of rows in a result set
*
* This method is not meant to be called directly. Use
* DB_result::numRows() instead. It can't be declared "protected"
* because DB_result is a separate object.
*
* @param resource $result PHP's query result resource
*
* @return int the number of rows. A DB_Error object on failure.
*
* @see DB_result::numRows()
*/
function numRows($result)
{
$rows = @sybase_num_rows($result);
if ($rows === false) {
return $this->sybaseRaiseError();
}
return $rows;
}
// }}}
// {{{ affectedRows()
/**
* Determines the number of rows affected by a data maniuplation query
*
* 0 is returned for queries that don't manipulate data.
*
* @return int the number of rows. A DB_Error object on failure.
*/
function affectedRows()
{
if (DB::isManip($this->last_query)) {
$result = @sybase_affected_rows($this->connection);
} else {
$result = 0;
}
return $result;
}
// }}}
// {{{ nextId()
/**
* Returns the next free id in a sequence
*
* @param string $seq_name name of the sequence
* @param boolean $ondemand when true, the seqence is automatically
* created if it does not exist
*
* @return int the next id number in the sequence.
* A DB_Error object on failure.
*
* @see DB_common::nextID(), DB_common::getSequenceName(),
* DB_sybase::createSequence(), DB_sybase::dropSequence()
*/
function nextId($seq_name, $ondemand = true)
{
$seqname = $this->getSequenceName($seq_name);
if (!@sybase_select_db($this->_db, $this->connection)) {
return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
}
$repeat = 0;
do {
$this->pushErrorHandling(PEAR_ERROR_RETURN);
$result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)");
$this->popErrorHandling();
if ($ondemand && DB::isError($result) &&
($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE))
{
$repeat = 1;
$result = $this->createSequence($seq_name);
if (DB::isError($result)) {
return $this->raiseError($result);
}
} elseif (!DB::isError($result)) {
$result =& $this->query("SELECT @@IDENTITY FROM $seqname");
$repeat = 0;
} else {
$repeat = false;
}
} while ($repeat);
if (DB::isError($result)) {
return $this->raiseError($result);
}
$result = $result->fetchRow(DB_FETCHMODE_ORDERED);
return $result[0];
}
/**
* Creates a new sequence
*
* @param string $seq_name name of the new sequence
*
* @return int DB_OK on success. A DB_Error object on failure.
*
* @see DB_common::createSequence(), DB_common::getSequenceName(),
* DB_sybase::nextID(), DB_sybase::dropSequence()
*/
function createSequence($seq_name)
{
return $this->query('CREATE TABLE '
. $this->getSequenceName($seq_name)
. ' (id numeric(10, 0) IDENTITY NOT NULL,'
. ' vapor int NULL)');
}
// }}}
// {{{ dropSequence()
/**
* Deletes a sequence
*
* @param string $seq_name name of the sequence to be deleted
*
* @return int DB_OK on success. A DB_Error object on failure.
*
* @see DB_common::dropSequence(), DB_common::getSequenceName(),
* DB_sybase::nextID(), DB_sybase::createSequence()
*/
function dropSequence($seq_name)
{
return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
}
// }}}
// {{{ autoCommit()
/**
* Enables or disables automatic commits
*
* @param bool $onoff true turns it on, false turns it off
*
* @return int DB_OK on success. A DB_Error object if the driver
* doesn't support auto-committing transactions.
*/
function autoCommit($onoff = false)
{
// XXX if $this->transaction_opcount > 0, we should probably
// issue a warning here.
$this->autocommit = $onoff ? true : false;
return DB_OK;
}
// }}}
// {{{ commit()
/**
* Commits the current transaction
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function commit()
{
if ($this->transaction_opcount > 0) {
if (!@sybase_select_db($this->_db, $this->connection)) {
return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
}
$result = @sybase_query('COMMIT', $this->connection);
$this->transaction_opcount = 0;
if (!$result) {
return $this->sybaseRaiseError();
}
}
return DB_OK;
}
// }}}
// {{{ rollback()
/**
* Reverts the current transaction
*
* @return int DB_OK on success. A DB_Error object on failure.
*/
function rollback()
{
if ($this->transaction_opcount > 0) {
if (!@sybase_select_db($this->_db, $this->connection)) {
return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
}
$result = @sybase_query('ROLLBACK', $this->connection);
$this->transaction_opcount = 0;
if (!$result) {
return $this->sybaseRaiseError();
}
}
return DB_OK;
}
// }}}
// {{{ sybaseRaiseError()
/**
* Produces a DB_Error object regarding the current problem
*
* @param int $errno if the error is being manually raised pass a
* DB_ERROR* constant here. If this isn't passed
* the error information gathered from the DBMS.
*
* @return object the DB_Error object
*
* @see DB_common::raiseError(),
* DB_sybase::errorNative(), DB_sybase::errorCode()
*/
function sybaseRaiseError($errno = null)
{
$native = $this->errorNative();
if ($errno === null) {
$errno = $this->errorCode($native);
}
return $this->raiseError($errno, null, null, null, $native);
}
// }}}
// {{{ errorNative()
/**
* Gets the DBMS' native error message produced by the last query
*
* @return string the DBMS' error message
*/
function errorNative()
{
return @sybase_get_last_message();
}
// }}}
// {{{ errorCode()
/**
* Determines PEAR::DB error code from the database's text error message.
*
* @param string $errormsg error message returned from the database
* @return integer an error number from a DB error constant
*/
function errorCode($errormsg)
{
static $error_regexps;
if (!isset($error_regexps)) {
$error_regexps = array(
'/Incorrect syntax near/'
=> DB_ERROR_SYNTAX,
'/^Unclosed quote before the character string [\"\'].*[\"\']\./'
=> DB_ERROR_SYNTAX,
'/Implicit conversion (from datatype|of NUMERIC value)/i'
=> DB_ERROR_INVALID_NUMBER,
'/Cannot drop the table [\"\'].+[\"\'], because it doesn\'t exist in the system catalogs\./'
=> DB_ERROR_NOSUCHTABLE,
'/Only the owner of object [\"\'].+[\"\'] or a user with System Administrator \(SA\) role can run this command\./'
=> DB_ERROR_ACCESS_VIOLATION,
'/^.+ permission denied on object .+, database .+, owner .+/'
=> DB_ERROR_ACCESS_VIOLATION,
'/^.* permission denied, database .+, owner .+/'
=> DB_ERROR_ACCESS_VIOLATION,
'/[^.*] not found\./'
=> DB_ERROR_NOSUCHTABLE,
'/There is already an object named/'
=> DB_ERROR_ALREADY_EXISTS,
'/Invalid column name/'
=> DB_ERROR_NOSUCHFIELD,
'/does not allow null values/'
=> DB_ERROR_CONSTRAINT_NOT_NULL,
'/Command has been aborted/'
=> DB_ERROR_CONSTRAINT,
'/^Cannot drop the index .* because it doesn\'t exist/i'
=> DB_ERROR_NOT_FOUND,
'/^There is already an index/i'
=> DB_ERROR_ALREADY_EXISTS,
'/^There are fewer columns in the INSERT statement than values specified/i'
=> DB_ERROR_VALUE_COUNT_ON_ROW,
);
}
foreach ($error_regexps as $regexp => $code) {
if (preg_match($regexp, $errormsg)) {
return $code;
}
}
return DB_ERROR;
}
// }}}
// {{{ tableInfo()
/**
* Returns information about a table or a result set
*
* NOTE: only supports 'table' and 'flags' if <var>$result</var>
* is a table name.
*
* @param object|string $result DB_result object from a query or a
* string containing the name of a table.
* While this also accepts a query result
* resource identifier, this behavior is
* deprecated.
* @param int $mode a valid tableInfo mode
*
* @return array an associative array with the information requested.
* A DB_Error object on failure.
*
* @see DB_common::tableInfo()
* @since Method available since Release 1.6.0
*/
function tableInfo($result, $mode = null)
{
if (is_string($result)) {
/*
* Probably received a table name.
* Create a result resource identifier.
*/
if (!@sybase_select_db($this->_db, $this->connection)) {
return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
}
$id = @sybase_query("SELECT * FROM $result WHERE 1=0",
$this->connection);
$got_string = true;
} elseif (isset($result->result)) {
/*
* Probably received a result object.
* Extract the result resource identifier.
*/
$id = $result->result;
$got_string = false;
} else {
/*
* Probably received a result resource identifier.
* Copy it.
* Deprecated. Here for compatibility only.
*/
$id = $result;
$got_string = false;
}
if (!is_resource($id)) {
return $this->sybaseRaiseError(DB_ERROR_NEED_MORE_DATA);
}
if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
$case_func = 'strtolower';
} else {
$case_func = 'strval';
}
$count = @sybase_num_fields($id);
$res = array();
if ($mode) {
$res['num_fields'] = $count;
}
for ($i = 0; $i < $count; $i++) {
$f = @sybase_fetch_field($id, $i);
// column_source is often blank
$res[$i] = array(
'table' => $got_string
? $case_func($result)
: $case_func($f->column_source),
'name' => $case_func($f->name),
'type' => $f->type,
'len' => $f->max_length,
'flags' => '',
);
if ($res[$i]['table']) {
$res[$i]['flags'] = $this->_sybase_field_flags(
$res[$i]['table'], $res[$i]['name']);
}
if ($mode & DB_TABLEINFO_ORDER) {
$res['order'][$res[$i]['name']] = $i;
}
if ($mode & DB_TABLEINFO_ORDERTABLE) {
$res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
}
}
// free the result only if we were called on a table
if ($got_string) {
@sybase_free_result($id);
}
return $res;
}
// }}}
// {{{ _sybase_field_flags()
/**
* Get the flags for a field
*
* Currently supports:
* + <samp>unique_key</samp> (unique index, unique check or primary_key)
* + <samp>multiple_key</samp> (multi-key index)
*
* @param string $table the table name
* @param string $column the field name
*
* @return string space delimited string of flags. Empty string if none.
*
* @access private
*/
function _sybase_field_flags($table, $column)
{
static $tableName = null;
static $flags = array();
if ($table != $tableName) {
$flags = array();
$tableName = $table;
// get unique/primary keys
$res = $this->getAll("sp_helpindex $table", DB_FETCHMODE_ASSOC);
if (!isset($res[0]['index_description'])) {
return '';
}
foreach ($res as $val) {
$keys = explode(', ', trim($val['index_keys']));
if (sizeof($keys) > 1) {
foreach ($keys as $key) {
$this->_add_flag($flags[$key], 'multiple_key');
}
}
if (strpos($val['index_description'], 'unique')) {
foreach ($keys as $key) {
$this->_add_flag($flags[$key], 'unique_key');
}
}
}
}
if (array_key_exists($column, $flags)) {
return(implode(' ', $flags[$column]));
}
return '';
}
// }}}
// {{{ _add_flag()
/**
* Adds a string to the flags array if the flag is not yet in there
* - if there is no flag present the array is created
*
* @param array $array reference of flags array to add a value to
* @param mixed $value value to add to the flag array
*
* @return void
*
* @access private
*/
function _add_flag(&$array, $value)
{
if (!is_array($array)) {
$array = array($value);
} elseif (!in_array($value, $array)) {
array_push($array, $value);
}
}
// }}}
// {{{ getSpecialQuery()
/**
* Obtains the query string needed for listing a given type of objects
*
* @param string $type the kind of objects you want to retrieve
*
* @return string the SQL query string or null if the driver doesn't
* support the object type requested
*
* @access protected
* @see DB_common::getListOf()
*/
function getSpecialQuery($type)
{
switch ($type) {
case 'tables':
return "SELECT name FROM sysobjects WHERE type = 'U'"
. ' ORDER BY name';
case 'views':
return "SELECT name FROM sysobjects WHERE type = 'V'";
default:
return null;
}
}
// }}}
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
*/
?>

1986
inc/HTTP/WebDAV/Server.php Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,755 @@
<?php
require_once "HTTP/WebDAV/Server.php";
require_once "System.php";
/**
* Filesystem access using WebDAV
*
* @access public
*/
class HTTP_WebDAV_Server_Filesystem extends HTTP_WebDAV_Server
{
/**
* Root directory for WebDAV access
*
* Defaults to webserver document root (set by ServeRequest)
*
* @access private
* @var string
*/
var $base = "";
/**
* MySQL Host where property and locking information is stored
*
* @access private
* @var string
*/
var $db_host = "localhost";
/**
* MySQL database for property/locking information storage
*
* @access private
* @var string
*/
var $db_name = "webdav";
/**
* MySQL user for property/locking db access
*
* @access private
* @var string
*/
var $db_user = "root";
/**
* MySQL password for property/locking db access
*
* @access private
* @var string
*/
var $db_passwd = "";
/**
* Serve a webdav request
*
* @access public
* @param string
*/
function ServeRequest($base = false)
{
// special treatment for litmus compliance test
// reply on its identifier header
// not needed for the test itself but eases debugging
if (function_exists("apache_request_headers")) {
foreach(apache_request_headers() as $key => $value) {
if (stristr($key,"litmus")) {
error_log("Litmus test $value");
header("X-Litmus-reply: ".$value);
}
}
}
// set root directory, defaults to webserver document root if not set
if ($base) {
$this->base = realpath($base); // TODO throw if not a directory
} else if (!$this->base) {
$this->base = $_SERVER['DOCUMENT_ROOT'];
}
// establish connection to property/locking db
mysql_connect($this->db_host, $this->db_user, $this->db_passwd) or die(mysql_error());
mysql_select_db($this->db_name) or die(mysql_error());
// TODO throw on connection problems
// let the base class do all the work
parent::ServeRequest();
}
/**
* No authentication is needed here
*
* @access private
* @param string HTTP Authentication type (Basic, Digest, ...)
* @param string Username
* @param string Password
* @return bool true on successful authentication
*/
function check_auth($type, $user, $pass)
{
return true;
}
/**
* PROPFIND method handler
*
* @param array general parameter passing array
* @param array return array for file properties
* @return bool true on success
*/
function PROPFIND(&$options, &$files)
{
// get absolute fs path to requested resource
$fspath = $this->base . $options["path"];
// sanity check
if (!file_exists($fspath)) {
return false;
}
// prepare property array
$files["files"] = array();
// store information for the requested path itself
$files["files"][] = $this->fileinfo($options["path"]);
// information for contained resources requested?
if (!empty($options["depth"])) { // TODO check for is_dir() first?
// make sure path ends with '/'
$options["path"] = $this->_slashify($options["path"]);
// try to open directory
$handle = @opendir($fspath);
if ($handle) {
// ok, now get all its contents
while ($filename = readdir($handle)) {
if ($filename != "." && $filename != "..") {
$files["files"][] = $this->fileinfo($options["path"].$filename);
}
}
// TODO recursion needed if "Depth: infinite"
}
}
// ok, all done
return true;
}
/**
* Get properties for a single file/resource
*
* @param string resource path
* @return array resource properties
*/
function fileinfo($path)
{
// map URI path to filesystem path
$fspath = $this->base . $path;
// create result array
$info = array();
// TODO remove slash append code when base clase is able to do it itself
$info["path"] = is_dir($fspath) ? $this->_slashify($path) : $path;
$info["props"] = array();
// no special beautified displayname here ...
$info["props"][] = $this->mkprop("displayname", strtoupper($path));
// creation and modification time
$info["props"][] = $this->mkprop("creationdate", filectime($fspath));
$info["props"][] = $this->mkprop("getlastmodified", filemtime($fspath));
// type and size (caller already made sure that path exists)
if (is_dir($fspath)) {
// directory (WebDAV collection)
$info["props"][] = $this->mkprop("resourcetype", "collection");
$info["props"][] = $this->mkprop("getcontenttype", "httpd/unix-directory");
} else {
// plain file (WebDAV resource)
$info["props"][] = $this->mkprop("resourcetype", "");
if (is_readable($fspath)) {
$info["props"][] = $this->mkprop("getcontenttype", $this->_mimetype($fspath));
} else {
$info["props"][] = $this->mkprop("getcontenttype", "application/x-non-readable");
}
$info["props"][] = $this->mkprop("getcontentlength", filesize($fspath));
}
// get additional properties from database
$query = "SELECT ns, name, value FROM properties WHERE path = '$path'";
$res = mysql_query($query);
while ($row = mysql_fetch_assoc($res)) {
$info["props"][] = $this->mkprop($row["ns"], $row["name"], $row["value"]);
}
mysql_free_result($res);
return $info;
}
/**
* detect if a given program is found in the search PATH
*
* helper function used by _mimetype() to detect if the
* external 'file' utility is available
*
* @param string program name
* @param string optional search path, defaults to $PATH
* @return bool true if executable program found in path
*/
function _can_execute($name, $path = false)
{
// path defaults to PATH from environment if not set
if ($path === false) {
$path = getenv("PATH");
}
// check method depends on operating system
if (!strncmp(PHP_OS, "WIN", 3)) {
// on Windows an appropriate COM or EXE file needs to exist
$exts = array(".exe", ".com");
$check_fn = "file_exists";
} else {
// anywhere else we look for an executable file of that name
$exts = array("");
$check_fn = "is_executable";
}
// now check the directories in the path for the program
foreach (explode(PATH_SEPARATOR, $path) as $dir) {
// skip invalid path entries
if (!file_exists($dir)) continue;
if (!is_dir($dir)) continue;
// and now look for the file
foreach ($exts as $ext) {
if ($check_fn("$dir/$name".$ext)) return true;
}
}
return false;
}
/**
* try to detect the mime type of a file
*
* @param string file path
* @return string guessed mime type
*/
function _mimetype($fspath)
{
if (@is_dir($fspath)) {
// directories are easy
return "httpd/unix-directory";
} else if (function_exists("mime_content_type")) {
// use mime magic extension if available
$mime_type = mime_content_type($fspath);
} else if ($this->_can_execute("file")) {
// it looks like we have a 'file' command,
// lets see it it does have mime support
$fp = popen("file -i '$fspath' 2>/dev/null", "r");
$reply = fgets($fp);
pclose($fp);
// popen will not return an error if the binary was not found
// and find may not have mime support using "-i"
// so we test the format of the returned string
// the reply begins with the requested filename
if (!strncmp($reply, "$fspath: ", strlen($fspath)+2)) {
$reply = substr($reply, strlen($fspath)+2);
// followed by the mime type (maybe including options)
if (preg_match('/^[[:alnum:]_-]+/[[:alnum:]_-]+;?.*/', $reply, $matches)) {
$mime_type = $matches[0];
}
}
}
if (empty($mime_type)) {
// Fallback solution: try to guess the type by the file extension
// TODO: add more ...
// TODO: it has been suggested to delegate mimetype detection
// to apache but this has at least three issues:
// - works only with apache
// - needs file to be within the document tree
// - requires apache mod_magic
// TODO: can we use the registry for this on Windows?
// OTOH if the server is Windos the clients are likely to
// be Windows, too, and tend do ignore the Content-Type
// anyway (overriding it with information taken from
// the registry)
// TODO: have a seperate PEAR class for mimetype detection?
switch (strtolower(strrchr(basename($fspath), "."))) {
case ".html":
$mime_type = "text/html";
break;
case ".gif":
$mime_type = "image/gif";
break;
case ".jpg":
$mime_type = "image/jpeg";
break;
default:
$mime_type = "application/octet-stream";
break;
}
}
return $mime_type;
}
/**
* GET method handler
*
* @param array parameter passing array
* @return bool true on success
*/
function GET(&$options)
{
// get absolute fs path to requested resource
$fspath = $this->base . $options["path"];
// sanity check
if (!file_exists($fspath)) return false;
// is this a collection?
if (is_dir($fspath)) {
return $this->GetDir($fspath, $options);
}
// detect resource type
$options['mimetype'] = $this->_mimetype($fspath);
// detect modification time
// see rfc2518, section 13.7
// some clients seem to treat this as a reverse rule
// requiering a Last-Modified header if the getlastmodified header was set
$options['mtime'] = filemtime($fspath);
// detect resource size
$options['size'] = filesize($fspath);
// no need to check result here, it is handled by the base class
$options['stream'] = fopen($fspath, "r");
return true;
}
/**
* GET method handler for directories
*
* This is a very simple mod_index lookalike.
* See RFC 2518, Section 8.4 on GET/HEAD for collections
*
* @param string directory path
* @return void function has to handle HTTP response itself
*/
function GetDir($fspath, &$options)
{
$path = $this->_slashify($options["path"]);
if ($path != $options["path"]) {
header("Location: ".$this->base_uri.$path);
exit;
}
// fixed width directory column format
$format = "%15s %-19s %-s\n";
$handle = @opendir($fspath);
if (!$handle) {
return false;
}
echo "<html><head><title>Index of ".htmlspecialchars($options['path'])."</title></head>\n";
echo "<h1>Index of ".htmlspecialchars($options['path'])."</h1>\n";
echo "<pre>";
printf($format, "Size", "Last modified", "Filename");
echo "<hr>";
while ($filename = readdir($handle)) {
if ($filename != "." && $filename != "..") {
$fullpath = $fspath."/".$filename;
$name = htmlspecialchars($filename);
printf($format,
number_format(filesize($fullpath)),
strftime("%Y-%m-%d %H:%M:%S", filemtime($fullpath)),
"<a href='$this->base_uri$path$name'>$name</a>");
}
}
echo "</pre>";
closedir($handle);
echo "</html>\n";
exit;
}
/**
* PUT method handler
*
* @param array parameter passing array
* @return bool true on success
*/
function PUT(&$options)
{
$fspath = $this->base . $options["path"];
if (!@is_dir(dirname($fspath))) {
return "409 Conflict";
}
$options["new"] = ! file_exists($fspath);
$fp = fopen($fspath, "w");
return $fp;
}
/**
* MKCOL method handler
*
* @param array general parameter passing array
* @return bool true on success
*/
function MKCOL($options)
{
$path = $this->base .$options["path"];
$parent = dirname($path);
$name = basename($path);
if (!file_exists($parent)) {
return "409 Conflict";
}
if (!is_dir($parent)) {
return "403 Forbidden";
}
if ( file_exists($parent."/".$name) ) {
return "405 Method not allowed";
}
if (!empty($_SERVER["CONTENT_LENGTH"])) { // no body parsing yet
return "415 Unsupported media type";
}
$stat = mkdir ($parent."/".$name,0777);
if (!$stat) {
return "403 Forbidden";
}
return ("201 Created");
}
/**
* DELETE method handler
*
* @param array general parameter passing array
* @return bool true on success
*/
function DELETE($options)
{
$path = $this->base . "/" .$options["path"];
if (!file_exists($path)) {
return "404 Not found";
}
if (is_dir($path)) {
$query = "DELETE FROM properties WHERE path LIKE '".$this->_slashify($options["path"])."%'";
mysql_query($query);
System::rm("-rf $path");
} else {
unlink ($path);
}
$query = "DELETE FROM properties WHERE path = '$options[path]'";
mysql_query($query);
return "204 No Content";
}
/**
* MOVE method handler
*
* @param array general parameter passing array
* @return bool true on success
*/
function MOVE($options)
{
return $this->COPY($options, true);
}
/**
* COPY method handler
*
* @param array general parameter passing array
* @return bool true on success
*/
function COPY($options, $del=false)
{
// TODO Property updates still broken (Litmus should detect this?)
if (!empty($_SERVER["CONTENT_LENGTH"])) { // no body parsing yet
return "415 Unsupported media type";
}
// no copying to different WebDAV Servers yet
if (isset($options["dest_url"])) {
return "502 bad gateway";
}
$source = $this->base .$options["path"];
if (!file_exists($source)) return "404 Not found";
$dest = $this->base . $options["dest"];
$new = !file_exists($dest);
$existing_col = false;
if (!$new) {
if ($del && is_dir($dest)) {
if (!$options["overwrite"]) {
return "412 precondition failed";
}
$dest .= basename($source);
if (file_exists($dest)) {
$options["dest"] .= basename($source);
} else {
$new = true;
$existing_col = true;
}
}
}
if (!$new) {
if ($options["overwrite"]) {
$stat = $this->DELETE(array("path" => $options["dest"]));
if (($stat{0} != "2") && (substr($stat, 0, 3) != "404")) {
return $stat;
}
} else {
return "412 precondition failed";
}
}
if (is_dir($source) && ($options["depth"] != "infinity")) {
// RFC 2518 Section 9.2, last paragraph
return "400 Bad request";
}
if ($del) {
if (!rename($source, $dest)) {
return "500 Internal server error";
}
$destpath = $this->_unslashify($options["dest"]);
if (is_dir($source)) {
$query = "UPDATE properties
SET path = REPLACE(path, '".$options["path"]."', '".$destpath."')
WHERE path LIKE '".$this->_slashify($options["path"])."%'";
mysql_query($query);
}
$query = "UPDATE properties
SET path = '".$destpath."'
WHERE path = '".$options["path"]."'";
mysql_query($query);
} else {
if (is_dir($source)) {
$files = System::find($source);
$files = array_reverse($files);
} else {
$files = array($source);
}
if (!is_array($files) || empty($files)) {
return "500 Internal server error";
}
foreach ($files as $file) {
if (is_dir($file)) {
$file = $this->_slashify($file);
}
$destfile = str_replace($source, $dest, $file);
if (is_dir($file)) {
if (!is_dir($destfile)) {
// TODO "mkdir -p" here? (only natively supported by PHP 5)
if (!mkdir($destfile)) {
return "409 Conflict";
}
} else {
error_log("existing dir '$destfile'");
}
} else {
if (!copy($file, $destfile)) {
return "409 Conflict";
}
}
}
$query = "INSERT INTO properties SELECT ... FROM properties WHERE path = '".$options['path']."'";
}
return ($new && !$existing_col) ? "201 Created" : "204 No Content";
}
/**
* PROPPATCH method handler
*
* @param array general parameter passing array
* @return bool true on success
*/
function PROPPATCH(&$options)
{
global $prefs, $tab;
$msg = "";
$path = $options["path"];
$dir = dirname($path)."/";
$base = basename($path);
foreach($options["props"] as $key => $prop) {
if ($prop["ns"] == "DAV:") {
$options["props"][$key]['status'] = "403 Forbidden";
} else {
if (isset($prop["val"])) {
$query = "REPLACE INTO properties SET path = '$options[path]', name = '$prop[name]', ns= '$prop[ns]', value = '$prop[val]'";
error_log($query);
} else {
$query = "DELETE FROM properties WHERE path = '$options[path]' AND name = '$prop[name]' AND ns = '$prop[ns]'";
}
mysql_query($query);
}
}
return "";
}
/**
* LOCK method handler
*
* @param array general parameter passing array
* @return bool true on success
*/
function LOCK(&$options)
{
if (isset($options["update"])) { // Lock Update
$query = "UPDATE locks SET expires = ".(time()+300);
mysql_query($query);
if (mysql_affected_rows()) {
$options["timeout"] = 300; // 5min hardcoded
return true;
} else {
return false;
}
}
$options["timeout"] = time()+300; // 5min. hardcoded
$query = "INSERT INTO locks
SET token = '$options[locktoken]'
, path = '$options[path]'
, owner = '$options[owner]'
, expires = '$options[timeout]'
, exclusivelock = " .($options['scope'] === "exclusive" ? "1" : "0")
;
mysql_query($query);
return mysql_affected_rows() ? "200 OK" : "409 Conflict";
}
/**
* UNLOCK method handler
*
* @param array general parameter passing array
* @return bool true on success
*/
function UNLOCK(&$options)
{
$query = "DELETE FROM locks
WHERE path = '$options[path]'
AND token = '$options[token]'";
mysql_query($query);
return mysql_affected_rows() ? "204 No Content" : "409 Conflict";
}
/**
* checkLock() helper
*
* @param string resource path to check for locks
* @return bool true on success
*/
function checkLock($path)
{
$result = false;
$query = "SELECT owner, token, expires, exclusivelock
FROM locks
WHERE path = '$path'
";
$res = mysql_query($query);
if ($res) {
$row = mysql_fetch_array($res);
mysql_free_result($res);
if ($row) {
$result = array( "type" => "write",
"scope" => $row["exclusivelock"] ? "exclusive" : "shared",
"depth" => 0,
"owner" => $row['owner'],
"token" => $row['token'],
"expires" => $row['expires']
);
}
}
return $result;
}
/**
* create database tables for property and lock storage
*
* @param void
* @return bool true on success
*/
function create_database()
{
// TODO
return false;
}
}
?>

View File

@ -0,0 +1,237 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Hartmut Holzgraefe <hholzgra@php.net> |
// | Christian Stocker <chregu@bitflux.ch> |
// +----------------------------------------------------------------------+
//
// $Id: _parse_lockinfo.php,v 1.2 2004/01/05 12:32:40 hholzgra Exp $
//
/**
* helper class for parsing LOCK request bodies
*
* @package HTTP_WebDAV_Server
* @author Hartmut Holzgraefe <hholzgra@php.net>
* @version 0.99.1dev
*/
class _parse_lockinfo
{
/**
* success state flag
*
* @var bool
* @access public
*/
var $success = false;
/**
* lock type, currently only "write"
*
* @var string
* @access public
*/
var $locktype = "";
/**
* lock scope, "shared" or "exclusive"
*
* @var string
* @access public
*/
var $lockscope = "";
/**
* lock owner information
*
* @var string
* @access public
*/
var $owner = "";
/**
* flag that is set during lock owner read
*
* @var bool
* @access private
*/
var $collect_owner = false;
/**
* constructor
*
* @param string path of stream to read
* @access public
*/
function _parse_lockinfo($path)
{
// we assume success unless problems occur
$this->success = true;
// remember if any input was parsed
$had_input = false;
// open stream
$f_in = fopen($path, "r");
if (!$f_in) {
$this->success = false;
return;
}
// create namespace aware parser
$xml_parser = xml_parser_create_ns("UTF-8", " ");
// set tag and data handlers
xml_set_element_handler($xml_parser,
array(&$this, "_startElement"),
array(&$this, "_endElement"));
xml_set_character_data_handler($xml_parser,
array(&$this, "_data"));
// we want a case sensitive parser
xml_parser_set_option($xml_parser,
XML_OPTION_CASE_FOLDING, false);
// parse input
while($this->success && !feof($f_in)) {
$line = fgets($f_in);
if (is_string($line)) {
$had_input = true;
$this->success &= xml_parse($xml_parser, $line, false);
}
}
// finish parsing
if($had_input) {
$this->success &= xml_parse($xml_parser, "", true);
}
// check if required tags where found
$this->success &= !empty($this->locktype);
$this->success &= !empty($this->lockscope);
// free parser resource
xml_parser_free($xml_parser);
// close input stream
fclose($f_in);
}
/**
* tag start handler
*
* @param resource parser
* @param string tag name
* @param array tag attributes
* @return void
* @access private
*/
function _startElement($parser, $name, $attrs)
{
// namespace handling
if (strstr($name, " ")) {
list($ns, $tag) = explode(" ", $name);
} else {
$ns = "";
$tag = $name;
}
if ($this->collect_owner) {
// everything within the <owner> tag needs to be collected
$ns_short = "";
$ns_attr = "";
if ($ns) {
if ($ns == "DAV:") {
$ns_short = "D:";
} else {
$ns_attr = " xmlns='$ns'";
}
}
$this->owner .= "<$ns_short$tag$ns_attr>";
} else if ($ns == "DAV:") {
// parse only the essential tags
switch ($tag) {
case "write":
$this->locktype = $tag;
break;
case "exclusive":
case "shared":
$this->lockscope = $tag;
break;
case "owner":
$this->collect_owner = true;
break;
}
}
}
/**
* data handler
*
* @param resource parser
* @param string data
* @return void
* @access private
*/
function _data($parser, $data)
{
// only the <owner> tag has data content
if ($this->collect_owner) {
$this->owner .= $data;
}
}
/**
* tag end handler
*
* @param resource parser
* @param string tag name
* @return void
* @access private
*/
function _endElement($parser, $name)
{
// namespace handling
if (strstr($name, " ")) {
list($ns, $tag) = explode(" ", $name);
} else {
$ns = "";
$tag = $name;
}
// <owner> finished?
if (($ns == "DAV:") && ($tag == "owner")) {
$this->collect_owner = false;
}
// within <owner> we have to collect everything
if ($this->collect_owner) {
$ns_short = "";
$ns_attr = "";
if ($ns) {
if ($ns == "DAV:") {
$ns_short = "D:";
} else {
$ns_attr = " xmlns='$ns'";
}
}
$this->owner .= "</$ns_short$tag$ns_attr>";
}
}
}
?>

View File

@ -0,0 +1,178 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Hartmut Holzgraefe <hholzgra@php.net> |
// | Christian Stocker <chregu@bitflux.ch> |
// +----------------------------------------------------------------------+
//
// $Id: _parse_propfind.php,v 1.2 2004/01/05 12:33:22 hholzgra Exp $
//
/**
* helper class for parsing PROPFIND request bodies
*
* @package HTTP_WebDAV_Server
* @author Hartmut Holzgraefe <hholzgra@php.net>
* @version 0.99.1dev
*/
class _parse_propfind
{
/**
* success state flag
*
* @var bool
* @access public
*/
var $success = false;
/**
* found properties are collected here
*
* @var array
* @access public
*/
var $props = false;
/**
* internal tag nesting depth counter
*
* @var int
* @access private
*/
var $depth = 0;
/**
* constructor
*
* @access public
*/
function _parse_propfind($path)
{
// success state flag
$this->success = true;
// property storage array
$this->props = array();
// internal tag depth counter
$this->depth = 0;
// remember if any input was parsed
$had_input = false;
// open input stream
$f_in = fopen($path, "r");
if (!$f_in) {
$this->success = false;
return;
}
// create XML parser
$xml_parser = xml_parser_create_ns("UTF-8", " ");
// set tag and data handlers
xml_set_element_handler($xml_parser,
array(&$this, "_startElement"),
array(&$this, "_endElement"));
// we want a case sensitive parser
xml_parser_set_option($xml_parser,
XML_OPTION_CASE_FOLDING, false);
// parse input
while($this->success && !feof($f_in)) {
$line = fgets($f_in);
if (is_string($line)) {
$had_input = true;
$this->success &= xml_parse($xml_parser, $line, false);
}
}
// finish parsing
if($had_input) {
$this->success &= xml_parse($xml_parser, "", true);
}
// free parser
xml_parser_free($xml_parser);
// close input stream
fclose($f_in);
// if no input was parsed it was a request
if(!count($this->props)) $this->props = "all"; // default
}
/**
* start tag handler
*
* @access private
* @param resource parser
* @param string tag name
* @param array tag attributes
*/
function _startElement($parser, $name, $attrs)
{
// name space handling
if (strstr($name, " ")) {
list($ns, $tag) = explode(" ", $name);
if ($ns == "")
$this->success = false;
} else {
$ns = "";
$tag = $name;
}
// special tags at level 1: <allprop> and <propname>
if ($this->depth == 1) {
if ($tag == "allprop")
$this->props = "all";
if ($tag == "propname")
$this->props = "names";
}
// requested properties are found at level 2
if ($this->depth == 2) {
$prop = array("name" => $tag);
if ($ns)
$prop["xmlns"] = $ns;
$this->props[] = $prop;
}
// increment depth count
$this->depth++;
}
/**
* end tag handler
*
* @access private
* @param resource parser
* @param string tag name
*/
function _endElement($parser, $name)
{
// here we only need to decrement the depth count
$this->depth--;
}
}
?>

View File

@ -0,0 +1,214 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Hartmut Holzgraefe <hholzgra@php.net> |
// | Christian Stocker <chregu@bitflux.ch> |
// +----------------------------------------------------------------------+
//
// $Id: _parse_proppatch.php,v 1.3 2004/01/05 12:41:34 hholzgra Exp $
//
/**
* helper class for parsing PROPPATCH request bodies
*
* @package HTTP_WebDAV_Server
* @author Hartmut Holzgraefe <hholzgra@php.net>
* @version 0.99.1dev
*/
class _parse_proppatch
{
/**
*
*
* @var
* @access
*/
var $success;
/**
*
*
* @var
* @access
*/
var $props;
/**
*
*
* @var
* @access
*/
var $depth;
/**
*
*
* @var
* @access
*/
var $mode;
/**
*
*
* @var
* @access
*/
var $current;
/**
* constructor
*
* @param string path of input stream
* @access public
*/
function _parse_proppatch($path)
{
$this->success = true;
$this->depth = 0;
$this->props = array();
$had_input = false;
$f_in = fopen($path, "r");
if (!$f_in) {
$this->success = false;
return;
}
$xml_parser = xml_parser_create_ns("UTF-8", " ");
xml_set_element_handler($xml_parser,
array(&$this, "_startElement"),
array(&$this, "_endElement"));
xml_set_character_data_handler($xml_parser,
array(&$this, "_data"));
xml_parser_set_option($xml_parser,
XML_OPTION_CASE_FOLDING, false);
while($this->success && !feof($f_in)) {
$line = fgets($f_in);
if (is_string($line)) {
$had_input = true;
$this->success &= xml_parse($xml_parser, $line, false);
}
}
if($had_input) {
$this->success &= xml_parse($xml_parser, "", true);
}
xml_parser_free($xml_parser);
fclose($f_in);
}
/**
* tag start handler
*
* @param resource parser
* @param string tag name
* @param array tag attributes
* @return void
* @access private
*/
function _startElement($parser, $name, $attrs)
{
if (strstr($name, " ")) {
list($ns, $tag) = explode(" ", $name);
if ($ns == "")
$this->success = false;
} else {
$ns = "";
$tag = $name;
}
if ($this->depth == 1) {
$this->mode = $tag;
}
if ($this->depth == 3) {
$prop = array("name" => $tag);
$this->current = array("name" => $tag, "ns" => $ns, "status"=> 200);
if ($this->mode == "set") {
$this->current["val"] = ""; // default set val
}
}
if ($this->depth >= 4) {
$this->current["val"] .= "<$tag";
foreach ($attr as $key => $val) {
$this->current["val"] .= ' '.$key.'="'.str_replace('"','&quot;', $val).'"';
}
$this->current["val"] .= ">";
}
$this->depth++;
}
/**
* tag end handler
*
* @param resource parser
* @param string tag name
* @return void
* @access private
*/
function _endElement($parser, $name)
{
if (strstr($name, " ")) {
list($ns, $tag) = explode(" ", $name);
if ($ns == "")
$this->success = false;
} else {
$ns = "";
$tag = $name;
}
$this->depth--;
if ($this->depth >= 4) {
$this->current["val"] .= "</$tag>";
}
if ($this->depth == 3) {
if (isset($this->current)) {
$this->props[] = $this->current;
unset($this->current);
}
}
}
/**
* input data handler
*
* @param resource parser
* @param string data
* @return void
* @access private
*/
function _data($parser, $data) {
if (isset($this->current)) {
$this->current["val"] .= $data;
}
}
}
?>

635
inc/Log.php Executable file
View File

@ -0,0 +1,635 @@
<?php
/**
* $Header: /repository/pear/Log/Log.php,v 1.46 2004/09/09 02:42:22 jon Exp $
* $Horde: horde/lib/Log.php,v 1.15 2000/06/29 23:39:45 jon Exp $
*
* @version $Revision: 1.46 $
* @package Log
*/
define('PEAR_LOG_EMERG', 0); /** System is unusable */
define('PEAR_LOG_ALERT', 1); /** Immediate action required */
define('PEAR_LOG_CRIT', 2); /** Critical conditions */
define('PEAR_LOG_ERR', 3); /** Error conditions */
define('PEAR_LOG_WARNING', 4); /** Warning conditions */
define('PEAR_LOG_NOTICE', 5); /** Normal but significant */
define('PEAR_LOG_INFO', 6); /** Informational */
define('PEAR_LOG_DEBUG', 7); /** Debug-level messages */
define('PEAR_LOG_ALL', bindec('11111111')); /** All messages */
define('PEAR_LOG_NONE', bindec('00000000')); /** No message */
/* Log types for PHP's native error_log() function. */
define('PEAR_LOG_TYPE_SYSTEM', 0); /** Use PHP's system logger */
define('PEAR_LOG_TYPE_MAIL', 1); /** Use PHP's mail() function */
define('PEAR_LOG_TYPE_DEBUG', 2); /** Use PHP's debugging connection */
define('PEAR_LOG_TYPE_FILE', 3); /** Append to a file */
/**
* The Log:: class implements both an abstraction for various logging
* mechanisms and the Subject end of a Subject-Observer pattern.
*
* @author Chuck Hagenbuch <chuck@horde.org>
* @author Jon Parise <jon@php.net>
* @since Horde 1.3
* @package Log
*/
class Log
{
/**
* Indicates whether or not the log can been opened / connected.
*
* @var boolean
* @access private
*/
var $_opened = false;
/**
* Instance-specific unique identification number.
*
* @var integer
* @access private
*/
var $_id = 0;
/**
* The label that uniquely identifies this set of log messages.
*
* @var string
* @access private
*/
var $_ident = '';
/**
* The default priority to use when logging an event.
*
* @var integer
* @access private
*/
var $_priority = PEAR_LOG_INFO;
/**
* The bitmask of allowed log levels.
* @var integer
* @access private
*/
var $_mask = PEAR_LOG_ALL;
/**
* Holds all Log_observer objects that wish to be notified of new messages.
*
* @var array
* @access private
*/
var $_listeners = array();
/**
* Attempts to return a concrete Log instance of type $handler.
*
* @param string $handler The type of concrete Log subclass to return.
* Attempt to dynamically include the code for
* this subclass. Currently, valid values are
* 'console', 'syslog', 'sql', 'file', and 'mcal'.
*
* @param string $name The name of the actually log file, table, or
* other specific store to use. Defaults to an
* empty string, with which the subclass will
* attempt to do something intelligent.
*
* @param string $ident The identity reported to the log system.
*
* @param array $conf A hash containing any additional configuration
* information that a subclass might need.
*
* @param int $level Log messages up to and including this level.
*
* @return object Log The newly created concrete Log instance, or an
* false on an error.
* @access public
* @since Log 1.0
*/
function &factory($handler, $name = '', $ident = '', $conf = array(),
$level = PEAR_LOG_DEBUG)
{
$handler = strtolower($handler);
$class = 'Log_' . $handler;
$classfile = 'Log/' . $handler . '.php';
/*
* Attempt to include our version of the named class, but don't treat
* a failure as fatal. The caller may have already included their own
* version of the named class.
*/
@include_once $classfile;
/* If the class exists, return a new instance of it. */
if (class_exists($class)) {
return new $class($name, $ident, $conf, $level);
}
return false;
}
/**
* Attempts to return a reference to a concrete Log instance of type
* $handler, only creating a new instance if no log instance with the same
* parameters currently exists.
*
* You should use this if there are multiple places you might create a
* logger, you don't want to create multiple loggers, and you don't want to
* check for the existance of one each time. The singleton pattern does all
* the checking work for you.
*
* <b>You MUST call this method with the $var = &Log::singleton() syntax.
* Without the ampersand (&) in front of the method name, you will not get
* a reference, you will get a copy.</b>
*
* @param string $handler The type of concrete Log subclass to return.
* Attempt to dynamically include the code for
* this subclass. Currently, valid values are
* 'console', 'syslog', 'sql', 'file', and 'mcal'.
*
* @param string $name The name of the actually log file, table, or
* other specific store to use. Defaults to an
* empty string, with which the subclass will
* attempt to do something intelligent.
*
* @param string $ident The identity reported to the log system.
*
* @param array $conf A hash containing any additional configuration
* information that a subclass might need.
*
* @param int $level Log messages up to and including this level.
*
* @return object Log The newly created concrete Log instance, or an
* false on an error.
* @access public
* @since Log 1.0
*/
function &singleton($handler, $name = '', $ident = '', $conf = array(),
$level = PEAR_LOG_DEBUG)
{
static $instances;
if (!isset($instances)) $instances = array();
$signature = serialize(array($handler, $name, $ident, $conf, $level));
if (!isset($instances[$signature])) {
$instances[$signature] = &Log::factory($handler, $name, $ident,
$conf, $level);
}
return $instances[$signature];
}
/**
* Abstract implementation of the open() method.
* @since Log 1.0
*/
function open()
{
return false;
}
/**
* Abstract implementation of the close() method.
* @since Log 1.0
*/
function close()
{
return false;
}
/**
* Abstract implementation of the flush() method.
* @since Log 1.8.2
*/
function flush()
{
return false;
}
/**
* Abstract implementation of the log() method.
* @since Log 1.0
*/
function log($message, $priority = null)
{
return false;
}
/**
* A convenience function for logging a emergency event. It will log a
* message at the PEAR_LOG_EMERG log level.
*
* @param mixed $message String or object containing the message
* to log.
*
* @return boolean True if the message was successfully logged.
*
* @access public
* @since Log 1.7.0
*/
function emerg($message)
{
return $this->log($message, PEAR_LOG_EMERG);
}
/**
* A convenience function for logging an alert event. It will log a
* message at the PEAR_LOG_ALERT log level.
*
* @param mixed $message String or object containing the message
* to log.
*
* @return boolean True if the message was successfully logged.
*
* @access public
* @since Log 1.7.0
*/
function alert($message)
{
return $this->log($message, PEAR_LOG_ALERT);
}
/**
* A convenience function for logging a critical event. It will log a
* message at the PEAR_LOG_CRIT log level.
*
* @param mixed $message String or object containing the message
* to log.
*
* @return boolean True if the message was successfully logged.
*
* @access public
* @since Log 1.7.0
*/
function crit($message)
{
return $this->log($message, PEAR_LOG_CRIT);
}
/**
* A convenience function for logging a error event. It will log a
* message at the PEAR_LOG_ERR log level.
*
* @param mixed $message String or object containing the message
* to log.
*
* @return boolean True if the message was successfully logged.
*
* @access public
* @since Log 1.7.0
*/
function err($message)
{
return $this->log($message, PEAR_LOG_ERR);
}
/**
* A convenience function for logging a warning event. It will log a
* message at the PEAR_LOG_WARNING log level.
*
* @param mixed $message String or object containing the message
* to log.
*
* @return boolean True if the message was successfully logged.
*
* @access public
* @since Log 1.7.0
*/
function warning($message)
{
return $this->log($message, PEAR_LOG_WARNING);
}
/**
* A convenience function for logging a notice event. It will log a
* message at the PEAR_LOG_NOTICE log level.
*
* @param mixed $message String or object containing the message
* to log.
*
* @return boolean True if the message was successfully logged.
*
* @access public
* @since Log 1.7.0
*/
function notice($message)
{
return $this->log($message, PEAR_LOG_NOTICE);
}
/**
* A convenience function for logging a information event. It will log a
* message at the PEAR_LOG_INFO log level.
*
* @param mixed $message String or object containing the message
* to log.
*
* @return boolean True if the message was successfully logged.
*
* @access public
* @since Log 1.7.0
*/
function info($message)
{
return $this->log($message, PEAR_LOG_INFO);
}
/**
* A convenience function for logging a debug event. It will log a
* message at the PEAR_LOG_DEBUG log level.
*
* @param mixed $message String or object containing the message
* to log.
*
* @return boolean True if the message was successfully logged.
*
* @access public
* @since Log 1.7.0
*/
function debug($message)
{
return $this->log($message, PEAR_LOG_DEBUG);
}
/**
* Returns the string representation of the message data.
*
* If $message is an object, _extractMessage() will attempt to extract
* the message text using a known method (such as a PEAR_Error object's
* getMessage() method). If a known method, cannot be found, the
* serialized representation of the object will be returned.
*
* If the message data is already a string, it will be returned unchanged.
*
* @param mixed $message The original message data. This may be a
* string or any object.
*
* @return string The string representation of the message.
*
* @access private
*/
function _extractMessage($message)
{
/*
* If we've been given an object, attempt to extract the message using
* a known method. If we can't find such a method, default to the
* "human-readable" version of the object.
*
* We also use the human-readable format for arrays.
*/
if (is_object($message)) {
if (method_exists($message, 'getmessage')) {
$message = $message->getMessage();
} else if (method_exists($message, 'tostring')) {
$message = $message->toString();
} else if (method_exists($message, '__tostring')) {
$message = (string)$message;
} else {
$message = print_r($message, true);
}
} else if (is_array($message)) {
if (isset($message['message'])) {
$message = $message['message'];
} else {
$message = print_r($message, true);
}
}
/* Otherwise, we assume the message is a string. */
return $message;
}
/**
* Returns the string representation of a PEAR_LOG_* integer constant.
*
* @param int $priority A PEAR_LOG_* integer constant.
*
* @return string The string representation of $level.
*
* @since Log 1.0
*/
function priorityToString($priority)
{
$levels = array(
PEAR_LOG_EMERG => 'emergency',
PEAR_LOG_ALERT => 'alert',
PEAR_LOG_CRIT => 'critical',
PEAR_LOG_ERR => 'error',
PEAR_LOG_WARNING => 'warning',
PEAR_LOG_NOTICE => 'notice',
PEAR_LOG_INFO => 'info',
PEAR_LOG_DEBUG => 'debug'
);
return $levels[$priority];
}
/**
* Calculate the log mask for the given priority.
*
* @param integer $priority The priority whose mask will be calculated.
*
* @return integer The calculated log mask.
*
* @access public
* @since Log 1.7.0
*/
function MASK($priority)
{
return (1 << $priority);
}
/**
* Calculate the log mask for all priorities up to the given priority.
*
* @param integer $priority The maximum priority covered by this mask.
*
* @return integer The calculated log mask.
*
* @access public
* @since Log 1.7.0
*/
function UPTO($priority)
{
return ((1 << ($priority + 1)) - 1);
}
/**
* Set and return the level mask for the current Log instance.
*
* @param integer $mask A bitwise mask of log levels.
*
* @return integer The current level mask.
*
* @access public
* @since Log 1.7.0
*/
function setMask($mask)
{
$this->_mask = $mask;
return $this->_mask;
}
/**
* Returns the current level mask.
*
* @return interger The current level mask.
*
* @access public
* @since Log 1.7.0
*/
function getMask()
{
return $this->_mask;
}
/**
* Check if the given priority is included in the current level mask.
*
* @param integer $priority The priority to check.
*
* @return boolean True if the given priority is included in the current
* log mask.
*
* @access private
* @since Log 1.7.0
*/
function _isMasked($priority)
{
return (Log::MASK($priority) & $this->_mask);
}
/**
* Returns the current default priority.
*
* @return integer The current default priority.
*
* @access public
* @since Log 1.8.4
*/
function getPriority()
{
return $this->_priority;
}
/**
* Sets the default priority to the specified value.
*
* @param integer $priority The new default priority.
*
* @access public
* @since Log 1.8.4
*/
function setPriority($priority)
{
$this->_priority = $priority;
}
/**
* Adds a Log_observer instance to the list of observers that are listening
* for messages emitted by this Log instance.
*
* @param object $observer The Log_observer instance to attach as a
* listener.
*
* @param boolean True if the observer is successfully attached.
*
* @access public
* @since Log 1.0
*/
function attach(&$observer)
{
if (!is_a($observer, 'Log_observer')) {
return false;
}
$this->_listeners[$observer->_id] = &$observer;
return true;
}
/**
* Removes a Log_observer instance from the list of observers.
*
* @param object $observer The Log_observer instance to detach from
* the list of listeners.
*
* @param boolean True if the observer is successfully detached.
*
* @access public
* @since Log 1.0
*/
function detach($observer)
{
if (!is_a($observer, 'Log_observer') ||
!isset($this->_listeners[$observer->_id])) {
return false;
}
unset($this->_listeners[$observer->_id]);
return true;
}
/**
* Informs each registered observer instance that a new message has been
* logged.
*
* @param array $event A hash describing the log event.
*
* @access private
*/
function _announce($event)
{
foreach ($this->_listeners as $id => $listener) {
if ($event['priority'] <= $this->_listeners[$id]->_priority) {
$this->_listeners[$id]->notify($event);
}
}
}
/**
* Indicates whether this is a composite class.
*
* @return boolean True if this is a composite class.
*
* @access public
* @since Log 1.0
*/
function isComposite()
{
return false;
}
/**
* Sets this Log instance's identification string.
*
* @param string $ident The new identification string.
*
* @access public
* @since Log 1.6.3
*/
function setIdent($ident)
{
$this->_ident = $ident;
}
/**
* Returns the current identification string.
*
* @return string The current Log instance's identification string.
*
* @access public
* @since Log 1.6.3
*/
function getIdent()
{
return $this->_ident;
}
}
?>

196
inc/Log/composite.php Executable file
View File

@ -0,0 +1,196 @@
<?php
/**
* $Header: /repository/pear/Log/Log/composite.php,v 1.23 2004/08/09 06:04:11 jon Exp $
* $Horde: horde/lib/Log/composite.php,v 1.2 2000/06/28 21:36:13 jon Exp $
*
* @version $Revision: 1.23 $
* @package Log
*/
/**
* The Log_composite:: class implements a Composite pattern which
* allows multiple Log implementations to receive the same events.
*
* @author Chuck Hagenbuch <chuck@horde.org>
* @author Jon Parise <jon@php.net>
*
* @since Horde 1.3
* @since Log 1.0
* @package Log
*
* @example composite.php Using the composite handler.
*/
class Log_composite extends Log
{
/**
* Array holding all of the Log instances to which log events should be
* sent.
*
* @var array
* @access private
*/
var $_children = array();
/**
* Constructs a new composite Log object.
*
* @param boolean $name This parameter is ignored.
* @param boolean $ident This parameter is ignored.
* @param boolean $conf This parameter is ignored.
* @param boolean $level This parameter is ignored.
*
* @access public
*/
function Log_composite($name = false, $ident = false, $conf = false,
$level = PEAR_LOG_DEBUG)
{
}
/**
* Opens the child connections.
*
* @access public
*/
function open()
{
if (!$this->_opened) {
foreach ($this->_children as $id => $child) {
$this->_children[$id]->open();
}
$this->_opened = true;
}
}
/**
* Closes any child instances.
*
* @access public
*/
function close()
{
if ($this->_opened) {
foreach ($this->_children as $id => $child) {
$this->_children[$id]->close();
}
$this->_opened = false;
}
}
/**
* Flushes all open child instances.
*
* @access public
* @since Log 1.8.2
*/
function flush()
{
if ($this->_opened) {
foreach ($this->_children as $id => $child) {
$this->_children[$id]->flush();
}
}
}
/**
* Sends $message and $priority to each child of this composite.
*
* @param mixed $message String or object containing the message
* to log.
* @param string $priority (optional) The priority of the message.
* Valid values are: PEAR_LOG_EMERG,
* PEAR_LOG_ALERT, PEAR_LOG_CRIT,
* PEAR_LOG_ERR, PEAR_LOG_WARNING,
* PEAR_LOG_NOTICE, PEAR_LOG_INFO, and
* PEAR_LOG_DEBUG.
*
* @return boolean True if the entry is successfully logged.
*
* @access public
*/
function log($message, $priority = null)
{
/* If a priority hasn't been specified, use the default value. */
if ($priority === null) {
$priority = $this->_priority;
}
foreach ($this->_children as $id => $child) {
$this->_children[$id]->log($message, $priority);
}
$this->_announce(array('priority' => $priority, 'message' => $message));
return true;
}
/**
* Returns true if this is a composite.
*
* @return boolean True if this is a composite class.
*
* @access public
*/
function isComposite()
{
return true;
}
/**
* Sets this identification string for all of this composite's children.
*
* @param string $ident The new identification string.
*
* @access public
* @since Log 1.6.7
*/
function setIdent($ident)
{
foreach ($this->_children as $id => $child) {
$this->_children[$id]->setIdent($ident);
}
}
/**
* Adds a Log instance to the list of children.
*
* @param object $child The Log instance to add.
*
* @return boolean True if the Log instance was successfully added.
*
* @access public
*/
function addChild(&$child)
{
/* Make sure this is a Log instance. */
if (!is_a($child, 'Log')) {
return false;
}
$this->_children[$child->_id] = &$child;
return true;
}
/**
* Removes a Log instance from the list of children.
*
* @param object $child The Log instance to remove.
*
* @return boolean True if the Log instance was successfully removed.
*
* @access public
*/
function removeChild($child)
{
if (!is_a($child, 'Log') || !isset($this->_children[$child->_id])) {
return false;
}
unset($this->_children[$child->_id]);
return true;
}
}
?>

190
inc/Log/console.php Executable file
View File

@ -0,0 +1,190 @@
<?php
/**
* $Header: /repository/pear/Log/Log/console.php,v 1.19 2004/01/19 08:02:40 jon Exp $
*
* @version $Revision: 1.19 $
* @package Log
*/
/**
* The Log_console class is a concrete implementation of the Log::
* abstract class which writes message to the text console.
*
* @author Jon Parise <jon@php.net>
* @since Log 1.1
* @package Log
*
* @example console.php Using the console handler.
*/
class Log_console extends Log
{
/**
* Handle to the current output stream.
* @var resource
* @access private
*/
var $_stream = STDOUT;
/**
* Should the output be buffered or displayed immediately?
* @var string
* @access private
*/
var $_buffering = false;
/**
* String holding the buffered output.
* @var string
* @access private
*/
var $_buffer = '';
/**
* String containing the format of a log line.
* @var string
* @access private
*/
var $_lineFormat = '%1$s %2$s [%3$s] %4$s';
/**
* String containing the timestamp format. It will be passed directly to
* strftime(). Note that the timestamp string will generated using the
* current locale.
* @var string
* @access private
*/
var $_timeFormat = '%b %d %H:%M:%S';
/**
* Hash that maps canonical format keys to position arguments for the
* "line format" string.
* @var array
* @access private
*/
var $_formatMap = array('%{timestamp}' => '%1$s',
'%{ident}' => '%2$s',
'%{priority}' => '%3$s',
'%{message}' => '%4$s',
'%\{' => '%%{');
/**
* Constructs a new Log_console object.
*
* @param string $name Ignored.
* @param string $ident The identity string.
* @param array $conf The configuration array.
* @param int $level Log messages up to and including this level.
* @access public
*/
function Log_console($name, $ident = '', $conf = array(),
$level = PEAR_LOG_DEBUG)
{
$this->_id = md5(microtime());
$this->_ident = $ident;
$this->_mask = Log::UPTO($level);
if (!empty($conf['stream'])) {
$this->_stream = $conf['stream'];
}
if (isset($conf['buffering'])) {
$this->_buffering = $conf['buffering'];
}
if (!empty($conf['lineFormat'])) {
$this->_lineFormat = str_replace(array_keys($this->_formatMap),
array_values($this->_formatMap),
$conf['lineFormat']);
}
if (!empty($conf['timeFormat'])) {
$this->_timeFormat = $conf['timeFormat'];
}
/*
* If output buffering has been requested, we need to register a
* shutdown function that will dump the buffer upon termination.
*/
if ($this->_buffering) {
register_shutdown_function(array(&$this, '_Log_console'));
}
}
/**
* Destructor
*/
function _Log_console()
{
$this->flush();
}
/**
* Flushes all pending ("buffered") data to the output stream.
*
* @access public
* @since Log 1.8.2
*/
function flush()
{
/*
* If output buffering is enabled, dump the contents of the buffer to
* the output stream.
*/
if ($this->_buffering && (strlen($this->_buffer) > 0)) {
fwrite($this->_stream, $this->_buffer);
$this->_buffer = '';
}
return fflush($this->_stream);
}
/**
* Writes $message to the text console. Also, passes the message
* along to any Log_observer instances that are observing this Log.
*
* @param mixed $message String or object containing the message to log.
* @param string $priority The priority of the message. Valid
* values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT,
* PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING,
* PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG.
* @return boolean True on success or false on failure.
* @access public
*/
function log($message, $priority = null)
{
/* If a priority hasn't been specified, use the default value. */
if ($priority === null) {
$priority = $this->_priority;
}
/* Abort early if the priority is above the maximum logging level. */
if (!$this->_isMasked($priority)) {
return false;
}
/* Extract the string representation of the message. */
$message = $this->_extractMessage($message);
/* Build the string containing the complete log line. */
$line = sprintf($this->_lineFormat, strftime($this->_timeFormat),
$this->_ident, $this->priorityToString($priority),
$message) . "\n";
/*
* If buffering is enabled, append this line to the output buffer.
* Otherwise, print the line to the output stream immediately.
*/
if ($this->_buffering) {
$this->_buffer .= $line;
} else {
fwrite($this->_stream, $line);
}
/* Notify observers about this log message. */
$this->_announce(array('priority' => $priority, 'message' => $message));
return true;
}
}
?>

229
inc/Log/daemon.php Executable file
View File

@ -0,0 +1,229 @@
<?php
// $Id: daemon.php,v 1.1 2004/12/21 06:55:38 jon Exp $
/**
* The Log_daemon class is a concrete implementation of the Log::
* abstract class which sends messages to syslog daemon on UNIX-like machines.
* This class uses the syslog protocol: http://www.ietf.org/rfc/rfc3164.txt
*
* @author Bart van der Schans <schans@dds.nl>
* @version $Revision: 1.1 $
* @package Log
*/
class Log_daemon extends Log {
/**
* Integer holding the log facility to use.
* @var string
*/
var $_name = LOG_DAEMON;
/**
* Var holding the resource pointer to the socket
* @var resource
*/
var $_socket;
/**
* The ip address or servername
* @see http://www.php.net/manual/en/transports.php
* @var string
*/
var $_ip = '127.0.0.1';
/**
* Protocol to use (tcp, udp, etc.)
* @see http://www.php.net/manual/en/transports.php
* @var string
*/
var $_proto = 'udp';
/**
* Port to connect to
* @var int
*/
var $_port = 514;
/**
* Maximum message length in bytes
* @var int
*/
var $_maxsize = 4096;
/**
* Socket timeout in seconds
* @var int
*/
var $_timeout = 1;
/**
* Constructs a new syslog object.
*
* @param string $name The syslog facility.
* @param string $ident The identity string.
* @param array $conf The configuration array.
* @param int $maxLevel Maximum level at which to log.
* @access public
*/
function Log_daemon($name, $ident = '', $conf = array(),
$level = PEAR_LOG_DEBUG)
{
/* Ensure we have a valid integer value for $name. */
if (empty($name) || !is_int($name)) {
$name = LOG_SYSLOG;
}
$this->_id = md5(microtime());
$this->_name = $name;
$this->_ident = $ident;
$this->_mask = Log::UPTO($level);
if (isset($conf['ip'])) {
$this->_ip = $conf['ip'];
}
if (isset($conf['proto'])) {
$this->_proto = $conf['proto'];
}
if (isset($conf['port'])) {
$this->_port = $conf['port'];
}
if (isset($conf['maxsize'])) {
$this->_maxsize = $conf['maxsize'];
}
if (isset($conf['timeout'])) {
$this->_timeout = $conf['timeout'];
}
$this->_proto = $this->_proto . '://';
register_shutdown_function(array(&$this, '_Log_daemon'));
}
/**
* Destructor.
*
* @access private
*/
function _Log_daemon()
{
$this->close();
}
/**
* Opens a connection to the system logger, if it has not already
* been opened. This is implicitly called by log(), if necessary.
* @access public
*/
function open()
{
if (!$this->_opened) {
$this->_opened = (bool)($this->_socket = @fsockopen(
$this->_proto . $this->_ip,
$this->_port,
$errno,
$errstr,
$this->_timeout));
}
return $this->_opened;
}
/**
* Closes the connection to the system logger, if it is open.
* @access public
*/
function close()
{
if ($this->_opened) {
$this->_opened = false;
return fclose($this->_socket);
}
return true;
}
/**
* Sends $message to the currently open syslog connection. Calls
* open() if necessary. Also passes the message along to any Log_observer
* instances that are observing this Log.
*
* @param string $message The textual message to be logged.
* @param int $priority (optional) The priority of the message. Valid
* values are: LOG_EMERG, LOG_ALERT, LOG_CRIT,
* LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO,
* and LOG_DEBUG. The default is LOG_INFO.
* @access public
*/
function log($message, $priority = null)
{
/* If a priority hasn't been specified, use the default value. */
if ($priority === null) {
$priority = $this->_priority;
}
/* Abort early if the priority is above the maximum logging level. */
if (!$this->_isMasked($priority)) {
return false;
}
/* If the connection isn't open and can't be opened, return failure. */
if (!$this->_opened && !$this->open()) {
return false;
}
/* Extract the string representation of the message. */
$message = $this->_extractMessage($message);
/* Set the facility level. */
$facility_level = intval($this->_name) +
intval($this->_toSyslog($priority));
/* Prepend ident info. */
if (!empty($this->_ident)) {
$message = $this->_ident . ' ' . $message;
}
/* Check for message length. */
if (strlen($message) > $this->_maxsize) {
$message = substr($message, 0, ($this->_maxsize) - 10) . ' [...]';
}
/* Write to socket. */
fwrite($this->_socket, '<' . $facility_level . '>' . $message . "\n");
$this->_announce(array('priority' => $priority, 'message' => $message));
}
/**
* Converts a PEAR_LOG_* constant into a syslog LOG_* constant.
*
* This function exists because, under Windows, not all of the LOG_*
* constants have unique values. Instead, the PEAR_LOG_* were introduced
* for global use, with the conversion to the LOG_* constants kept local to
* to the syslog driver.
*
* @param int $priority PEAR_LOG_* value to convert to LOG_* value.
*
* @return The LOG_* representation of $priority.
*
* @access private
*/
function _toSyslog($priority)
{
static $priorities = array(
PEAR_LOG_EMERG => LOG_EMERG,
PEAR_LOG_ALERT => LOG_ALERT,
PEAR_LOG_CRIT => LOG_CRIT,
PEAR_LOG_ERR => LOG_ERR,
PEAR_LOG_WARNING => LOG_WARNING,
PEAR_LOG_NOTICE => LOG_NOTICE,
PEAR_LOG_INFO => LOG_INFO,
PEAR_LOG_DEBUG => LOG_DEBUG
);
/* If we're passed an unknown priority, default to LOG_INFO. */
if (!is_int($priority) || !in_array($priority, $priorities)) {
return LOG_INFO;
}
return $priorities[$priority];
}
}

108
inc/Log/display.php Executable file
View File

@ -0,0 +1,108 @@
<?php
/**
* $Header: /repository/pear/Log/Log/display.php,v 1.6 2004/11/27 21:46:50 jon Exp $
*
* @version $Revision: 1.6 $
* @package Log
*/
/**
* The Log_display class is a concrete implementation of the Log::
* abstract class which writes message into browser in usual PHP maner.
* This may be useful because when you use PEAR::setErrorHandling in
* PEAR_ERROR_CALLBACK mode error messages are not displayed by
* PHP error handler.
*
* @author Paul Yanchenko <pusher@inaco.ru>
* @since Log 1.8.0
* @package Log
*
* @example display.php Using the display handler.
*/
class Log_display extends Log
{
/**
* String to output before an error message
* @var string
* @access private
*/
var $_error_prepend = '';
/**
* String to output after an error message
* @var string
* @access private
*/
var $_error_append = '';
/**
* Constructs a new Log_display object.
*
* @param string $name Ignored.
* @param string $ident The identity string.
* @param array $conf The configuration array.
* @param int $level Log messages up to and including this level.
* @access public
*/
function Log_display($name = '', $ident = '', $conf = array(),
$level = PEAR_LOG_DEBUG)
{
$this->_id = md5(microtime());
$this->_ident = $ident;
$this->_mask = Log::UPTO($level);
if (!empty($conf['error_prepend'])) {
$this->_error_prepend = $conf['error_prepend'];
} else {
$this->_error_prepend = ini_get('error_prepend_string');
}
if (!empty($conf['error_append'])) {
$this->_error_append = $conf['error_append'];
} else {
$this->_error_append = ini_get('error_append_string');
}
}
/**
* Writes $message to the text browser. Also, passes the message
* along to any Log_observer instances that are observing this Log.
*
* @param mixed $message String or object containing the message to log.
* @param string $priority The priority of the message. Valid
* values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT,
* PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING,
* PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG.
* @return boolean True on success or false on failure.
* @access public
*/
function log($message, $priority = null)
{
/* If a priority hasn't been specified, use the default value. */
if ($priority === null) {
$priority = $this->_priority;
}
/* Abort early if the priority is above the maximum logging level. */
if (!$this->_isMasked($priority)) {
return false;
}
/* Extract the string representation of the message. */
$message = $this->_extractMessage($message);
/* Build and output the complete log line. */
echo $this->_error_prepend .
'<b>' . ucfirst($this->priorityToString($priority)) . '</b>: '.
nl2br(htmlspecialchars($message)) .
$this->_error_append . "<br />\n";
/* Notify observers about this log message. */
$this->_announce(array('priority' => $priority, 'message' => $message));
return true;
}
}
?>

104
inc/Log/error_log.php Executable file
View File

@ -0,0 +1,104 @@
<?php
/**
* $Header: /repository/pear/Log/Log/error_log.php,v 1.6 2004/01/19 08:02:40 jon Exp $
*
* @version $Revision: 1.6 $
* @package Log
*/
/**
* The Log_error_log class is a concrete implementation of the Log abstract
* class that logs messages using PHP's error_log() function.
*
* @author Jon Parise <jon@php.net>
* @since Log 1.7.0
* @package Log
*
* @example error_log.php Using the error_log handler.
*/
class Log_error_log extends Log
{
/**
* The error_log() log type.
* @var integer
* @access private
*/
var $_type = PEAR_LOG_TYPE_SYSTEM;
/**
* The type-specific destination value.
* @var string
* @access private
*/
var $_destination = '';
/**
* Additional headers to pass to the mail() function when the
* PEAR_LOG_TYPE_MAIL type is used.
* @var string
* @access private
*/
var $_extra_headers = '';
/**
* Constructs a new Log_error_log object.
*
* @param string $name Ignored.
* @param string $ident The identity string.
* @param array $conf The configuration array.
* @param int $level Log messages up to and including this level.
* @access public
*/
function Log_error_log($name, $ident = '', $conf = array(),
$level = PEAR_LOG_DEBUG)
{
$this->_id = md5(microtime());
$this->_type = $name;
$this->_ident = $ident;
$this->_mask = Log::UPTO($level);
if (!empty($conf['destination'])) {
$this->_destination = $conf['destination'];
}
if (!empty($conf['extra_headers'])) {
$this->_extra_headers = $conf['extra_headers'];
}
}
/**
* Logs $message using PHP's error_log() function. The message is also
* passed along to any Log_observer instances that are observing this Log.
*
* @param mixed $message String or object containing the message to log.
* @param string $priority The priority of the message. Valid
* values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT,
* PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING,
* PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG.
* @return boolean True on success or false on failure.
* @access public
*/
function log($message, $priority = null)
{
/* If a priority hasn't been specified, use the default value. */
if ($priority === null) {
$priority = $this->_priority;
}
/* Abort early if the priority is above the maximum logging level. */
if (!$this->_isMasked($priority)) {
return false;
}
/* Extract the string representation of the message. */
$message = $this->_extractMessage($message);
$success = error_log($this->_ident . ': ' . $message, $this->_type,
$this->_destination, $this->_extra_headers);
$this->_announce(array('priority' => $priority, 'message' => $message));
return $success;
}
}
?>

286
inc/Log/file.php Executable file
View File

@ -0,0 +1,286 @@
<?php
/**
* $Header: /repository/pear/Log/Log/file.php,v 1.37 2004/01/19 08:02:40 jon Exp $
*
* @version $Revision: 1.37 $
* @package Log
*/
/**
* The Log_file class is a concrete implementation of the Log abstract
* class that logs messages to a text file.
*
* @author Jon Parise <jon@php.net>
* @author Roman Neuhauser <neuhauser@bellavista.cz>
* @since Log 1.0
* @package Log
*
* @example file.php Using the file handler.
*/
class Log_file extends Log
{
/**
* String containing the name of the log file.
* @var string
* @access private
*/
var $_filename = 'php.log';
/**
* Handle to the log file.
* @var resource
* @access private
*/
var $_fp = false;
/**
* Should new log entries be append to an existing log file, or should the
* a new log file overwrite an existing one?
* @var boolean
* @access private
*/
var $_append = true;
/**
* Integer (in octal) containing the log file's permissions mode.
* @var integer
* @access private
*/
var $_mode = 0644;
/**
* String containing the format of a log line.
* @var string
* @access private
*/
var $_lineFormat = '%1$s %2$s [%3$s] %4$s';
/**
* String containing the timestamp format. It will be passed directly to
* strftime(). Note that the timestamp string will generated using the
* current locale.
* @var string
* @access private
*/
var $_timeFormat = '%b %d %H:%M:%S';
/**
* Hash that maps canonical format keys to position arguments for the
* "line format" string.
* @var array
* @access private
*/
var $_formatMap = array('%{timestamp}' => '%1$s',
'%{ident}' => '%2$s',
'%{priority}' => '%3$s',
'%{message}' => '%4$s',
'%\{' => '%%{');
/**
* String containing the end-on-line character sequence.
* @var string
* @access private
*/
var $_eol = "\n";
/**
* Constructs a new Log_file object.
*
* @param string $name Ignored.
* @param string $ident The identity string.
* @param array $conf The configuration array.
* @param int $level Log messages up to and including this level.
* @access public
*/
function Log_file($name, $ident = '', $conf = array(),
$level = PEAR_LOG_DEBUG)
{
$this->_id = md5(microtime());
$this->_filename = $name;
$this->_ident = $ident;
$this->_mask = Log::UPTO($level);
if (isset($conf['append'])) {
$this->_append = $conf['append'];
}
if (!empty($conf['mode'])) {
$this->_mode = $conf['mode'];
}
if (!empty($conf['lineFormat'])) {
$this->_lineFormat = str_replace(array_keys($this->_formatMap),
array_values($this->_formatMap),
$conf['lineFormat']);
}
if (!empty($conf['timeFormat'])) {
$this->_timeFormat = $conf['timeFormat'];
}
if (!empty($conf['eol'])) {
$this->_eol = $conf['eol'];
} else {
$this->_eol = (strstr(PHP_OS, 'WIN')) ? "\r\n" : "\n";
}
register_shutdown_function(array(&$this, '_Log_file'));
}
/**
* Destructor
*/
function _Log_file()
{
if ($this->_opened) {
$this->close();
}
}
/**
* Creates the given directory path. If the parent directories don't
* already exist, they will be created, too.
*
* @param string $path The full directory path to create.
* @param integer $mode The permissions mode with which the
* directories will be created.
*
* @return True if the full path is successfully created or already
* exists.
*
* @access private
*/
function _mkpath($path, $mode = 0700)
{
static $depth = 0;
/* Guard against potentially infinite recursion. */
if ($depth++ > 25) {
trigger_error("_mkpath(): Maximum recursion depth (25) exceeded",
E_USER_WARNING);
return false;
}
/* We're only interested in the directory component of the path. */
$path = dirname($path);
/* If the directory already exists, return success immediately. */
if (is_dir($path)) {
$depth = 0;
return true;
}
/*
* In order to understand recursion, you must first understand
* recursion ...
*/
if ($this->_mkpath($path, $mode) === false) {
return false;
}
return @mkdir($path, $mode);
}
/**
* Opens the log file for output. If the specified log file does not
* already exist, it will be created. By default, new log entries are
* appended to the end of the log file.
*
* This is implicitly called by log(), if necessary.
*
* @access public
*/
function open()
{
if (!$this->_opened) {
/* If the log file's directory doesn't exist, create it. */
if (!is_dir(dirname($this->_filename))) {
$this->_mkpath($this->_filename);
}
/* Obtain a handle to the log file. */
$this->_fp = fopen($this->_filename, ($this->_append) ? 'a' : 'w');
$this->_opened = ($this->_fp !== false);
/* Attempt to set the log file's mode. */
@chmod($this->_filename, $this->_mode);
}
return $this->_opened;
}
/**
* Closes the log file if it is open.
*
* @access public
*/
function close()
{
/* If the log file is open, close it. */
if ($this->_opened && fclose($this->_fp)) {
$this->_opened = false;
}
return ($this->_opened === false);
}
/**
* Flushes all pending data to the file handle.
*
* @access public
* @since Log 1.8.2
*/
function flush()
{
return fflush($this->_fp);
}
/**
* Logs $message to the output window. The message is also passed along
* to any Log_observer instances that are observing this Log.
*
* @param mixed $message String or object containing the message to log.
* @param string $priority The priority of the message. Valid
* values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT,
* PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING,
* PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG.
* @return boolean True on success or false on failure.
* @access public
*/
function log($message, $priority = null)
{
/* If a priority hasn't been specified, use the default value. */
if ($priority === null) {
$priority = $this->_priority;
}
/* Abort early if the priority is above the maximum logging level. */
if (!$this->_isMasked($priority)) {
return false;
}
/* If the log file isn't already open, open it now. */
if (!$this->_opened && !$this->open()) {
return false;
}
/* Extract the string representation of the message. */
$message = $this->_extractMessage($message);
/* Build the string containing the complete log line. */
$line = sprintf($this->_lineFormat, strftime($this->_timeFormat),
$this->_ident, $this->priorityToString($priority),
$message) . $this->_eol;
/* Write the log line to the log file. */
$success = (fwrite($this->_fp, $line) !== false);
/* Notify observers about this log message. */
$this->_announce(array('priority' => $priority, 'message' => $message));
return $success;
}
}
?>

222
inc/Log/mail.php Executable file
View File

@ -0,0 +1,222 @@
<?php
/**
* $Header: /repository/pear/Log/Log/mail.php,v 1.21 2004/01/19 08:02:40 jon Exp $
*
* @version $Revision: 1.21 $
* @package Log
*/
/**
* The Log_mail class is a concrete implementation of the Log:: abstract class
* which sends log messages to a mailbox.
* The mail is actually sent when you close() the logger, or when the destructor
* is called (when the script is terminated).
*
* PLEASE NOTE that you must create a Log_mail object using =&, like this :
* $logger =& Log::factory("mail", "recipient@example.com", ...)
*
* This is a PEAR requirement for destructors to work properly.
* See http://pear.php.net/manual/en/class.pear.php
*
* @author Ronnie Garcia <ronnie@mk2.net>
* @author Jon Parise <jon@php.net>
* @since Log 1.3
* @package Log
*
* @example mail.php Using the mail handler.
*/
class Log_mail extends Log
{
/**
* String holding the recipient's email address.
* @var string
* @access private
*/
var $_recipient = '';
/**
* String holding the sender's email address.
* @var string
* @access private
*/
var $_from = '';
/**
* String holding the email's subject.
* @var string
* @access private
*/
var $_subject = '[Log_mail] Log message';
/**
* String holding an optional preamble for the log messages.
* @var string
* @access private
*/
var $_preamble = '';
/**
* String holding the mail message body.
* @var string
* @access private
*/
var $_message = '';
/**
* Constructs a new Log_mail object.
*
* Here is how you can customize the mail driver with the conf[] hash :
* $conf['from'] : the mail's "From" header line,
* $conf['subject'] : the mail's "Subject" line.
*
* @param string $name The filename of the logfile.
* @param string $ident The identity string.
* @param array $conf The configuration array.
* @param int $level Log messages up to and including this level.
* @access public
*/
function Log_mail($name, $ident = '', $conf = array(),
$level = PEAR_LOG_DEBUG)
{
$this->_id = md5(microtime());
$this->_recipient = $name;
$this->_ident = $ident;
$this->_mask = Log::UPTO($level);
if (!empty($conf['from'])) {
$this->_from = $conf['from'];
} else {
$this->_from = ini_get('sendmail_from');
}
if (!empty($conf['subject'])) {
$this->_subject = $conf['subject'];
}
if (!empty($conf['preamble'])) {
$this->_preamble = $conf['preamble'];
}
/* register the destructor */
register_shutdown_function(array(&$this, '_Log_mail'));
}
/**
* Destructor. Calls close().
*
* @access private
*/
function _Log_mail()
{
$this->close();
}
/**
* Starts a new mail message.
* This is implicitly called by log(), if necessary.
*
* @access public
*/
function open()
{
if (!$this->_opened) {
if (!empty($this->_preamble)) {
$this->_message = $this->_preamble . "\n\n";
}
$this->_opened = true;
}
return $this->_opened;
}
/**
* Closes the message, if it is open, and sends the mail.
* This is implicitly called by the destructor, if necessary.
*
* @access public
*/
function close()
{
if ($this->_opened) {
if (!empty($this->_message)) {
$headers = "From: $this->_from\n";
$headers .= "User-Agent: Log_mail";
if (mail($this->_recipient, $this->_subject, $this->_message,
$headers) == false) {
error_log("Log_mail: Failure executing mail()", 0);
return false;
}
/* Clear the message string now that the email has been sent. */
$this->_message = '';
}
$this->_opened = false;
}
return ($this->_opened === false);
}
/**
* Flushes the log output by forcing the email message to be sent now.
* Events that are logged after flush() is called will be appended to a
* new email message.
*
* @access public
* @since Log 1.8.2
*/
function flush()
{
/*
* It's sufficient to simply call close() to flush the output.
* The next call to log() will cause the handler to be reopened.
*/
return $this->close();
}
/**
* Writes $message to the currently open mail message.
* Calls open(), if necessary.
*
* @param mixed $message String or object containing the message to log.
* @param string $priority The priority of the message. Valid
* values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT,
* PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING,
* PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG.
* @return boolean True on success or false on failure.
* @access public
*/
function log($message, $priority = null)
{
/* If a priority hasn't been specified, use the default value. */
if ($priority === null) {
$priority = $this->_priority;
}
/* Abort early if the priority is above the maximum logging level. */
if (!$this->_isMasked($priority)) {
return false;
}
/* If the message isn't open and can't be opened, return failure. */
if (!$this->_opened && !$this->open()) {
return false;
}
/* Extract the string representation of the message. */
$message = $this->_extractMessage($message);
$entry = sprintf("%s %s [%s] %s\n", strftime('%b %d %H:%M:%S'),
$this->_ident, Log::priorityToString($priority),
$message);
$this->_message .= $entry;
$this->_announce(array('priority' => $priority, 'message' => $message));
return true;
}
}
?>

171
inc/Log/mcal.php Executable file
View File

@ -0,0 +1,171 @@
<?php
/**
* $Header: /repository/pear/Log/Log/mcal.php,v 1.17 2004/01/19 08:02:40 jon Exp $
* $Horde: horde/lib/Log/mcal.php,v 1.2 2000/06/28 21:36:13 jon Exp $
*
* @version $Revision: 1.17 $
* @package Log
*/
/**
* The Log_mcal class is a concrete implementation of the Log::
* abstract class which sends messages to a local or remote calendar
* store accessed through MCAL.
*
* @author Chuck Hagenbuch <chuck@horde.org>
* @since Horde 1.3
* @since Log 1.0
* @package Log
*/
class Log_mcal extends Log {
/**
* holding the calendar specification to connect to.
* @var string
* @access private
*/
var $_calendar = '{localhost/mstore}';
/**
* holding the username to use.
* @var string
* @access private
*/
var $_username = '';
/**
* holding the password to use.
* @var string
* @access private
*/
var $_password = '';
/**
* holding the options to pass to the calendar stream.
* @var integer
* @access private
*/
var $_options = 0;
/**
* ResourceID of the MCAL stream.
* @var string
* @access private
*/
var $_stream = '';
/**
* Integer holding the log facility to use.
* @var string
* @access private
*/
var $_name = LOG_SYSLOG;
/**
* Constructs a new Log_mcal object.
*
* @param string $name The category to use for our events.
* @param string $ident The identity string.
* @param array $conf The configuration array.
* @param int $level Log messages up to and including this level.
* @access public
*/
function Log_mcal($name, $ident = '', $conf = array(),
$level = PEAR_LOG_DEBUG)
{
$this->_id = md5(microtime());
$this->_name = $name;
$this->_ident = $ident;
$this->_mask = Log::UPTO($level);
$this->_calendar = $conf['calendar'];
$this->_username = $conf['username'];
$this->_password = $conf['password'];
$this->_options = $conf['options'];
}
/**
* Opens a calendar stream, if it has not already been
* opened. This is implicitly called by log(), if necessary.
* @access public
*/
function open()
{
if (!$this->_opened) {
$this->_stream = mcal_open($this->_calendar, $this->_username,
$this->_password, $this->_options);
$this->_opened = true;
}
return $this->_opened;
}
/**
* Closes the calendar stream, if it is open.
* @access public
*/
function close()
{
if ($this->_opened) {
mcal_close($this->_stream);
$this->_opened = false;
}
return ($this->_opened === false);
}
/**
* Logs $message and associated information to the currently open
* calendar stream. Calls open() if necessary. Also passes the
* message along to any Log_observer instances that are observing
* this Log.
*
* @param mixed $message String or object containing the message to log.
* @param string $priority The priority of the message. Valid
* values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT,
* PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING,
* PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG.
* @return boolean True on success or false on failure.
* @access public
*/
function log($message, $priority = null)
{
/* If a priority hasn't been specified, use the default value. */
if ($priority === null) {
$priority = $this->_priority;
}
/* Abort early if the priority is above the maximum logging level. */
if (!$this->_isMasked($priority)) {
return false;
}
/* If the connection isn't open and can't be opened, return failure. */
if (!$this->_opened && !$this->open()) {
return false;
}
/* Extract the string representation of the message. */
$message = $this->_extractMessage($message);
$date_str = date('Y:n:j:G:i:s');
$dates = explode(':', $date_str);
mcal_event_init($this->_stream);
mcal_event_set_title($this->_stream, $this->_ident);
mcal_event_set_category($this->_stream, $this->_name);
mcal_event_set_description($this->_stream, $message);
mcal_event_add_attribute($this->_stream, 'priority', $priority);
mcal_event_set_start($this->_stream, $dates[0], $dates[1], $dates[2],
$dates[3], $dates[4], $dates[5]);
mcal_event_set_end($this->_stream, $dates[0], $dates[1], $dates[2],
$dates[3], $dates[4], $dates[5]);
mcal_append_event($this->_stream);
$this->_announce(array('priority' => $priority, 'message' => $message));
return true;
}
}
?>

68
inc/Log/null.php Executable file
View File

@ -0,0 +1,68 @@
<?php
/**
* $Header: /repository/pear/Log/Log/null.php,v 1.3 2004/01/19 08:02:40 jon Exp $
*
* @version $Revision: 1.3 $
* @package Log
*/
/**
* The Log_null class is a concrete implementation of the Log:: abstract
* class. It simply consumes log events.
*
* @author Jon Parise <jon@php.net>
* @since Log 1.8.2
* @package Log
*
* @example null.php Using the null handler.
*/
class Log_null extends Log
{
/**
* Constructs a new Log_null object.
*
* @param string $name Ignored.
* @param string $ident The identity string.
* @param array $conf The configuration array.
* @param int $level Log messages up to and including this level.
* @access public
*/
function Log_null($name, $ident = '', $conf = array(),
$level = PEAR_LOG_DEBUG)
{
$this->_id = md5(microtime());
$this->_ident = $ident;
$this->_mask = Log::UPTO($level);
}
/**
* Simply consumes the log event. The message will still be passed
* along to any Log_observer instances that are observing this Log.
*
* @param mixed $message String or object containing the message to log.
* @param string $priority The priority of the message. Valid
* values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT,
* PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING,
* PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG.
* @return boolean True on success or false on failure.
* @access public
*/
function log($message, $priority = null)
{
/* If a priority hasn't been specified, use the default value. */
if ($priority === null) {
$priority = $this->_priority;
}
/* Abort early if the priority is above the maximum logging level. */
if (!$this->_isMasked($priority)) {
return false;
}
$this->_announce(array('priority' => $priority, 'message' => $message));
return true;
}
}
?>

126
inc/Log/observer.php Executable file
View File

@ -0,0 +1,126 @@
<?php
/**
* $Header: /repository/pear/Log/Log/observer.php,v 1.12 2004/01/11 20:49:49 jon Exp $
* $Horde: horde/lib/Log/observer.php,v 1.5 2000/06/28 21:36:13 jon Exp $
*
* @version $Revision: 1.12 $
* @package Log
*/
/**
* The Log_observer:: class implements the Observer end of a Subject-Observer
* pattern for watching log activity and taking actions on exceptional events.
*
* @author Chuck Hagenbuch <chuck@horde.org>
* @since Horde 1.3
* @since Log 1.0
* @package Log
*
* @example observer_mail.php An example Log_observer implementation.
*/
class Log_observer
{
/**
* Instance-specific unique identification number.
*
* @var integer
* @access private
*/
var $_id = 0;
/**
* The minimum priority level of message that we want to hear about.
* PEAR_LOG_EMERG is the highest priority, so we will only hear messages
* with an integer priority value less than or equal to ours. It defaults
* to PEAR_LOG_INFO, which listens to everything except PEAR_LOG_DEBUG.
*
* @var string
* @access private
*/
var $_priority = PEAR_LOG_INFO;
/**
* Creates a new basic Log_observer instance.
*
* @param integer $priority The highest priority at which to receive
* log event notifications.
*
* @access public
*/
function Log_observer($priority = PEAR_LOG_INFO)
{
$this->_id = md5(microtime());
$this->_priority = $priority;
}
/**
* Attempts to return a new concrete Log_observer instance of the requested
* type.
*
* @param string $type The type of concreate Log_observer subclass
* to return.
* @param integer $priority The highest priority at which to receive
* log event notifications.
* @param array $conf Optional associative array of additional
* configuration values.
*
* @return object The newly created concrete Log_observer
* instance, or an false on an error.
*/
function &factory($type, $priority = PEAR_LOG_INFO, $conf = array())
{
$type = strtolower($type);
$class = 'Log_observer_' . $type;
/* Support both the new-style and old-style file naming conventions. */
if (file_exists(dirname(__FILE__) . '/observer_' . $type . '.php')) {
$classfile = 'Log/observer_' . $type . '.php';
$newstyle = true;
} else {
$classfile = 'Log/' . $type . '.php';
$newstyle = false;
}
/* Issue a warning if the old-style conventions are being used. */
if (!$newstyle)
{
trigger_error('Using old-style Log_observer conventions',
E_USER_WARNING);
}
/*
* Attempt to include our version of the named class, but don't treat
* a failure as fatal. The caller may have already included their own
* version of the named class.
*/
@include_once $classfile;
/* If the class exists, return a new instance of it. */
if (class_exists($class)) {
/* Support both new-style and old-style construction. */
if ($newstyle) {
return new $class($priority, $conf);
} else {
return new $class($priority);
}
}
return false;
}
/**
* This is a stub method to make sure that Log_Observer classes do
* something when they are notified of a message. The default behavior
* is to just print the message, which is obviously not desireable in
* practically any situation - which is why you need to override this
* method. :)
*
* @param array $event A hash describing the log event.
*/
function notify($event)
{
print_r($event);
}
}
?>

225
inc/Log/sql.php Executable file
View File

@ -0,0 +1,225 @@
<?php
/**
* $Header: /repository/pear/Log/Log/sql.php,v 1.34 2004/08/19 06:35:57 jon Exp $
* $Horde: horde/lib/Log/sql.php,v 1.12 2000/08/16 20:27:34 chuck Exp $
*
* @version $Revision: 1.34 $
* @package Log
*/
/** PEAR's DB package */
require_once 'DB.php';
/**
* The Log_sql class is a concrete implementation of the Log::
* abstract class which sends messages to an SQL server. Each entry
* occupies a separate row in the database.
*
* This implementation uses PHP's PEAR database abstraction layer.
*
* CREATE TABLE log_table (
* id INT NOT NULL,
* logtime TIMESTAMP NOT NULL,
* ident CHAR(16) NOT NULL,
* priority INT NOT NULL,
* message VARCHAR(200),
* PRIMARY KEY (id)
* );
*
* @author Jon Parise <jon@php.net>
* @since Horde 1.3
* @since Log 1.0
* @package Log
*
* @example sql.php Using the SQL handler.
*/
class Log_sql extends Log {
/**
* Array containing the dsn information.
* @var string
* @access private
*/
var $_dsn = '';
/**
* Object holding the database handle.
* @var object
* @access private
*/
var $_db = null;
/**
* Flag indicating that we're using an existing database connection.
* @var boolean
* @access private
*/
var $_existingConnection = false;
/**
* String holding the database table to use.
* @var string
* @access private
*/
var $_table = 'log_table';
/**
* String holding the name of the ID sequence.
* @var string
* @access private
*/
var $_sequence = 'log_id';
/**
* Maximum length of the $ident string. This corresponds to the size of
* the 'ident' column in the SQL table.
* @var integer
* @access private
*/
var $_identLimit = 16;
/**
* Constructs a new sql logging object.
*
* @param string $name The target SQL table.
* @param string $ident The identification field.
* @param array $conf The connection configuration array.
* @param int $level Log messages up to and including this level.
* @access public
*/
function Log_sql($name, $ident = '', $conf = array(),
$level = PEAR_LOG_DEBUG)
{
$this->_id = md5(microtime());
$this->_table = $name;
$this->_mask = Log::UPTO($level);
/* If a specific sequence name was provided, use it. */
if (!empty($conf['sequence'])) {
$this->_sequence = $conf['sequence'];
}
/* If a specific sequence name was provided, use it. */
if (isset($conf['identLimit'])) {
$this->_identLimit = $conf['identLimit'];
}
/* Now that the ident limit is confirmed, set the ident string. */
$this->setIdent($ident);
/* If an existing database connection was provided, use it. */
if (isset($conf['db'])) {
$this->_db = &$conf['db'];
$this->_existingConnection = true;
$this->_opened = true;
} else {
$this->_dsn = $conf['dsn'];
}
}
/**
* Opens a connection to the database, if it has not already
* been opened. This is implicitly called by log(), if necessary.
*
* @return boolean True on success, false on failure.
* @access public
*/
function open()
{
if (!$this->_opened) {
$this->_db = &DB::connect($this->_dsn, true);
if (DB::isError($this->_db)) {
return false;
}
$this->_opened = true;
}
return $this->_opened;
}
/**
* Closes the connection to the database if it is still open and we were
* the ones that opened it. It is the caller's responsible to close an
* existing connection that was passed to us via $conf['db'].
*
* @return boolean True on success, false on failure.
* @access public
*/
function close()
{
if ($this->_opened && !$this->_existingConnection) {
$this->_opened = false;
return $this->_db->disconnect();
}
return ($this->_opened === false);
}
/**
* Sets this Log instance's identification string. Note that this
* SQL-specific implementation will limit the length of the $ident string
* to sixteen (16) characters.
*
* @param string $ident The new identification string.
*
* @access public
* @since Log 1.8.5
*/
function setIdent($ident)
{
$this->_ident = substr($ident, 0, $this->_identLimit);
}
/**
* Inserts $message to the currently open database. Calls open(),
* if necessary. Also passes the message along to any Log_observer
* instances that are observing this Log.
*
* @param mixed $message String or object containing the message to log.
* @param string $priority The priority of the message. Valid
* values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT,
* PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING,
* PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG.
* @return boolean True on success or false on failure.
* @access public
*/
function log($message, $priority = null)
{
/* If a priority hasn't been specified, use the default value. */
if ($priority === null) {
$priority = $this->_priority;
}
/* Abort early if the priority is above the maximum logging level. */
if (!$this->_isMasked($priority)) {
return false;
}
/* If the connection isn't open and can't be opened, return failure. */
if (!$this->_opened && !$this->open()) {
return false;
}
/* Extract the string representation of the message. */
$message = $this->_extractMessage($message);
/* Build the SQL query for this log entry insertion. */
$id = $this->_db->nextId($this->_sequence);
$q = sprintf('insert into %s (id, logtime, ident, priority, message)' .
'values(%d, CURRENT_TIMESTAMP, %s, %d, %s)',
$this->_table, $id, $this->_db->quote($this->_ident),
$priority, $this->_db->quote($message));
$result = $this->_db->query($q);
if (DB::isError($result)) {
return false;
}
$this->_announce(array('priority' => $priority, 'message' => $message));
return true;
}
}
?>

238
inc/Log/sqlite.php Executable file
View File

@ -0,0 +1,238 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP version 4.0 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Bertrand Mansion <bmansion@mamasam.com> |
// +----------------------------------------------------------------------+
//
// $Id: sqlite.php,v 1.3 2004/01/19 08:02:40 jon Exp $
/**
* The Log_sqlite class is a concrete implementation of the Log::
* abstract class which sends messages to an Sqlite database.
* Each entry occupies a separate row in the database.
*
* This implementation uses PHP native Sqlite functions.
*
* CREATE TABLE log_table (
* id INTEGER PRIMARY KEY NOT NULL,
* logtime NOT NULL,
* ident CHAR(16) NOT NULL,
* priority INT NOT NULL,
* message
* );
*
* @author Bertrand Mansion <bmansion@mamasam.com>
* @author Jon Parise <jon@php.net>
* @since Log 1.8.3
* @package Log
*
* @example sqlite.php Using the Sqlite handler.
*/
class Log_sqlite extends Log
{
/**
* Array containing the connection defaults
* @var array
* @access private
*/
var $_options = array('mode' => 0666,
'persistent' => false);
/**
* Object holding the database handle.
* @var object
* @access private
*/
var $_db = null;
/**
* Flag indicating that we're using an existing database connection.
* @var boolean
* @access private
*/
var $_existingConnection = false;
/**
* String holding the database table to use.
* @var string
* @access private
*/
var $_table = 'log_table';
/**
* Constructs a new sql logging object.
*
* @param string $name The target SQL table.
* @param string $ident The identification field.
* @param mixed $conf Can be an array of configuration options used
* to open a new database connection
* or an already opened sqlite connection.
* @param int $level Log messages up to and including this level.
* @access public
*/
function Log_sqlite($name, $ident = '', &$conf, $level = PEAR_LOG_DEBUG)
{
$this->_id = md5(microtime());
$this->_table = $name;
$this->_ident = $ident;
$this->_mask = Log::UPTO($level);
if (is_array($conf)) {
foreach ($conf as $k => $opt) {
$this->_options[$k] = $opt;
}
} else {
// If an existing database connection was provided, use it.
$this->_db =& $conf;
$this->_existingConnection = true;
}
}
/**
* Opens a connection to the database, if it has not already
* been opened. This is implicitly called by log(), if necessary.
*
* @return boolean True on success, false on failure.
* @access public
*/
function open()
{
if (is_resource($this->_db)) {
$this->_opened = true;
return $this->_createTable();
} else {
/* Set the connection function based on the 'persistent' option. */
if (empty($this->_options['persistent'])) {
$connectFunction = 'sqlite_open';
} else {
$connectFunction = 'sqlite_popen';
}
/* Attempt to connect to the database. */
if ($this->_db = $connectFunction($this->_options['filename'],
(int)$this->_options['mode'],
$error)) {
$this->_opened = true;
return $this->_createTable();
}
}
return $this->_opened;
}
/**
* Closes the connection to the database if it is still open and we were
* the ones that opened it. It is the caller's responsible to close an
* existing connection that was passed to us via $conf['db'].
*
* @return boolean True on success, false on failure.
* @access public
*/
function close()
{
/* We never close existing connections. */
if ($this->_existingConnection) {
return false;
}
if ($this->_opened) {
$this->_opened = false;
sqlite_close($this->_db);
}
return ($this->_opened === false);
}
/**
* Inserts $message to the currently open database. Calls open(),
* if necessary. Also passes the message along to any Log_observer
* instances that are observing this Log.
*
* @param mixed $message String or object containing the message to log.
* @param string $priority The priority of the message. Valid
* values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT,
* PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING,
* PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG.
* @return boolean True on success or false on failure.
* @access public
*/
function log($message, $priority = null)
{
/* If a priority hasn't been specified, use the default value. */
if ($priority === null) {
$priority = $this->_priority;
}
/* Abort early if the priority is above the maximum logging level. */
if (!$this->_isMasked($priority)) {
return false;
}
/* If the connection isn't open and can't be opened, return failure. */
if (!$this->_opened && !$this->open()) {
return false;
}
// Extract the string representation of the message.
$message = $this->_extractMessage($message);
// Build the SQL query for this log entry insertion.
$q = sprintf('INSERT INTO [%s] (logtime, ident, priority, message) ' .
"VALUES ('%s', '%s', %d, '%s')",
$this->_table,
strftime('%Y-%m-%d %H:%M:%S', time()),
sqlite_escape_string($this->_ident),
$priority,
sqlite_escape_string($message));
if (!($res = @sqlite_unbuffered_query($this->_db, $q))) {
return false;
}
$this->_announce(array('priority' => $priority, 'message' => $message));
return true;
}
/**
* Checks whether the log table exists and creates it if necessary.
*
* @return boolean True on success or false on failure.
* @access private
*/
function _createTable()
{
$q = "SELECT name FROM sqlite_master WHERE name='" . $this->_table .
"' AND type='table'";
$res = sqlite_query($this->_db, $q);
if (sqlite_num_rows($res) == 0) {
$q = 'CREATE TABLE [' . $this->_table . '] (' .
'id INTEGER PRIMARY KEY NOT NULL, ' .
'logtime NOT NULL, ' .
'ident CHAR(16) NOT NULL, ' .
'priority INT NOT NULL, ' .
'message)';
if (!($res = sqlite_unbuffered_query($this->_db, $q))) {
return false;
}
}
return true;
}
}
?>

160
inc/Log/syslog.php Executable file
View File

@ -0,0 +1,160 @@
<?php
/**
* $Header: /repository/pear/Log/Log/syslog.php,v 1.22 2004/01/19 08:02:40 jon Exp $
* $Horde: horde/lib/Log/syslog.php,v 1.6 2000/06/28 21:36:13 jon Exp $
*
* @version $Revision: 1.22 $
* @package Log
*/
/**
* The Log_syslog class is a concrete implementation of the Log::
* abstract class which sends messages to syslog on UNIX-like machines
* (PHP emulates this with the Event Log on Windows machines).
*
* @author Chuck Hagenbuch <chuck@horde.org>
* @since Horde 1.3
* @since Log 1.0
* @package Log
*
* @example syslog.php Using the syslog handler.
*/
class Log_syslog extends Log
{
/**
* Integer holding the log facility to use.
* @var string
* @access private
*/
var $_name = LOG_SYSLOG;
/**
* Constructs a new syslog object.
*
* @param string $name The syslog facility.
* @param string $ident The identity string.
* @param array $conf The configuration array.
* @param int $level Log messages up to and including this level.
* @access public
*/
function Log_syslog($name, $ident = '', $conf = array(),
$level = PEAR_LOG_DEBUG)
{
/* Ensure we have a valid integer value for $name. */
if (empty($name) || !is_int($name)) {
$name = LOG_SYSLOG;
}
$this->_id = md5(microtime());
$this->_name = $name;
$this->_ident = $ident;
$this->_mask = Log::UPTO($level);
}
/**
* Opens a connection to the system logger, if it has not already
* been opened. This is implicitly called by log(), if necessary.
* @access public
*/
function open()
{
if (!$this->_opened) {
openlog($this->_ident, LOG_PID, $this->_name);
$this->_opened = true;
}
return $this->_opened;
}
/**
* Closes the connection to the system logger, if it is open.
* @access public
*/
function close()
{
if ($this->_opened) {
closelog();
$this->_opened = false;
}
return ($this->_opened === false);
}
/**
* Sends $message to the currently open syslog connection. Calls
* open() if necessary. Also passes the message along to any Log_observer
* instances that are observing this Log.
*
* @param mixed $message String or object containing the message to log.
* @param int $priority (optional) The priority of the message. Valid
* values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT,
* PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING,
* PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG.
* @return boolean True on success or false on failure.
* @access public
*/
function log($message, $priority = null)
{
/* If a priority hasn't been specified, use the default value. */
if ($priority === null) {
$priority = $this->_priority;
}
/* Abort early if the priority is above the maximum logging level. */
if (!$this->_isMasked($priority)) {
return false;
}
/* If the connection isn't open and can't be opened, return failure. */
if (!$this->_opened && !$this->open()) {
return false;
}
/* Extract the string representation of the message. */
$message = $this->_extractMessage($message);
if (!syslog($this->_toSyslog($priority), $message)) {
return false;
}
$this->_announce(array('priority' => $priority, 'message' => $message));
return true;
}
/**
* Converts a PEAR_LOG_* constant into a syslog LOG_* constant.
*
* This function exists because, under Windows, not all of the LOG_*
* constants have unique values. Instead, the PEAR_LOG_* were introduced
* for global use, with the conversion to the LOG_* constants kept local to
* to the syslog driver.
*
* @param int $priority PEAR_LOG_* value to convert to LOG_* value.
*
* @return The LOG_* representation of $priority.
*
* @access private
*/
function _toSyslog($priority)
{
static $priorities = array(
PEAR_LOG_EMERG => LOG_EMERG,
PEAR_LOG_ALERT => LOG_ALERT,
PEAR_LOG_CRIT => LOG_CRIT,
PEAR_LOG_ERR => LOG_ERR,
PEAR_LOG_WARNING => LOG_WARNING,
PEAR_LOG_NOTICE => LOG_NOTICE,
PEAR_LOG_INFO => LOG_INFO,
PEAR_LOG_DEBUG => LOG_DEBUG
);
/* If we're passed an unknown priority, default to LOG_INFO. */
if (!is_int($priority) || !in_array($priority, $priorities)) {
return LOG_INFO;
}
return $priorities[$priority];
}
}
?>

256
inc/Log/win.php Executable file
View File

@ -0,0 +1,256 @@
<?php
/**
* $Header: /repository/pear/Log/Log/win.php,v 1.16 2004/09/08 23:35:53 jon Exp $
*
* @version $Revision: 1.16 $
* @package Log
*/
/**
* The Log_win class is a concrete implementation of the Log abstract
* class that logs messages to a separate browser window.
*
* The concept for this log handler is based on part by Craig Davis' article
* entitled "JavaScript Power PHP Debugging:
*
* http://www.zend.com/zend/tut/tutorial-DebugLib.php
*
* @author Jon Parise <jon@php.net>
* @since Log 1.7.0
* @package Log
*
* @example win.php Using the window handler.
*/
class Log_win extends Log
{
/**
* The name of the output window.
* @var string
* @access private
*/
var $_name = 'LogWindow';
/**
* The title of the output window.
* @var string
* @access private
*/
var $_title = 'Log Output Window';
/**
* Mapping of log priorities to colors.
* @var array
* @access private
*/
var $_colors = array(
PEAR_LOG_EMERG => 'red',
PEAR_LOG_ALERT => 'orange',
PEAR_LOG_CRIT => 'yellow',
PEAR_LOG_ERR => 'green',
PEAR_LOG_WARNING => 'blue',
PEAR_LOG_NOTICE => 'indigo',
PEAR_LOG_INFO => 'violet',
PEAR_LOG_DEBUG => 'black'
);
/**
* String buffer that holds line that are pending output.
* @var array
* @access private
*/
var $_buffer = array();
/**
* Constructs a new Log_win object.
*
* @param string $name Ignored.
* @param string $ident The identity string.
* @param array $conf The configuration array.
* @param int $level Log messages up to and including this level.
* @access public
*/
function Log_win($name, $ident = '', $conf = array(),
$level = PEAR_LOG_DEBUG)
{
$this->_id = md5(microtime());
$this->_name = $name;
$this->_ident = $ident;
$this->_mask = Log::UPTO($level);
if (isset($conf['title'])) {
$this->_title = $conf['title'];
}
if (isset($conf['colors']) && is_array($conf['colors'])) {
$this->_colors = $conf['colors'];
}
register_shutdown_function(array(&$this, '_Log_win'));
}
/**
* Destructor
*/
function _Log_win()
{
if ($this->_opened || (count($this->_buffer) > 0)) {
$this->close();
}
}
/**
* The first time open() is called, it will open a new browser window and
* prepare it for output.
*
* This is implicitly called by log(), if necessary.
*
* @access public
*/
function open()
{
if (!$this->_opened) {
$win = $this->_name;
if (!empty($this->_ident)) {
$identHeader = "$win.document.writeln('<th>Ident</th>')";
} else {
$identHeader = '';
}
echo <<< END_OF_SCRIPT
<script language="JavaScript">
$win = window.open('', '{$this->_name}', 'toolbar=no,scrollbars,width=600,height=400');
$win.document.writeln('<html>');
$win.document.writeln('<head>');
$win.document.writeln('<title>{$this->_title}</title>');
$win.document.writeln('<style type="text/css">');
$win.document.writeln('body { font-family: monospace; font-size: 8pt; }');
$win.document.writeln('td,th { font-size: 8pt; }');
$win.document.writeln('td,th { border-bottom: #999999 solid 1px; }');
$win.document.writeln('td,th { border-right: #999999 solid 1px; }');
$win.document.writeln('</style>');
$win.document.writeln('</head>');
$win.document.writeln('<body>');
$win.document.writeln('<table border="0" cellpadding="2" cellspacing="0">');
$win.document.writeln('<tr><th>Time</th>');
$identHeader
$win.document.writeln('<th>Priority</th><th width="100%">Message</th></tr>');
</script>
END_OF_SCRIPT;
$this->_opened = true;
}
return $this->_opened;
}
/**
* Closes the output stream if it is open. If there are still pending
* lines in the output buffer, the output window will be opened so that
* the buffer can be drained.
*
* @access public
*/
function close()
{
/*
* If there are still lines waiting to be written, open the output
* window so that we can drain the buffer.
*/
if (!$this->_opened && (count($this->_buffer) > 0)) {
$this->open();
}
if ($this->_opened) {
$this->_writeln('</table>');
$this->_writeln('</body></html>');
$this->_opened = false;
}
return ($this->_opened === false);
}
/**
* Writes a single line of text to the output window.
*
* @param string $line The line of text to write.
*
* @access private
*/
function _writeln($line)
{
/* Add this line to our output buffer. */
$this->_buffer[] = $line;
/* Buffer the output until this page's headers have been sent. */
if (!headers_sent()) {
return;
}
/* If we haven't already opened the output window, do so now. */
if (!$this->_opened && !$this->open()) {
return false;
}
/* Drain the buffer to the output window. */
$win = $this->_name;
foreach ($this->_buffer as $line) {
echo "<script language='JavaScript'>\n";
echo "$win.document.writeln('" . addslashes($line) . "');\n";
echo "self.focus();\n";
echo "</script>\n";
}
/* Now that the buffer has been drained, clear it. */
$this->_buffer = array();
}
/**
* Logs $message to the output window. The message is also passed along
* to any Log_observer instances that are observing this Log.
*
* @param mixed $message String or object containing the message to log.
* @param string $priority The priority of the message. Valid
* values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT,
* PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING,
* PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG.
* @return boolean True on success or false on failure.
* @access public
*/
function log($message, $priority = null)
{
/* If a priority hasn't been specified, use the default value. */
if ($priority === null) {
$priority = $this->_priority;
}
/* Abort early if the priority is above the maximum logging level. */
if (!$this->_isMasked($priority)) {
return false;
}
/* Extract the string representation of the message. */
$message = $this->_extractMessage($message);
list($usec, $sec) = explode(' ', microtime());
/* Build the output line that contains the log entry row. */
$line = '<tr align="left" valign="top">';
$line .= sprintf('<td>%s.%s</td>',
strftime('%T', $sec), substr($usec, 2, 2));
if (!empty($this->_ident)) {
$line .= '<td>' . $this->_ident . '</td>';
}
$line .= '<td>' . ucfirst($this->priorityToString($priority)) . '</td>';
$line .= sprintf('<td style="color: %s">%s</td>',
$this->_colors[$priority],
preg_replace('/\r\n|\n|\r/', '<br />', $message));
$line .= '</tr>';
$this->_writeln($line);
$this->_announce(array('priority' => $priority, 'message' => $message));
return true;
}
}
?>

212
inc/Mail.php Executable file
View File

@ -0,0 +1,212 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Chuck Hagenbuch <chuck@horde.org> |
// +----------------------------------------------------------------------+
//
// $Id: Mail.php,v 1.11 2005/06/26 23:37:18 jon Exp $
require_once 'PEAR.php';
/**
* PEAR's Mail:: interface. Defines the interface for implementing
* mailers under the PEAR hierarchy, and provides supporting functions
* useful in multiple mailer backends.
*
* @access public
* @version $Revision: 1.11 $
* @package Mail
*/
class Mail
{
/**
* Line terminator used for separating header lines.
* @var string
*/
var $sep = "\r\n";
/**
* Provides an interface for generating Mail:: objects of various
* types
*
* @param string $driver The kind of Mail:: object to instantiate.
* @param array $params The parameters to pass to the Mail:: object.
* @return object Mail a instance of the driver class or if fails a PEAR Error
* @access public
*/
function &factory($driver, $params = array())
{
$driver = strtolower($driver);
@include_once 'Mail/' . $driver . '.php';
$class = 'Mail_' . $driver;
if (class_exists($class)) {
$mailer = &new $class($params);
} else {
$mailer = PEAR::raiseError('Unable to find class for driver ' . $driver);
}
return $mailer;
}
/**
* Implements Mail::send() function using php's built-in mail()
* command.
*
* @param mixed $recipients Either a comma-seperated list of recipients
* (RFC822 compliant), or an array of recipients,
* each RFC822 valid. This may contain recipients not
* specified in the headers, for Bcc:, resending
* messages, etc.
*
* @param array $headers The array of headers to send with the mail, in an
* associative array, where the array key is the
* header name (ie, 'Subject'), and the array value
* is the header value (ie, 'test'). The header
* produced from those values would be 'Subject:
* test'.
*
* @param string $body The full text of the message body, including any
* Mime parts, etc.
*
* @return mixed Returns true on success, or a PEAR_Error
* containing a descriptive error message on
* failure.
* @access public
* @deprecated use Mail_mail::send instead
*/
function send($recipients, $headers, $body)
{
// if we're passed an array of recipients, implode it.
if (is_array($recipients)) {
$recipients = implode(', ', $recipients);
}
// get the Subject out of the headers array so that we can
// pass it as a seperate argument to mail().
$subject = '';
if (isset($headers['Subject'])) {
$subject = $headers['Subject'];
unset($headers['Subject']);
}
// flatten the headers out.
list(,$text_headers) = Mail::prepareHeaders($headers);
return mail($recipients, $subject, $body, $text_headers);
}
/**
* Take an array of mail headers and return a string containing
* text usable in sending a message.
*
* @param array $headers The array of headers to prepare, in an associative
* array, where the array key is the header name (ie,
* 'Subject'), and the array value is the header
* value (ie, 'test'). The header produced from those
* values would be 'Subject: test'.
*
* @return mixed Returns false if it encounters a bad address,
* otherwise returns an array containing two
* elements: Any From: address found in the headers,
* and the plain text version of the headers.
* @access private
*/
function prepareHeaders($headers)
{
$lines = array();
$from = null;
foreach ($headers as $key => $value) {
if (strcasecmp($key, 'From') === 0) {
include_once 'Mail/RFC822.php';
$parser = &new Mail_RFC822();
$addresses = $parser->parseAddressList($value, 'localhost', false);
if (PEAR::isError($addresses)) {
return $addresses;
}
$from = $addresses[0]->mailbox . '@' . $addresses[0]->host;
// Reject envelope From: addresses with spaces.
if (strstr($from, ' ')) {
return false;
}
$lines[] = $key . ': ' . $value;
} elseif (strcasecmp($key, 'Received') === 0) {
$received = array();
if (is_array($value)) {
foreach ($value as $line) {
$received[] = $key . ': ' . $line;
}
}
else {
$received[] = $key . ': ' . $value;
}
// Put Received: headers at the top. Spam detectors often
// flag messages with Received: headers after the Subject:
// as spam.
$lines = array_merge($received, $lines);
} else {
// If $value is an array (i.e., a list of addresses), convert
// it to a comma-delimited string of its elements (addresses).
if (is_array($value)) {
$value = implode(', ', $value);
}
$lines[] = $key . ': ' . $value;
}
}
return array($from, join($this->sep, $lines) . $this->sep);
}
/**
* Take a set of recipients and parse them, returning an array of
* bare addresses (forward paths) that can be passed to sendmail
* or an smtp server with the rcpt to: command.
*
* @param mixed Either a comma-seperated list of recipients
* (RFC822 compliant), or an array of recipients,
* each RFC822 valid.
*
* @return array An array of forward paths (bare addresses).
* @access private
*/
function parseRecipients($recipients)
{
include_once 'Mail/RFC822.php';
// if we're passed an array, assume addresses are valid and
// implode them before parsing.
if (is_array($recipients)) {
$recipients = implode(', ', $recipients);
}
// Parse recipients, leaving out all personal info. This is
// for smtp recipients, etc. All relevant personal information
// should already be in the headers.
$addresses = Mail_RFC822::parseAddressList($recipients, 'localhost', false);
$recipients = array();
if (is_array($addresses)) {
foreach ($addresses as $ob) {
$recipients[] = $ob->mailbox . '@' . $ob->host;
}
}
return $recipients;
}
}

923
inc/Mail/RFC822.php Executable file
View File

@ -0,0 +1,923 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2001-2002, Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Authors: Richard Heyes <richard@phpguru.org> |
// | Chuck Hagenbuch <chuck@horde.org> |
// +-----------------------------------------------------------------------+
/**
* RFC 822 Email address list validation Utility
*
* What is it?
*
* This class will take an address string, and parse it into it's consituent
* parts, be that either addresses, groups, or combinations. Nested groups
* are not supported. The structure it returns is pretty straight forward,
* and is similar to that provided by the imap_rfc822_parse_adrlist(). Use
* print_r() to view the structure.
*
* How do I use it?
*
* $address_string = 'My Group: "Richard" <richard@localhost> (A comment), ted@example.com (Ted Bloggs), Barney;';
* $structure = Mail_RFC822::parseAddressList($address_string, 'example.com', true)
* print_r($structure);
*
* @author Richard Heyes <richard@phpguru.org>
* @author Chuck Hagenbuch <chuck@horde.org>
* @version $Revision: 1.21 $
* @license BSD
* @package Mail
*/
class Mail_RFC822 {
/**
* The address being parsed by the RFC822 object.
* @var string $address
*/
var $address = '';
/**
* The default domain to use for unqualified addresses.
* @var string $default_domain
*/
var $default_domain = 'localhost';
/**
* Should we return a nested array showing groups, or flatten everything?
* @var boolean $nestGroups
*/
var $nestGroups = true;
/**
* Whether or not to validate atoms for non-ascii characters.
* @var boolean $validate
*/
var $validate = true;
/**
* The array of raw addresses built up as we parse.
* @var array $addresses
*/
var $addresses = array();
/**
* The final array of parsed address information that we build up.
* @var array $structure
*/
var $structure = array();
/**
* The current error message, if any.
* @var string $error
*/
var $error = null;
/**
* An internal counter/pointer.
* @var integer $index
*/
var $index = null;
/**
* The number of groups that have been found in the address list.
* @var integer $num_groups
* @access public
*/
var $num_groups = 0;
/**
* A variable so that we can tell whether or not we're inside a
* Mail_RFC822 object.
* @var boolean $mailRFC822
*/
var $mailRFC822 = true;
/**
* A limit after which processing stops
* @var int $limit
*/
var $limit = null;
/**
* Sets up the object. The address must either be set here or when
* calling parseAddressList(). One or the other.
*
* @access public
* @param string $address The address(es) to validate.
* @param string $default_domain Default domain/host etc. If not supplied, will be set to localhost.
* @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing.
* @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
*
* @return object Mail_RFC822 A new Mail_RFC822 object.
*/
function Mail_RFC822($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
{
if (isset($address)) $this->address = $address;
if (isset($default_domain)) $this->default_domain = $default_domain;
if (isset($nest_groups)) $this->nestGroups = $nest_groups;
if (isset($validate)) $this->validate = $validate;
if (isset($limit)) $this->limit = $limit;
}
/**
* Starts the whole process. The address must either be set here
* or when creating the object. One or the other.
*
* @access public
* @param string $address The address(es) to validate.
* @param string $default_domain Default domain/host etc.
* @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing.
* @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
*
* @return array A structured array of addresses.
*/
function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
{
if (!isset($this) || !isset($this->mailRFC822)) {
$obj = new Mail_RFC822($address, $default_domain, $nest_groups, $validate, $limit);
return $obj->parseAddressList();
}
if (isset($address)) $this->address = $address;
if (isset($default_domain)) $this->default_domain = $default_domain;
if (isset($nest_groups)) $this->nestGroups = $nest_groups;
if (isset($validate)) $this->validate = $validate;
if (isset($limit)) $this->limit = $limit;
$this->structure = array();
$this->addresses = array();
$this->error = null;
$this->index = null;
// Unfold any long lines in $this->address.
$this->address = preg_replace('/\r?\n/', "\r\n", $this->address);
$this->address = preg_replace('/\r\n(\t| )+/', ' ', $this->address);
while ($this->address = $this->_splitAddresses($this->address));
if ($this->address === false || isset($this->error)) {
require_once 'PEAR.php';
return PEAR::raiseError($this->error);
}
// Validate each address individually. If we encounter an invalid
// address, stop iterating and return an error immediately.
foreach ($this->addresses as $address) {
$valid = $this->_validateAddress($address);
if ($valid === false || isset($this->error)) {
require_once 'PEAR.php';
return PEAR::raiseError($this->error);
}
if (!$this->nestGroups) {
$this->structure = array_merge($this->structure, $valid);
} else {
$this->structure[] = $valid;
}
}
return $this->structure;
}
/**
* Splits an address into separate addresses.
*
* @access private
* @param string $address The addresses to split.
* @return boolean Success or failure.
*/
function _splitAddresses($address)
{
if (!empty($this->limit) && count($this->addresses) == $this->limit) {
return '';
}
if ($this->_isGroup($address) && !isset($this->error)) {
$split_char = ';';
$is_group = true;
} elseif (!isset($this->error)) {
$split_char = ',';
$is_group = false;
} elseif (isset($this->error)) {
return false;
}
// Split the string based on the above ten or so lines.
$parts = explode($split_char, $address);
$string = $this->_splitCheck($parts, $split_char);
// If a group...
if ($is_group) {
// If $string does not contain a colon outside of
// brackets/quotes etc then something's fubar.
// First check there's a colon at all:
if (strpos($string, ':') === false) {
$this->error = 'Invalid address: ' . $string;
return false;
}
// Now check it's outside of brackets/quotes:
if (!$this->_splitCheck(explode(':', $string), ':')) {
return false;
}
// We must have a group at this point, so increase the counter:
$this->num_groups++;
}
// $string now contains the first full address/group.
// Add to the addresses array.
$this->addresses[] = array(
'address' => trim($string),
'group' => $is_group
);
// Remove the now stored address from the initial line, the +1
// is to account for the explode character.
$address = trim(substr($address, strlen($string) + 1));
// If the next char is a comma and this was a group, then
// there are more addresses, otherwise, if there are any more
// chars, then there is another address.
if ($is_group && substr($address, 0, 1) == ','){
$address = trim(substr($address, 1));
return $address;
} elseif (strlen($address) > 0) {
return $address;
} else {
return '';
}
// If you got here then something's off
return false;
}
/**
* Checks for a group at the start of the string.
*
* @access private
* @param string $address The address to check.
* @return boolean Whether or not there is a group at the start of the string.
*/
function _isGroup($address)
{
// First comma not in quotes, angles or escaped:
$parts = explode(',', $address);
$string = $this->_splitCheck($parts, ',');
// Now we have the first address, we can reliably check for a
// group by searching for a colon that's not escaped or in
// quotes or angle brackets.
if (count($parts = explode(':', $string)) > 1) {
$string2 = $this->_splitCheck($parts, ':');
return ($string2 !== $string);
} else {
return false;
}
}
/**
* A common function that will check an exploded string.
*
* @access private
* @param array $parts The exloded string.
* @param string $char The char that was exploded on.
* @return mixed False if the string contains unclosed quotes/brackets, or the string on success.
*/
function _splitCheck($parts, $char)
{
$string = $parts[0];
for ($i = 0; $i < count($parts); $i++) {
if ($this->_hasUnclosedQuotes($string)
|| $this->_hasUnclosedBrackets($string, '<>')
|| $this->_hasUnclosedBrackets($string, '[]')
|| $this->_hasUnclosedBrackets($string, '()')
|| substr($string, -1) == '\\') {
if (isset($parts[$i + 1])) {
$string = $string . $char . $parts[$i + 1];
} else {
$this->error = 'Invalid address spec. Unclosed bracket or quotes';
return false;
}
} else {
$this->index = $i;
break;
}
}
return $string;
}
/**
* Checks if a string has an unclosed quotes or not.
*
* @access private
* @param string $string The string to check.
* @return boolean True if there are unclosed quotes inside the string, false otherwise.
*/
function _hasUnclosedQuotes($string)
{
$string = explode('"', $string);
$string_cnt = count($string);
for ($i = 0; $i < (count($string) - 1); $i++)
if (substr($string[$i], -1) == '\\')
$string_cnt--;
return ($string_cnt % 2 === 0);
}
/**
* Checks if a string has an unclosed brackets or not. IMPORTANT:
* This function handles both angle brackets and square brackets;
*
* @access private
* @param string $string The string to check.
* @param string $chars The characters to check for.
* @return boolean True if there are unclosed brackets inside the string, false otherwise.
*/
function _hasUnclosedBrackets($string, $chars)
{
$num_angle_start = substr_count($string, $chars[0]);
$num_angle_end = substr_count($string, $chars[1]);
$this->_hasUnclosedBracketsSub($string, $num_angle_start, $chars[0]);
$this->_hasUnclosedBracketsSub($string, $num_angle_end, $chars[1]);
if ($num_angle_start < $num_angle_end) {
$this->error = 'Invalid address spec. Unmatched quote or bracket (' . $chars . ')';
return false;
} else {
return ($num_angle_start > $num_angle_end);
}
}
/**
* Sub function that is used only by hasUnclosedBrackets().
*
* @access private
* @param string $string The string to check.
* @param integer &$num The number of occurences.
* @param string $char The character to count.
* @return integer The number of occurences of $char in $string, adjusted for backslashes.
*/
function _hasUnclosedBracketsSub($string, &$num, $char)
{
$parts = explode($char, $string);
for ($i = 0; $i < count($parts); $i++){
if (substr($parts[$i], -1) == '\\' || $this->_hasUnclosedQuotes($parts[$i]))
$num--;
if (isset($parts[$i + 1]))
$parts[$i + 1] = $parts[$i] . $char . $parts[$i + 1];
}
return $num;
}
/**
* Function to begin checking the address.
*
* @access private
* @param string $address The address to validate.
* @return mixed False on failure, or a structured array of address information on success.
*/
function _validateAddress($address)
{
$is_group = false;
$addresses = array();
if ($address['group']) {
$is_group = true;
// Get the group part of the name
$parts = explode(':', $address['address']);
$groupname = $this->_splitCheck($parts, ':');
$structure = array();
// And validate the group part of the name.
if (!$this->_validatePhrase($groupname)){
$this->error = 'Group name did not validate.';
return false;
} else {
// Don't include groups if we are not nesting
// them. This avoids returning invalid addresses.
if ($this->nestGroups) {
$structure = new stdClass;
$structure->groupname = $groupname;
}
}
$address['address'] = ltrim(substr($address['address'], strlen($groupname . ':')));
}
// If a group then split on comma and put into an array.
// Otherwise, Just put the whole address in an array.
if ($is_group) {
while (strlen($address['address']) > 0) {
$parts = explode(',', $address['address']);
$addresses[] = $this->_splitCheck($parts, ',');
$address['address'] = trim(substr($address['address'], strlen(end($addresses) . ',')));
}
} else {
$addresses[] = $address['address'];
}
// Check that $addresses is set, if address like this:
// Groupname:;
// Then errors were appearing.
if (!count($addresses)){
$this->error = 'Empty group.';
return false;
}
// Trim the whitespace from all of the address strings.
array_map('trim', $addresses);
// Validate each mailbox.
// Format could be one of: name <geezer@domain.com>
// geezer@domain.com
// geezer
// ... or any other format valid by RFC 822.
for ($i = 0; $i < count($addresses); $i++) {
if (!$this->validateMailbox($addresses[$i])) {
if (empty($this->error)) {
$this->error = 'Validation failed for: ' . $addresses[$i];
}
return false;
}
}
// Nested format
if ($this->nestGroups) {
if ($is_group) {
$structure->addresses = $addresses;
} else {
$structure = $addresses[0];
}
// Flat format
} else {
if ($is_group) {
$structure = array_merge($structure, $addresses);
} else {
$structure = $addresses;
}
}
return $structure;
}
/**
* Function to validate a phrase.
*
* @access private
* @param string $phrase The phrase to check.
* @return boolean Success or failure.
*/
function _validatePhrase($phrase)
{
// Splits on one or more Tab or space.
$parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY);
$phrase_parts = array();
while (count($parts) > 0){
$phrase_parts[] = $this->_splitCheck($parts, ' ');
for ($i = 0; $i < $this->index + 1; $i++)
array_shift($parts);
}
foreach ($phrase_parts as $part) {
// If quoted string:
if (substr($part, 0, 1) == '"') {
if (!$this->_validateQuotedString($part)) {
return false;
}
continue;
}
// Otherwise it's an atom:
if (!$this->_validateAtom($part)) return false;
}
return true;
}
/**
* Function to validate an atom which from rfc822 is:
* atom = 1*<any CHAR except specials, SPACE and CTLs>
*
* If validation ($this->validate) has been turned off, then
* validateAtom() doesn't actually check anything. This is so that you
* can split a list of addresses up before encoding personal names
* (umlauts, etc.), for example.
*
* @access private
* @param string $atom The string to check.
* @return boolean Success or failure.
*/
function _validateAtom($atom)
{
if (!$this->validate) {
// Validation has been turned off; assume the atom is okay.
return true;
}
// Check for any char from ASCII 0 - ASCII 127
if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) {
return false;
}
// Check for specials:
if (preg_match('/[][()<>@,;\\:". ]/', $atom)) {
return false;
}
// Check for control characters (ASCII 0-31):
if (preg_match('/[\\x00-\\x1F]+/', $atom)) {
return false;
}
return true;
}
/**
* Function to validate quoted string, which is:
* quoted-string = <"> *(qtext/quoted-pair) <">
*
* @access private
* @param string $qstring The string to check
* @return boolean Success or failure.
*/
function _validateQuotedString($qstring)
{
// Leading and trailing "
$qstring = substr($qstring, 1, -1);
// Perform check, removing quoted characters first.
return !preg_match('/[\x0D\\\\"]/', preg_replace('/\\\\./', '', $qstring));
}
/**
* Function to validate a mailbox, which is:
* mailbox = addr-spec ; simple address
* / phrase route-addr ; name and route-addr
*
* @access public
* @param string &$mailbox The string to check.
* @return boolean Success or failure.
*/
function validateMailbox(&$mailbox)
{
// A couple of defaults.
$phrase = '';
$comment = '';
$comments = array();
// Catch any RFC822 comments and store them separately.
$_mailbox = $mailbox;
while (strlen(trim($_mailbox)) > 0) {
$parts = explode('(', $_mailbox);
$before_comment = $this->_splitCheck($parts, '(');
if ($before_comment != $_mailbox) {
// First char should be a (.
$comment = substr(str_replace($before_comment, '', $_mailbox), 1);
$parts = explode(')', $comment);
$comment = $this->_splitCheck($parts, ')');
$comments[] = $comment;
// +1 is for the trailing )
$_mailbox = substr($_mailbox, strpos($_mailbox, $comment)+strlen($comment)+1);
} else {
break;
}
}
foreach ($comments as $comment) {
$mailbox = str_replace("($comment)", '', $mailbox);
}
$mailbox = trim($mailbox);
// Check for name + route-addr
if (substr($mailbox, -1) == '>' && substr($mailbox, 0, 1) != '<') {
$parts = explode('<', $mailbox);
$name = $this->_splitCheck($parts, '<');
$phrase = trim($name);
$route_addr = trim(substr($mailbox, strlen($name.'<'), -1));
if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false) {
return false;
}
// Only got addr-spec
} else {
// First snip angle brackets if present.
if (substr($mailbox, 0, 1) == '<' && substr($mailbox, -1) == '>') {
$addr_spec = substr($mailbox, 1, -1);
} else {
$addr_spec = $mailbox;
}
if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
return false;
}
}
// Construct the object that will be returned.
$mbox = new stdClass();
// Add the phrase (even if empty) and comments
$mbox->personal = $phrase;
$mbox->comment = isset($comments) ? $comments : array();
if (isset($route_addr)) {
$mbox->mailbox = $route_addr['local_part'];
$mbox->host = $route_addr['domain'];
$route_addr['adl'] !== '' ? $mbox->adl = $route_addr['adl'] : '';
} else {
$mbox->mailbox = $addr_spec['local_part'];
$mbox->host = $addr_spec['domain'];
}
$mailbox = $mbox;
return true;
}
/**
* This function validates a route-addr which is:
* route-addr = "<" [route] addr-spec ">"
*
* Angle brackets have already been removed at the point of
* getting to this function.
*
* @access private
* @param string $route_addr The string to check.
* @return mixed False on failure, or an array containing validated address/route information on success.
*/
function _validateRouteAddr($route_addr)
{
// Check for colon.
if (strpos($route_addr, ':') !== false) {
$parts = explode(':', $route_addr);
$route = $this->_splitCheck($parts, ':');
} else {
$route = $route_addr;
}
// If $route is same as $route_addr then the colon was in
// quotes or brackets or, of course, non existent.
if ($route === $route_addr){
unset($route);
$addr_spec = $route_addr;
if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
return false;
}
} else {
// Validate route part.
if (($route = $this->_validateRoute($route)) === false) {
return false;
}
$addr_spec = substr($route_addr, strlen($route . ':'));
// Validate addr-spec part.
if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
return false;
}
}
if (isset($route)) {
$return['adl'] = $route;
} else {
$return['adl'] = '';
}
$return = array_merge($return, $addr_spec);
return $return;
}
/**
* Function to validate a route, which is:
* route = 1#("@" domain) ":"
*
* @access private
* @param string $route The string to check.
* @return mixed False on failure, or the validated $route on success.
*/
function _validateRoute($route)
{
// Split on comma.
$domains = explode(',', trim($route));
foreach ($domains as $domain) {
$domain = str_replace('@', '', trim($domain));
if (!$this->_validateDomain($domain)) return false;
}
return $route;
}
/**
* Function to validate a domain, though this is not quite what
* you expect of a strict internet domain.
*
* domain = sub-domain *("." sub-domain)
*
* @access private
* @param string $domain The string to check.
* @return mixed False on failure, or the validated domain on success.
*/
function _validateDomain($domain)
{
// Note the different use of $subdomains and $sub_domains
$subdomains = explode('.', $domain);
while (count($subdomains) > 0) {
$sub_domains[] = $this->_splitCheck($subdomains, '.');
for ($i = 0; $i < $this->index + 1; $i++)
array_shift($subdomains);
}
foreach ($sub_domains as $sub_domain) {
if (!$this->_validateSubdomain(trim($sub_domain)))
return false;
}
// Managed to get here, so return input.
return $domain;
}
/**
* Function to validate a subdomain:
* subdomain = domain-ref / domain-literal
*
* @access private
* @param string $subdomain The string to check.
* @return boolean Success or failure.
*/
function _validateSubdomain($subdomain)
{
if (preg_match('|^\[(.*)]$|', $subdomain, $arr)){
if (!$this->_validateDliteral($arr[1])) return false;
} else {
if (!$this->_validateAtom($subdomain)) return false;
}
// Got here, so return successful.
return true;
}
/**
* Function to validate a domain literal:
* domain-literal = "[" *(dtext / quoted-pair) "]"
*
* @access private
* @param string $dliteral The string to check.
* @return boolean Success or failure.
*/
function _validateDliteral($dliteral)
{
return !preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) && $matches[1] != '\\';
}
/**
* Function to validate an addr-spec.
*
* addr-spec = local-part "@" domain
*
* @access private
* @param string $addr_spec The string to check.
* @return mixed False on failure, or the validated addr-spec on success.
*/
function _validateAddrSpec($addr_spec)
{
$addr_spec = trim($addr_spec);
// Split on @ sign if there is one.
if (strpos($addr_spec, '@') !== false) {
$parts = explode('@', $addr_spec);
$local_part = $this->_splitCheck($parts, '@');
$domain = substr($addr_spec, strlen($local_part . '@'));
// No @ sign so assume the default domain.
} else {
$local_part = $addr_spec;
$domain = $this->default_domain;
}
if (($local_part = $this->_validateLocalPart($local_part)) === false) return false;
if (($domain = $this->_validateDomain($domain)) === false) return false;
// Got here so return successful.
return array('local_part' => $local_part, 'domain' => $domain);
}
/**
* Function to validate the local part of an address:
* local-part = word *("." word)
*
* @access private
* @param string $local_part
* @return mixed False on failure, or the validated local part on success.
*/
function _validateLocalPart($local_part)
{
$parts = explode('.', $local_part);
$words = array();
// Split the local_part into words.
while (count($parts) > 0){
$words[] = $this->_splitCheck($parts, '.');
for ($i = 0; $i < $this->index + 1; $i++) {
array_shift($parts);
}
}
// Validate each word.
foreach ($words as $word) {
// If this word contains an unquoted space, it is invalid. (6.2.4)
if (strpos($word, ' ') && $word[0] !== '"')
{
return false;
}
if ($this->_validatePhrase(trim($word)) === false) return false;
}
// Managed to get here, so return the input.
return $local_part;
}
/**
* Returns an approximate count of how many addresses are in the
* given string. This is APPROXIMATE as it only splits based on a
* comma which has no preceding backslash. Could be useful as
* large amounts of addresses will end up producing *large*
* structures when used with parseAddressList().
*
* @param string $data Addresses to count
* @return int Approximate count
*/
function approximateCount($data)
{
return count(preg_split('/(?<!\\\\),/', $data));
}
/**
* This is a email validating function separate to the rest of the
* class. It simply validates whether an email is of the common
* internet form: <user>@<domain>. This can be sufficient for most
* people. Optional stricter mode can be utilised which restricts
* mailbox characters allowed to alphanumeric, full stop, hyphen
* and underscore.
*
* @param string $data Address to check
* @param boolean $strict Optional stricter mode
* @return mixed False if it fails, an indexed array
* username/domain if it matches
*/
function isValidInetAddress($data, $strict = false)
{
$regex = $strict ? '/^([.0-9a-z_-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,4})$/i' : '/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,4})$/i';
if (preg_match($regex, trim($data), $matches)) {
return array($matches[1], $matches[2]);
} else {
return false;
}
}
}

130
inc/Mail/mail.php Executable file
View File

@ -0,0 +1,130 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Chuck Hagenbuch <chuck@horde.org> |
// +----------------------------------------------------------------------+
//
// $Id: mail.php,v 1.13 2004/09/09 02:08:55 jon Exp $
/**
* internal PHP-mail() implementation of the PEAR Mail:: interface.
* @package Mail
* @version $Revision: 1.13 $
*/
class Mail_mail extends Mail {
/**
* Any arguments to pass to the mail() function.
* @var string
*/
var $_params = '';
/**
* Constructor.
*
* Instantiates a new Mail_mail:: object based on the parameters
* passed in.
*
* @param array $params Extra arguments for the mail() function.
*/
function Mail_mail($params = null)
{
/* The other mail implementations accept parameters as arrays.
* In the interest of being consistent, explode an array into
* a string of parameter arguments. */
if (is_array($params)) {
$this->_params = join(' ', $params);
} else {
$this->_params = $params;
}
/* Because the mail() function may pass headers as command
* line arguments, we can't guarantee the use of the standard
* "\r\n" separator. Instead, we use the system's native line
* separator. */
$this->sep = (strstr(PHP_OS, 'WIN')) ? "\r\n" : "\n";
}
/**
* Implements Mail_mail::send() function using php's built-in mail()
* command.
*
* @param mixed $recipients Either a comma-seperated list of recipients
* (RFC822 compliant), or an array of recipients,
* each RFC822 valid. This may contain recipients not
* specified in the headers, for Bcc:, resending
* messages, etc.
*
* @param array $headers The array of headers to send with the mail, in an
* associative array, where the array key is the
* header name (ie, 'Subject'), and the array value
* is the header value (ie, 'test'). The header
* produced from those values would be 'Subject:
* test'.
*
* @param string $body The full text of the message body, including any
* Mime parts, etc.
*
* @return mixed Returns true on success, or a PEAR_Error
* containing a descriptive error message on
* failure.
*
* @access public
*/
function send($recipients, $headers, $body)
{
// If we're passed an array of recipients, implode it.
if (is_array($recipients)) {
$recipients = implode(', ', $recipients);
}
// Get the Subject out of the headers array so that we can
// pass it as a seperate argument to mail().
$subject = '';
if (isset($headers['Subject'])) {
$subject = $headers['Subject'];
unset($headers['Subject']);
}
// Flatten the headers out.
$headerElements = $this->prepareHeaders($headers);
if (PEAR::isError($headerElements)) {
return $headerElements;
}
list(, $text_headers) = $headerElements;
/*
* We only use mail()'s optional fifth parameter if the additional
* parameters have been provided and we're not running in safe mode.
*/
if (empty($this->_params) || ini_get('safe_mode')) {
$result = mail($recipients, $subject, $body, $text_headers);
} else {
$result = mail($recipients, $subject, $body, $text_headers,
$this->_params);
}
/*
* If the mail() function returned failure, we need to create a
* PEAR_Error object and return it instead of the boolean result.
*/
if ($result === false) {
$result = PEAR::raiseError('mail() returned failure');
}
return $result;
}
}

713
inc/Mail/mime.php Executable file
View File

@ -0,0 +1,713 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | Copyright (c) 2003-2005 The PHP Group |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@phpguru.org> |
// | Tomas V.V.Cox <cox@idecnet.com> (port to PEAR) |
// +-----------------------------------------------------------------------+
//
// $Id: mime.php,v 1.39 2005/06/13 21:24:16 cipri Exp $
require_once('PEAR.php');
require_once('Mail/mimePart.php');
/**
* Mime mail composer class. Can handle: text and html bodies, embedded html
* images and attachments.
* Documentation and examples of this class are avaible here:
* http://pear.php.net/manual/
*
* @notes This class is based on HTML Mime Mail class from
* Richard Heyes <richard@phpguru.org> which was based also
* in the mime_mail.class by Tobias Ratschiller <tobias@dnet.it> and
* Sascha Schumann <sascha@schumann.cx>
*
* @author Richard Heyes <richard.heyes@heyes-computing.net>
* @author Tomas V.V.Cox <cox@idecnet.com>
* @package Mail
* @access public
*/
class Mail_mime
{
/**
* Contains the plain text part of the email
* @var string
*/
var $_txtbody;
/**
* Contains the html part of the email
* @var string
*/
var $_htmlbody;
/**
* contains the mime encoded text
* @var string
*/
var $_mime;
/**
* contains the multipart content
* @var string
*/
var $_multipart;
/**
* list of the attached images
* @var array
*/
var $_html_images = array();
/**
* list of the attachements
* @var array
*/
var $_parts = array();
/**
* Build parameters
* @var array
*/
var $_build_params = array();
/**
* Headers for the mail
* @var array
*/
var $_headers = array();
/**
* End Of Line sequence (for serialize)
* @var string
*/
var $_eol;
/**
* Constructor function
*
* @access public
*/
function Mail_mime($crlf = "\r\n")
{
$this->_setEOL($crlf);
$this->_build_params = array(
'text_encoding' => '7bit',
'html_encoding' => 'quoted-printable',
'7bit_wrap' => 998,
'html_charset' => 'ISO-8859-1',
'text_charset' => 'ISO-8859-1',
'head_charset' => 'ISO-8859-1'
);
}
/**
* Wakeup (unserialize) - re-sets EOL constant
*
* @access private
*/
function __wakeup()
{
$this->_setEOL($this->_eol);
}
/**
* Accessor function to set the body text. Body text is used if
* it's not an html mail being sent or else is used to fill the
* text/plain part that emails clients who don't support
* html should show.
*
* @param string $data Either a string or
* the file name with the contents
* @param bool $isfile If true the first param should be treated
* as a file name, else as a string (default)
* @param bool $append If true the text or file is appended to
* the existing body, else the old body is
* overwritten
* @return mixed true on success or PEAR_Error object
* @access public
*/
function setTXTBody($data, $isfile = false, $append = false)
{
if (!$isfile) {
if (!$append) {
$this->_txtbody = $data;
} else {
$this->_txtbody .= $data;
}
} else {
$cont = $this->_file2str($data);
if (PEAR::isError($cont)) {
return $cont;
}
if (!$append) {
$this->_txtbody = $cont;
} else {
$this->_txtbody .= $cont;
}
}
return true;
}
/**
* Adds a html part to the mail
*
* @param string $data Either a string or the file name with the
* contents
* @param bool $isfile If true the first param should be treated
* as a file name, else as a string (default)
* @return mixed true on success or PEAR_Error object
* @access public
*/
function setHTMLBody($data, $isfile = false)
{
if (!$isfile) {
$this->_htmlbody = $data;
} else {
$cont = $this->_file2str($data);
if (PEAR::isError($cont)) {
return $cont;
}
$this->_htmlbody = $cont;
}
return true;
}
/**
* Adds an image to the list of embedded images.
*
* @param string $file The image file name OR image data itself
* @param string $c_type The content type
* @param string $name The filename of the image.
* Only use if $file is the image data
* @param bool $isfilename Whether $file is a filename or not
* Defaults to true
* @return mixed true on success or PEAR_Error object
* @access public
*/
function addHTMLImage($file, $c_type='application/octet-stream',
$name = '', $isfilename = true)
{
$filedata = ($isfilename === true) ? $this->_file2str($file)
: $file;
if ($isfilename === true) {
$filename = ($name == '' ? basename($file) : basename($name));
} else {
$filename = basename($name);
}
if (PEAR::isError($filedata)) {
return $filedata;
}
$this->_html_images[] = array(
'body' => $filedata,
'name' => $filename,
'c_type' => $c_type,
'cid' => md5(uniqid(time()))
);
return true;
}
/**
* Adds a file to the list of attachments.
*
* @param string $file The file name of the file to attach
* OR the file data itself
* @param string $c_type The content type
* @param string $name The filename of the attachment
* Only use if $file is the file data
* @param bool $isFilename Whether $file is a filename or not
* Defaults to true
* @return mixed true on success or PEAR_Error object
* @access public
*/
function addAttachment($file, $c_type = 'application/octet-stream',
$name = '', $isfilename = true,
$encoding = 'base64')
{
$filedata = ($isfilename === true) ? $this->_file2str($file)
: $file;
if ($isfilename === true) {
// Force the name the user supplied, otherwise use $file
$filename = (!empty($name)) ? $name : $file;
} else {
$filename = $name;
}
if (empty($filename)) {
return PEAR::raiseError(
'The supplied filename for the attachment can\'t be empty'
);
}
$filename = basename($filename);
if (PEAR::isError($filedata)) {
return $filedata;
}
$this->_parts[] = array(
'body' => $filedata,
'name' => $filename,
'c_type' => $c_type,
'encoding' => $encoding
);
return true;
}
/**
* Get the contents of the given file name as string
*
* @param string $file_name path of file to process
* @return string contents of $file_name
* @access private
*/
function &_file2str($file_name)
{
if (!is_readable($file_name)) {
return PEAR::raiseError('File is not readable ' . $file_name);
}
if (!$fd = fopen($file_name, 'rb')) {
return PEAR::raiseError('Could not open ' . $file_name);
}
$filesize = filesize($file_name);
if ($filesize == 0){
$cont = "";
}else{
$cont = fread($fd, $filesize);
}
fclose($fd);
return $cont;
}
/**
* Adds a text subpart to the mimePart object and
* returns it during the build process.
*
* @param mixed The object to add the part to, or
* null if a new object is to be created.
* @param string The text to add.
* @return object The text mimePart object
* @access private
*/
function &_addTextPart(&$obj, $text)
{
$params['content_type'] = 'text/plain';
$params['encoding'] = $this->_build_params['text_encoding'];
$params['charset'] = $this->_build_params['text_charset'];
if (is_object($obj)) {
return $obj->addSubpart($text, $params);
} else {
return new Mail_mimePart($text, $params);
}
}
/**
* Adds a html subpart to the mimePart object and
* returns it during the build process.
*
* @param mixed The object to add the part to, or
* null if a new object is to be created.
* @return object The html mimePart object
* @access private
*/
function &_addHtmlPart(&$obj)
{
$params['content_type'] = 'text/html';
$params['encoding'] = $this->_build_params['html_encoding'];
$params['charset'] = $this->_build_params['html_charset'];
if (is_object($obj)) {
return $obj->addSubpart($this->_htmlbody, $params);
} else {
return new Mail_mimePart($this->_htmlbody, $params);
}
}
/**
* Creates a new mimePart object, using multipart/mixed as
* the initial content-type and returns it during the
* build process.
*
* @return object The multipart/mixed mimePart object
* @access private
*/
function &_addMixedPart()
{
$params['content_type'] = 'multipart/mixed';
return new Mail_mimePart('', $params);
}
/**
* Adds a multipart/alternative part to a mimePart
* object (or creates one), and returns it during
* the build process.
*
* @param mixed The object to add the part to, or
* null if a new object is to be created.
* @return object The multipart/mixed mimePart object
* @access private
*/
function &_addAlternativePart(&$obj)
{
$params['content_type'] = 'multipart/alternative';
if (is_object($obj)) {
return $obj->addSubpart('', $params);
} else {
return new Mail_mimePart('', $params);
}
}
/**
* Adds a multipart/related part to a mimePart
* object (or creates one), and returns it during
* the build process.
*
* @param mixed The object to add the part to, or
* null if a new object is to be created
* @return object The multipart/mixed mimePart object
* @access private
*/
function &_addRelatedPart(&$obj)
{
$params['content_type'] = 'multipart/related';
if (is_object($obj)) {
return $obj->addSubpart('', $params);
} else {
return new Mail_mimePart('', $params);
}
}
/**
* Adds an html image subpart to a mimePart object
* and returns it during the build process.
*
* @param object The mimePart to add the image to
* @param array The image information
* @return object The image mimePart object
* @access private
*/
function &_addHtmlImagePart(&$obj, $value)
{
$params['content_type'] = $value['c_type'];
$params['encoding'] = 'base64';
$params['disposition'] = 'inline';
$params['dfilename'] = $value['name'];
$params['cid'] = $value['cid'];
$obj->addSubpart($value['body'], $params);
}
/**
* Adds an attachment subpart to a mimePart object
* and returns it during the build process.
*
* @param object The mimePart to add the image to
* @param array The attachment information
* @return object The image mimePart object
* @access private
*/
function &_addAttachmentPart(&$obj, $value)
{
$params['content_type'] = $value['c_type'];
$params['encoding'] = $value['encoding'];
$params['disposition'] = 'attachment';
$params['dfilename'] = $value['name'];
$obj->addSubpart($value['body'], $params);
}
/**
* Builds the multipart message from the list ($this->_parts) and
* returns the mime content.
*
* @param array Build parameters that change the way the email
* is built. Should be associative. Can contain:
* text_encoding - What encoding to use for plain text
* Default is 7bit
* html_encoding - What encoding to use for html
* Default is quoted-printable
* 7bit_wrap - Number of characters before text is
* wrapped in 7bit encoding
* Default is 998
* html_charset - The character set to use for html.
* Default is iso-8859-1
* text_charset - The character set to use for text.
* Default is iso-8859-1
* head_charset - The character set to use for headers.
* Default is iso-8859-1
* @return string The mime content
* @access public
*/
function &get($build_params = null)
{
if (isset($build_params)) {
while (list($key, $value) = each($build_params)) {
$this->_build_params[$key] = $value;
}
}
if (!empty($this->_html_images) AND isset($this->_htmlbody)) {
foreach ($this->_html_images as $value) {
$regex = '#(\s)((?i)src|background|href(?-i))\s*=\s*(["\']?)' . preg_quote($value['name'], '#') .
'\3#';
$rep = '\1\2=\3cid:' . $value['cid'] .'\3';
$this->_htmlbody = preg_replace($regex, $rep,
$this->_htmlbody
);
}
}
$null = null;
$attachments = !empty($this->_parts) ? true : false;
$html_images = !empty($this->_html_images) ? true : false;
$html = !empty($this->_htmlbody) ? true : false;
$text = (!$html AND !empty($this->_txtbody)) ? true : false;
switch (true) {
case $text AND !$attachments:
$message =& $this->_addTextPart($null, $this->_txtbody);
break;
case !$text AND !$html AND $attachments:
$message =& $this->_addMixedPart();
for ($i = 0; $i < count($this->_parts); $i++) {
$this->_addAttachmentPart($message, $this->_parts[$i]);
}
break;
case $text AND $attachments:
$message =& $this->_addMixedPart();
$this->_addTextPart($message, $this->_txtbody);
for ($i = 0; $i < count($this->_parts); $i++) {
$this->_addAttachmentPart($message, $this->_parts[$i]);
}
break;
case $html AND !$attachments AND !$html_images:
if (isset($this->_txtbody)) {
$message =& $this->_addAlternativePart($null);
$this->_addTextPart($message, $this->_txtbody);
$this->_addHtmlPart($message);
} else {
$message =& $this->_addHtmlPart($null);
}
break;
case $html AND !$attachments AND $html_images:
if (isset($this->_txtbody)) {
$message =& $this->_addAlternativePart($null);
$this->_addTextPart($message, $this->_txtbody);
$related =& $this->_addRelatedPart($message);
} else {
$message =& $this->_addRelatedPart($null);
$related =& $message;
}
$this->_addHtmlPart($related);
for ($i = 0; $i < count($this->_html_images); $i++) {
$this->_addHtmlImagePart($related, $this->_html_images[$i]);
}
break;
case $html AND $attachments AND !$html_images:
$message =& $this->_addMixedPart();
if (isset($this->_txtbody)) {
$alt =& $this->_addAlternativePart($message);
$this->_addTextPart($alt, $this->_txtbody);
$this->_addHtmlPart($alt);
} else {
$this->_addHtmlPart($message);
}
for ($i = 0; $i < count($this->_parts); $i++) {
$this->_addAttachmentPart($message, $this->_parts[$i]);
}
break;
case $html AND $attachments AND $html_images:
$message =& $this->_addMixedPart();
if (isset($this->_txtbody)) {
$alt =& $this->_addAlternativePart($message);
$this->_addTextPart($alt, $this->_txtbody);
$rel =& $this->_addRelatedPart($alt);
} else {
$rel =& $this->_addRelatedPart($message);
}
$this->_addHtmlPart($rel);
for ($i = 0; $i < count($this->_html_images); $i++) {
$this->_addHtmlImagePart($rel, $this->_html_images[$i]);
}
for ($i = 0; $i < count($this->_parts); $i++) {
$this->_addAttachmentPart($message, $this->_parts[$i]);
}
break;
}
if (isset($message)) {
$output = $message->encode();
$this->_headers = array_merge($this->_headers,
$output['headers']);
return $output['body'];
} else {
return false;
}
}
/**
* Returns an array with the headers needed to prepend to the email
* (MIME-Version and Content-Type). Format of argument is:
* $array['header-name'] = 'header-value';
*
* @param array $xtra_headers Assoc array with any extra headers.
* Optional.
* @return array Assoc array with the mime headers
* @access public
*/
function &headers($xtra_headers = null)
{
// Content-Type header should already be present,
// So just add mime version header
$headers['MIME-Version'] = '1.0';
if (isset($xtra_headers)) {
$headers = array_merge($headers, $xtra_headers);
}
$this->_headers = array_merge($headers, $this->_headers);
return $this->_encodeHeaders($this->_headers);
}
/**
* Get the text version of the headers
* (usefull if you want to use the PHP mail() function)
*
* @param array $xtra_headers Assoc array with any extra headers.
* Optional.
* @return string Plain text headers
* @access public
*/
function txtHeaders($xtra_headers = null)
{
$headers = $this->headers($xtra_headers);
$ret = '';
foreach ($headers as $key => $val) {
$ret .= "$key: $val" . MAIL_MIME_CRLF;
}
return $ret;
}
/**
* Sets the Subject header
*
* @param string $subject String to set the subject to
* access public
*/
function setSubject($subject)
{
$this->_headers['Subject'] = $subject;
}
/**
* Set an email to the From (the sender) header
*
* @param string $email The email direction to add
* @access public
*/
function setFrom($email)
{
$this->_headers['From'] = $email;
}
/**
* Add an email to the Cc (carbon copy) header
* (multiple calls to this method are allowed)
*
* @param string $email The email direction to add
* @access public
*/
function addCc($email)
{
if (isset($this->_headers['Cc'])) {
$this->_headers['Cc'] .= ", $email";
} else {
$this->_headers['Cc'] = $email;
}
}
/**
* Add an email to the Bcc (blank carbon copy) header
* (multiple calls to this method are allowed)
*
* @param string $email The email direction to add
* @access public
*/
function addBcc($email)
{
if (isset($this->_headers['Bcc'])) {
$this->_headers['Bcc'] .= ", $email";
} else {
$this->_headers['Bcc'] = $email;
}
}
/**
* Encodes a header as per RFC2047
*
* @param string $input The header data to encode
* @return string Encoded data
* @access private
*/
function _encodeHeaders($input)
{
foreach ($input as $hdr_name => $hdr_value) {
preg_match_all('/(\w*[\x80-\xFF]+\w*)/', $hdr_value, $matches);
foreach ($matches[1] as $value) {
$replacement = preg_replace('/([\x80-\xFF])/e',
'"=" .
strtoupper(dechex(ord("\1")))',
$value);
$hdr_value = str_replace($value, '=?' .
$this->_build_params['head_charset'] .
'?Q?' . $replacement . '?=',
$hdr_value);
}
$input[$hdr_name] = $hdr_value;
}
return $input;
}
/**
* Set the object's end-of-line and define the constant if applicable
*
* @param string $eol End Of Line sequence
* @access private
*/
function _setEOL($eol)
{
$this->_eol = $eol;
if (!defined('MAIL_MIME_CRLF')) {
define('MAIL_MIME_CRLF', $this->_eol, true);
}
}
} // End of class
?>

837
inc/Mail/mimeDecode.php Executable file
View File

@ -0,0 +1,837 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | Copyright (c) 2003-2005 The PHP Group |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@phpguru.org> |
// +-----------------------------------------------------------------------+
require_once 'PEAR.php';
/**
* +----------------------------- IMPORTANT ------------------------------+
* | Usage of this class compared to native php extensions such as |
* | mailparse or imap, is slow and may be feature deficient. If available|
* | you are STRONGLY recommended to use the php extensions. |
* +----------------------------------------------------------------------+
*
* Mime Decoding class
*
* This class will parse a raw mime email and return
* the structure. Returned structure is similar to
* that returned by imap_fetchstructure().
*
* USAGE: (assume $input is your raw email)
*
* $decode = new Mail_mimeDecode($input, "\r\n");
* $structure = $decode->decode();
* print_r($structure);
*
* Or statically:
*
* $params['input'] = $input;
* $structure = Mail_mimeDecode::decode($params);
* print_r($structure);
*
* TODO:
* o Implement multipart/appledouble
* o UTF8: ???
> 4. We have also found a solution for decoding the UTF-8
> headers. Therefore I made the following function:
>
> function decode_utf8($txt) {
> $trans=array("Å&#8216;"=>"õ","ű"=>"û","Å<EFBFBD>"=>"Ã&#8226;","Å°"
=>"Ã&#8250;");
> $txt=strtr($txt,$trans);
> return(utf8_decode($txt));
> }
>
> And I have inserted the following line to the class:
>
> if (strtolower($charset)=="utf-8") $text=decode_utf8($text);
>
> ... before the following one in the "_decodeHeader" function:
>
> $input = str_replace($encoded, $text, $input);
>
> This way from now on it can easily decode the UTF-8 headers too.
*
* @author Richard Heyes <richard@phpguru.org>
* @version $Revision: 1.46 $
* @package Mail
*/
class Mail_mimeDecode extends PEAR
{
/**
* The raw email to decode
* @var string
*/
var $_input;
/**
* The header part of the input
* @var string
*/
var $_header;
/**
* The body part of the input
* @var string
*/
var $_body;
/**
* If an error occurs, this is used to store the message
* @var string
*/
var $_error;
/**
* Flag to determine whether to include bodies in the
* returned object.
* @var boolean
*/
var $_include_bodies;
/**
* Flag to determine whether to decode bodies
* @var boolean
*/
var $_decode_bodies;
/**
* Flag to determine whether to decode headers
* @var boolean
*/
var $_decode_headers;
/**
* Constructor.
*
* Sets up the object, initialise the variables, and splits and
* stores the header and body of the input.
*
* @param string The input to decode
* @access public
*/
function Mail_mimeDecode($input)
{
list($header, $body) = $this->_splitBodyHeader($input);
$this->_input = $input;
$this->_header = $header;
$this->_body = $body;
$this->_decode_bodies = false;
$this->_include_bodies = true;
}
/**
* Begins the decoding process. If called statically
* it will create an object and call the decode() method
* of it.
*
* @param array An array of various parameters that determine
* various things:
* include_bodies - Whether to include the body in the returned
* object.
* decode_bodies - Whether to decode the bodies
* of the parts. (Transfer encoding)
* decode_headers - Whether to decode headers
* input - If called statically, this will be treated
* as the input
* @return object Decoded results
* @access public
*/
function decode($params = null)
{
// determine if this method has been called statically
$isStatic = !(isset($this) && get_class($this) == __CLASS__);
// Have we been called statically?
// If so, create an object and pass details to that.
if ($isStatic AND isset($params['input'])) {
$obj = new Mail_mimeDecode($params['input']);
$structure = $obj->decode($params);
// Called statically but no input
} elseif ($isStatic) {
return PEAR::raiseError('Called statically and no input given');
// Called via an object
} else {
$this->_include_bodies = isset($params['include_bodies']) ?
$params['include_bodies'] : false;
$this->_decode_bodies = isset($params['decode_bodies']) ?
$params['decode_bodies'] : false;
$this->_decode_headers = isset($params['decode_headers']) ?
$params['decode_headers'] : false;
$structure = $this->_decode($this->_header, $this->_body);
if ($structure === false) {
$structure = $this->raiseError($this->_error);
}
}
return $structure;
}
/**
* Performs the decoding. Decodes the body string passed to it
* If it finds certain content-types it will call itself in a
* recursive fashion
*
* @param string Header section
* @param string Body section
* @return object Results of decoding process
* @access private
*/
function _decode($headers, $body, $default_ctype = 'text/plain')
{
$return = new stdClass;
$return->headers = array();
$headers = $this->_parseHeaders($headers);
foreach ($headers as $value) {
if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) {
$return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]);
$return->headers[strtolower($value['name'])][] = $value['value'];
} elseif (isset($return->headers[strtolower($value['name'])])) {
$return->headers[strtolower($value['name'])][] = $value['value'];
} else {
$return->headers[strtolower($value['name'])] = $value['value'];
}
}
reset($headers);
while (list($key, $value) = each($headers)) {
$headers[$key]['name'] = strtolower($headers[$key]['name']);
switch ($headers[$key]['name']) {
case 'content-type':
$content_type = $this->_parseHeaderValue($headers[$key]['value']);
if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
$return->ctype_primary = $regs[1];
$return->ctype_secondary = $regs[2];
}
if (isset($content_type['other'])) {
while (list($p_name, $p_value) = each($content_type['other'])) {
$return->ctype_parameters[$p_name] = $p_value;
}
}
break;
case 'content-disposition':
$content_disposition = $this->_parseHeaderValue($headers[$key]['value']);
$return->disposition = $content_disposition['value'];
if (isset($content_disposition['other'])) {
while (list($p_name, $p_value) = each($content_disposition['other'])) {
$return->d_parameters[$p_name] = $p_value;
}
}
break;
case 'content-transfer-encoding':
$content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']);
break;
}
}
if (isset($content_type)) {
switch (strtolower($content_type['value'])) {
case 'text/plain':
$encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
break;
case 'text/html':
$encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
break;
case 'multipart/parallel':
case 'multipart/report': // RFC1892
case 'multipart/signed': // PGP
case 'multipart/digest':
case 'multipart/alternative':
case 'multipart/related':
case 'multipart/mixed':
if(!isset($content_type['other']['boundary'])){
$this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
return false;
}
$default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';
$parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
for ($i = 0; $i < count($parts); $i++) {
list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]);
$part = $this->_decode($part_header, $part_body, $default_ctype);
if($part === false)
$part = $this->raiseError($this->_error);
$return->parts[] = $part;
}
break;
case 'message/rfc822':
$obj = &new Mail_mimeDecode($body);
$return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies,
'decode_bodies' => $this->_decode_bodies,
'decode_headers' => $this->_decode_headers));
unset($obj);
break;
default:
if(!isset($content_transfer_encoding['value']))
$content_transfer_encoding['value'] = '7bit';
$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null;
break;
}
} else {
$ctype = explode('/', $default_ctype);
$return->ctype_primary = $ctype[0];
$return->ctype_secondary = $ctype[1];
$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null;
}
return $return;
}
/**
* Given the output of the above function, this will return an
* array of references to the parts, indexed by mime number.
*
* @param object $structure The structure to go through
* @param string $mime_number Internal use only.
* @return array Mime numbers
*/
function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '')
{
$return = array();
if (!empty($structure->parts)) {
if ($mime_number != '') {
$structure->mime_id = $prepend . $mime_number;
$return[$prepend . $mime_number] = &$structure;
}
for ($i = 0; $i < count($structure->parts); $i++) {
if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') {
$prepend = $prepend . $mime_number . '.';
$_mime_number = '';
} else {
$_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1));
}
$arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend);
foreach ($arr as $key => $val) {
$no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key];
}
}
} else {
if ($mime_number == '') {
$mime_number = '1';
}
$structure->mime_id = $prepend . $mime_number;
$no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure;
}
return $return;
}
/**
* Given a string containing a header and body
* section, this function will split them (at the first
* blank line) and return them.
*
* @param string Input to split apart
* @return array Contains header and body section
* @access private
*/
function _splitBodyHeader($input)
{
if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) {
return array($match[1], $match[2]);
}
$this->_error = 'Could not split header and body';
return false;
}
/**
* Parse headers given in $input and return
* as assoc array.
*
* @param string Headers to parse
* @return array Contains parsed headers
* @access private
*/
function _parseHeaders($input)
{
if ($input !== '') {
// Unfold the input
$input = preg_replace("/\r?\n/", "\r\n", $input);
$input = preg_replace("/\r\n(\t| )+/", ' ', $input);
$headers = explode("\r\n", trim($input));
foreach ($headers as $value) {
$hdr_name = substr($value, 0, $pos = strpos($value, ':'));
$hdr_value = substr($value, $pos+1);
if($hdr_value[0] == ' ')
$hdr_value = substr($hdr_value, 1);
$return[] = array(
'name' => $hdr_name,
'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value
);
}
} else {
$return = array();
}
return $return;
}
/**
* Function to parse a header value,
* extract first part, and any secondary
* parts (after ;) This function is not as
* robust as it could be. Eg. header comments
* in the wrong place will probably break it.
*
* @param string Header value to parse
* @return array Contains parsed result
* @access private
*/
function _parseHeaderValue($input)
{
if (($pos = strpos($input, ';')) !== false) {
$return['value'] = trim(substr($input, 0, $pos));
$input = trim(substr($input, $pos+1));
if (strlen($input) > 0) {
// This splits on a semi-colon, if there's no preceeding backslash
// Now works with quoted values; had to glue the \; breaks in PHP
// the regex is already bordering on incomprehensible
$splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/';
preg_match_all($splitRegex, $input, $matches);
$parameters = array();
for ($i=0; $i<count($matches[0]); $i++) {
$param = $matches[0][$i];
while (substr($param, -2) == '\;') {
$param .= $matches[0][++$i];
}
$parameters[] = $param;
}
for ($i = 0; $i < count($parameters); $i++) {
$param_name = trim(substr($parameters[$i], 0, $pos = strpos($parameters[$i], '=')), "'\";\t\\ ");
$param_value = trim(str_replace('\;', ';', substr($parameters[$i], $pos + 1)), "'\";\t\\ ");
if ($param_value[0] == '"') {
$param_value = substr($param_value, 1, -1);
}
$return['other'][$param_name] = $param_value;
$return['other'][strtolower($param_name)] = $param_value;
}
}
} else {
$return['value'] = trim($input);
}
return $return;
}
/**
* This function splits the input based
* on the given boundary
*
* @param string Input to parse
* @return array Contains array of resulting mime parts
* @access private
*/
function _boundarySplit($input, $boundary)
{
$parts = array();
$bs_possible = substr($boundary, 2, -2);
$bs_check = '\"' . $bs_possible . '\"';
if ($boundary == $bs_check) {
$boundary = $bs_possible;
}
$tmp = explode('--' . $boundary, $input);
for ($i = 1; $i < count($tmp) - 1; $i++) {
$parts[] = $tmp[$i];
}
return $parts;
}
/**
* Given a header, this function will decode it
* according to RFC2047. Probably not *exactly*
* conformant, but it does pass all the given
* examples (in RFC2047).
*
* @param string Input header value to decode
* @return string Decoded header value
* @access private
*/
function _decodeHeader($input)
{
// Remove white space between encoded-words
$input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
// For each encoded-word...
while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {
$encoded = $matches[1];
$charset = $matches[2];
$encoding = $matches[3];
$text = $matches[4];
switch (strtolower($encoding)) {
case 'b':
$text = base64_decode($text);
break;
case 'q':
$text = str_replace('_', ' ', $text);
preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
foreach($matches[1] as $value)
$text = str_replace('='.$value, chr(hexdec($value)), $text);
break;
}
$input = str_replace($encoded, $text, $input);
}
return $input;
}
/**
* Given a body string and an encoding type,
* this function will decode and return it.
*
* @param string Input body to decode
* @param string Encoding type to use.
* @return string Decoded body
* @access private
*/
function _decodeBody($input, $encoding = '7bit')
{
switch (strtolower($encoding)) {
case '7bit':
return $input;
break;
case 'quoted-printable':
return $this->_quotedPrintableDecode($input);
break;
case 'base64':
return base64_decode($input);
break;
default:
return $input;
}
}
/**
* Given a quoted-printable string, this
* function will decode and return it.
*
* @param string Input body to decode
* @return string Decoded body
* @access private
*/
function _quotedPrintableDecode($input)
{
// Remove soft line breaks
$input = preg_replace("/=\r?\n/", '', $input);
// Replace encoded characters
$input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input);
return $input;
}
/**
* Checks the input for uuencoded files and returns
* an array of them. Can be called statically, eg:
*
* $files =& Mail_mimeDecode::uudecode($some_text);
*
* It will check for the begin 666 ... end syntax
* however and won't just blindly decode whatever you
* pass it.
*
* @param string Input body to look for attahcments in
* @return array Decoded bodies, filenames and permissions
* @access public
* @author Unknown
*/
function &uudecode($input)
{
// Find all uuencoded sections
preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches);
for ($j = 0; $j < count($matches[3]); $j++) {
$str = $matches[3][$j];
$filename = $matches[2][$j];
$fileperm = $matches[1][$j];
$file = '';
$str = preg_split("/\r?\n/", trim($str));
$strlen = count($str);
for ($i = 0; $i < $strlen; $i++) {
$pos = 1;
$d = 0;
$len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077);
while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) {
$c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
$c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
$c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
$c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20);
$file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
$file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
$file .= chr(((($c2 - ' ') & 077) << 6) | (($c3 - ' ') & 077));
$pos += 4;
$d += 3;
}
if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) {
$c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
$c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
$c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
$file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
$file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
$pos += 3;
$d += 2;
}
if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) {
$c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
$c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
$file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
}
}
$files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file);
}
return $files;
}
/**
* getSendArray() returns the arguments required for Mail::send()
* used to build the arguments for a mail::send() call
*
* Usage:
* $mailtext = Full email (for example generated by a template)
* $decoder = new Mail_mimeDecode($mailtext);
* $parts = $decoder->getSendArray();
* if (!PEAR::isError($parts) {
* list($recipents,$headers,$body) = $parts;
* $mail = Mail::factory('smtp');
* $mail->send($recipents,$headers,$body);
* } else {
* echo $parts->message;
* }
* @return mixed array of recipeint, headers,body or Pear_Error
* @access public
* @author Alan Knowles <alan@akbkhome.com>
*/
function getSendArray()
{
// prevent warning if this is not set
$this->_decode_headers = FALSE;
$headerlist =$this->_parseHeaders($this->_header);
$to = "";
if (!$headerlist) {
return $this->raiseError("Message did not contain headers");
}
foreach($headerlist as $item) {
$header[$item['name']] = $item['value'];
switch (strtolower($item['name'])) {
case "to":
case "cc":
case "bcc":
$to = ",".$item['value'];
default:
break;
}
}
if ($to == "") {
return $this->raiseError("Message did not contain any recipents");
}
$to = substr($to,1);
return array($to,$header,$this->_body);
}
/**
* Returns a xml copy of the output of
* Mail_mimeDecode::decode. Pass the output in as the
* argument. This function can be called statically. Eg:
*
* $output = $obj->decode();
* $xml = Mail_mimeDecode::getXML($output);
*
* The DTD used for this should have been in the package. Or
* alternatively you can get it from cvs, or here:
* http://www.phpguru.org/xmail/xmail.dtd.
*
* @param object Input to convert to xml. This should be the
* output of the Mail_mimeDecode::decode function
* @return string XML version of input
* @access public
*/
function getXML($input)
{
$crlf = "\r\n";
$output = '<?xml version=\'1.0\'?>' . $crlf .
'<!DOCTYPE email SYSTEM "http://www.phpguru.org/xmail/xmail.dtd">' . $crlf .
'<email>' . $crlf .
Mail_mimeDecode::_getXML($input) .
'</email>';
return $output;
}
/**
* Function that does the actual conversion to xml. Does a single
* mimepart at a time.
*
* @param object Input to convert to xml. This is a mimepart object.
* It may or may not contain subparts.
* @param integer Number of tabs to indent
* @return string XML version of input
* @access private
*/
function _getXML($input, $indent = 1)
{
$htab = "\t";
$crlf = "\r\n";
$output = '';
$headers = @(array)$input->headers;
foreach ($headers as $hdr_name => $hdr_value) {
// Multiple headers with this name
if (is_array($headers[$hdr_name])) {
for ($i = 0; $i < count($hdr_value); $i++) {
$output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent);
}
// Only one header of this sort
} else {
$output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent);
}
}
if (!empty($input->parts)) {
for ($i = 0; $i < count($input->parts); $i++) {
$output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf .
Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) .
str_repeat($htab, $indent) . '</mimepart>' . $crlf;
}
} elseif (isset($input->body)) {
$output .= $crlf . str_repeat($htab, $indent) . '<body><![CDATA[' .
$input->body . ']]></body>' . $crlf;
}
return $output;
}
/**
* Helper function to _getXML(). Returns xml of a header.
*
* @param string Name of header
* @param string Value of header
* @param integer Number of tabs to indent
* @return string XML version of input
* @access private
*/
function _getXML_helper($hdr_name, $hdr_value, $indent)
{
$htab = "\t";
$crlf = "\r\n";
$return = '';
$new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value);
$new_hdr_name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name)));
// Sort out any parameters
if (!empty($new_hdr_value['other'])) {
foreach ($new_hdr_value['other'] as $paramname => $paramvalue) {
$params[] = str_repeat($htab, $indent) . $htab . '<parameter>' . $crlf .
str_repeat($htab, $indent) . $htab . $htab . '<paramname>' . htmlspecialchars($paramname) . '</paramname>' . $crlf .
str_repeat($htab, $indent) . $htab . $htab . '<paramvalue>' . htmlspecialchars($paramvalue) . '</paramvalue>' . $crlf .
str_repeat($htab, $indent) . $htab . '</parameter>' . $crlf;
}
$params = implode('', $params);
} else {
$params = '';
}
$return = str_repeat($htab, $indent) . '<header>' . $crlf .
str_repeat($htab, $indent) . $htab . '<headername>' . htmlspecialchars($new_hdr_name) . '</headername>' . $crlf .
str_repeat($htab, $indent) . $htab . '<headervalue>' . htmlspecialchars($new_hdr_value['value']) . '</headervalue>' . $crlf .
$params .
str_repeat($htab, $indent) . '</header>' . $crlf;
return $return;
}
} // End of class
?>

351
inc/Mail/mimePart.php Executable file
View File

@ -0,0 +1,351 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@phpguru.org> |
// +-----------------------------------------------------------------------+
/**
*
* Raw mime encoding class
*
* What is it?
* This class enables you to manipulate and build
* a mime email from the ground up.
*
* Why use this instead of mime.php?
* mime.php is a userfriendly api to this class for
* people who aren't interested in the internals of
* mime mail. This class however allows full control
* over the email.
*
* Eg.
*
* // Since multipart/mixed has no real body, (the body is
* // the subpart), we set the body argument to blank.
*
* $params['content_type'] = 'multipart/mixed';
* $email = new Mail_mimePart('', $params);
*
* // Here we add a text part to the multipart we have
* // already. Assume $body contains plain text.
*
* $params['content_type'] = 'text/plain';
* $params['encoding'] = '7bit';
* $text = $email->addSubPart($body, $params);
*
* // Now add an attachment. Assume $attach is
* the contents of the attachment
*
* $params['content_type'] = 'application/zip';
* $params['encoding'] = 'base64';
* $params['disposition'] = 'attachment';
* $params['dfilename'] = 'example.zip';
* $attach =& $email->addSubPart($body, $params);
*
* // Now build the email. Note that the encode
* // function returns an associative array containing two
* // elements, body and headers. You will need to add extra
* // headers, (eg. Mime-Version) before sending.
*
* $email = $message->encode();
* $email['headers'][] = 'Mime-Version: 1.0';
*
*
* Further examples are available at http://www.phpguru.org
*
* TODO:
* - Set encode() to return the $obj->encoded if encode()
* has already been run. Unless a flag is passed to specifically
* re-build the message.
*
* @author Richard Heyes <richard@phpguru.org>
* @version $Revision: 1.13 $
* @package Mail
*/
class Mail_mimePart {
/**
* The encoding type of this part
* @var string
*/
var $_encoding;
/**
* An array of subparts
* @var array
*/
var $_subparts;
/**
* The output of this part after being built
* @var string
*/
var $_encoded;
/**
* Headers for this part
* @var array
*/
var $_headers;
/**
* The body of this part (not encoded)
* @var string
*/
var $_body;
/**
* Constructor.
*
* Sets up the object.
*
* @param $body - The body of the mime part if any.
* @param $params - An associative array of parameters:
* content_type - The content type for this part eg multipart/mixed
* encoding - The encoding to use, 7bit, 8bit, base64, or quoted-printable
* cid - Content ID to apply
* disposition - Content disposition, inline or attachment
* dfilename - Optional filename parameter for content disposition
* description - Content description
* charset - Character set to use
* @access public
*/
function Mail_mimePart($body = '', $params = array())
{
if (!defined('MAIL_MIMEPART_CRLF')) {
define('MAIL_MIMEPART_CRLF', defined('MAIL_MIME_CRLF') ? MAIL_MIME_CRLF : "\r\n", TRUE);
}
foreach ($params as $key => $value) {
switch ($key) {
case 'content_type':
$headers['Content-Type'] = $value . (isset($charset) ? '; charset="' . $charset . '"' : '');
break;
case 'encoding':
$this->_encoding = $value;
$headers['Content-Transfer-Encoding'] = $value;
break;
case 'cid':
$headers['Content-ID'] = '<' . $value . '>';
break;
case 'disposition':
$headers['Content-Disposition'] = $value . (isset($dfilename) ? '; filename="' . $dfilename . '"' : '');
break;
case 'dfilename':
if (isset($headers['Content-Disposition'])) {
$headers['Content-Disposition'] .= '; filename="' . $value . '"';
} else {
$dfilename = $value;
}
break;
case 'description':
$headers['Content-Description'] = $value;
break;
case 'charset':
if (isset($headers['Content-Type'])) {
$headers['Content-Type'] .= '; charset="' . $value . '"';
} else {
$charset = $value;
}
break;
}
}
// Default content-type
if (!isset($headers['Content-Type'])) {
$headers['Content-Type'] = 'text/plain';
}
//Default encoding
if (!isset($this->_encoding)) {
$this->_encoding = '7bit';
}
// Assign stuff to member variables
$this->_encoded = array();
$this->_headers = $headers;
$this->_body = $body;
}
/**
* encode()
*
* Encodes and returns the email. Also stores
* it in the encoded member variable
*
* @return An associative array containing two elements,
* body and headers. The headers element is itself
* an indexed array.
* @access public
*/
function encode()
{
$encoded =& $this->_encoded;
if (!empty($this->_subparts)) {
srand((double)microtime()*1000000);
$boundary = '=_' . md5(rand() . microtime());
$this->_headers['Content-Type'] .= ';' . MAIL_MIMEPART_CRLF . "\t" . 'boundary="' . $boundary . '"';
// Add body parts to $subparts
for ($i = 0; $i < count($this->_subparts); $i++) {
$headers = array();
$tmp = $this->_subparts[$i]->encode();
foreach ($tmp['headers'] as $key => $value) {
$headers[] = $key . ': ' . $value;
}
$subparts[] = implode(MAIL_MIMEPART_CRLF, $headers) . MAIL_MIMEPART_CRLF . MAIL_MIMEPART_CRLF . $tmp['body'];
}
$encoded['body'] = '--' . $boundary . MAIL_MIMEPART_CRLF .
implode('--' . $boundary . MAIL_MIMEPART_CRLF, $subparts) .
'--' . $boundary.'--' . MAIL_MIMEPART_CRLF;
} else {
$encoded['body'] = $this->_getEncodedData($this->_body, $this->_encoding) . MAIL_MIMEPART_CRLF;
}
// Add headers to $encoded
$encoded['headers'] =& $this->_headers;
return $encoded;
}
/**
* &addSubPart()
*
* Adds a subpart to current mime part and returns
* a reference to it
*
* @param $body The body of the subpart, if any.
* @param $params The parameters for the subpart, same
* as the $params argument for constructor.
* @return A reference to the part you just added. It is
* crucial if using multipart/* in your subparts that
* you use =& in your script when calling this function,
* otherwise you will not be able to add further subparts.
* @access public
*/
function &addSubPart($body, $params)
{
$this->_subparts[] = new Mail_mimePart($body, $params);
return $this->_subparts[count($this->_subparts) - 1];
}
/**
* _getEncodedData()
*
* Returns encoded data based upon encoding passed to it
*
* @param $data The data to encode.
* @param $encoding The encoding type to use, 7bit, base64,
* or quoted-printable.
* @access private
*/
function _getEncodedData($data, $encoding)
{
switch ($encoding) {
case '8bit':
case '7bit':
return $data;
break;
case 'quoted-printable':
return $this->_quotedPrintableEncode($data);
break;
case 'base64':
return rtrim(chunk_split(base64_encode($data), 76, MAIL_MIMEPART_CRLF));
break;
default:
return $data;
}
}
/**
* quoteadPrintableEncode()
*
* Encodes data to quoted-printable standard.
*
* @param $input The data to encode
* @param $line_max Optional max line length. Should
* not be more than 76 chars
*
* @access private
*/
function _quotedPrintableEncode($input , $line_max = 76)
{
$lines = preg_split("/\r?\n/", $input);
$eol = MAIL_MIMEPART_CRLF;
$escape = '=';
$output = '';
while(list(, $line) = each($lines)){
$linlen = strlen($line);
$newline = '';
for ($i = 0; $i < $linlen; $i++) {
$char = substr($line, $i, 1);
$dec = ord($char);
if (($dec == 32) AND ($i == ($linlen - 1))){ // convert space at eol only
$char = '=20';
} elseif(($dec == 9) AND ($i == ($linlen - 1))) { // convert tab at eol only
$char = '=09';
} elseif($dec == 9) {
; // Do nothing if a tab.
} elseif(($dec == 61) OR ($dec < 32 ) OR ($dec > 126)) {
$char = $escape . strtoupper(sprintf('%02s', dechex($dec)));
}
if ((strlen($newline) + strlen($char)) >= $line_max) { // MAIL_MIMEPART_CRLF is not counted
$output .= $newline . $escape . $eol; // soft line break; " =\r\n" is okay
$newline = '';
}
$newline .= $char;
} // end of for
$output .= $newline . $eol;
}
$output = substr($output, 0, -1 * strlen($eol)); // Don't want last crlf
return $output;
}
} // End of class
?>

60
inc/Mail/null.php Executable file
View File

@ -0,0 +1,60 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Phil Kernick <philk@rotfl.com.au> |
// +----------------------------------------------------------------------+
//
// $Id: null.php,v 1.2 2004/04/06 05:19:03 jon Exp $
//
/**
* Null implementation of the PEAR Mail:: interface.
* @access public
* @package Mail
* @version $Revision: 1.2 $
*/
class Mail_null extends Mail {
/**
* Implements Mail_null::send() function. Silently discards all
* mail.
*
* @param mixed $recipients Either a comma-seperated list of recipients
* (RFC822 compliant), or an array of recipients,
* each RFC822 valid. This may contain recipients not
* specified in the headers, for Bcc:, resending
* messages, etc.
*
* @param array $headers The array of headers to send with the mail, in an
* associative array, where the array key is the
* header name (ie, 'Subject'), and the array value
* is the header value (ie, 'test'). The header
* produced from those values would be 'Subject:
* test'.
*
* @param string $body The full text of the message body, including any
* Mime parts, etc.
*
* @return mixed Returns true on success, or a PEAR_Error
* containing a descriptive error message on
* failure.
* @access public
*/
function send($recipients, $headers, $body)
{
return true;
}
}

145
inc/Mail/sendmail.php Executable file
View File

@ -0,0 +1,145 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Chuck Hagenbuch <chuck@horde.org> |
// +----------------------------------------------------------------------+
/**
* Sendmail implementation of the PEAR Mail:: interface.
* @access public
* @package Mail
* @version $Revision: 1.10 $
*/
class Mail_sendmail extends Mail {
/**
* The location of the sendmail or sendmail wrapper binary on the
* filesystem.
* @var string
*/
var $sendmail_path = '/usr/sbin/sendmail';
/**
* Any extra command-line parameters to pass to the sendmail or
* sendmail wrapper binary.
* @var string
*/
var $sendmail_args = '';
/**
* Constructor.
*
* Instantiates a new Mail_sendmail:: object based on the parameters
* passed in. It looks for the following parameters:
* sendmail_path The location of the sendmail binary on the
* filesystem. Defaults to '/usr/sbin/sendmail'.
*
* sendmail_args Any extra parameters to pass to the sendmail
* or sendmail wrapper binary.
*
* If a parameter is present in the $params array, it replaces the
* default.
*
* @param array $params Hash containing any parameters different from the
* defaults.
* @access public
*/
function Mail_sendmail($params)
{
if (isset($params['sendmail_path'])) $this->sendmail_path = $params['sendmail_path'];
if (isset($params['sendmail_args'])) $this->sendmail_args = $params['sendmail_args'];
/*
* Because we need to pass message headers to the sendmail program on
* the commandline, we can't guarantee the use of the standard "\r\n"
* separator. Instead, we use the system's native line separator.
*/
$this->sep = (strstr(PHP_OS, 'WIN')) ? "\r\n" : "\n";
}
/**
* Implements Mail::send() function using the sendmail
* command-line binary.
*
* @param mixed $recipients Either a comma-seperated list of recipients
* (RFC822 compliant), or an array of recipients,
* each RFC822 valid. This may contain recipients not
* specified in the headers, for Bcc:, resending
* messages, etc.
*
* @param array $headers The array of headers to send with the mail, in an
* associative array, where the array key is the
* header name (ie, 'Subject'), and the array value
* is the header value (ie, 'test'). The header
* produced from those values would be 'Subject:
* test'.
*
* @param string $body The full text of the message body, including any
* Mime parts, etc.
*
* @return mixed Returns true on success, or a PEAR_Error
* containing a descriptive error message on
* failure.
* @access public
*/
function send($recipients, $headers, $body)
{
$recipients = $this->parseRecipients($recipients);
if (PEAR::isError($recipients)) {
return $recipients;
}
$recipients = escapeShellCmd(implode(' ', $recipients));
$headerElements = $this->prepareHeaders($headers);
if (PEAR::isError($headerElements)) {
return $headerElements;
}
list($from, $text_headers) = $headerElements;
if (!isset($from)) {
return PEAR::raiseError('No from address given.');
} elseif (strstr($from, ' ') ||
strstr($from, ';') ||
strstr($from, '&') ||
strstr($from, '`')) {
return PEAR::raiseError('From address specified with dangerous characters.');
}
$result = 0;
if (@is_file($this->sendmail_path)) {
$from = escapeShellCmd($from);
$mail = popen($this->sendmail_path . (!empty($this->sendmail_args) ? ' ' . $this->sendmail_args : '') . " -f$from -- $recipients", 'w');
fputs($mail, $text_headers);
fputs($mail, $this->sep); // newline to end the headers section
fputs($mail, $body);
$result = pclose($mail);
if (version_compare(phpversion(), '4.2.3') == -1) {
// With older php versions, we need to shift the
// pclose result to get the exit code.
$result = $result >> 8 & 0xFF;
}
} else {
return PEAR::raiseError('sendmail [' . $this->sendmail_path . '] is not a valid file');
}
if ($result != 0) {
return PEAR::raiseError('sendmail returned error code ' . $result,
$result);
}
return true;
}
}

223
inc/Mail/smtp.php Executable file
View File

@ -0,0 +1,223 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Chuck Hagenbuch <chuck@horde.org> |
// | Jon Parise <jon@php.net> |
// +----------------------------------------------------------------------+
/**
* SMTP implementation of the PEAR Mail:: interface. Requires the PEAR
* Net_SMTP:: class.
* @access public
* @package Mail
* @version $Revision: 1.20 $
*/
class Mail_smtp extends Mail {
/**
* The SMTP host to connect to.
* @var string
*/
var $host = 'localhost';
/**
* The port the SMTP server is on.
* @var integer
*/
var $port = 25;
/**
* Should SMTP authentication be used?
*
* This value may be set to true, false or the name of a specific
* authentication method.
*
* If the value is set to true, the Net_SMTP package will attempt to use
* the best authentication method advertised by the remote SMTP server.
*
* @var mixed
*/
var $auth = false;
/**
* The username to use if the SMTP server requires authentication.
* @var string
*/
var $username = '';
/**
* The password to use if the SMTP server requires authentication.
* @var string
*/
var $password = '';
/**
* Hostname or domain that will be sent to the remote SMTP server in the
* HELO / EHLO message.
*
* @var string
*/
var $localhost = 'localhost';
/**
* SMTP connection timeout value. NULL indicates no timeout.
*
* @var integer
*/
var $timeout = null;
/**
* Whether to use VERP or not. If not a boolean, the string value
* will be used as the VERP separators.
*
* @var mixed boolean or string
*/
var $verp = false;
/**
* Turn on Net_SMTP debugging?
*
* @var boolean $debug
*/
var $debug = false;
/**
* Constructor.
*
* Instantiates a new Mail_smtp:: object based on the parameters
* passed in. It looks for the following parameters:
* host The server to connect to. Defaults to localhost.
* port The port to connect to. Defaults to 25.
* auth SMTP authentication. Defaults to none.
* username The username to use for SMTP auth. No default.
* password The password to use for SMTP auth. No default.
* localhost The local hostname / domain. Defaults to localhost.
* timeout The SMTP connection timeout. Defaults to none.
* verp Whether to use VERP or not. Defaults to false.
* debug Activate SMTP debug mode? Defaults to false.
*
* If a parameter is present in the $params array, it replaces the
* default.
*
* @param array Hash containing any parameters different from the
* defaults.
* @access public
*/
function Mail_smtp($params)
{
if (isset($params['host'])) $this->host = $params['host'];
if (isset($params['port'])) $this->port = $params['port'];
if (isset($params['auth'])) $this->auth = $params['auth'];
if (isset($params['username'])) $this->username = $params['username'];
if (isset($params['password'])) $this->password = $params['password'];
if (isset($params['localhost'])) $this->localhost = $params['localhost'];
if (isset($params['timeout'])) $this->timeout = $params['timeout'];
if (isset($params['verp'])) $this->verp = $params['verp'];
if (isset($params['debug'])) $this->debug = (boolean)$params['debug'];
}
/**
* Implements Mail::send() function using SMTP.
*
* @param mixed $recipients Either a comma-seperated list of recipients
* (RFC822 compliant), or an array of recipients,
* each RFC822 valid. This may contain recipients not
* specified in the headers, for Bcc:, resending
* messages, etc.
*
* @param array $headers The array of headers to send with the mail, in an
* associative array, where the array key is the
* header name (e.g., 'Subject'), and the array value
* is the header value (e.g., 'test'). The header
* produced from those values would be 'Subject:
* test'.
*
* @param string $body The full text of the message body, including any
* Mime parts, etc.
*
* @return mixed Returns true on success, or a PEAR_Error
* containing a descriptive error message on
* failure.
* @access public
*/
function send($recipients, $headers, $body)
{
include_once 'Net/SMTP.php';
if (!($smtp = &new Net_SMTP($this->host, $this->port, $this->localhost))) {
return PEAR::raiseError('unable to instantiate Net_SMTP object');
}
if ($this->debug) {
$smtp->setDebug(true);
}
if (PEAR::isError($smtp->connect($this->timeout))) {
return PEAR::raiseError('unable to connect to smtp server ' .
$this->host . ':' . $this->port);
}
if ($this->auth) {
$method = is_string($this->auth) ? $this->auth : '';
if (PEAR::isError($smtp->auth($this->username, $this->password,
$method))) {
return PEAR::raiseError('unable to authenticate to smtp server');
}
}
$headerElements = $this->prepareHeaders($headers);
if (PEAR::isError($headerElements)) {
return $headerElements;
}
list($from, $text_headers) = $headerElements;
/* Since few MTAs are going to allow this header to be forged
* unless it's in the MAIL FROM: exchange, we'll use
* Return-Path instead of From: if it's set. */
if (!empty($headers['Return-Path'])) {
$from = $headers['Return-Path'];
}
if (!isset($from)) {
return PEAR::raiseError('No from address given');
}
$args['verp'] = $this->verp;
if (PEAR::isError($smtp->mailFrom($from, $args))) {
return PEAR::raiseError('unable to set sender to [' . $from . ']');
}
$recipients = $this->parseRecipients($recipients);
if (PEAR::isError($recipients)) {
return $recipients;
}
foreach ($recipients as $recipient) {
if (PEAR::isError($res = $smtp->rcptTo($recipient))) {
return PEAR::raiseError('unable to add recipient [' .
$recipient . ']: ' . $res->getMessage());
}
}
if (PEAR::isError($smtp->data($text_headers . "\r\n" . $body))) {
return PEAR::raiseError('unable to send data');
}
$smtp->disconnect();
return true;
}
}

19
inc/Mail/xmail.dtd Executable file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!ENTITY lt "&#38;#60;">
<!ENTITY gt "&#62;">
<!ENTITY amp "&#38;#38;">
<!ENTITY apos "&#39;">
<!ENTITY quot "&#34;">
<!ENTITY crlf "&#13;&#10;">
<!ELEMENT email (header+, (body | mimepart+))>
<!ELEMENT mimepart (header+, (body | mimepart+))>
<!ELEMENT body (#PCDATA)>
<!ELEMENT header ((headername|headervalue|parameter)*)>
<!ELEMENT headername (#PCDATA)>
<!ELEMENT headervalue (#PCDATA)>
<!ELEMENT parameter ((paramname|paramvalue)+)>
<!ELEMENT paramvalue (#PCDATA)>
<!ELEMENT paramname (#PCDATA)>

70
inc/Mail/xmail.xsl Executable file
View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="text" omit-xml-declaration="yes" indent="no"/>
<xsl:preserve-space elements="headervalue paramvalue body"/>
<xsl:template name="mimepart">
<xsl:variable name="boundary">
<xsl:for-each select="./header">
<xsl:if test="string(./headername) = 'Content-Type'">
<xsl:for-each select="./parameter">
<xsl:if test="string(./paramname) = 'boundary'">
<xsl:value-of select="paramvalue"/>
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="header">
<xsl:value-of select="headername"/>
<xsl:text>: </xsl:text>
<xsl:value-of select="headervalue"/>
<xsl:if test="count(./parameter) = 0">
<xsl:text>&#13;&#10;</xsl:text>
</xsl:if>
<xsl:for-each select="parameter">
<xsl:text>;&#13;&#10;&#09;</xsl:text>
<xsl:value-of select="paramname"/>
<xsl:text>="</xsl:text>
<xsl:value-of select="paramvalue"/>
<xsl:text>"</xsl:text>
</xsl:for-each>
<xsl:if test="count(./parameter) > 0">
<xsl:text>&#13;&#10;</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>&#13;&#10;</xsl:text>
<!-- Which to do, print a body or process subparts? -->
<xsl:choose>
<xsl:when test="count(./mimepart) = 0">
<xsl:value-of select="body"/>
<xsl:text>&#13;&#10;</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="mimepart">
<xsl:text>--</xsl:text><xsl:value-of select="$boundary"/><xsl:text>&#13;&#10;</xsl:text>
<xsl:call-template name="mimepart"/>
</xsl:for-each>
<xsl:text>--</xsl:text><xsl:value-of select="$boundary"/><xsl:text>--&#13;&#10;</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- This is where the stylesheet really starts, matching the top level email element -->
<xsl:template match="email">
<xsl:call-template name="mimepart"/>
</xsl:template>
</xsl:stylesheet>

970
inc/Net/SMTP.php Executable file
View File

@ -0,0 +1,970 @@
<?php
/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Chuck Hagenbuch <chuck@horde.org> |
// | Jon Parise <jon@php.net> |
// | Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar> |
// +----------------------------------------------------------------------+
require_once 'PEAR.php';
require_once 'Net/Socket.php';
/**
* Provides an implementation of the SMTP protocol using PEAR's
* Net_Socket:: class.
*
* @package Net_SMTP
* @author Chuck Hagenbuch <chuck@horde.org>
* @author Jon Parise <jon@php.net>
* @author Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>
*
* @example basic.php A basic implementation of the Net_SMTP package.
*/
class Net_SMTP
{
/**
* The server to connect to.
* @var string
* @access public
*/
var $host = 'localhost';
/**
* The port to connect to.
* @var int
* @access public
*/
var $port = 25;
/**
* The value to give when sending EHLO or HELO.
* @var string
* @access public
*/
var $localhost = 'localhost';
/**
* List of supported authentication methods, in preferential order.
* @var array
* @access public
*/
var $auth_methods = array('DIGEST-MD5', 'CRAM-MD5', 'LOGIN', 'PLAIN');
/**
* Should debugging output be enabled?
* @var boolean
* @access private
*/
var $_debug = false;
/**
* The socket resource being used to connect to the SMTP server.
* @var resource
* @access private
*/
var $_socket = null;
/**
* The most recent server response code.
* @var int
* @access private
*/
var $_code = -1;
/**
* The most recent server response arguments.
* @var array
* @access private
*/
var $_arguments = array();
/**
* Stores detected features of the SMTP server.
* @var array
* @access private
*/
var $_esmtp = array();
/**
* Instantiates a new Net_SMTP object, overriding any defaults
* with parameters that are passed in.
*
* @param string The server to connect to.
* @param int The port to connect to.
* @param string The value to give when sending EHLO or HELO.
*
* @access public
* @since 1.0
*/
function Net_SMTP($host = null, $port = null, $localhost = null)
{
if (isset($host)) $this->host = $host;
if (isset($port)) $this->port = $port;
if (isset($localhost)) $this->localhost = $localhost;
$this->_socket = new Net_Socket();
/*
* Include the Auth_SASL package. If the package is not available,
* we disable the authentication methods that depend upon it.
*/
if ((@include_once 'Auth/SASL.php') === false) {
$pos = array_search('DIGEST-MD5', $this->auth_methods);
unset($this->auth_methods[$pos]);
$pos = array_search('CRAM-MD5', $this->auth_methods);
unset($this->auth_methods[$pos]);
}
}
/**
* Set the value of the debugging flag.
*
* @param boolean $debug New value for the debugging flag.
*
* @access public
* @since 1.1.0
*/
function setDebug($debug)
{
$this->_debug = $debug;
}
/**
* Send the given string of data to the server.
*
* @param string $data The string of data to send.
*
* @return mixed True on success or a PEAR_Error object on failure.
*
* @access private
* @since 1.1.0
*/
function _send($data)
{
if ($this->_debug) {
echo "DEBUG: Send: $data\n";
}
if (PEAR::isError($error = $this->_socket->write($data))) {
return new PEAR_Error('Failed to write to socket: ' .
$error->getMessage());
}
return true;
}
/**
* Send a command to the server with an optional string of arguments.
* A carriage return / linefeed (CRLF) sequence will be appended to each
* command string before it is sent to the SMTP server.
*
* @param string $command The SMTP command to send to the server.
* @param string $args A string of optional arguments to append
* to the command.
*
* @return mixed The result of the _send() call.
*
* @access private
* @since 1.1.0
*/
function _put($command, $args = '')
{
if (!empty($args)) {
return $this->_send($command . ' ' . $args . "\r\n");
}
return $this->_send($command . "\r\n");
}
/**
* Read a reply from the SMTP server. The reply consists of a response
* code and a response message.
*
* @param mixed $valid The set of valid response codes. These
* may be specified as an array of integer
* values or as a single integer value.
*
* @return mixed True if the server returned a valid response code or
* a PEAR_Error object is an error condition is reached.
*
* @access private
* @since 1.1.0
*
* @see getResponse
*/
function _parseResponse($valid)
{
$this->_code = -1;
$this->_arguments = array();
while ($line = $this->_socket->readLine()) {
if ($this->_debug) {
echo "DEBUG: Recv: $line\n";
}
/* If we receive an empty line, the connection has been closed. */
if (empty($line)) {
$this->disconnect();
return new PEAR_Error("Connection was unexpectedly closed");
}
/* Read the code and store the rest in the arguments array. */
$code = substr($line, 0, 3);
$this->_arguments[] = trim(substr($line, 4));
/* Check the syntax of the response code. */
if (is_numeric($code)) {
$this->_code = (int)$code;
} else {
$this->_code = -1;
break;
}
/* If this is not a multiline response, we're done. */
if (substr($line, 3, 1) != '-') {
break;
}
}
/* Compare the server's response code with the valid code. */
if (is_int($valid) && ($this->_code === $valid)) {
return true;
}
/* If we were given an array of valid response codes, check each one. */
if (is_array($valid)) {
foreach ($valid as $valid_code) {
if ($this->_code === $valid_code) {
return true;
}
}
}
return new PEAR_Error("Invalid response code received from server");
}
/**
* Return a 2-tuple containing the last response from the SMTP server.
*
* @return array A two-element array: the first element contains the
* response code as an integer and the second element
* contains the response's arguments as a string.
*
* @access public
* @since 1.1.0
*/
function getResponse()
{
return array($this->_code, join("\n", $this->_arguments));
}
/**
* Attempt to connect to the SMTP server.
*
* @param int $timeout The timeout value (in seconds) for the
* socket connection.
* @param bool $persistent Should a persistent socket connection
* be used?
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access public
* @since 1.0
*/
function connect($timeout = null, $persistent = false)
{
$result = $this->_socket->connect($this->host, $this->port,
$persistent, $timeout);
if (PEAR::isError($result)) {
return new PEAR_Error('Failed to connect socket: ' .
$result->getMessage());
}
if (PEAR::isError($error = $this->_parseResponse(220))) {
return $error;
}
if (PEAR::isError($error = $this->_negotiate())) {
return $error;
}
return true;
}
/**
* Attempt to disconnect from the SMTP server.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access public
* @since 1.0
*/
function disconnect()
{
if (PEAR::isError($error = $this->_put('QUIT'))) {
return $error;
}
if (PEAR::isError($error = $this->_parseResponse(221))) {
return $error;
}
if (PEAR::isError($error = $this->_socket->disconnect())) {
return new PEAR_Error('Failed to disconnect socket: ' .
$error->getMessage());
}
return true;
}
/**
* Attempt to send the EHLO command and obtain a list of ESMTP
* extensions available, and failing that just send HELO.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
*
* @access private
* @since 1.1.0
*/
function _negotiate()
{
if (PEAR::isError($error = $this->_put('EHLO', $this->localhost))) {
return $error;
}
if (PEAR::isError($this->_parseResponse(250))) {
/* If we receive a 503 response, we're already authenticated. */
if ($this->_code === 503) {
return true;
}
/* If the EHLO failed, try the simpler HELO command. */
if (PEAR::isError($error = $this->_put('HELO', $this->localhost))) {
return $error;
}
if (PEAR::isError($this->_parseResponse(250))) {
return new PEAR_Error('HELO was not accepted: ', $this->_code);
}
return true;
}
foreach ($this->_arguments as $argument) {
$verb = strtok($argument, ' ');
$arguments = substr($argument, strlen($verb) + 1,
strlen($argument) - strlen($verb) - 1);
$this->_esmtp[$verb] = $arguments;
}
return true;
}
/**
* Returns the name of the best authentication method that the server
* has advertised.
*
* @return mixed Returns a string containing the name of the best
* supported authentication method or a PEAR_Error object
* if a failure condition is encountered.
* @access private
* @since 1.1.0
*/
function _getBestAuthMethod()
{
$available_methods = explode(' ', $this->_esmtp['AUTH']);
foreach ($this->auth_methods as $method) {
if (in_array($method, $available_methods)) {
return $method;
}
}
return new PEAR_Error('No supported authentication methods');
}
/**
* Attempt to do SMTP authentication.
*
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
* @param string The requested authentication method. If none is
* specified, the best supported method will be used.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access public
* @since 1.0
*/
function auth($uid, $pwd , $method = '')
{
if (empty($this->_esmtp['AUTH'])) {
return new PEAR_Error('SMTP server does no support authentication');
}
/*
* If no method has been specified, get the name of the best supported
* method advertised by the SMTP server.
*/
if (empty($method)) {
if (PEAR::isError($method = $this->_getBestAuthMethod())) {
/* Return the PEAR_Error object from _getBestAuthMethod(). */
return $method;
}
} else {
$method = strtoupper($method);
if (!in_array($method, $this->auth_methods)) {
return new PEAR_Error("$method is not a supported authentication method");
}
}
switch ($method) {
case 'DIGEST-MD5':
$result = $this->_authDigest_MD5($uid, $pwd);
break;
case 'CRAM-MD5':
$result = $this->_authCRAM_MD5($uid, $pwd);
break;
case 'LOGIN':
$result = $this->_authLogin($uid, $pwd);
break;
case 'PLAIN':
$result = $this->_authPlain($uid, $pwd);
break;
default:
$result = new PEAR_Error("$method is not a supported authentication method");
break;
}
/* If an error was encountered, return the PEAR_Error object. */
if (PEAR::isError($result)) {
return $result;
}
/* RFC-2554 requires us to re-negotiate ESMTP after an AUTH. */
if (PEAR::isError($error = $this->_negotiate())) {
return $error;
}
return true;
}
/**
* Authenticates the user using the DIGEST-MD5 method.
*
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access private
* @since 1.1.0
*/
function _authDigest_MD5($uid, $pwd)
{
if (PEAR::isError($error = $this->_put('AUTH', 'DIGEST-MD5'))) {
return $error;
}
/* 334: Continue authentication request */
if (PEAR::isError($error = $this->_parseResponse(334))) {
/* 503: Error: already authenticated */
if ($this->_code === 503) {
return true;
}
return $error;
}
$challenge = base64_decode($this->_arguments[0]);
$digest = &Auth_SASL::factory('digestmd5');
$auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge,
$this->host, "smtp"));
if (PEAR::isError($error = $this->_put($auth_str))) {
return $error;
}
/* 334: Continue authentication request */
if (PEAR::isError($error = $this->_parseResponse(334))) {
return $error;
}
/*
* We don't use the protocol's third step because SMTP doesn't allow
* subsequent authentication, so we just silently ignore it.
*/
if (PEAR::isError($error = $this->_put(' '))) {
return $error;
}
/* 235: Authentication successful */
if (PEAR::isError($error = $this->_parseResponse(235))) {
return $error;
}
}
/**
* Authenticates the user using the CRAM-MD5 method.
*
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access private
* @since 1.1.0
*/
function _authCRAM_MD5($uid, $pwd)
{
if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) {
return $error;
}
/* 334: Continue authentication request */
if (PEAR::isError($error = $this->_parseResponse(334))) {
/* 503: Error: already authenticated */
if ($this->_code === 503) {
return true;
}
return $error;
}
$challenge = base64_decode($this->_arguments[0]);
$cram = &Auth_SASL::factory('crammd5');
$auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
if (PEAR::isError($error = $this->_put($auth_str))) {
return $error;
}
/* 235: Authentication successful */
if (PEAR::isError($error = $this->_parseResponse(235))) {
return $error;
}
}
/**
* Authenticates the user using the LOGIN method.
*
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access private
* @since 1.1.0
*/
function _authLogin($uid, $pwd)
{
if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) {
return $error;
}
/* 334: Continue authentication request */
if (PEAR::isError($error = $this->_parseResponse(334))) {
/* 503: Error: already authenticated */
if ($this->_code === 503) {
return true;
}
return $error;
}
if (PEAR::isError($error = $this->_put(base64_encode($uid)))) {
return $error;
}
/* 334: Continue authentication request */
if (PEAR::isError($error = $this->_parseResponse(334))) {
return $error;
}
if (PEAR::isError($error = $this->_put(base64_encode($pwd)))) {
return $error;
}
/* 235: Authentication successful */
if (PEAR::isError($error = $this->_parseResponse(235))) {
return $error;
}
return true;
}
/**
* Authenticates the user using the PLAIN method.
*
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access private
* @since 1.1.0
*/
function _authPlain($uid, $pwd)
{
if (PEAR::isError($error = $this->_put('AUTH', 'PLAIN'))) {
return $error;
}
/* 334: Continue authentication request */
if (PEAR::isError($error = $this->_parseResponse(334))) {
/* 503: Error: already authenticated */
if ($this->_code === 503) {
return true;
}
return $error;
}
$auth_str = base64_encode(chr(0) . $uid . chr(0) . $pwd);
if (PEAR::isError($error = $this->_put($auth_str))) {
return $error;
}
/* 235: Authentication successful */
if (PEAR::isError($error = $this->_parseResponse(235))) {
return $error;
}
return true;
}
/**
* Send the HELO command.
*
* @param string The domain name to say we are.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access public
* @since 1.0
*/
function helo($domain)
{
if (PEAR::isError($error = $this->_put('HELO', $domain))) {
return $error;
}
if (PEAR::isError($error = $this->_parseResponse(250))) {
return $error;
}
return true;
}
/**
* Send the MAIL FROM: command.
*
* @param string The sender (reverse path) to set.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access public
* @since 1.0
*/
function mailFrom($sender)
{
if (PEAR::isError($error = $this->_put('MAIL', "FROM:<$sender>"))) {
return $error;
}
if (PEAR::isError($error = $this->_parseResponse(250))) {
return $error;
}
return true;
}
/**
* Send the RCPT TO: command.
*
* @param string The recipient (forward path) to add.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access public
* @since 1.0
*/
function rcptTo($recipient)
{
if (PEAR::isError($error = $this->_put('RCPT', "TO:<$recipient>"))) {
return $error;
}
if (PEAR::isError($error = $this->_parseResponse(array(250, 251)))) {
return $error;
}
return true;
}
/**
* Quote the data so that it meets SMTP standards.
*
* This is provided as a separate public function to facilitate easier
* overloading for the cases where it is desirable to customize the
* quoting behavior.
*
* @param string The message text to quote. The string must be passed
* by reference, and the text will be modified in place.
*
* @access public
* @since 1.2
*/
function quotedata(&$data)
{
/*
* Change Unix (\n) and Mac (\r) linefeeds into Internet-standard CRLF
* (\r\n) linefeeds.
*/
$data = preg_replace("/([^\r]{1})\n/", "\\1\r\n", $data);
$data = preg_replace("/\n\n/", "\n\r\n", $data);
/*
* Because a single leading period (.) signifies an end to the data,
* legitimate leading periods need to be "doubled" (e.g. '..').
*/
$data = preg_replace("/\n\./", "\n..", $data);
}
/**
* Send the DATA command.
*
* @param string The message body to send.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access public
* @since 1.0
*/
function data($data)
{
/*
* RFC 1870, section 3, subsection 3 states "a value of zero indicates
* that no fixed maximum message size is in force". Furthermore, it
* says that if "the parameter is omitted no information is conveyed
* about the server's fixed maximum message size".
*/
if (isset($this->_esmtp['SIZE']) && ($this->_esmtp['SIZE'] > 0)) {
if (strlen($data) >= $this->_esmtp['SIZE']) {
$this->disconnect();
return new PEAR_Error('Message size excedes the server limit');
}
}
/* Quote the data based on the SMTP standards. */
$this->quotedata($data);
if (PEAR::isError($error = $this->_put('DATA'))) {
return $error;
}
if (PEAR::isError($error = $this->_parseResponse(354))) {
return $error;
}
if (PEAR::isError($this->_send($data . "\r\n.\r\n"))) {
return new PEAR_Error('write to socket failed');
}
if (PEAR::isError($error = $this->_parseResponse(250))) {
return $error;
}
return true;
}
/**
* Send the SEND FROM: command.
*
* @param string The reverse path to send.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access public
* @since 1.2.6
*/
function sendFrom($path)
{
if (PEAR::isError($error = $this->_put('SEND', "FROM:<$path>"))) {
return $error;
}
if (PEAR::isError($error = $this->_parseResponse(250))) {
return $error;
}
return true;
}
/**
* Backwards-compatibility wrapper for sendFrom().
*
* @param string The reverse path to send.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
*
* @access public
* @since 1.0
* @deprecated 1.2.6
*/
function send_from($path)
{
return sendFrom($path);
}
/**
* Send the SOML FROM: command.
*
* @param string The reverse path to send.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access public
* @since 1.2.6
*/
function somlFrom($path)
{
if (PEAR::isError($error = $this->_put('SOML', "FROM:<$path>"))) {
return $error;
}
if (PEAR::isError($error = $this->_parseResponse(250))) {
return $error;
}
return true;
}
/**
* Backwards-compatibility wrapper for somlFrom().
*
* @param string The reverse path to send.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
*
* @access public
* @since 1.0
* @deprecated 1.2.6
*/
function soml_from($path)
{
return somlFrom($path);
}
/**
* Send the SAML FROM: command.
*
* @param string The reverse path to send.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access public
* @since 1.2.6
*/
function samlFrom($path)
{
if (PEAR::isError($error = $this->_put('SAML', "FROM:<$path>"))) {
return $error;
}
if (PEAR::isError($error = $this->_parseResponse(250))) {
return $error;
}
return true;
}
/**
* Backwards-compatibility wrapper for samlFrom().
*
* @param string The reverse path to send.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
*
* @access public
* @since 1.0
* @deprecated 1.2.6
*/
function saml_from($path)
{
return samlFrom($path);
}
/**
* Send the RSET command.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access public
* @since 1.0
*/
function rset()
{
if (PEAR::isError($error = $this->_put('RSET'))) {
return $error;
}
if (PEAR::isError($error = $this->_parseResponse(250))) {
return $error;
}
return true;
}
/**
* Send the VRFY command.
*
* @param string The string to verify
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access public
* @since 1.0
*/
function vrfy($string)
{
/* Note: 251 is also a valid response code */
if (PEAR::isError($error = $this->_put('VRFY', $string))) {
return $error;
}
if (PEAR::isError($error = $this->_parseResponse(250))) {
return $error;
}
return true;
}
/**
* Send the NOOP command.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access public
* @since 1.0
*/
function noop()
{
if (PEAR::isError($error = $this->_put('NOOP'))) {
return $error;
}
if (PEAR::isError($error = $this->_parseResponse(250))) {
return $error;
}
return true;
}
/**
* Backwards-compatibility method. identifySender()'s functionality is
* now handled internally.
*
* @return boolean This method always return true.
*
* @access public
* @since 1.0
*/
function identifySender()
{
return true;
}
}
?>

528
inc/Net/Socket.php Executable file
View File

@ -0,0 +1,528 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Stig Bakken <ssb@php.net> |
// | Chuck Hagenbuch <chuck@horde.org> |
// +----------------------------------------------------------------------+
//
// $Id: Socket.php,v 1.24 2005/02/03 20:40:16 chagenbu Exp $
require_once 'PEAR.php';
define('NET_SOCKET_READ', 1);
define('NET_SOCKET_WRITE', 2);
define('NET_SOCKET_ERROR', 3);
/**
* Generalized Socket class.
*
* @version 1.1
* @author Stig Bakken <ssb@php.net>
* @author Chuck Hagenbuch <chuck@horde.org>
*/
class Net_Socket extends PEAR {
/**
* Socket file pointer.
* @var resource $fp
*/
var $fp = null;
/**
* Whether the socket is blocking. Defaults to true.
* @var boolean $blocking
*/
var $blocking = true;
/**
* Whether the socket is persistent. Defaults to false.
* @var boolean $persistent
*/
var $persistent = false;
/**
* The IP address to connect to.
* @var string $addr
*/
var $addr = '';
/**
* The port number to connect to.
* @var integer $port
*/
var $port = 0;
/**
* Number of seconds to wait on socket connections before assuming
* there's no more data. Defaults to no timeout.
* @var integer $timeout
*/
var $timeout = false;
/**
* Number of bytes to read at a time in readLine() and
* readAll(). Defaults to 2048.
* @var integer $lineLength
*/
var $lineLength = 2048;
/**
* Connect to the specified port. If called when the socket is
* already connected, it disconnects and connects again.
*
* @param string $addr IP address or host name.
* @param integer $port TCP port number.
* @param boolean $persistent (optional) Whether the connection is
* persistent (kept open between requests
* by the web server).
* @param integer $timeout (optional) How long to wait for data.
* @param array $options See options for stream_context_create.
*
* @access public
*
* @return boolean | PEAR_Error True on success or a PEAR_Error on failure.
*/
function connect($addr, $port = 0, $persistent = null, $timeout = null, $options = null)
{
if (is_resource($this->fp)) {
@fclose($this->fp);
$this->fp = null;
}
if (!$addr) {
return $this->raiseError('$addr cannot be empty');
} elseif (strspn($addr, '.0123456789') == strlen($addr) ||
strstr($addr, '/') !== false) {
$this->addr = $addr;
} else {
$this->addr = @gethostbyname($addr);
}
$this->port = $port % 65536;
if ($persistent !== null) {
$this->persistent = $persistent;
}
if ($timeout !== null) {
$this->timeout = $timeout;
}
$openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen';
$errno = 0;
$errstr = '';
if ($options && function_exists('stream_context_create')) {
if ($this->timeout) {
$timeout = $this->timeout;
} else {
$timeout = 0;
}
$context = stream_context_create($options);
$fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout, $context);
} else {
if ($this->timeout) {
$fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $this->timeout);
} else {
$fp = @$openfunc($this->addr, $this->port, $errno, $errstr);
}
}
if (!$fp) {
return $this->raiseError($errstr, $errno);
}
$this->fp = $fp;
return $this->setBlocking($this->blocking);
}
/**
* Disconnects from the peer, closes the socket.
*
* @access public
* @return mixed true on success or an error object otherwise
*/
function disconnect()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
@fclose($this->fp);
$this->fp = null;
return true;
}
/**
* Find out if the socket is in blocking mode.
*
* @access public
* @return boolean The current blocking mode.
*/
function isBlocking()
{
return $this->blocking;
}
/**
* Sets whether the socket connection should be blocking or
* not. A read call to a non-blocking socket will return immediately
* if there is no data available, whereas it will block until there
* is data for blocking sockets.
*
* @param boolean $mode True for blocking sockets, false for nonblocking.
* @access public
* @return mixed true on success or an error object otherwise
*/
function setBlocking($mode)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$this->blocking = $mode;
socket_set_blocking($this->fp, $this->blocking);
return true;
}
/**
* Sets the timeout value on socket descriptor,
* expressed in the sum of seconds and microseconds
*
* @param integer $seconds Seconds.
* @param integer $microseconds Microseconds.
* @access public
* @return mixed true on success or an error object otherwise
*/
function setTimeout($seconds, $microseconds)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return socket_set_timeout($this->fp, $seconds, $microseconds);
}
/**
* Returns information about an existing socket resource.
* Currently returns four entries in the result array:
*
* <p>
* timed_out (bool) - The socket timed out waiting for data<br>
* blocked (bool) - The socket was blocked<br>
* eof (bool) - Indicates EOF event<br>
* unread_bytes (int) - Number of bytes left in the socket buffer<br>
* </p>
*
* @access public
* @return mixed Array containing information about existing socket resource or an error object otherwise
*/
function getStatus()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return socket_get_status($this->fp);
}
/**
* Get a specified line of data
*
* @access public
* @return $size bytes of data from the socket, or a PEAR_Error if
* not connected.
*/
function gets($size)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return @fgets($this->fp, $size);
}
/**
* Read a specified amount of data. This is guaranteed to return,
* and has the added benefit of getting everything in one fread()
* chunk; if you know the size of the data you're getting
* beforehand, this is definitely the way to go.
*
* @param integer $size The number of bytes to read from the socket.
* @access public
* @return $size bytes of data from the socket, or a PEAR_Error if
* not connected.
*/
function read($size)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return @fread($this->fp, $size);
}
/**
* Write a specified amount of data.
*
* @param string $data Data to write.
* @param integer $blocksize Amount of data to write at once.
* NULL means all at once.
*
* @access public
* @return mixed true on success or an error object otherwise
*/
function write($data, $blocksize = null)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
if (is_null($blocksize) && !OS_WINDOWS) {
return fwrite($this->fp, $data);
} else {
if (is_null($blocksize)) {
$blocksize = 1024;
}
$pos = 0;
$size = strlen($data);
while ($pos < $size) {
$written = @fwrite($this->fp, substr($data, $pos, $blocksize));
if ($written === false) {
return false;
}
$pos += $written;
}
return $pos;
}
}
/**
* Write a line of data to the socket, followed by a trailing "\r\n".
*
* @access public
* @return mixed fputs result, or an error
*/
function writeLine($data)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return fwrite($this->fp, $data . "\r\n");
}
/**
* Tests for end-of-file on a socket descriptor.
*
* @access public
* @return bool
*/
function eof()
{
return (is_resource($this->fp) && feof($this->fp));
}
/**
* Reads a byte of data
*
* @access public
* @return 1 byte of data from the socket, or a PEAR_Error if
* not connected.
*/
function readByte()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return ord(@fread($this->fp, 1));
}
/**
* Reads a word of data
*
* @access public
* @return 1 word of data from the socket, or a PEAR_Error if
* not connected.
*/
function readWord()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$buf = @fread($this->fp, 2);
return (ord($buf[0]) + (ord($buf[1]) << 8));
}
/**
* Reads an int of data
*
* @access public
* @return integer 1 int of data from the socket, or a PEAR_Error if
* not connected.
*/
function readInt()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$buf = @fread($this->fp, 4);
return (ord($buf[0]) + (ord($buf[1]) << 8) +
(ord($buf[2]) << 16) + (ord($buf[3]) << 24));
}
/**
* Reads a zero-terminated string of data
*
* @access public
* @return string, or a PEAR_Error if
* not connected.
*/
function readString()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$string = '';
while (($char = @fread($this->fp, 1)) != "\x00") {
$string .= $char;
}
return $string;
}
/**
* Reads an IP Address and returns it in a dot formated string
*
* @access public
* @return Dot formated string, or a PEAR_Error if
* not connected.
*/
function readIPAddress()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$buf = @fread($this->fp, 4);
return sprintf("%s.%s.%s.%s", ord($buf[0]), ord($buf[1]),
ord($buf[2]), ord($buf[3]));
}
/**
* Read until either the end of the socket or a newline, whichever
* comes first. Strips the trailing newline from the returned data.
*
* @access public
* @return All available data up to a newline, without that
* newline, or until the end of the socket, or a PEAR_Error if
* not connected.
*/
function readLine()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$line = '';
$timeout = time() + $this->timeout;
while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) {
$line .= @fgets($this->fp, $this->lineLength);
if (substr($line, -1) == "\n") {
return rtrim($line, "\r\n");
}
}
return $line;
}
/**
* Read until the socket closes, or until there is no more data in
* the inner PHP buffer. If the inner buffer is empty, in blocking
* mode we wait for at least 1 byte of data. Therefore, in
* blocking mode, if there is no data at all to be read, this
* function will never exit (unless the socket is closed on the
* remote end).
*
* @access public
*
* @return string All data until the socket closes, or a PEAR_Error if
* not connected.
*/
function readAll()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$data = '';
while (!feof($this->fp)) {
$data .= @fread($this->fp, $this->lineLength);
}
return $data;
}
/**
* Runs the equivalent of the select() system call on the socket
* with a timeout specified by tv_sec and tv_usec.
*
* @param integer $state Which of read/write/error to check for.
* @param integer $tv_sec Number of seconds for timeout.
* @param integer $tv_usec Number of microseconds for timeout.
*
* @access public
* @return False if select fails, integer describing which of read/write/error
* are ready, or PEAR_Error if not connected.
*/
function select($state, $tv_sec, $tv_usec = 0)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$read = null;
$write = null;
$except = null;
if ($state & NET_SOCKET_READ) {
$read[] = $this->fp;
}
if ($state & NET_SOCKET_WRITE) {
$write[] = $this->fp;
}
if ($state & NET_SOCKET_ERROR) {
$except[] = $this->fp;
}
if (false === ($sr = stream_select($read, $write, $except, $tv_sec, $tv_usec))) {
return false;
}
$result = 0;
if (count($read)) {
$result |= NET_SOCKET_READ;
}
if (count($write)) {
$result |= NET_SOCKET_WRITE;
}
if (count($except)) {
$result |= NET_SOCKET_ERROR;
}
return $result;
}
}

287
inc/OS/Guess.php Executable file
View File

@ -0,0 +1,287 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Stig Bakken <ssb@php.net> |
// | |
// +----------------------------------------------------------------------+
//
// $Id: Guess.php,v 1.13.4.1 2004/10/19 04:15:56 cellog Exp $
// {{{ uname examples
// php_uname() without args returns the same as 'uname -a', or a PHP-custom
// string for Windows.
// PHP versions prior to 4.3 return the uname of the host where PHP was built,
// as of 4.3 it returns the uname of the host running the PHP code.
//
// PC RedHat Linux 7.1:
// Linux host.example.com 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686 unknown
//
// PC Debian Potato:
// Linux host 2.4.17 #2 SMP Tue Feb 12 15:10:04 CET 2002 i686 unknown
//
// PC FreeBSD 3.3:
// FreeBSD host.example.com 3.3-STABLE FreeBSD 3.3-STABLE #0: Mon Feb 21 00:42:31 CET 2000 root@example.com:/usr/src/sys/compile/CONFIG i386
//
// PC FreeBSD 4.3:
// FreeBSD host.example.com 4.3-RELEASE FreeBSD 4.3-RELEASE #1: Mon Jun 25 11:19:43 EDT 2001 root@example.com:/usr/src/sys/compile/CONFIG i386
//
// PC FreeBSD 4.5:
// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb 6 23:59:23 CET 2002 root@example.com:/usr/src/sys/compile/CONFIG i386
//
// PC FreeBSD 4.5 w/uname from GNU shellutils:
// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb i386 unknown
//
// HP 9000/712 HP-UX 10:
// HP-UX iq B.10.10 A 9000/712 2008429113 two-user license
//
// HP 9000/712 HP-UX 10 w/uname from GNU shellutils:
// HP-UX host B.10.10 A 9000/712 unknown
//
// IBM RS6000/550 AIX 4.3:
// AIX host 3 4 000003531C00
//
// AIX 4.3 w/uname from GNU shellutils:
// AIX host 3 4 000003531C00 unknown
//
// SGI Onyx IRIX 6.5 w/uname from GNU shellutils:
// IRIX64 host 6.5 01091820 IP19 mips
//
// SGI Onyx IRIX 6.5:
// IRIX64 host 6.5 01091820 IP19
//
// SparcStation 20 Solaris 8 w/uname from GNU shellutils:
// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc
//
// SparcStation 20 Solaris 8:
// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc SUNW,SPARCstation-20
//
// Mac OS X (Darwin)
// Darwin home-eden.local 7.5.0 Darwin Kernel Version 7.5.0: Thu Aug 5 19:26:16 PDT 2004; root:xnu/xnu-517.7.21.obj~3/RELEASE_PPC Power Macintosh
//
// Mac OS X early versions
//
// }}}
/* TODO:
* - define endianness, to allow matchSignature("bigend") etc.
*/
class OS_Guess
{
var $sysname;
var $nodename;
var $cpu;
var $release;
var $extra;
function OS_Guess($uname = null)
{
list($this->sysname,
$this->release,
$this->cpu,
$this->extra,
$this->nodename) = $this->parseSignature($uname);
}
function parseSignature($uname = null)
{
static $sysmap = array(
'HP-UX' => 'hpux',
'IRIX64' => 'irix',
);
static $cpumap = array(
'i586' => 'i386',
'i686' => 'i386',
'ppc' => 'powerpc',
);
if ($uname === null) {
$uname = php_uname();
}
$parts = split('[[:space:]]+', trim($uname));
$n = count($parts);
$release = $machine = $cpu = '';
$sysname = $parts[0];
$nodename = $parts[1];
$cpu = $parts[$n-1];
$extra = '';
if ($cpu == 'unknown') {
$cpu = $parts[$n-2];
}
switch ($sysname) {
case 'AIX':
$release = "$parts[3].$parts[2]";
break;
case 'Windows':
switch ($parts[1]) {
case '95/98':
$release = '9x';
break;
default:
$release = $parts[1];
break;
}
$cpu = 'i386';
break;
case 'Linux':
$extra = $this->_detectGlibcVersion();
// use only the first two digits from the kernel version
$release = ereg_replace('^([[:digit:]]+\.[[:digit:]]+).*', '\1', $parts[2]);
break;
case 'Mac' :
$sysname = 'darwin';
$nodename = $parts[2];
$release = $parts[3];
if ($cpu == 'Macintosh') {
if ($parts[$n - 2] == 'Power') {
$cpu = 'powerpc';
}
}
break;
case 'Darwin' :
if ($cpu == 'Macintosh') {
if ($parts[$n - 2] == 'Power') {
$cpu = 'powerpc';
}
}
$release = ereg_replace('^([[:digit:]]+\.[[:digit:]]+).*', '\1', $parts[2]);
break;
default:
$release = ereg_replace('-.*', '', $parts[2]);
break;
}
if (isset($sysmap[$sysname])) {
$sysname = $sysmap[$sysname];
} else {
$sysname = strtolower($sysname);
}
if (isset($cpumap[$cpu])) {
$cpu = $cpumap[$cpu];
}
return array($sysname, $release, $cpu, $extra, $nodename);
}
function _detectGlibcVersion()
{
// Use glibc's <features.h> header file to
// get major and minor version number:
include_once "System.php";
$tmpfile = System::mktemp("glibctest");
$fp = fopen($tmpfile, "w");
fwrite($fp, "#include <features.h>\n__GLIBC__ __GLIBC_MINOR__\n");
fclose($fp);
$cpp = popen("/usr/bin/cpp $tmpfile", "r");
$major = $minor = 0;
while ($line = fgets($cpp, 1024)) {
if ($line{0} == '#' || trim($line) == '') {
continue;
}
if (list($major, $minor) = explode(' ', trim($line))) {
break;
}
}
pclose($cpp);
unlink($tmpfile);
if (!($major && $minor) && is_link('/lib/libc.so.6')) {
// Let's try reading the libc.so.6 symlink
if (ereg('^libc-([.*])\.so$', basename(readlink('/lib/libc.so.6')), $matches)) {
list($major, $minor) = explode('.', $matches);
}
}
if (!($major && $minor)) {
return '';
}
return "glibc{$major}.{$minor}";
}
function getSignature()
{
if (empty($this->extra)) {
return "{$this->sysname}-{$this->release}-{$this->cpu}";
}
return "{$this->sysname}-{$this->release}-{$this->cpu}-{$this->extra}";
}
function getSysname()
{
return $this->sysname;
}
function getNodename()
{
return $this->nodename;
}
function getCpu()
{
return $this->cpu;
}
function getRelease()
{
return $this->release;
}
function getExtra()
{
return $this->extra;
}
function matchSignature($match)
{
if (is_array($match)) {
$fragments = $match;
} else {
$fragments = explode('-', $match);
}
$n = count($fragments);
$matches = 0;
if ($n > 0) {
$matches += $this->_matchFragment($fragments[0], $this->sysname);
}
if ($n > 1) {
$matches += $this->_matchFragment($fragments[1], $this->release);
}
if ($n > 2) {
$matches += $this->_matchFragment($fragments[2], $this->cpu);
}
if ($n > 3) {
$matches += $this->_matchFragment($fragments[3], $this->extra);
}
return ($matches == $n);
}
function _matchFragment($fragment, $value)
{
if (strcspn($fragment, '*?') < strlen($fragment)) {
$reg = '^' . str_replace(array('*', '?', '/'), array('.*', '.', '\\/'), $fragment) . '$';
return eregi($reg, $value);
}
return ($fragment == '*' || !strcasecmp($fragment, $value));
}
}
/*
* Local Variables:
* indent-tabs-mode: nil
* c-basic-offset: 4
* End:
*/
?>

1055
inc/PEAR.php Executable file

File diff suppressed because it is too large Load Diff

208
inc/PEAR/Autoloader.php Executable file
View File

@ -0,0 +1,208 @@
<?php
// /* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Stig Bakken <ssb@php.net> |
// | |
// +----------------------------------------------------------------------+
//
// $Id: Autoloader.php,v 1.11 2004/02/27 02:21:29 cellog Exp $
if (!extension_loaded("overload")) {
// die hard without ext/overload
die("Rebuild PHP with the `overload' extension to use PEAR_Autoloader");
}
require_once "PEAR.php";
/**
* This class is for objects where you want to separate the code for
* some methods into separate classes. This is useful if you have a
* class with not-frequently-used methods that contain lots of code
* that you would like to avoid always parsing.
*
* The PEAR_Autoloader class provides autoloading and aggregation.
* The autoloading lets you set up in which classes the separated
* methods are found. Aggregation is the technique used to import new
* methods, an instance of each class providing separated methods is
* stored and called every time the aggregated method is called.
*
* @author Stig Sæther Bakken <ssb@php.net>
*/
class PEAR_Autoloader extends PEAR
{
// {{{ properties
/**
* Map of methods and classes where they are defined
*
* @var array
*
* @access private
*/
var $_autoload_map = array();
/**
* Map of methods and aggregate objects
*
* @var array
*
* @access private
*/
var $_method_map = array();
// }}}
// {{{ addAutoload()
/**
* Add one or more autoload entries.
*
* @param string $method which method to autoload
*
* @param string $classname (optional) which class to find the method in.
* If the $method parameter is an array, this
* parameter may be omitted (and will be ignored
* if not), and the $method parameter will be
* treated as an associative array with method
* names as keys and class names as values.
*
* @return void
*
* @access public
*/
function addAutoload($method, $classname = null)
{
if (is_array($method)) {
array_walk($method, create_function('$a,&$b', '$b = strtolower($b);'));
$this->_autoload_map = array_merge($this->_autoload_map, $method);
} else {
$this->_autoload_map[strtolower($method)] = $classname;
}
}
// }}}
// {{{ removeAutoload()
/**
* Remove an autoload entry.
*
* @param string $method which method to remove the autoload entry for
*
* @return bool TRUE if an entry was removed, FALSE if not
*
* @access public
*/
function removeAutoload($method)
{
$method = strtolower($method);
$ok = isset($this->_autoload_map[$method]);
unset($this->_autoload_map[$method]);
return $ok;
}
// }}}
// {{{ addAggregateObject()
/**
* Add an aggregate object to this object. If the specified class
* is not defined, loading it will be attempted following PEAR's
* file naming scheme. All the methods in the class will be
* aggregated, except private ones (name starting with an
* underscore) and constructors.
*
* @param string $classname what class to instantiate for the object.
*
* @return void
*
* @access public
*/
function addAggregateObject($classname)
{
$classname = strtolower($classname);
if (!class_exists($classname)) {
$include_file = preg_replace('/[^a-z0-9]/i', '_', $classname);
include_once $include_file;
}
$obj =& new $classname;
$methods = get_class_methods($classname);
foreach ($methods as $method) {
// don't import priviate methods and constructors
if ($method{0} != '_' && $method != $classname) {
$this->_method_map[$method] = $obj;
}
}
}
// }}}
// {{{ removeAggregateObject()
/**
* Remove an aggregate object.
*
* @param string $classname the class of the object to remove
*
* @return bool TRUE if an object was removed, FALSE if not
*
* @access public
*/
function removeAggregateObject($classname)
{
$ok = false;
$classname = strtolower($classname);
reset($this->_method_map);
while (list($method, $obj) = each($this->_method_map)) {
if (is_a($obj, $classname)) {
unset($this->_method_map[$method]);
$ok = true;
}
}
return $ok;
}
// }}}
// {{{ __call()
/**
* Overloaded object call handler, called each time an
* undefined/aggregated method is invoked. This method repeats
* the call in the right aggregate object and passes on the return
* value.
*
* @param string $method which method that was called
*
* @param string $args An array of the parameters passed in the
* original call
*
* @return mixed The return value from the aggregated method, or a PEAR
* error if the called method was unknown.
*/
function __call($method, $args, &$retval)
{
$method = strtolower($method);
if (empty($this->_method_map[$method]) && isset($this->_autoload_map[$method])) {
$this->addAggregateObject($this->_autoload_map[$method]);
}
if (isset($this->_method_map[$method])) {
$retval = call_user_func_array(array($this->_method_map[$method], $method), $args);
return true;
}
return false;
}
// }}}
}
overload("PEAR_Autoloader");
?>

426
inc/PEAR/Builder.php Executable file
View File

@ -0,0 +1,426 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Stig Sæther Bakken <ssb@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Builder.php,v 1.16.2.3 2005/02/17 17:55:01 cellog Exp $
require_once 'PEAR/Common.php';
/**
* Class to handle building (compiling) extensions.
*
* @author Stig Sæther Bakken <ssb@php.net>
*/
class PEAR_Builder extends PEAR_Common
{
// {{{ properties
var $php_api_version = 0;
var $zend_module_api_no = 0;
var $zend_extension_api_no = 0;
var $extensions_built = array();
var $current_callback = null;
// used for msdev builds
var $_lastline = null;
var $_firstline = null;
// }}}
// {{{ constructor
/**
* PEAR_Builder constructor.
*
* @param object $ui user interface object (instance of PEAR_Frontend_*)
*
* @access public
*/
function PEAR_Builder(&$ui)
{
parent::PEAR_Common();
$this->setFrontendObject($ui);
}
// }}}
// {{{ _build_win32()
/**
* Build an extension from source on windows.
* requires msdev
*/
function _build_win32($descfile, $callback = null)
{
if (PEAR::isError($info = $this->infoFromDescriptionFile($descfile))) {
return $info;
}
$dir = dirname($descfile);
$old_cwd = getcwd();
if (!@chdir($dir)) {
return $this->raiseError("could not chdir to $dir");
}
$this->log(2, "building in $dir");
$dsp = $info['package'].'.dsp';
if (!@is_file("$dir/$dsp")) {
return $this->raiseError("The DSP $dsp does not exist.");
}
// XXX TODO: make release build type configurable
$command = 'msdev '.$dsp.' /MAKE "'.$info['package']. ' - Release"';
$this->current_callback = $callback;
$err = $this->_runCommand($command, array(&$this, 'msdevCallback'));
if (PEAR::isError($err)) {
return $err;
}
// figure out the build platform and type
$platform = 'Win32';
$buildtype = 'Release';
if (preg_match('/.*?'.$info['package'].'\s-\s(\w+)\s(.*?)-+/i',$this->_firstline,$matches)) {
$platform = $matches[1];
$buildtype = $matches[2];
}
if (preg_match('/(.*)?\s-\s(\d+).*?(\d+)/',$this->_lastline,$matches)) {
if ($matches[2]) {
// there were errors in the build
return $this->raiseError("There were errors during compilation.");
}
$out = $matches[1];
} else {
return $this->raiseError("Did not understand the completion status returned from msdev.exe.");
}
// msdev doesn't tell us the output directory :/
// open the dsp, find /out and use that directory
$dsptext = join(file($dsp),'');
// this regex depends on the build platform and type having been
// correctly identified above.
$regex ='/.*?!IF\s+"\$\(CFG\)"\s+==\s+("'.
$info['package'].'\s-\s'.
$platform.'\s'.
$buildtype.'").*?'.
'\/out:"(.*?)"/is';
if ($dsptext && preg_match($regex,$dsptext,$matches)) {
// what we get back is a relative path to the output file itself.
$outfile = realpath($matches[2]);
} else {
return $this->raiseError("Could not retrieve output information from $dsp.");
}
if (@copy($outfile, "$dir/$out")) {
$outfile = "$dir/$out";
}
$built_files[] = array(
'file' => "$outfile",
'php_api' => $this->php_api_version,
'zend_mod_api' => $this->zend_module_api_no,
'zend_ext_api' => $this->zend_extension_api_no,
);
return $built_files;
}
// }}}
// {{{ msdevCallback()
function msdevCallback($what, $data)
{
if (!$this->_firstline)
$this->_firstline = $data;
$this->_lastline = $data;
}
// }}}
// {{{ _harventInstDir
/**
* @param string
* @param string
* @param array
* @access private
*/
function _harvestInstDir($dest_prefix, $dirname, &$built_files)
{
$d = opendir($dirname);
if (!$d)
return false;
$ret = true;
while (($ent = readdir($d)) !== false) {
if ($ent{0} == '.')
continue;
$full = $dirname . DIRECTORY_SEPARATOR . $ent;
if (is_dir($full)) {
if (!$this->_harvestInstDir(
$dest_prefix . DIRECTORY_SEPARATOR . $ent,
$full, $built_files)) {
$ret = false;
break;
}
} else {
$dest = $dest_prefix . DIRECTORY_SEPARATOR . $ent;
$built_files[] = array(
'file' => $full,
'dest' => $dest,
'php_api' => $this->php_api_version,
'zend_mod_api' => $this->zend_module_api_no,
'zend_ext_api' => $this->zend_extension_api_no,
);
}
}
closedir($d);
return $ret;
}
// }}}
// {{{ build()
/**
* Build an extension from source. Runs "phpize" in the source
* directory, but compiles in a temporary directory
* (/var/tmp/pear-build-USER/PACKAGE-VERSION).
*
* @param string $descfile path to XML package description file
*
* @param mixed $callback callback function used to report output,
* see PEAR_Builder::_runCommand for details
*
* @return array an array of associative arrays with built files,
* format:
* array( array( 'file' => '/path/to/ext.so',
* 'php_api' => YYYYMMDD,
* 'zend_mod_api' => YYYYMMDD,
* 'zend_ext_api' => YYYYMMDD ),
* ... )
*
* @access public
*
* @see PEAR_Builder::_runCommand
* @see PEAR_Common::infoFromDescriptionFile
*/
function build($descfile, $callback = null)
{
if (PEAR_OS == "Windows") {
return $this->_build_win32($descfile,$callback);
}
if (PEAR_OS != 'Unix') {
return $this->raiseError("building extensions not supported on this platform");
}
if (PEAR::isError($info = $this->infoFromDescriptionFile($descfile))) {
return $info;
}
$dir = dirname($descfile);
$old_cwd = getcwd();
if (!@chdir($dir)) {
return $this->raiseError("could not chdir to $dir");
}
$vdir = "$info[package]-$info[version]";
if (is_dir($vdir)) {
chdir($vdir);
}
$dir = getcwd();
$this->log(2, "building in $dir");
$this->current_callback = $callback;
putenv('PATH=' . $this->config->get('bin_dir') . ':' . getenv('PATH'));
$err = $this->_runCommand("phpize", array(&$this, 'phpizeCallback'));
if (PEAR::isError($err)) {
return $err;
}
if (!$err) {
return $this->raiseError("`phpize' failed");
}
// {{{ start of interactive part
$configure_command = "$dir/configure";
if (isset($info['configure_options'])) {
foreach ($info['configure_options'] as $o) {
list($r) = $this->ui->userDialog('build',
array($o['prompt']),
array('text'),
array(@$o['default']));
if (substr($o['name'], 0, 5) == 'with-' &&
($r == 'yes' || $r == 'autodetect')) {
$configure_command .= " --$o[name]";
} else {
$configure_command .= " --$o[name]=".trim($r);
}
}
}
// }}} end of interactive part
// FIXME make configurable
if(!$user=getenv('USER')){
$user='defaultuser';
}
$build_basedir = "/var/tmp/pear-build-$user";
$build_dir = "$build_basedir/$info[package]-$info[version]";
$inst_dir = "$build_basedir/install-$info[package]-$info[version]";
$this->log(1, "building in $build_dir");
if (is_dir($build_dir)) {
System::rm('-rf', $build_dir);
}
if (!System::mkDir(array('-p', $build_dir))) {
return $this->raiseError("could not create build dir: $build_dir");
}
$this->addTempFile($build_dir);
if (!System::mkDir(array('-p', $inst_dir))) {
return $this->raiseError("could not create temporary install dir: $inst_dir");
}
$this->addTempFile($inst_dir);
if (getenv('MAKE')) {
$make_command = getenv('MAKE');
} else {
$make_command = 'make';
}
$to_run = array(
$configure_command,
$make_command,
"$make_command INSTALL_ROOT=\"$inst_dir\" install",
"find \"$inst_dir\" -ls"
);
if (!@chdir($build_dir)) {
return $this->raiseError("could not chdir to $build_dir");
}
putenv('PHP_PEAR_VERSION=@PEAR-VER@');
foreach ($to_run as $cmd) {
$err = $this->_runCommand($cmd, $callback);
if (PEAR::isError($err)) {
chdir($old_cwd);
return $err;
}
if (!$err) {
chdir($old_cwd);
return $this->raiseError("`$cmd' failed");
}
}
if (!($dp = opendir("modules"))) {
chdir($old_cwd);
return $this->raiseError("no `modules' directory found");
}
$built_files = array();
$prefix = exec("php-config --prefix");
$this->_harvestInstDir($prefix, $inst_dir . DIRECTORY_SEPARATOR . $prefix, $built_files);
chdir($old_cwd);
return $built_files;
}
// }}}
// {{{ phpizeCallback()
/**
* Message callback function used when running the "phpize"
* program. Extracts the API numbers used. Ignores other message
* types than "cmdoutput".
*
* @param string $what the type of message
* @param mixed $data the message
*
* @return void
*
* @access public
*/
function phpizeCallback($what, $data)
{
if ($what != 'cmdoutput') {
return;
}
$this->log(1, rtrim($data));
if (preg_match('/You should update your .aclocal.m4/', $data)) {
return;
}
$matches = array();
if (preg_match('/^\s+(\S[^:]+):\s+(\d{8})/', $data, $matches)) {
$member = preg_replace('/[^a-z]/', '_', strtolower($matches[1]));
$apino = (int)$matches[2];
if (isset($this->$member)) {
$this->$member = $apino;
//$msg = sprintf("%-22s : %d", $matches[1], $apino);
//$this->log(1, $msg);
}
}
}
// }}}
// {{{ _runCommand()
/**
* Run an external command, using a message callback to report
* output. The command will be run through popen and output is
* reported for every line with a "cmdoutput" message with the
* line string, including newlines, as payload.
*
* @param string $command the command to run
*
* @param mixed $callback (optional) function to use as message
* callback
*
* @return bool whether the command was successful (exit code 0
* means success, any other means failure)
*
* @access private
*/
function _runCommand($command, $callback = null)
{
$this->log(1, "running: $command");
$pp = @popen("$command 2>&1", "r");
if (!$pp) {
return $this->raiseError("failed to run `$command'");
}
if ($callback && $callback[0]->debug == 1) {
$olddbg = $callback[0]->debug;
$callback[0]->debug = 2;
}
while ($line = fgets($pp, 1024)) {
if ($callback) {
call_user_func($callback, 'cmdoutput', $line);
} else {
$this->log(2, rtrim($line));
}
}
if ($callback && isset($olddbg)) {
$callback[0]->debug = $olddbg;
}
$exitcode = @pclose($pp);
return ($exitcode == 0);
}
// }}}
// {{{ log()
function log($level, $msg)
{
if ($this->current_callback) {
if ($this->debug >= $level) {
call_user_func($this->current_callback, 'output', $msg);
}
return;
}
return PEAR_Common::log($level, $msg);
}
// }}}
}
?>

398
inc/PEAR/Command.php Executable file
View File

@ -0,0 +1,398 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Stig Bakken <ssb@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Command.php,v 1.24 2004/05/16 15:43:30 pajoye Exp $
require_once "PEAR.php";
/**
* List of commands and what classes they are implemented in.
* @var array command => implementing class
*/
$GLOBALS['_PEAR_Command_commandlist'] = array();
/**
* List of shortcuts to common commands.
* @var array shortcut => command
*/
$GLOBALS['_PEAR_Command_shortcuts'] = array();
/**
* Array of command objects
* @var array class => object
*/
$GLOBALS['_PEAR_Command_objects'] = array();
/**
* Which user interface class is being used.
* @var string class name
*/
$GLOBALS['_PEAR_Command_uiclass'] = 'PEAR_Frontend_CLI';
/**
* Instance of $_PEAR_Command_uiclass.
* @var object
*/
$GLOBALS['_PEAR_Command_uiobject'] = null;
/**
* PEAR command class, a simple factory class for administrative
* commands.
*
* How to implement command classes:
*
* - The class must be called PEAR_Command_Nnn, installed in the
* "PEAR/Common" subdir, with a method called getCommands() that
* returns an array of the commands implemented by the class (see
* PEAR/Command/Install.php for an example).
*
* - The class must implement a run() function that is called with three
* params:
*
* (string) command name
* (array) assoc array with options, freely defined by each
* command, for example:
* array('force' => true)
* (array) list of the other parameters
*
* The run() function returns a PEAR_CommandResponse object. Use
* these methods to get information:
*
* int getStatus() Returns PEAR_COMMAND_(SUCCESS|FAILURE|PARTIAL)
* *_PARTIAL means that you need to issue at least
* one more command to complete the operation
* (used for example for validation steps).
*
* string getMessage() Returns a message for the user. Remember,
* no HTML or other interface-specific markup.
*
* If something unexpected happens, run() returns a PEAR error.
*
* - DON'T OUTPUT ANYTHING! Return text for output instead.
*
* - DON'T USE HTML! The text you return will be used from both Gtk,
* web and command-line interfaces, so for now, keep everything to
* plain text.
*
* - DON'T USE EXIT OR DIE! Always use pear errors. From static
* classes do PEAR::raiseError(), from other classes do
* $this->raiseError().
*/
class PEAR_Command
{
// {{{ factory()
/**
* Get the right object for executing a command.
*
* @param string $command The name of the command
* @param object $config Instance of PEAR_Config object
*
* @return object the command object or a PEAR error
*
* @access public
* @static
*/
function factory($command, &$config)
{
if (empty($GLOBALS['_PEAR_Command_commandlist'])) {
PEAR_Command::registerCommands();
}
if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) {
$command = $GLOBALS['_PEAR_Command_shortcuts'][$command];
}
if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) {
return PEAR::raiseError("unknown command `$command'");
}
$class = $GLOBALS['_PEAR_Command_commandlist'][$command];
if (!class_exists($class)) {
return PEAR::raiseError("unknown command `$command'");
}
$ui =& PEAR_Command::getFrontendObject();
$obj = &new $class($ui, $config);
return $obj;
}
// }}}
// {{{ & getFrontendObject()
/**
* Get instance of frontend object.
*
* @return object
* @static
*/
function &getFrontendObject()
{
if (empty($GLOBALS['_PEAR_Command_uiobject'])) {
$GLOBALS['_PEAR_Command_uiobject'] = &new $GLOBALS['_PEAR_Command_uiclass'];
}
return $GLOBALS['_PEAR_Command_uiobject'];
}
// }}}
// {{{ & setFrontendClass()
/**
* Load current frontend class.
*
* @param string $uiclass Name of class implementing the frontend
*
* @return object the frontend object, or a PEAR error
* @static
*/
function &setFrontendClass($uiclass)
{
if (is_object($GLOBALS['_PEAR_Command_uiobject']) &&
is_a($GLOBALS['_PEAR_Command_uiobject'], $uiclass)) {
return $GLOBALS['_PEAR_Command_uiobject'];
}
if (!class_exists($uiclass)) {
$file = str_replace('_', '/', $uiclass) . '.php';
if (PEAR_Command::isIncludeable($file)) {
include_once $file;
}
}
if (class_exists($uiclass)) {
$obj = &new $uiclass;
// quick test to see if this class implements a few of the most
// important frontend methods
if (method_exists($obj, 'userConfirm')) {
$GLOBALS['_PEAR_Command_uiobject'] = &$obj;
$GLOBALS['_PEAR_Command_uiclass'] = $uiclass;
return $obj;
} else {
$err = PEAR::raiseError("not a frontend class: $uiclass");
return $err;
}
}
$err = PEAR::raiseError("no such class: $uiclass");
return $err;
}
// }}}
// {{{ setFrontendType()
// }}}
// {{{ isIncludeable()
/**
* @param string $path relative or absolute include path
* @return boolean
* @static
*/
function isIncludeable($path)
{
if (file_exists($path) && is_readable($path)) {
return true;
}
$ipath = explode(PATH_SEPARATOR, ini_get('include_path'));
foreach ($ipath as $include) {
$test = realpath($include . DIRECTORY_SEPARATOR . $path);
if (file_exists($test) && is_readable($test)) {
return true;
}
}
return false;
}
/**
* Set current frontend.
*
* @param string $uitype Name of the frontend type (for example "CLI")
*
* @return object the frontend object, or a PEAR error
* @static
*/
function setFrontendType($uitype)
{
$uiclass = 'PEAR_Frontend_' . $uitype;
return PEAR_Command::setFrontendClass($uiclass);
}
// }}}
// {{{ registerCommands()
/**
* Scan through the Command directory looking for classes
* and see what commands they implement.
*
* @param bool (optional) if FALSE (default), the new list of
* commands should replace the current one. If TRUE,
* new entries will be merged with old.
*
* @param string (optional) where (what directory) to look for
* classes, defaults to the Command subdirectory of
* the directory from where this file (__FILE__) is
* included.
*
* @return bool TRUE on success, a PEAR error on failure
*
* @access public
* @static
*/
function registerCommands($merge = false, $dir = null)
{
if ($dir === null) {
$dir = dirname(__FILE__) . '/Command';
}
$dp = @opendir($dir);
if (empty($dp)) {
return PEAR::raiseError("registerCommands: opendir($dir) failed");
}
if (!$merge) {
$GLOBALS['_PEAR_Command_commandlist'] = array();
}
while ($entry = readdir($dp)) {
if ($entry{0} == '.' || substr($entry, -4) != '.php' || $entry == 'Common.php') {
continue;
}
$class = "PEAR_Command_".substr($entry, 0, -4);
$file = "$dir/$entry";
include_once $file;
// List of commands
if (empty($GLOBALS['_PEAR_Command_objects'][$class])) {
$GLOBALS['_PEAR_Command_objects'][$class] = &new $class($ui, $config);
}
$implements = $GLOBALS['_PEAR_Command_objects'][$class]->getCommands();
foreach ($implements as $command => $desc) {
$GLOBALS['_PEAR_Command_commandlist'][$command] = $class;
$GLOBALS['_PEAR_Command_commanddesc'][$command] = $desc;
}
$shortcuts = $GLOBALS['_PEAR_Command_objects'][$class]->getShortcuts();
foreach ($shortcuts as $shortcut => $command) {
$GLOBALS['_PEAR_Command_shortcuts'][$shortcut] = $command;
}
}
@closedir($dp);
return true;
}
// }}}
// {{{ getCommands()
/**
* Get the list of currently supported commands, and what
* classes implement them.
*
* @return array command => implementing class
*
* @access public
* @static
*/
function getCommands()
{
if (empty($GLOBALS['_PEAR_Command_commandlist'])) {
PEAR_Command::registerCommands();
}
return $GLOBALS['_PEAR_Command_commandlist'];
}
// }}}
// {{{ getShortcuts()
/**
* Get the list of command shortcuts.
*
* @return array shortcut => command
*
* @access public
* @static
*/
function getShortcuts()
{
if (empty($GLOBALS['_PEAR_Command_shortcuts'])) {
PEAR_Command::registerCommands();
}
return $GLOBALS['_PEAR_Command_shortcuts'];
}
// }}}
// {{{ getGetoptArgs()
/**
* Compiles arguments for getopt.
*
* @param string $command command to get optstring for
* @param string $short_args (reference) short getopt format
* @param array $long_args (reference) long getopt format
*
* @return void
*
* @access public
* @static
*/
function getGetoptArgs($command, &$short_args, &$long_args)
{
if (empty($GLOBALS['_PEAR_Command_commandlist'])) {
PEAR_Command::registerCommands();
}
if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) {
return null;
}
$class = $GLOBALS['_PEAR_Command_commandlist'][$command];
$obj = &$GLOBALS['_PEAR_Command_objects'][$class];
return $obj->getGetoptArgs($command, $short_args, $long_args);
}
// }}}
// {{{ getDescription()
/**
* Get description for a command.
*
* @param string $command Name of the command
*
* @return string command description
*
* @access public
* @static
*/
function getDescription($command)
{
if (!isset($GLOBALS['_PEAR_Command_commanddesc'][$command])) {
return null;
}
return $GLOBALS['_PEAR_Command_commanddesc'][$command];
}
// }}}
// {{{ getHelp()
/**
* Get help for command.
*
* @param string $command Name of the command to return help for
*
* @access public
* @static
*/
function getHelp($command)
{
$cmds = PEAR_Command::getCommands();
if (isset($cmds[$command])) {
$class = $cmds[$command];
return $GLOBALS['_PEAR_Command_objects'][$class]->getHelp($command);
}
return false;
}
// }}}
}
?>

155
inc/PEAR/Command/Auth.php Executable file
View File

@ -0,0 +1,155 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Stig Bakken <ssb@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Auth.php,v 1.15 2004/01/08 17:33:13 sniper Exp $
require_once "PEAR/Command/Common.php";
require_once "PEAR/Remote.php";
require_once "PEAR/Config.php";
/**
* PEAR commands for managing configuration data.
*
*/
class PEAR_Command_Auth extends PEAR_Command_Common
{
// {{{ properties
var $commands = array(
'login' => array(
'summary' => 'Connects and authenticates to remote server',
'shortcut' => 'li',
'function' => 'doLogin',
'options' => array(),
'doc' => '
Log in to the remote server. To use remote functions in the installer
that require any kind of privileges, you need to log in first. The
username and password you enter here will be stored in your per-user
PEAR configuration (~/.pearrc on Unix-like systems). After logging
in, your username and password will be sent along in subsequent
operations on the remote server.',
),
'logout' => array(
'summary' => 'Logs out from the remote server',
'shortcut' => 'lo',
'function' => 'doLogout',
'options' => array(),
'doc' => '
Logs out from the remote server. This command does not actually
connect to the remote server, it only deletes the stored username and
password from your user configuration.',
)
);
// }}}
// {{{ constructor
/**
* PEAR_Command_Auth constructor.
*
* @access public
*/
function PEAR_Command_Auth(&$ui, &$config)
{
parent::PEAR_Command_Common($ui, $config);
}
// }}}
// {{{ doLogin()
/**
* Execute the 'login' command.
*
* @param string $command command name
*
* @param array $options option_name => value
*
* @param array $params list of additional parameters
*
* @return bool TRUE on success, FALSE for unknown commands, or
* a PEAR error on failure
*
* @access public
*/
function doLogin($command, $options, $params)
{
$server = $this->config->get('master_server');
$remote = new PEAR_Remote($this->config);
$username = $this->config->get('username');
if (empty($username)) {
$username = @$_ENV['USER'];
}
$this->ui->outputData("Logging in to $server.", $command);
list($username, $password) = $this->ui->userDialog(
$command,
array('Username', 'Password'),
array('text', 'password'),
array($username, '')
);
$username = trim($username);
$password = trim($password);
$this->config->set('username', $username);
$this->config->set('password', $password);
$remote->expectError(401);
$ok = $remote->call('logintest');
$remote->popExpect();
if ($ok === true) {
$this->ui->outputData("Logged in.", $command);
$this->config->store();
} else {
return $this->raiseError("Login failed!");
}
}
// }}}
// {{{ doLogout()
/**
* Execute the 'logout' command.
*
* @param string $command command name
*
* @param array $options option_name => value
*
* @param array $params list of additional parameters
*
* @return bool TRUE on success, FALSE for unknown commands, or
* a PEAR error on failure
*
* @access public
*/
function doLogout($command, $options, $params)
{
$server = $this->config->get('master_server');
$this->ui->outputData("Logging out from $server.", $command);
$this->config->remove('username');
$this->config->remove('password');
$this->config->store();
}
// }}}
}
?>

89
inc/PEAR/Command/Build.php Executable file
View File

@ -0,0 +1,89 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Stig Bakken <ssb@php.net> |
// | Tomas V.V.Cox <cox@idecnet.com> |
// | |
// +----------------------------------------------------------------------+
//
// $Id: Build.php,v 1.9 2004/01/08 17:33:13 sniper Exp $
require_once "PEAR/Command/Common.php";
require_once "PEAR/Builder.php";
/**
* PEAR commands for building extensions.
*
*/
class PEAR_Command_Build extends PEAR_Command_Common
{
// {{{ properties
var $commands = array(
'build' => array(
'summary' => 'Build an Extension From C Source',
'function' => 'doBuild',
'shortcut' => 'b',
'options' => array(),
'doc' => '[package.xml]
Builds one or more extensions contained in a package.'
),
);
// }}}
// {{{ constructor
/**
* PEAR_Command_Build constructor.
*
* @access public
*/
function PEAR_Command_Build(&$ui, &$config)
{
parent::PEAR_Command_Common($ui, $config);
}
// }}}
// {{{ doBuild()
function doBuild($command, $options, $params)
{
if (sizeof($params) < 1) {
$params[0] = 'package.xml';
}
$builder = &new PEAR_Builder($this->ui);
$this->debug = $this->config->get('verbose');
$err = $builder->build($params[0], array(&$this, 'buildCallback'));
if (PEAR::isError($err)) {
return $err;
}
return true;
}
// }}}
// {{{ buildCallback()
function buildCallback($what, $data)
{
if (($what == 'cmdoutput' && $this->debug > 1) ||
($what == 'output' && $this->debug > 0)) {
$this->ui->outputData(rtrim($data), 'build');
}
}
// }}}
}

249
inc/PEAR/Command/Common.php Executable file
View File

@ -0,0 +1,249 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Stig Sæther Bakken <ssb@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Common.php,v 1.24 2004/01/08 17:33:13 sniper Exp $
require_once "PEAR.php";
class PEAR_Command_Common extends PEAR
{
// {{{ properties
/**
* PEAR_Config object used to pass user system and configuration
* on when executing commands
*
* @var object
*/
var $config;
/**
* User Interface object, for all interaction with the user.
* @var object
*/
var $ui;
var $_deps_rel_trans = array(
'lt' => '<',
'le' => '<=',
'eq' => '=',
'ne' => '!=',
'gt' => '>',
'ge' => '>=',
'has' => '=='
);
var $_deps_type_trans = array(
'pkg' => 'package',
'extension' => 'extension',
'php' => 'PHP',
'prog' => 'external program',
'ldlib' => 'external library for linking',
'rtlib' => 'external runtime library',
'os' => 'operating system',
'websrv' => 'web server',
'sapi' => 'SAPI backend'
);
// }}}
// {{{ constructor
/**
* PEAR_Command_Common constructor.
*
* @access public
*/
function PEAR_Command_Common(&$ui, &$config)
{
parent::PEAR();
$this->config = &$config;
$this->ui = &$ui;
}
// }}}
// {{{ getCommands()
/**
* Return a list of all the commands defined by this class.
* @return array list of commands
* @access public
*/
function getCommands()
{
$ret = array();
foreach (array_keys($this->commands) as $command) {
$ret[$command] = $this->commands[$command]['summary'];
}
return $ret;
}
// }}}
// {{{ getShortcuts()
/**
* Return a list of all the command shortcuts defined by this class.
* @return array shortcut => command
* @access public
*/
function getShortcuts()
{
$ret = array();
foreach (array_keys($this->commands) as $command) {
if (isset($this->commands[$command]['shortcut'])) {
$ret[$this->commands[$command]['shortcut']] = $command;
}
}
return $ret;
}
// }}}
// {{{ getOptions()
function getOptions($command)
{
return @$this->commands[$command]['options'];
}
// }}}
// {{{ getGetoptArgs()
function getGetoptArgs($command, &$short_args, &$long_args)
{
$short_args = "";
$long_args = array();
if (empty($this->commands[$command])) {
return;
}
reset($this->commands[$command]);
while (list($option, $info) = each($this->commands[$command]['options'])) {
$larg = $sarg = '';
if (isset($info['arg'])) {
if ($info['arg']{0} == '(') {
$larg = '==';
$sarg = '::';
$arg = substr($info['arg'], 1, -1);
} else {
$larg = '=';
$sarg = ':';
$arg = $info['arg'];
}
}
if (isset($info['shortopt'])) {
$short_args .= $info['shortopt'] . $sarg;
}
$long_args[] = $option . $larg;
}
}
// }}}
// {{{ getHelp()
/**
* Returns the help message for the given command
*
* @param string $command The command
* @return mixed A fail string if the command does not have help or
* a two elements array containing [0]=>help string,
* [1]=> help string for the accepted cmd args
*/
function getHelp($command)
{
$config = &PEAR_Config::singleton();
$help = @$this->commands[$command]['doc'];
if (empty($help)) {
// XXX (cox) Fallback to summary if there is no doc (show both?)
if (!$help = @$this->commands[$command]['summary']) {
return "No help for command \"$command\"";
}
}
if (preg_match_all('/{config\s+([^\}]+)}/e', $help, $matches)) {
foreach($matches[0] as $k => $v) {
$help = preg_replace("/$v/", $config->get($matches[1][$k]), $help);
}
}
return array($help, $this->getHelpArgs($command));
}
// }}}
// {{{ getHelpArgs()
/**
* Returns the help for the accepted arguments of a command
*
* @param string $command
* @return string The help string
*/
function getHelpArgs($command)
{
if (isset($this->commands[$command]['options']) &&
count($this->commands[$command]['options']))
{
$help = "Options:\n";
foreach ($this->commands[$command]['options'] as $k => $v) {
if (isset($v['arg'])) {
if ($v['arg']{0} == '(') {
$arg = substr($v['arg'], 1, -1);
$sapp = " [$arg]";
$lapp = "[=$arg]";
} else {
$sapp = " $v[arg]";
$lapp = "=$v[arg]";
}
} else {
$sapp = $lapp = "";
}
if (isset($v['shortopt'])) {
$s = $v['shortopt'];
@$help .= " -$s$sapp, --$k$lapp\n";
} else {
@$help .= " --$k$lapp\n";
}
$p = " ";
$doc = rtrim(str_replace("\n", "\n$p", $v['doc']));
$help .= " $doc\n";
}
return $help;
}
return null;
}
// }}}
// {{{ run()
function run($command, $options, $params)
{
$func = @$this->commands[$command]['function'];
if (empty($func)) {
// look for shortcuts
foreach (array_keys($this->commands) as $cmd) {
if (@$this->commands[$cmd]['shortcut'] == $command) {
$command = $cmd;
$func = @$this->commands[$command]['function'];
if (empty($func)) {
return $this->raiseError("unknown command `$command'");
}
break;
}
}
}
return $this->$func($command, $options, $params);
}
// }}}
}
?>

225
inc/PEAR/Command/Config.php Executable file
View File

@ -0,0 +1,225 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Stig Bakken <ssb@php.net> |
// | Tomas V.V.Cox <cox@idecnet.com> |
// | |
// +----------------------------------------------------------------------+
//
// $Id: Config.php,v 1.27 2004/06/15 16:48:49 pajoye Exp $
require_once "PEAR/Command/Common.php";
require_once "PEAR/Config.php";
/**
* PEAR commands for managing configuration data.
*
*/
class PEAR_Command_Config extends PEAR_Command_Common
{
// {{{ properties
var $commands = array(
'config-show' => array(
'summary' => 'Show All Settings',
'function' => 'doConfigShow',
'shortcut' => 'csh',
'options' => array(),
'doc' => '
Displays all configuration values. An optional argument
may be used to tell which configuration layer to display. Valid
configuration layers are "user", "system" and "default".
',
),
'config-get' => array(
'summary' => 'Show One Setting',
'function' => 'doConfigGet',
'shortcut' => 'cg',
'options' => array(),
'doc' => '<parameter> [layer]
Displays the value of one configuration parameter. The
first argument is the name of the parameter, an optional second argument
may be used to tell which configuration layer to look in. Valid configuration
layers are "user", "system" and "default". If no layer is specified, a value
will be picked from the first layer that defines the parameter, in the order
just specified.
',
),
'config-set' => array(
'summary' => 'Change Setting',
'function' => 'doConfigSet',
'shortcut' => 'cs',
'options' => array(),
'doc' => '<parameter> <value> [layer]
Sets the value of one configuration parameter. The first argument is
the name of the parameter, the second argument is the new value. Some
parameters are subject to validation, and the command will fail with
an error message if the new value does not make sense. An optional
third argument may be used to specify in which layer to set the
configuration parameter. The default layer is "user".
',
),
'config-help' => array(
'summary' => 'Show Information About Setting',
'function' => 'doConfigHelp',
'shortcut' => 'ch',
'options' => array(),
'doc' => '[parameter]
Displays help for a configuration parameter. Without arguments it
displays help for all configuration parameters.
',
),
);
// }}}
// {{{ constructor
/**
* PEAR_Command_Config constructor.
*
* @access public
*/
function PEAR_Command_Config(&$ui, &$config)
{
parent::PEAR_Command_Common($ui, $config);
}
// }}}
// {{{ doConfigShow()
function doConfigShow($command, $options, $params)
{
// $params[0] -> the layer
if ($error = $this->_checkLayer(@$params[0])) {
return $this->raiseError($error);
}
$keys = $this->config->getKeys();
sort($keys);
$data = array('caption' => 'Configuration:');
foreach ($keys as $key) {
$type = $this->config->getType($key);
$value = $this->config->get($key, @$params[0]);
if ($type == 'password' && $value) {
$value = '********';
}
if ($value === false) {
$value = 'false';
} elseif ($value === true) {
$value = 'true';
}
$data['data'][$this->config->getGroup($key)][] = array($this->config->getPrompt($key) , $key, $value);
}
$this->ui->outputData($data, $command);
return true;
}
// }}}
// {{{ doConfigGet()
function doConfigGet($command, $options, $params)
{
// $params[0] -> the parameter
// $params[1] -> the layer
if ($error = $this->_checkLayer(@$params[1])) {
return $this->raiseError($error);
}
if (sizeof($params) < 1 || sizeof($params) > 2) {
return $this->raiseError("config-get expects 1 or 2 parameters");
} elseif (sizeof($params) == 1) {
$this->ui->outputData($this->config->get($params[0]), $command);
} else {
$data = $this->config->get($params[0], $params[1]);
$this->ui->outputData($data, $command);
}
return true;
}
// }}}
// {{{ doConfigSet()
function doConfigSet($command, $options, $params)
{
// $param[0] -> a parameter to set
// $param[1] -> the value for the parameter
// $param[2] -> the layer
$failmsg = '';
if (sizeof($params) < 2 || sizeof($params) > 3) {
$failmsg .= "config-set expects 2 or 3 parameters";
return PEAR::raiseError($failmsg);
}
if ($error = $this->_checkLayer(@$params[2])) {
$failmsg .= $error;
return PEAR::raiseError($failmsg);
}
if (!call_user_func_array(array(&$this->config, 'set'), $params))
{
$failmsg = "config-set (" . implode(", ", $params) . ") failed";
} else {
$this->config->store();
}
if ($failmsg) {
return $this->raiseError($failmsg);
}
return true;
}
// }}}
// {{{ doConfigHelp()
function doConfigHelp($command, $options, $params)
{
if (empty($params)) {
$params = $this->config->getKeys();
}
$data['caption'] = "Config help" . ((count($params) == 1) ? " for $params[0]" : '');
$data['headline'] = array('Name', 'Type', 'Description');
$data['border'] = true;
foreach ($params as $name) {
$type = $this->config->getType($name);
$docs = $this->config->getDocs($name);
if ($type == 'set') {
$docs = rtrim($docs) . "\nValid set: " .
implode(' ', $this->config->getSetValues($name));
}
$data['data'][] = array($name, $type, $docs);
}
$this->ui->outputData($data, $command);
}
// }}}
// {{{ _checkLayer()
/**
* Checks if a layer is defined or not
*
* @param string $layer The layer to search for
* @return mixed False on no error or the error message
*/
function _checkLayer($layer = null)
{
if (!empty($layer) && $layer != 'default') {
$layers = $this->config->getLayers();
if (!in_array($layer, $layers)) {
return " only the layers: \"" . implode('" or "', $layers) . "\" are supported";
}
}
return false;
}
// }}}
}
?>

470
inc/PEAR/Command/Install.php Executable file
View File

@ -0,0 +1,470 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Stig Sæther Bakken <ssb@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Install.php,v 1.53.2.1 2004/10/19 04:08:42 cellog Exp $
require_once "PEAR/Command/Common.php";
require_once "PEAR/Installer.php";
/**
* PEAR commands for installation or deinstallation/upgrading of
* packages.
*
*/
class PEAR_Command_Install extends PEAR_Command_Common
{
// {{{ properties
var $commands = array(
'install' => array(
'summary' => 'Install Package',
'function' => 'doInstall',
'shortcut' => 'i',
'options' => array(
'force' => array(
'shortopt' => 'f',
'doc' => 'will overwrite newer installed packages',
),
'nodeps' => array(
'shortopt' => 'n',
'doc' => 'ignore dependencies, install anyway',
),
'register-only' => array(
'shortopt' => 'r',
'doc' => 'do not install files, only register the package as installed',
),
'soft' => array(
'shortopt' => 's',
'doc' => 'soft install, fail silently, or upgrade if already installed',
),
'nobuild' => array(
'shortopt' => 'B',
'doc' => 'don\'t build C extensions',
),
'nocompress' => array(
'shortopt' => 'Z',
'doc' => 'request uncompressed files when downloading',
),
'installroot' => array(
'shortopt' => 'R',
'arg' => 'DIR',
'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)',
),
'ignore-errors' => array(
'doc' => 'force install even if there were errors',
),
'alldeps' => array(
'shortopt' => 'a',
'doc' => 'install all required and optional dependencies',
),
'onlyreqdeps' => array(
'shortopt' => 'o',
'doc' => 'install all required dependencies',
),
),
'doc' => '<package> ...
Installs one or more PEAR packages. You can specify a package to
install in four ways:
"Package-1.0.tgz" : installs from a local file
"http://example.com/Package-1.0.tgz" : installs from
anywhere on the net.
"package.xml" : installs the package described in
package.xml. Useful for testing, or for wrapping a PEAR package in
another package manager such as RPM.
"Package" : queries your configured server
({config master_server}) and downloads the newest package with
the preferred quality/state ({config preferred_state}).
More than one package may be specified at once. It is ok to mix these
four ways of specifying packages.
'),
'upgrade' => array(
'summary' => 'Upgrade Package',
'function' => 'doInstall',
'shortcut' => 'up',
'options' => array(
'force' => array(
'shortopt' => 'f',
'doc' => 'overwrite newer installed packages',
),
'nodeps' => array(
'shortopt' => 'n',
'doc' => 'ignore dependencies, upgrade anyway',
),
'register-only' => array(
'shortopt' => 'r',
'doc' => 'do not install files, only register the package as upgraded',
),
'nobuild' => array(
'shortopt' => 'B',
'doc' => 'don\'t build C extensions',
),
'nocompress' => array(
'shortopt' => 'Z',
'doc' => 'request uncompressed files when downloading',
),
'installroot' => array(
'shortopt' => 'R',
'arg' => 'DIR',
'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)',
),
'ignore-errors' => array(
'doc' => 'force install even if there were errors',
),
'alldeps' => array(
'shortopt' => 'a',
'doc' => 'install all required and optional dependencies',
),
'onlyreqdeps' => array(
'shortopt' => 'o',
'doc' => 'install all required dependencies',
),
),
'doc' => '<package> ...
Upgrades one or more PEAR packages. See documentation for the
"install" command for ways to specify a package.
When upgrading, your package will be updated if the provided new
package has a higher version number (use the -f option if you need to
upgrade anyway).
More than one package may be specified at once.
'),
'upgrade-all' => array(
'summary' => 'Upgrade All Packages',
'function' => 'doInstall',
'shortcut' => 'ua',
'options' => array(
'nodeps' => array(
'shortopt' => 'n',
'doc' => 'ignore dependencies, upgrade anyway',
),
'register-only' => array(
'shortopt' => 'r',
'doc' => 'do not install files, only register the package as upgraded',
),
'nobuild' => array(
'shortopt' => 'B',
'doc' => 'don\'t build C extensions',
),
'nocompress' => array(
'shortopt' => 'Z',
'doc' => 'request uncompressed files when downloading',
),
'installroot' => array(
'shortopt' => 'R',
'arg' => 'DIR',
'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)',
),
'ignore-errors' => array(
'doc' => 'force install even if there were errors',
),
),
'doc' => '
Upgrades all packages that have a newer release available. Upgrades are
done only if there is a release available of the state specified in
"preferred_state" (currently {config preferred_state}), or a state considered
more stable.
'),
'uninstall' => array(
'summary' => 'Un-install Package',
'function' => 'doUninstall',
'shortcut' => 'un',
'options' => array(
'nodeps' => array(
'shortopt' => 'n',
'doc' => 'ignore dependencies, uninstall anyway',
),
'register-only' => array(
'shortopt' => 'r',
'doc' => 'do not remove files, only register the packages as not installed',
),
'installroot' => array(
'shortopt' => 'R',
'arg' => 'DIR',
'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)',
),
'ignore-errors' => array(
'doc' => 'force install even if there were errors',
),
),
'doc' => '<package> ...
Uninstalls one or more PEAR packages. More than one package may be
specified at once.
'),
'bundle' => array(
'summary' => 'Unpacks a Pecl Package',
'function' => 'doBundle',
'shortcut' => 'bun',
'options' => array(
'destination' => array(
'shortopt' => 'd',
'arg' => 'DIR',
'doc' => 'Optional destination directory for unpacking (defaults to current path or "ext" if exists)',
),
'force' => array(
'shortopt' => 'f',
'doc' => 'Force the unpacking even if there were errors in the package',
),
),
'doc' => '<package>
Unpacks a Pecl Package into the selected location. It will download the
package if needed.
'),
);
// }}}
// {{{ constructor
/**
* PEAR_Command_Install constructor.
*
* @access public
*/
function PEAR_Command_Install(&$ui, &$config)
{
parent::PEAR_Command_Common($ui, $config);
}
// }}}
// {{{ doInstall()
function doInstall($command, $options, $params)
{
require_once 'PEAR/Downloader.php';
if (empty($this->installer)) {
$this->installer = &new PEAR_Installer($this->ui);
}
if ($command == 'upgrade') {
$options['upgrade'] = true;
}
if ($command == 'upgrade-all') {
include_once "PEAR/Remote.php";
$options['upgrade'] = true;
$remote = &new PEAR_Remote($this->config);
$state = $this->config->get('preferred_state');
if (empty($state) || $state == 'any') {
$latest = $remote->call("package.listLatestReleases");
} else {
$latest = $remote->call("package.listLatestReleases", $state);
}
if (PEAR::isError($latest)) {
return $latest;
}
$reg = new PEAR_Registry($this->config->get('php_dir'));
$installed = array_flip($reg->listPackages());
$params = array();
foreach ($latest as $package => $info) {
$package = strtolower($package);
if (!isset($installed[$package])) {
// skip packages we don't have installed
continue;
}
$inst_version = $reg->packageInfo($package, 'version');
if (version_compare("$info[version]", "$inst_version", "le")) {
// installed version is up-to-date
continue;
}
$params[] = $package;
$this->ui->outputData(array('data' => "Will upgrade $package"), $command);
}
}
$this->downloader = &new PEAR_Downloader($this->ui, $options, $this->config);
$errors = array();
$downloaded = array();
$this->downloader->download($params);
$errors = $this->downloader->getErrorMsgs();
if (count($errors)) {
$err['data'] = array($errors);
$err['headline'] = 'Install Errors';
$this->ui->outputData($err);
return $this->raiseError("$command failed");
}
$downloaded = $this->downloader->getDownloadedPackages();
$this->installer->sortPkgDeps($downloaded);
foreach ($downloaded as $pkg) {
PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
$info = $this->installer->install($pkg['file'], $options, $this->config);
PEAR::popErrorHandling();
if (PEAR::isError($info)) {
$this->ui->outputData('ERROR: ' .$info->getMessage());
continue;
}
if (is_array($info)) {
if ($this->config->get('verbose') > 0) {
$label = "$info[package] $info[version]";
$out = array('data' => "$command ok: $label");
if (isset($info['release_warnings'])) {
$out['release_warnings'] = $info['release_warnings'];
}
$this->ui->outputData($out, $command);
}
} else {
return $this->raiseError("$command failed");
}
}
return true;
}
// }}}
// {{{ doUninstall()
function doUninstall($command, $options, $params)
{
if (empty($this->installer)) {
$this->installer = &new PEAR_Installer($this->ui);
}
if (sizeof($params) < 1) {
return $this->raiseError("Please supply the package(s) you want to uninstall");
}
include_once 'PEAR/Registry.php';
$reg = new PEAR_Registry($this->config->get('php_dir'));
$newparams = array();
$badparams = array();
foreach ($params as $pkg) {
$info = $reg->packageInfo($pkg);
if ($info === null) {
$badparams[] = $pkg;
} else {
$newparams[] = $info;
}
}
$this->installer->sortPkgDeps($newparams, true);
$params = array();
foreach($newparams as $info) {
$params[] = $info['info']['package'];
}
$params = array_merge($params, $badparams);
foreach ($params as $pkg) {
if ($this->installer->uninstall($pkg, $options)) {
if ($this->config->get('verbose') > 0) {
$this->ui->outputData("uninstall ok: $pkg", $command);
}
} else {
return $this->raiseError("uninstall failed: $pkg");
}
}
return true;
}
// }}}
// }}}
// {{{ doBundle()
/*
(cox) It just downloads and untars the package, does not do
any check that the PEAR_Installer::_installFile() does.
*/
function doBundle($command, $options, $params)
{
if (empty($this->installer)) {
$this->installer = &new PEAR_Downloader($this->ui);
}
$installer = &$this->installer;
if (sizeof($params) < 1) {
return $this->raiseError("Please supply the package you want to bundle");
}
$pkgfile = $params[0];
$need_download = false;
if (preg_match('#^(http|ftp)://#', $pkgfile)) {
$need_download = true;
} elseif (!@is_file($pkgfile)) {
if ($installer->validPackageName($pkgfile)) {
$pkgfile = $installer->getPackageDownloadUrl($pkgfile);
$need_download = true;
} else {
if (strlen($pkgfile)) {
return $this->raiseError("Could not open the package file: $pkgfile");
} else {
return $this->raiseError("No package file given");
}
}
}
// Download package -----------------------------------------------
if ($need_download) {
$downloaddir = $installer->config->get('download_dir');
if (empty($downloaddir)) {
if (PEAR::isError($downloaddir = System::mktemp('-d'))) {
return $downloaddir;
}
$installer->log(2, '+ tmp dir created at ' . $downloaddir);
}
$callback = $this->ui ? array(&$installer, '_downloadCallback') : null;
$file = $installer->downloadHttp($pkgfile, $this->ui, $downloaddir, $callback);
if (PEAR::isError($file)) {
return $this->raiseError($file);
}
$pkgfile = $file;
}
// Parse xml file -----------------------------------------------
$pkginfo = $installer->infoFromTgzFile($pkgfile);
if (PEAR::isError($pkginfo)) {
return $this->raiseError($pkginfo);
}
$installer->validatePackageInfo($pkginfo, $errors, $warnings);
// XXX We allow warnings, do we have to do it?
if (count($errors)) {
if (empty($options['force'])) {
return $this->raiseError("The following errors where found:\n".
implode("\n", $errors));
} else {
$this->log(0, "warning : the following errors were found:\n".
implode("\n", $errors));
}
}
$pkgname = $pkginfo['package'];
// Unpacking -------------------------------------------------
if (isset($options['destination'])) {
if (!is_dir($options['destination'])) {
System::mkdir('-p ' . $options['destination']);
}
$dest = realpath($options['destination']);
} else {
$pwd = getcwd();
if (is_dir($pwd . DIRECTORY_SEPARATOR . 'ext')) {
$dest = $pwd . DIRECTORY_SEPARATOR . 'ext';
} else {
$dest = $pwd;
}
}
$dest .= DIRECTORY_SEPARATOR . $pkgname;
$orig = $pkgname . '-' . $pkginfo['version'];
$tar = new Archive_Tar($pkgfile);
if (!@$tar->extractModify($dest, $orig)) {
return $this->raiseError("unable to unpack $pkgfile");
}
$this->ui->outputData("Package ready at '$dest'");
// }}}
}
// }}}
}
?>

101
inc/PEAR/Command/Mirror.php Executable file
View File

@ -0,0 +1,101 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Alexander Merz <alexmerz@php.net> |
// | |
// +----------------------------------------------------------------------+
//
// $Id: Mirror.php,v 1.5 2004/03/18 12:23:57 mj Exp $
require_once "PEAR/Command/Common.php";
require_once "PEAR/Command.php";
require_once "PEAR/Remote.php";
require_once "PEAR.php";
/**
* PEAR commands for providing file mirrors
*
*/
class PEAR_Command_Mirror extends PEAR_Command_Common
{
// {{{ properties
var $commands = array(
'download-all' => array(
'summary' => 'Downloads each available package from master_server',
'function' => 'doDownloadAll',
'shortcut' => 'da',
'options' => array(),
'doc' => '
Requests a list of available packages from the package server
(master_server) and downloads them to current working directory'
),
);
// }}}
// {{{ constructor
/**
* PEAR_Command_Mirror constructor.
*
* @access public
* @param object PEAR_Frontend a reference to an frontend
* @param object PEAR_Config a reference to the configuration data
*/
function PEAR_Command_Mirror(&$ui, &$config)
{
parent::PEAR_Command_Common($ui, $config);
}
// }}}
// {{{ doDownloadAll()
/**
* retrieves a list of avaible Packages from master server
* and downloads them
*
* @access public
* @param string $command the command
* @param array $options the command options before the command
* @param array $params the stuff after the command name
* @return bool true if succesful
* @throw PEAR_Error
*/
function doDownloadAll($command, $options, $params)
{
$this->config->set("php_dir", ".");
$remote = &new PEAR_Remote($this->config);
$remoteInfo = $remote->call("package.listAll");
if (PEAR::isError($remoteInfo)) {
return $remoteInfo;
}
$cmd = &PEAR_Command::factory("download", $this->config);
if (PEAR::isError($cmd)) {
return $cmd;
}
foreach ($remoteInfo as $pkgn => $pkg) {
/**
* Error handling not neccesary, because already done by
* the download command
*/
$cmd->run("download", array(), array($pkgn));
}
return true;
}
// }}}
}

819
inc/PEAR/Command/Package.php Executable file
View File

@ -0,0 +1,819 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Stig Bakken <ssb@php.net> |
// | Martin Jansen <mj@php.net> |
// | Greg Beaver <cellog@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Package.php,v 1.61.2.7 2005/02/17 17:47:55 cellog Exp $
require_once 'PEAR/Common.php';
require_once 'PEAR/Command/Common.php';
class PEAR_Command_Package extends PEAR_Command_Common
{
// {{{ properties
var $commands = array(
'package' => array(
'summary' => 'Build Package',
'function' => 'doPackage',
'shortcut' => 'p',
'options' => array(
'nocompress' => array(
'shortopt' => 'Z',
'doc' => 'Do not gzip the package file'
),
'showname' => array(
'shortopt' => 'n',
'doc' => 'Print the name of the packaged file.',
),
),
'doc' => '[descfile]
Creates a PEAR package from its description file (usually called
package.xml).
'
),
'package-validate' => array(
'summary' => 'Validate Package Consistency',
'function' => 'doPackageValidate',
'shortcut' => 'pv',
'options' => array(),
'doc' => '
',
),
'cvsdiff' => array(
'summary' => 'Run a "cvs diff" for all files in a package',
'function' => 'doCvsDiff',
'shortcut' => 'cd',
'options' => array(
'quiet' => array(
'shortopt' => 'q',
'doc' => 'Be quiet',
),
'reallyquiet' => array(
'shortopt' => 'Q',
'doc' => 'Be really quiet',
),
'date' => array(
'shortopt' => 'D',
'doc' => 'Diff against revision of DATE',
'arg' => 'DATE',
),
'release' => array(
'shortopt' => 'R',
'doc' => 'Diff against tag for package release REL',
'arg' => 'REL',
),
'revision' => array(
'shortopt' => 'r',
'doc' => 'Diff against revision REV',
'arg' => 'REV',
),
'context' => array(
'shortopt' => 'c',
'doc' => 'Generate context diff',
),
'unified' => array(
'shortopt' => 'u',
'doc' => 'Generate unified diff',
),
'ignore-case' => array(
'shortopt' => 'i',
'doc' => 'Ignore case, consider upper- and lower-case letters equivalent',
),
'ignore-whitespace' => array(
'shortopt' => 'b',
'doc' => 'Ignore changes in amount of white space',
),
'ignore-blank-lines' => array(
'shortopt' => 'B',
'doc' => 'Ignore changes that insert or delete blank lines',
),
'brief' => array(
'doc' => 'Report only whether the files differ, no details',
),
'dry-run' => array(
'shortopt' => 'n',
'doc' => 'Don\'t do anything, just pretend',
),
),
'doc' => '<package.xml>
Compares all the files in a package. Without any options, this
command will compare the current code with the last checked-in code.
Using the -r or -R option you may compare the current code with that
of a specific release.
',
),
'cvstag' => array(
'summary' => 'Set CVS Release Tag',
'function' => 'doCvsTag',
'shortcut' => 'ct',
'options' => array(
'quiet' => array(
'shortopt' => 'q',
'doc' => 'Be quiet',
),
'reallyquiet' => array(
'shortopt' => 'Q',
'doc' => 'Be really quiet',
),
'slide' => array(
'shortopt' => 'F',
'doc' => 'Move (slide) tag if it exists',
),
'delete' => array(
'shortopt' => 'd',
'doc' => 'Remove tag',
),
'dry-run' => array(
'shortopt' => 'n',
'doc' => 'Don\'t do anything, just pretend',
),
),
'doc' => '<package.xml>
Sets a CVS tag on all files in a package. Use this command after you have
packaged a distribution tarball with the "package" command to tag what
revisions of what files were in that release. If need to fix something
after running cvstag once, but before the tarball is released to the public,
use the "slide" option to move the release tag.
',
),
'run-tests' => array(
'summary' => 'Run Regression Tests',
'function' => 'doRunTests',
'shortcut' => 'rt',
'options' => array(
'recur' => array(
'shortopt' => 'r',
'doc' => 'Run tests in child directories, recursively. 4 dirs deep maximum',
),
'ini' => array(
'shortopt' => 'i',
'doc' => 'actual string of settings to pass to php in format " -d setting=blah"',
'arg' => 'SETTINGS'
),
'realtimelog' => array(
'shortopt' => 'l',
'doc' => 'Log test runs/results as they are run',
),
),
'doc' => '[testfile|dir ...]
Run regression tests with PHP\'s regression testing script (run-tests.php).',
),
'package-dependencies' => array(
'summary' => 'Show package dependencies',
'function' => 'doPackageDependencies',
'shortcut' => 'pd',
'options' => array(),
'doc' => '
List all depencies the package has.'
),
'sign' => array(
'summary' => 'Sign a package distribution file',
'function' => 'doSign',
'shortcut' => 'si',
'options' => array(),
'doc' => '<package-file>
Signs a package distribution (.tar or .tgz) file with GnuPG.',
),
'makerpm' => array(
'summary' => 'Builds an RPM spec file from a PEAR package',
'function' => 'doMakeRPM',
'shortcut' => 'rpm',
'options' => array(
'spec-template' => array(
'shortopt' => 't',
'arg' => 'FILE',
'doc' => 'Use FILE as RPM spec file template'
),
'rpm-pkgname' => array(
'shortopt' => 'p',
'arg' => 'FORMAT',
'doc' => 'Use FORMAT as format string for RPM package name, %s is replaced
by the PEAR package name, defaults to "PEAR::%s".',
),
),
'doc' => '<package-file>
Creates an RPM .spec file for wrapping a PEAR package inside an RPM
package. Intended to be used from the SPECS directory, with the PEAR
package tarball in the SOURCES directory:
$ pear makerpm ../SOURCES/Net_Socket-1.0.tgz
Wrote RPM spec file PEAR::Net_Geo-1.0.spec
$ rpm -bb PEAR::Net_Socket-1.0.spec
...
Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm
',
),
);
var $output;
// }}}
// {{{ constructor
/**
* PEAR_Command_Package constructor.
*
* @access public
*/
function PEAR_Command_Package(&$ui, &$config)
{
parent::PEAR_Command_Common($ui, $config);
}
// }}}
// {{{ _displayValidationResults()
function _displayValidationResults($err, $warn, $strict = false)
{
foreach ($err as $e) {
$this->output .= "Error: $e\n";
}
foreach ($warn as $w) {
$this->output .= "Warning: $w\n";
}
$this->output .= sprintf('Validation: %d error(s), %d warning(s)'."\n",
sizeof($err), sizeof($warn));
if ($strict && sizeof($err) > 0) {
$this->output .= "Fix these errors and try again.";
return false;
}
return true;
}
// }}}
// {{{ doPackage()
function doPackage($command, $options, $params)
{
$this->output = '';
include_once 'PEAR/Packager.php';
if (sizeof($params) < 1) {
$params[0] = "package.xml";
}
$pkginfofile = isset($params[0]) ? $params[0] : 'package.xml';
$packager =& new PEAR_Packager();
$err = $warn = array();
$dir = dirname($pkginfofile);
$compress = empty($options['nocompress']) ? true : false;
$result = $packager->package($pkginfofile, $compress);
if (PEAR::isError($result)) {
$this->ui->outputData($this->output, $command);
return $this->raiseError($result);
}
// Don't want output, only the package file name just created
if (isset($options['showname'])) {
$this->output = $result;
}
if (PEAR::isError($result)) {
$this->output .= "Package failed: ".$result->getMessage();
}
$this->ui->outputData($this->output, $command);
return true;
}
// }}}
// {{{ doPackageValidate()
function doPackageValidate($command, $options, $params)
{
$this->output = '';
if (sizeof($params) < 1) {
$params[0] = "package.xml";
}
$obj = new PEAR_Common;
$info = null;
if ($fp = @fopen($params[0], "r")) {
$test = fread($fp, 5);
fclose($fp);
if ($test == "<?xml") {
$info = $obj->infoFromDescriptionFile($params[0]);
}
}
if (empty($info)) {
$info = $obj->infoFromTgzFile($params[0]);
}
if (PEAR::isError($info)) {
return $this->raiseError($info);
}
$obj->validatePackageInfo($info, $err, $warn);
$this->_displayValidationResults($err, $warn);
$this->ui->outputData($this->output, $command);
return true;
}
// }}}
// {{{ doCvsTag()
function doCvsTag($command, $options, $params)
{
$this->output = '';
$_cmd = $command;
if (sizeof($params) < 1) {
$help = $this->getHelp($command);
return $this->raiseError("$command: missing parameter: $help[0]");
}
$obj = new PEAR_Common;
$info = $obj->infoFromDescriptionFile($params[0]);
if (PEAR::isError($info)) {
return $this->raiseError($info);
}
$err = $warn = array();
$obj->validatePackageInfo($info, $err, $warn);
if (!$this->_displayValidationResults($err, $warn, true)) {
$this->ui->outputData($this->output, $command);
break;
}
$version = $info['version'];
$cvsversion = preg_replace('/[^a-z0-9]/i', '_', $version);
$cvstag = "RELEASE_$cvsversion";
$files = array_keys($info['filelist']);
$command = "cvs";
if (isset($options['quiet'])) {
$command .= ' -q';
}
if (isset($options['reallyquiet'])) {
$command .= ' -Q';
}
$command .= ' tag';
if (isset($options['slide'])) {
$command .= ' -F';
}
if (isset($options['delete'])) {
$command .= ' -d';
}
$command .= ' ' . $cvstag . ' ' . escapeshellarg($params[0]);
foreach ($files as $file) {
$command .= ' ' . escapeshellarg($file);
}
if ($this->config->get('verbose') > 1) {
$this->output .= "+ $command\n";
}
$this->output .= "+ $command\n";
if (empty($options['dry-run'])) {
$fp = popen($command, "r");
while ($line = fgets($fp, 1024)) {
$this->output .= rtrim($line)."\n";
}
pclose($fp);
}
$this->ui->outputData($this->output, $_cmd);
return true;
}
// }}}
// {{{ doCvsDiff()
function doCvsDiff($command, $options, $params)
{
$this->output = '';
if (sizeof($params) < 1) {
$help = $this->getHelp($command);
return $this->raiseError("$command: missing parameter: $help[0]");
}
$obj = new PEAR_Common;
$info = $obj->infoFromDescriptionFile($params[0]);
if (PEAR::isError($info)) {
return $this->raiseError($info);
}
$files = array_keys($info['filelist']);
$cmd = "cvs";
if (isset($options['quiet'])) {
$cmd .= ' -q';
unset($options['quiet']);
}
if (isset($options['reallyquiet'])) {
$cmd .= ' -Q';
unset($options['reallyquiet']);
}
if (isset($options['release'])) {
$cvsversion = preg_replace('/[^a-z0-9]/i', '_', $options['release']);
$cvstag = "RELEASE_$cvsversion";
$options['revision'] = $cvstag;
unset($options['release']);
}
$execute = true;
if (isset($options['dry-run'])) {
$execute = false;
unset($options['dry-run']);
}
$cmd .= ' diff';
// the rest of the options are passed right on to "cvs diff"
foreach ($options as $option => $optarg) {
$arg = @$this->commands[$command]['options'][$option]['arg'];
$short = @$this->commands[$command]['options'][$option]['shortopt'];
$cmd .= $short ? " -$short" : " --$option";
if ($arg && $optarg) {
$cmd .= ($short ? '' : '=') . escapeshellarg($optarg);
}
}
foreach ($files as $file) {
$cmd .= ' ' . escapeshellarg($file);
}
if ($this->config->get('verbose') > 1) {
$this->output .= "+ $cmd\n";
}
if ($execute) {
$fp = popen($cmd, "r");
while ($line = fgets($fp, 1024)) {
$this->output .= rtrim($line)."\n";
}
pclose($fp);
}
$this->ui->outputData($this->output, $command);
return true;
}
// }}}
// {{{ doRunTests()
function doRunTests($command, $options, $params)
{
include_once 'PEAR/RunTest.php';
$log = new PEAR_Common;
$log->ui = &$this->ui; // slightly hacky, but it will work
$run = new PEAR_RunTest($log);
$tests = array();
if (isset($options['recur'])) {
$depth = 4;
} else {
$depth = 1;
}
if (!count($params)) {
$params[] = '.';
}
foreach ($params as $p) {
if (is_dir($p)) {
$dir = System::find(array($p, '-type', 'f',
'-maxdepth', $depth,
'-name', '*.phpt'));
$tests = array_merge($tests, $dir);
} else {
if (!@file_exists($p)) {
if (!preg_match('/\.phpt$/', $p)) {
$p .= '.phpt';
}
$dir = System::find(array(dirname($p), '-type', 'f',
'-maxdepth', $depth,
'-name', $p));
$tests = array_merge($tests, $dir);
} else {
$tests[] = $p;
}
}
}
$ini_settings = '';
if (isset($options['ini'])) {
$ini_settings .= $options['ini'];
}
if (isset($_ENV['TEST_PHP_INCLUDE_PATH'])) {
$ini_settings .= " -d include_path={$_ENV['TEST_PHP_INCLUDE_PATH']}";
}
if ($ini_settings) {
$this->ui->outputData('Using INI settings: "' . $ini_settings . '"');
}
$skipped = $passed = $failed = array();
$this->ui->outputData('Running ' . count($tests) . ' tests', $command);
$start = time();
if (isset($options['realtimelog'])) {
@unlink('run-tests.log');
}
foreach ($tests as $t) {
if (isset($options['realtimelog'])) {
$fp = @fopen('run-tests.log', 'a');
if ($fp) {
fwrite($fp, "Running test $t...");
fclose($fp);
}
}
$result = $run->run($t, $ini_settings);
if (OS_WINDOWS) {
for($i=0;$i<2000;$i++) {
$i = $i; // delay - race conditions on windows
}
}
if (isset($options['realtimelog'])) {
$fp = @fopen('run-tests.log', 'a');
if ($fp) {
fwrite($fp, "$result\n");
fclose($fp);
}
}
if ($result == 'FAILED') {
$failed[] = $t;
}
if ($result == 'PASSED') {
$passed[] = $t;
}
if ($result == 'SKIPPED') {
$skipped[] = $t;
}
}
$total = date('i:s', time() - $start);
if (count($failed)) {
$output = "TOTAL TIME: $total\n";
$output .= count($passed) . " PASSED TESTS\n";
$output .= count($skipped) . " SKIPPED TESTS\n";
$output .= count($failed) . " FAILED TESTS:\n";
foreach ($failed as $failure) {
$output .= $failure . "\n";
}
if (isset($options['realtimelog'])) {
$fp = @fopen('run-tests.log', 'a');
} else {
$fp = @fopen('run-tests.log', 'w');
}
if ($fp) {
fwrite($fp, $output, strlen($output));
fclose($fp);
$this->ui->outputData('wrote log to "' . realpath('run-tests.log') . '"', $command);
}
} elseif (@file_exists('run-tests.log') && !@is_dir('run-tests.log')) {
@unlink('run-tests.log');
}
$this->ui->outputData('TOTAL TIME: ' . $total);
$this->ui->outputData(count($passed) . ' PASSED TESTS', $command);
$this->ui->outputData(count($skipped) . ' SKIPPED TESTS', $command);
if (count($failed)) {
$this->ui->outputData(count($failed) . ' FAILED TESTS:', $command);
foreach ($failed as $failure) {
$this->ui->outputData($failure, $command);
}
}
return true;
}
// }}}
// {{{ doPackageDependencies()
function doPackageDependencies($command, $options, $params)
{
// $params[0] -> the PEAR package to list its information
if (sizeof($params) != 1) {
return $this->raiseError("bad parameter(s), try \"help $command\"");
}
$obj = new PEAR_Common();
if (PEAR::isError($info = $obj->infoFromAny($params[0]))) {
return $this->raiseError($info);
}
if (is_array($info['release_deps'])) {
$data = array(
'caption' => 'Dependencies for ' . $info['package'],
'border' => true,
'headline' => array("Type", "Name", "Relation", "Version"),
);
foreach ($info['release_deps'] as $d) {
if (isset($this->_deps_rel_trans[$d['rel']])) {
$rel = $this->_deps_rel_trans[$d['rel']];
} else {
$rel = $d['rel'];
}
if (isset($this->_deps_type_trans[$d['type']])) {
$type = ucfirst($this->_deps_type_trans[$d['type']]);
} else {
$type = $d['type'];
}
if (isset($d['name'])) {
$name = $d['name'];
} else {
$name = '';
}
if (isset($d['version'])) {
$version = $d['version'];
} else {
$version = '';
}
$data['data'][] = array($type, $name, $rel, $version);
}
$this->ui->outputData($data, $command);
return true;
}
// Fallback
$this->ui->outputData("This package does not have any dependencies.", $command);
}
// }}}
// {{{ doSign()
function doSign($command, $options, $params)
{
// should move most of this code into PEAR_Packager
// so it'll be easy to implement "pear package --sign"
if (sizeof($params) != 1) {
return $this->raiseError("bad parameter(s), try \"help $command\"");
}
if (!file_exists($params[0])) {
return $this->raiseError("file does not exist: $params[0]");
}
$obj = new PEAR_Common;
$info = $obj->infoFromTgzFile($params[0]);
if (PEAR::isError($info)) {
return $this->raiseError($info);
}
include_once "Archive/Tar.php";
include_once "System.php";
$tar = new Archive_Tar($params[0]);
$tmpdir = System::mktemp('-d pearsign');
if (!$tar->extractList('package.xml package.sig', $tmpdir)) {
return $this->raiseError("failed to extract tar file");
}
if (file_exists("$tmpdir/package.sig")) {
return $this->raiseError("package already signed");
}
@unlink("$tmpdir/package.sig");
$input = $this->ui->userDialog($command,
array('GnuPG Passphrase'),
array('password'));
$gpg = popen("gpg --batch --passphrase-fd 0 --armor --detach-sign --output $tmpdir/package.sig $tmpdir/package.xml 2>/dev/null", "w");
if (!$gpg) {
return $this->raiseError("gpg command failed");
}
fwrite($gpg, "$input[0]\r");
if (pclose($gpg) || !file_exists("$tmpdir/package.sig")) {
return $this->raiseError("gpg sign failed");
}
$tar->addModify("$tmpdir/package.sig", '', $tmpdir);
return true;
}
// }}}
// {{{ doMakeRPM()
/*
(cox)
TODO:
- Fill the rpm dependencies in the template file.
IDEAS:
- Instead of mapping the role to rpm vars, perhaps it's better
to use directly the pear cmd to install the files by itself
in %postrun so:
pear -d php_dir=%{_libdir}/php/pear -d test_dir=.. <package>
*/
function doMakeRPM($command, $options, $params)
{
if (sizeof($params) != 1) {
return $this->raiseError("bad parameter(s), try \"help $command\"");
}
if (!file_exists($params[0])) {
return $this->raiseError("file does not exist: $params[0]");
}
include_once "Archive/Tar.php";
include_once "PEAR/Installer.php";
include_once "System.php";
$tar = new Archive_Tar($params[0]);
$tmpdir = System::mktemp('-d pear2rpm');
$instroot = System::mktemp('-d pear2rpm');
$tmp = $this->config->get('verbose');
$this->config->set('verbose', 0);
$installer = new PEAR_Installer($this->ui);
$info = $installer->install($params[0],
array('installroot' => $instroot,
'nodeps' => true));
$pkgdir = "$info[package]-$info[version]";
$info['rpm_xml_dir'] = '/var/lib/pear';
$this->config->set('verbose', $tmp);
if (!$tar->extractList("package.xml", $tmpdir, $pkgdir)) {
return $this->raiseError("failed to extract $params[0]");
}
if (!file_exists("$tmpdir/package.xml")) {
return $this->raiseError("no package.xml found in $params[0]");
}
if (isset($options['spec-template'])) {
$spec_template = $options['spec-template'];
} else {
$spec_template = $this->config->get('data_dir') .
'/PEAR/template.spec';
}
if (isset($options['rpm-pkgname'])) {
$rpm_pkgname_format = $options['rpm-pkgname'];
} else {
$rpm_pkgname_format = "PEAR::%s";
}
$info['extra_headers'] = '';
$info['doc_files'] = '';
$info['files'] = '';
$info['rpm_package'] = sprintf($rpm_pkgname_format, $info['package']);
$srcfiles = 0;
foreach ($info['filelist'] as $name => $attr) {
if (!isset($attr['role'])) {
continue;
}
$name = preg_replace('![/:\\\\]!', '/', $name);
if ($attr['role'] == 'doc') {
$info['doc_files'] .= " $name";
// Map role to the rpm vars
} else {
$c_prefix = '%{_libdir}/php/pear';
switch ($attr['role']) {
case 'php':
$prefix = $c_prefix; break;
case 'ext':
$prefix = '%{_libdir}/php'; break; // XXX good place?
case 'src':
$srcfiles++;
$prefix = '%{_includedir}/php'; break; // XXX good place?
case 'test':
$prefix = "$c_prefix/tests/" . $info['package']; break;
case 'data':
$prefix = "$c_prefix/data/" . $info['package']; break;
case 'script':
$prefix = '%{_bindir}'; break;
}
$name = str_replace('\\', '/', $name);
$info['files'] .= "$prefix/$name\n";
}
}
if ($srcfiles > 0) {
include_once "OS/Guess.php";
$os = new OS_Guess;
$arch = $os->getCpu();
} else {
$arch = 'noarch';
}
$cfg = array('master_server', 'php_dir', 'ext_dir', 'doc_dir',
'bin_dir', 'data_dir', 'test_dir');
foreach ($cfg as $k) {
$info[$k] = $this->config->get($k);
}
$info['arch'] = $arch;
$fp = @fopen($spec_template, "r");
if (!$fp) {
return $this->raiseError("could not open RPM spec file template $spec_template: $php_errormsg");
}
$spec_contents = preg_replace('/@([a-z0-9_-]+)@/e', '$info["\1"]', fread($fp, filesize($spec_template)));
fclose($fp);
$spec_file = "$info[rpm_package]-$info[version].spec";
$wp = fopen($spec_file, "wb");
if (!$wp) {
return $this->raiseError("could not write RPM spec file $spec_file: $php_errormsg");
}
fwrite($wp, $spec_contents);
fclose($wp);
$this->ui->outputData("Wrote RPM spec file $spec_file", $command);
return true;
}
// }}}
}
?>

351
inc/PEAR/Command/Registry.php Executable file
View File

@ -0,0 +1,351 @@
<?php
// /* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Stig Bakken <ssb@php.net> |
// | |
// +----------------------------------------------------------------------+
//
// $Id: Registry.php,v 1.36 2004/01/08 17:33:13 sniper Exp $
require_once 'PEAR/Command/Common.php';
require_once 'PEAR/Registry.php';
require_once 'PEAR/Config.php';
class PEAR_Command_Registry extends PEAR_Command_Common
{
// {{{ properties
var $commands = array(
'list' => array(
'summary' => 'List Installed Packages',
'function' => 'doList',
'shortcut' => 'l',
'options' => array(),
'doc' => '[package]
If invoked without parameters, this command lists the PEAR packages
installed in your php_dir ({config php_dir)). With a parameter, it
lists the files in that package.
',
),
'shell-test' => array(
'summary' => 'Shell Script Test',
'function' => 'doShellTest',
'shortcut' => 'st',
'options' => array(),
'doc' => '<package> [[relation] version]
Tests if a package is installed in the system. Will exit(1) if it is not.
<relation> The version comparison operator. One of:
<, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne
<version> The version to compare with
'),
'info' => array(
'summary' => 'Display information about a package',
'function' => 'doInfo',
'shortcut' => 'in',
'options' => array(),
'doc' => '<package>
Displays information about a package. The package argument may be a
local package file, an URL to a package file, or the name of an
installed package.'
)
);
// }}}
// {{{ constructor
/**
* PEAR_Command_Registry constructor.
*
* @access public
*/
function PEAR_Command_Registry(&$ui, &$config)
{
parent::PEAR_Command_Common($ui, $config);
}
// }}}
// {{{ doList()
function _sortinfo($a, $b)
{
return strcmp($a['package'], $b['package']);
}
function doList($command, $options, $params)
{
$reg = new PEAR_Registry($this->config->get('php_dir'));
if (sizeof($params) == 0) {
$installed = $reg->packageInfo();
usort($installed, array(&$this, '_sortinfo'));
$i = $j = 0;
$data = array(
'caption' => 'Installed packages:',
'border' => true,
'headline' => array('Package', 'Version', 'State')
);
foreach ($installed as $package) {
$data['data'][] = array($package['package'],
$package['version'],
@$package['release_state']);
}
if (count($installed)==0) {
$data = '(no packages installed)';
}
$this->ui->outputData($data, $command);
} else {
if (file_exists($params[0]) && !is_dir($params[0])) {
include_once "PEAR/Common.php";
$obj = &new PEAR_Common;
$info = $obj->infoFromAny($params[0]);
$headings = array('Package File', 'Install Path');
$installed = false;
} else {
$info = $reg->packageInfo($params[0]);
$headings = array('Type', 'Install Path');
$installed = true;
}
if (PEAR::isError($info)) {
return $this->raiseError($info);
}
if ($info === null) {
return $this->raiseError("`$params[0]' not installed");
}
$list = $info['filelist'];
if ($installed) {
$caption = 'Installed Files For ' . $params[0];
} else {
$caption = 'Contents of ' . basename($params[0]);
}
$data = array(
'caption' => $caption,
'border' => true,
'headline' => $headings);
foreach ($list as $file => $att) {
if ($installed) {
if (empty($att['installed_as'])) {
continue;
}
$data['data'][] = array($att['role'], $att['installed_as']);
} else {
if (isset($att['baseinstalldir'])) {
$dest = $att['baseinstalldir'] . DIRECTORY_SEPARATOR .
$file;
} else {
$dest = $file;
}
switch ($att['role']) {
case 'test':
case 'data':
if ($installed) {
break 2;
}
$dest = '-- will not be installed --';
break;
case 'doc':
$dest = $this->config->get('doc_dir') . DIRECTORY_SEPARATOR .
$dest;
break;
case 'php':
default:
$dest = $this->config->get('php_dir') . DIRECTORY_SEPARATOR .
$dest;
}
$dest = preg_replace('!/+!', '/', $dest);
$file = preg_replace('!/+!', '/', $file);
$data['data'][] = array($file, $dest);
}
}
$this->ui->outputData($data, $command);
}
return true;
}
// }}}
// {{{ doShellTest()
function doShellTest($command, $options, $params)
{
$this->pushErrorHandling(PEAR_ERROR_RETURN);
$reg = &new PEAR_Registry($this->config->get('php_dir'));
// "pear shell-test Foo"
if (sizeof($params) == 1) {
if (!$reg->packageExists($params[0])) {
exit(1);
}
// "pear shell-test Foo 1.0"
} elseif (sizeof($params) == 2) {
$v = $reg->packageInfo($params[0], 'version');
if (!$v || !version_compare("$v", "{$params[1]}", "ge")) {
exit(1);
}
// "pear shell-test Foo ge 1.0"
} elseif (sizeof($params) == 3) {
$v = $reg->packageInfo($params[0], 'version');
if (!$v || !version_compare("$v", "{$params[2]}", $params[1])) {
exit(1);
}
} else {
$this->popErrorHandling();
$this->raiseError("$command: expects 1 to 3 parameters");
exit(1);
}
}
// }}}
// {{{ doInfo
function doInfo($command, $options, $params)
{
// $params[0] The package for showing info
if (sizeof($params) != 1) {
return $this->raiseError("This command only accepts one param: ".
"the package you want information");
}
if (@is_file($params[0])) {
$obj = &new PEAR_Common();
$info = $obj->infoFromAny($params[0]);
} else {
$reg = &new PEAR_Registry($this->config->get('php_dir'));
$info = $reg->packageInfo($params[0]);
}
if (PEAR::isError($info)) {
return $info;
}
if (empty($info)) {
$this->raiseError("Nothing found for `$params[0]'");
return;
}
unset($info['filelist']);
unset($info['changelog']);
$keys = array_keys($info);
$longtext = array('description', 'summary');
foreach ($keys as $key) {
if (is_array($info[$key])) {
switch ($key) {
case 'maintainers': {
$i = 0;
$mstr = '';
foreach ($info[$key] as $m) {
if ($i++ > 0) {
$mstr .= "\n";
}
$mstr .= $m['name'] . " <";
if (isset($m['email'])) {
$mstr .= $m['email'];
} else {
$mstr .= $m['handle'] . '@php.net';
}
$mstr .= "> ($m[role])";
}
$info[$key] = $mstr;
break;
}
case 'release_deps': {
$i = 0;
$dstr = '';
foreach ($info[$key] as $d) {
if (isset($this->_deps_rel_trans[$d['rel']])) {
$rel = $this->_deps_rel_trans[$d['rel']];
} else {
$rel = $d['rel'];
}
if (isset($this->_deps_type_trans[$d['type']])) {
$type = ucfirst($this->_deps_type_trans[$d['type']]);
} else {
$type = $d['type'];
}
if (isset($d['name'])) {
$name = $d['name'] . ' ';
} else {
$name = '';
}
if (isset($d['version'])) {
$version = $d['version'] . ' ';
} else {
$version = '';
}
$dstr .= "$type $name$rel $version\n";
}
$info[$key] = $dstr;
break;
}
case 'provides' : {
$debug = $this->config->get('verbose');
if ($debug < 2) {
$pstr = 'Classes: ';
} else {
$pstr = '';
}
$i = 0;
foreach ($info[$key] as $p) {
if ($debug < 2 && $p['type'] != "class") {
continue;
}
// Only print classes when verbosity mode is < 2
if ($debug < 2) {
if ($i++ > 0) {
$pstr .= ", ";
}
$pstr .= $p['name'];
} else {
if ($i++ > 0) {
$pstr .= "\n";
}
$pstr .= ucfirst($p['type']) . " " . $p['name'];
if (isset($p['explicit']) && $p['explicit'] == 1) {
$pstr .= " (explicit)";
}
}
}
$info[$key] = $pstr;
break;
}
default: {
$info[$key] = implode(", ", $info[$key]);
break;
}
}
}
if ($key == '_lastmodified') {
$hdate = date('Y-m-d', $info[$key]);
unset($info[$key]);
$info['Last Modified'] = $hdate;
} else {
$info[$key] = trim($info[$key]);
if (in_array($key, $longtext)) {
$info[$key] = preg_replace('/ +/', ' ', $info[$key]);
}
}
}
$caption = 'About ' . $info['package'] . '-' . $info['version'];
$data = array(
'caption' => $caption,
'border' => true);
foreach ($info as $key => $value) {
$key = ucwords(trim(str_replace('_', ' ', $key)));
$data['data'][] = array($key, $value);
}
$data['raw'] = $info;
$this->ui->outputData($data, 'package-info');
}
// }}}
}
?>

435
inc/PEAR/Command/Remote.php Executable file
View File

@ -0,0 +1,435 @@
<?php
// /* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Stig Bakken <ssb@php.net> |
// | |
// +----------------------------------------------------------------------+
//
// $Id: Remote.php,v 1.39 2004/04/03 15:56:00 cellog Exp $
require_once 'PEAR/Command/Common.php';
require_once 'PEAR/Common.php';
require_once 'PEAR/Remote.php';
require_once 'PEAR/Registry.php';
class PEAR_Command_Remote extends PEAR_Command_Common
{
// {{{ command definitions
var $commands = array(
'remote-info' => array(
'summary' => 'Information About Remote Packages',
'function' => 'doRemoteInfo',
'shortcut' => 'ri',
'options' => array(),
'doc' => '<package>
Get details on a package from the server.',
),
'list-upgrades' => array(
'summary' => 'List Available Upgrades',
'function' => 'doListUpgrades',
'shortcut' => 'lu',
'options' => array(),
'doc' => '
List releases on the server of packages you have installed where
a newer version is available with the same release state (stable etc.).'
),
'remote-list' => array(
'summary' => 'List Remote Packages',
'function' => 'doRemoteList',
'shortcut' => 'rl',
'options' => array(),
'doc' => '
Lists the packages available on the configured server along with the
latest stable release of each package.',
),
'search' => array(
'summary' => 'Search remote package database',
'function' => 'doSearch',
'shortcut' => 'sp',
'options' => array(),
'doc' => '
Lists all packages which match the search parameters (first param
is package name, second package info)',
),
'list-all' => array(
'summary' => 'List All Packages',
'function' => 'doListAll',
'shortcut' => 'la',
'options' => array(),
'doc' => '
Lists the packages available on the configured server along with the
latest stable release of each package.',
),
'download' => array(
'summary' => 'Download Package',
'function' => 'doDownload',
'shortcut' => 'd',
'options' => array(
'nocompress' => array(
'shortopt' => 'Z',
'doc' => 'download an uncompressed (.tar) file',
),
),
'doc' => '{package|package-version}
Download a package tarball. The file will be named as suggested by the
server, for example if you download the DB package and the latest stable
version of DB is 1.2, the downloaded file will be DB-1.2.tgz.',
),
'clear-cache' => array(
'summary' => 'Clear XML-RPC Cache',
'function' => 'doClearCache',
'shortcut' => 'cc',
'options' => array(),
'doc' => '
Clear the XML-RPC cache. See also the cache_ttl configuration
parameter.
',
),
);
// }}}
// {{{ constructor
/**
* PEAR_Command_Remote constructor.
*
* @access public
*/
function PEAR_Command_Remote(&$ui, &$config)
{
parent::PEAR_Command_Common($ui, $config);
}
// }}}
// {{{ doRemoteInfo()
function doRemoteInfo($command, $options, $params)
{
if (sizeof($params) != 1) {
return $this->raiseError("$command expects one param: the remote package name");
}
$r = new PEAR_Remote($this->config);
$info = $r->call('package.info', $params[0]);
if (PEAR::isError($info)) {
return $this->raiseError($info);
}
$reg = new PEAR_Registry($this->config->get('php_dir'));
$installed = $reg->packageInfo($info['name']);
$info['installed'] = $installed['version'] ? $installed['version'] : '- no -';
$this->ui->outputData($info, $command);
return true;
}
// }}}
// {{{ doRemoteList()
function doRemoteList($command, $options, $params)
{
$r = new PEAR_Remote($this->config);
$list_options = false;
if ($this->config->get('preferred_state') == 'stable')
$list_options = true;
$available = $r->call('package.listAll', $list_options);
if (PEAR::isError($available)) {
return $this->raiseError($available);
}
$i = $j = 0;
$data = array(
'caption' => 'Available packages:',
'border' => true,
'headline' => array('Package', 'Version'),
);
foreach ($available as $name => $info) {
$data['data'][] = array($name, isset($info['stable']) ? $info['stable'] : '-n/a-');
}
if (count($available)==0) {
$data = '(no packages installed yet)';
}
$this->ui->outputData($data, $command);
return true;
}
// }}}
// {{{ doListAll()
function doListAll($command, $options, $params)
{
$r = new PEAR_Remote($this->config);
$reg = new PEAR_Registry($this->config->get('php_dir'));
$list_options = false;
if ($this->config->get('preferred_state') == 'stable')
$list_options = true;
$available = $r->call('package.listAll', $list_options);
if (PEAR::isError($available)) {
return $this->raiseError($available);
}
if (!is_array($available)) {
return $this->raiseError('The package list could not be fetched from the remote server. Please try again. (Debug info: "'.$available.'")');
}
$data = array(
'caption' => 'All packages:',
'border' => true,
'headline' => array('Package', 'Latest', 'Local'),
);
$local_pkgs = $reg->listPackages();
foreach ($available as $name => $info) {
$installed = $reg->packageInfo($name);
$desc = $info['summary'];
if (isset($params[$name]))
$desc .= "\n\n".$info['description'];
if (isset($options['mode']))
{
if ($options['mode'] == 'installed' && !isset($installed['version']))
continue;
if ($options['mode'] == 'notinstalled' && isset($installed['version']))
continue;
if ($options['mode'] == 'upgrades'
&& (!isset($installed['version']) || $installed['version'] == $info['stable']))
{
continue;
}
}
$pos = array_search(strtolower($name), $local_pkgs);
if ($pos !== false) {
unset($local_pkgs[$pos]);
}
$data['data'][$info['category']][] = array(
$name,
@$info['stable'],
@$installed['version'],
@$desc,
@$info['deps'],
);
}
foreach ($local_pkgs as $name) {
$info = $reg->packageInfo($name);
$data['data']['Local'][] = array(
$info['package'],
'',
$info['version'],
$info['summary'],
@$info['release_deps']
);
}
$this->ui->outputData($data, $command);
return true;
}
// }}}
// {{{ doSearch()
function doSearch($command, $options, $params)
{
if ((!isset($params[0]) || empty($params[0]))
&& (!isset($params[1]) || empty($params[1])))
{
return $this->raiseError('no valid search string supplied');
};
$r = new PEAR_Remote($this->config);
$reg = new PEAR_Registry($this->config->get('php_dir'));
$available = $r->call('package.listAll', true, false);
if (PEAR::isError($available)) {
return $this->raiseError($available);
}
$data = array(
'caption' => 'Matched packages:',
'border' => true,
'headline' => array('Package', 'Stable/(Latest)', 'Local'),
);
foreach ($available as $name => $info) {
$found = (!empty($params[0]) && stristr($name, $params[0]) !== false);
if (!$found && !(isset($params[1]) && !empty($params[1])
&& (stristr($info['summary'], $params[1]) !== false
|| stristr($info['description'], $params[1]) !== false)))
{
continue;
};
$installed = $reg->packageInfo($name);
$desc = $info['summary'];
if (isset($params[$name]))
$desc .= "\n\n".$info['description'];
$unstable = '';
if ($info['unstable']) {
$unstable = '/(' . $info['unstable'] . $info['state'] . ')';
}
if (!isset($info['stable']) || !$info['stable']) {
$info['stable'] = 'none';
}
$data['data'][$info['category']][] = array(
$name,
$info['stable'] . $unstable,
$installed['version'],
$desc,
);
}
if (!isset($data['data'])) {
return $this->raiseError('no packages found');
}
$this->ui->outputData($data, $command);
return true;
}
// }}}
// {{{ doDownload()
function doDownload($command, $options, $params)
{
//$params[0] -> The package to download
if (count($params) != 1) {
return PEAR::raiseError("download expects one argument: the package to download");
}
$server = $this->config->get('master_server');
if (!ereg('^http://', $params[0])) {
$getoption = isset($options['nocompress'])&&$options['nocompress']==1?'?uncompress=on':'';
$pkgfile = "http://$server/get/$params[0]".$getoption;
} else {
$pkgfile = $params[0];
}
$this->bytes_downloaded = 0;
$saved = PEAR_Common::downloadHttp($pkgfile, $this->ui, '.',
array(&$this, 'downloadCallback'));
if (PEAR::isError($saved)) {
return $this->raiseError($saved);
}
$fname = basename($saved);
$this->ui->outputData("File $fname downloaded ($this->bytes_downloaded bytes)", $command);
return true;
}
function downloadCallback($msg, $params = null)
{
if ($msg == 'done') {
$this->bytes_downloaded = $params;
}
}
// }}}
// {{{ doListUpgrades()
function doListUpgrades($command, $options, $params)
{
include_once "PEAR/Registry.php";
$remote = new PEAR_Remote($this->config);
if (empty($params[0])) {
$state = $this->config->get('preferred_state');
} else {
$state = $params[0];
}
$caption = 'Available Upgrades';
if (empty($state) || $state == 'any') {
$latest = $remote->call("package.listLatestReleases");
} else {
$latest = $remote->call("package.listLatestReleases", $state);
$caption .= ' (' . implode(', ', PEAR_Common::betterStates($state, true)) . ')';
}
$caption .= ':';
if (PEAR::isError($latest)) {
return $latest;
}
$reg = new PEAR_Registry($this->config->get('php_dir'));
$inst = array_flip($reg->listPackages());
$data = array(
'caption' => $caption,
'border' => 1,
'headline' => array('Package', 'Local', 'Remote', 'Size'),
);
foreach ((array)$latest as $pkg => $info) {
$package = strtolower($pkg);
if (!isset($inst[$package])) {
// skip packages we don't have installed
continue;
}
extract($info);
$pkginfo = $reg->packageInfo($package);
$inst_version = $pkginfo['version'];
$inst_state = $pkginfo['release_state'];
if (version_compare("$version", "$inst_version", "le")) {
// installed version is up-to-date
continue;
}
if ($filesize >= 20480) {
$filesize += 1024 - ($filesize % 1024);
$fs = sprintf("%dkB", $filesize / 1024);
} elseif ($filesize > 0) {
$filesize += 103 - ($filesize % 103);
$fs = sprintf("%.1fkB", $filesize / 1024.0);
} else {
$fs = " -"; // XXX center instead
}
$data['data'][] = array($pkg, "$inst_version ($inst_state)", "$version ($state)", $fs);
}
if (empty($data['data'])) {
$this->ui->outputData('No upgrades available');
} else {
$this->ui->outputData($data, $command);
}
return true;
}
// }}}
// {{{ doClearCache()
function doClearCache($command, $options, $params)
{
$cache_dir = $this->config->get('cache_dir');
$verbose = $this->config->get('verbose');
$output = '';
if (!($dp = @opendir($cache_dir))) {
return $this->raiseError("opendir($cache_dir) failed: $php_errormsg");
}
if ($verbose >= 1) {
$output .= "reading directory $cache_dir\n";
}
$num = 0;
while ($ent = readdir($dp)) {
if (preg_match('/^xmlrpc_cache_[a-z0-9]{32}$/', $ent)) {
$path = $cache_dir . DIRECTORY_SEPARATOR . $ent;
$ok = @unlink($path);
if ($ok) {
if ($verbose >= 2) {
$output .= "deleted $path\n";
}
$num++;
} elseif ($verbose >= 1) {
$output .= "failed to delete $path\n";
}
}
}
closedir($dp);
if ($verbose >= 1) {
$output .= "$num cache entries cleared\n";
}
$this->ui->outputData(rtrim($output), $command);
return $num;
}
// }}}
}
?>

2094
inc/PEAR/Common.php Executable file

File diff suppressed because it is too large Load Diff

1169
inc/PEAR/Config.php Executable file

File diff suppressed because it is too large Load Diff

487
inc/PEAR/Dependency.php Executable file
View File

@ -0,0 +1,487 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Tomas V.V.Cox <cox@idecnet.com> |
// | Stig Bakken <ssb@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Dependency.php,v 1.36.4.1 2004/12/27 07:04:19 cellog Exp $
require_once "PEAR.php";
define('PEAR_DEPENDENCY_MISSING', -1);
define('PEAR_DEPENDENCY_CONFLICT', -2);
define('PEAR_DEPENDENCY_UPGRADE_MINOR', -3);
define('PEAR_DEPENDENCY_UPGRADE_MAJOR', -4);
define('PEAR_DEPENDENCY_BAD_DEPENDENCY', -5);
define('PEAR_DEPENDENCY_MISSING_OPTIONAL', -6);
define('PEAR_DEPENDENCY_CONFLICT_OPTIONAL', -7);
define('PEAR_DEPENDENCY_UPGRADE_MINOR_OPTIONAL', -8);
define('PEAR_DEPENDENCY_UPGRADE_MAJOR_OPTIONAL', -9);
/**
* Dependency check for PEAR packages
*
* The class is based on the dependency RFC that can be found at
* http://cvs.php.net/cvs.php/pearweb/rfc. It requires PHP >= 4.1
*
* @author Tomas V.V.Vox <cox@idecnet.com>
* @author Stig Bakken <ssb@php.net>
*/
class PEAR_Dependency
{
// {{{ constructor
/**
* Constructor
*
* @access public
* @param object Registry object
* @return void
*/
function PEAR_Dependency(&$registry)
{
$this->registry = &$registry;
}
// }}}
// {{{ callCheckMethod()
/**
* This method maps the XML dependency definition to the
* corresponding one from PEAR_Dependency
*
* <pre>
* $opts => Array
* (
* [type] => pkg
* [rel] => ge
* [version] => 3.4
* [name] => HTML_Common
* [optional] => false
* )
* </pre>
*
* @param string Error message
* @param array Options
* @return boolean
*/
function callCheckMethod(&$errmsg, $opts)
{
$rel = isset($opts['rel']) ? $opts['rel'] : 'has';
$req = isset($opts['version']) ? $opts['version'] : null;
$name = isset($opts['name']) ? $opts['name'] : null;
$opt = (isset($opts['optional']) && $opts['optional'] == 'yes') ?
$opts['optional'] : null;
$errmsg = '';
switch ($opts['type']) {
case 'pkg':
return $this->checkPackage($errmsg, $name, $req, $rel, $opt);
break;
case 'ext':
return $this->checkExtension($errmsg, $name, $req, $rel, $opt);
break;
case 'php':
return $this->checkPHP($errmsg, $req, $rel);
break;
case 'prog':
return $this->checkProgram($errmsg, $name);
break;
case 'os':
return $this->checkOS($errmsg, $name);
break;
case 'sapi':
return $this->checkSAPI($errmsg, $name);
break;
case 'zend':
return $this->checkZend($errmsg, $name);
break;
default:
return "'{$opts['type']}' dependency type not supported";
}
}
// }}}
// {{{ checkPackage()
/**
* Package dependencies check method
*
* @param string $errmsg Empty string, it will be populated with an error message, if any
* @param string $name Name of the package to test
* @param string $req The package version required
* @param string $relation How to compare versions with each other
* @param bool $opt Whether the relationship is optional
*
* @return mixed bool false if no error or the error string
*/
function checkPackage(&$errmsg, $name, $req = null, $relation = 'has',
$opt = false)
{
if (is_string($req) && substr($req, 0, 2) == 'v.') {
$req = substr($req, 2);
}
switch ($relation) {
case 'has':
if (!$this->registry->packageExists($name)) {
if ($opt) {
$errmsg = "package `$name' is recommended to utilize some features.";
return PEAR_DEPENDENCY_MISSING_OPTIONAL;
}
$errmsg = "requires package `$name'";
return PEAR_DEPENDENCY_MISSING;
}
return false;
case 'not':
if ($this->registry->packageExists($name)) {
$errmsg = "conflicts with package `$name'";
return PEAR_DEPENDENCY_CONFLICT;
}
return false;
case 'lt':
case 'le':
case 'eq':
case 'ne':
case 'ge':
case 'gt':
$version = $this->registry->packageInfo($name, 'version');
if (!$this->registry->packageExists($name)
|| !version_compare("$version", "$req", $relation))
{
$code = $this->codeFromRelation($relation, $version, $req, $opt);
if ($opt) {
$errmsg = "package `$name' version " . $this->signOperator($relation) .
" $req is recommended to utilize some features.";
if ($version) {
$errmsg .= " Installed version is $version";
}
return $code;
}
$errmsg = "requires package `$name' " .
$this->signOperator($relation) . " $req";
return $code;
}
return false;
}
$errmsg = "relation '$relation' with requirement '$req' is not supported (name=$name)";
return PEAR_DEPENDENCY_BAD_DEPENDENCY;
}
// }}}
// {{{ checkPackageUninstall()
/**
* Check package dependencies on uninstall
*
* @param string $error The resultant error string
* @param string $warning The resultant warning string
* @param string $name Name of the package to test
*
* @return bool true if there were errors
*/
function checkPackageUninstall(&$error, &$warning, $package)
{
$error = null;
$packages = $this->registry->listPackages();
foreach ($packages as $pkg) {
if ($pkg == $package) {
continue;
}
$deps = $this->registry->packageInfo($pkg, 'release_deps');
if (empty($deps)) {
continue;
}
foreach ($deps as $dep) {
if ($dep['type'] == 'pkg' && strcasecmp($dep['name'], $package) == 0) {
if ($dep['rel'] == 'ne' || $dep['rel'] == 'not') {
continue;
}
if (isset($dep['optional']) && $dep['optional'] == 'yes') {
$warning .= "\nWarning: Package '$pkg' optionally depends on '$package'";
} else {
$error .= "Package '$pkg' depends on '$package'\n";
}
}
}
}
return ($error) ? true : false;
}
// }}}
// {{{ checkExtension()
/**
* Extension dependencies check method
*
* @param string $name Name of the extension to test
* @param string $req_ext_ver Required extension version to compare with
* @param string $relation How to compare versions with eachother
* @param bool $opt Whether the relationship is optional
*
* @return mixed bool false if no error or the error string
*/
function checkExtension(&$errmsg, $name, $req = null, $relation = 'has',
$opt = false)
{
if ($relation == 'not') {
if (extension_loaded($name)) {
$errmsg = "conflicts with PHP extension '$name'";
return PEAR_DEPENDENCY_CONFLICT;
} else {
return false;
}
}
if (!extension_loaded($name)) {
if ($relation == 'not') {
return false;
}
if ($opt) {
$errmsg = "'$name' PHP extension is recommended to utilize some features";
return PEAR_DEPENDENCY_MISSING_OPTIONAL;
}
$errmsg = "'$name' PHP extension is not installed";
return PEAR_DEPENDENCY_MISSING;
}
if ($relation == 'has') {
return false;
}
$code = false;
if (is_string($req) && substr($req, 0, 2) == 'v.') {
$req = substr($req, 2);
}
$ext_ver = phpversion($name);
$operator = $relation;
// Force params to be strings, otherwise the comparation will fail (ex. 0.9==0.90)
if (!version_compare("$ext_ver", "$req", $operator)) {
$errmsg = "'$name' PHP extension version " .
$this->signOperator($operator) . " $req is required";
$code = $this->codeFromRelation($relation, $ext_ver, $req, $opt);
if ($opt) {
$errmsg = "'$name' PHP extension version " . $this->signOperator($operator) .
" $req is recommended to utilize some features";
return $code;
}
}
return $code;
}
// }}}
// {{{ checkOS()
/**
* Operating system dependencies check method
*
* @param string $os Name of the operating system
*
* @return mixed bool false if no error or the error string
*/
function checkOS(&$errmsg, $os)
{
// XXX Fixme: Implement a more flexible way, like
// comma separated values or something similar to PEAR_OS
static $myos;
if (empty($myos)) {
include_once "OS/Guess.php";
$myos = new OS_Guess();
}
// only 'has' relation is currently supported
if ($myos->matchSignature($os)) {
return false;
}
$errmsg = "'$os' operating system not supported";
return PEAR_DEPENDENCY_CONFLICT;
}
// }}}
// {{{ checkPHP()
/**
* PHP version check method
*
* @param string $req which version to compare
* @param string $relation how to compare the version
*
* @return mixed bool false if no error or the error string
*/
function checkPHP(&$errmsg, $req, $relation = 'ge')
{
// this would be a bit stupid, but oh well :)
if ($relation == 'has') {
return false;
}
if ($relation == 'not') {
$errmsg = 'Invalid dependency - "not" is not allowed for php dependencies, ' .
'php cannot conflict with itself';
return PEAR_DEPENDENCY_BAD_DEPENDENCY;
}
if (substr($req, 0, 2) == 'v.') {
$req = substr($req,2, strlen($req) - 2);
}
$php_ver = phpversion();
$operator = $relation;
if (!version_compare("$php_ver", "$req", $operator)) {
$errmsg = "PHP version " . $this->signOperator($operator) .
" $req is required";
return PEAR_DEPENDENCY_CONFLICT;
}
return false;
}
// }}}
// {{{ checkProgram()
/**
* External program check method. Looks for executable files in
* directories listed in the PATH environment variable.
*
* @param string $program which program to look for
*
* @return mixed bool false if no error or the error string
*/
function checkProgram(&$errmsg, $program)
{
// XXX FIXME honor safe mode
$exe_suffix = OS_WINDOWS ? '.exe' : '';
$path_elements = explode(PATH_SEPARATOR, getenv('PATH'));
foreach ($path_elements as $dir) {
$file = $dir . DIRECTORY_SEPARATOR . $program . $exe_suffix;
if (@file_exists($file) && @is_executable($file)) {
return false;
}
}
$errmsg = "'$program' program is not present in the PATH";
return PEAR_DEPENDENCY_MISSING;
}
// }}}
// {{{ checkSAPI()
/**
* SAPI backend check method. Version comparison is not yet
* available here.
*
* @param string $name name of SAPI backend
* @param string $req which version to compare
* @param string $relation how to compare versions (currently
* hardcoded to 'has')
* @return mixed bool false if no error or the error string
*/
function checkSAPI(&$errmsg, $name, $req = null, $relation = 'has')
{
// XXX Fixme: There is no way to know if the user has or
// not other SAPI backends installed than the installer one
$sapi_backend = php_sapi_name();
// Version comparisons not supported, sapi backends don't have
// version information yet.
if ($sapi_backend == $name) {
return false;
}
$errmsg = "'$sapi_backend' SAPI backend not supported";
return PEAR_DEPENDENCY_CONFLICT;
}
// }}}
// {{{ checkZend()
/**
* Zend version check method
*
* @param string $req which version to compare
* @param string $relation how to compare the version
*
* @return mixed bool false if no error or the error string
*/
function checkZend(&$errmsg, $req, $relation = 'ge')
{
if (substr($req, 0, 2) == 'v.') {
$req = substr($req,2, strlen($req) - 2);
}
$zend_ver = zend_version();
$operator = substr($relation,0,2);
if (!version_compare("$zend_ver", "$req", $operator)) {
$errmsg = "Zend version " . $this->signOperator($operator) .
" $req is required";
return PEAR_DEPENDENCY_CONFLICT;
}
return false;
}
// }}}
// {{{ signOperator()
/**
* Converts text comparing operators to them sign equivalents
*
* Example: 'ge' to '>='
*
* @access public
* @param string Operator
* @return string Sign equivalent
*/
function signOperator($operator)
{
switch($operator) {
case 'lt': return '<';
case 'le': return '<=';
case 'gt': return '>';
case 'ge': return '>=';
case 'eq': return '==';
case 'ne': return '!=';
default:
return $operator;
}
}
// }}}
// {{{ codeFromRelation()
/**
* Convert relation into corresponding code
*
* @access public
* @param string Relation
* @param string Version
* @param string Requirement
* @param bool Optional dependency indicator
* @return integer
*/
function codeFromRelation($relation, $version, $req, $opt = false)
{
$code = PEAR_DEPENDENCY_BAD_DEPENDENCY;
switch ($relation) {
case 'gt': case 'ge': case 'eq':
// upgrade
$have_major = preg_replace('/\D.*/', '', $version);
$need_major = preg_replace('/\D.*/', '', $req);
if ($need_major > $have_major) {
$code = $opt ? PEAR_DEPENDENCY_UPGRADE_MAJOR_OPTIONAL :
PEAR_DEPENDENCY_UPGRADE_MAJOR;
} else {
$code = $opt ? PEAR_DEPENDENCY_UPGRADE_MINOR_OPTIONAL :
PEAR_DEPENDENCY_UPGRADE_MINOR;
}
break;
case 'lt': case 'le': case 'ne':
$code = $opt ? PEAR_DEPENDENCY_CONFLICT_OPTIONAL :
PEAR_DEPENDENCY_CONFLICT;
break;
}
return $code;
}
// }}}
}
?>

680
inc/PEAR/Downloader.php Executable file
View File

@ -0,0 +1,680 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Stig Bakken <ssb@php.net> |
// | Tomas V.V.Cox <cox@idecnet.com> |
// | Martin Jansen <mj@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Downloader.php,v 1.17.2.1 2004/10/22 22:54:03 cellog Exp $
require_once 'PEAR/Common.php';
require_once 'PEAR/Registry.php';
require_once 'PEAR/Dependency.php';
require_once 'PEAR/Remote.php';
require_once 'System.php';
define('PEAR_INSTALLER_OK', 1);
define('PEAR_INSTALLER_FAILED', 0);
define('PEAR_INSTALLER_SKIPPED', -1);
define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2);
/**
* Administration class used to download PEAR packages and maintain the
* installed package database.
*
* @since PEAR 1.4
* @author Greg Beaver <cellog@php.net>
*/
class PEAR_Downloader extends PEAR_Common
{
/**
* @var PEAR_Config
* @access private
*/
var $_config;
/**
* @var PEAR_Registry
* @access private
*/
var $_registry;
/**
* @var PEAR_Remote
* @access private
*/
var $_remote;
/**
* Preferred Installation State (snapshot, devel, alpha, beta, stable)
* @var string|null
* @access private
*/
var $_preferredState;
/**
* Options from command-line passed to Install.
*
* Recognized options:<br />
* - onlyreqdeps : install all required dependencies as well
* - alldeps : install all dependencies, including optional
* - installroot : base relative path to install files in
* - force : force a download even if warnings would prevent it
* @see PEAR_Command_Install
* @access private
* @var array
*/
var $_options;
/**
* Downloaded Packages after a call to download().
*
* Format of each entry:
*
* <code>
* array('pkg' => 'package_name', 'file' => '/path/to/local/file',
* 'info' => array() // parsed package.xml
* );
* </code>
* @access private
* @var array
*/
var $_downloadedPackages = array();
/**
* Packages slated for download.
*
* This is used to prevent downloading a package more than once should it be a dependency
* for two packages to be installed.
* Format of each entry:
*
* <pre>
* array('package_name1' => parsed package.xml, 'package_name2' => parsed package.xml,
* );
* </pre>
* @access private
* @var array
*/
var $_toDownload = array();
/**
* Array of every package installed, with names lower-cased.
*
* Format:
* <code>
* array('package1' => 0, 'package2' => 1, );
* </code>
* @var array
*/
var $_installed = array();
/**
* @var array
* @access private
*/
var $_errorStack = array();
// {{{ PEAR_Downloader()
function PEAR_Downloader(&$ui, $options, &$config)
{
$this->_options = $options;
$this->_config = &$config;
$this->_preferredState = $this->_config->get('preferred_state');
$this->ui = &$ui;
if (!$this->_preferredState) {
// don't inadvertantly use a non-set preferred_state
$this->_preferredState = null;
}
$php_dir = $this->_config->get('php_dir');
if (isset($this->_options['installroot'])) {
if (substr($this->_options['installroot'], -1) == DIRECTORY_SEPARATOR) {
$this->_options['installroot'] = substr($this->_options['installroot'], 0, -1);
}
$php_dir = $this->_prependPath($php_dir, $this->_options['installroot']);
}
$this->_registry = &new PEAR_Registry($php_dir);
$this->_remote = &new PEAR_Remote($config);
if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) {
$this->_installed = $this->_registry->listPackages();
array_walk($this->_installed, create_function('&$v,$k','$v = strtolower($v);'));
$this->_installed = array_flip($this->_installed);
}
parent::PEAR_Common();
}
// }}}
// {{{ configSet()
function configSet($key, $value, $layer = 'user')
{
$this->_config->set($key, $value, $layer);
$this->_preferredState = $this->_config->get('preferred_state');
if (!$this->_preferredState) {
// don't inadvertantly use a non-set preferred_state
$this->_preferredState = null;
}
}
// }}}
// {{{ setOptions()
function setOptions($options)
{
$this->_options = $options;
}
// }}}
// {{{ _downloadFile()
/**
* @param string filename to download
* @param string version/state
* @param string original value passed to command-line
* @param string|null preferred state (snapshot/devel/alpha/beta/stable)
* Defaults to configuration preferred state
* @return null|PEAR_Error|string
* @access private
*/
function _downloadFile($pkgfile, $version, $origpkgfile, $state = null)
{
if (is_null($state)) {
$state = $this->_preferredState;
}
// {{{ check the package filename, and whether it's already installed
$need_download = false;
if (preg_match('#^(http|ftp)://#', $pkgfile)) {
$need_download = true;
} elseif (!@is_file($pkgfile)) {
if ($this->validPackageName($pkgfile)) {
if ($this->_registry->packageExists($pkgfile)) {
if (empty($this->_options['upgrade']) && empty($this->_options['force'])) {
$errors[] = "$pkgfile already installed";
return;
}
}
$pkgfile = $this->getPackageDownloadUrl($pkgfile, $version);
$need_download = true;
} else {
if (strlen($pkgfile)) {
$errors[] = "Could not open the package file: $pkgfile";
} else {
$errors[] = "No package file given";
}
return;
}
}
// }}}
// {{{ Download package -----------------------------------------------
if ($need_download) {
$downloaddir = $this->_config->get('download_dir');
if (empty($downloaddir)) {
if (PEAR::isError($downloaddir = System::mktemp('-d'))) {
return $downloaddir;
}
$this->log(3, '+ tmp dir created at ' . $downloaddir);
}
$callback = $this->ui ? array(&$this, '_downloadCallback') : null;
$this->pushErrorHandling(PEAR_ERROR_RETURN);
$file = $this->downloadHttp($pkgfile, $this->ui, $downloaddir, $callback);
$this->popErrorHandling();
if (PEAR::isError($file)) {
if ($this->validPackageName($origpkgfile)) {
if (!PEAR::isError($info = $this->_remote->call('package.info',
$origpkgfile))) {
if (!count($info['releases'])) {
return $this->raiseError('Package ' . $origpkgfile .
' has no releases');
} else {
return $this->raiseError('No releases of preferred state "'
. $state . '" exist for package ' . $origpkgfile .
'. Use ' . $origpkgfile . '-state to install another' .
' state (like ' . $origpkgfile .'-beta)',
PEAR_INSTALLER_ERROR_NO_PREF_STATE);
}
} else {
return $pkgfile;
}
} else {
return $this->raiseError($file);
}
}
$pkgfile = $file;
}
// }}}
return $pkgfile;
}
// }}}
// {{{ getPackageDownloadUrl()
function getPackageDownloadUrl($package, $version = null)
{
if ($version) {
$package .= "-$version";
}
if ($this === null || $this->_config === null) {
$package = "http://pear.php.net/get/$package";
} else {
$package = "http://" . $this->_config->get('master_server') .
"/get/$package";
}
if (!extension_loaded("zlib")) {
$package .= '?uncompress=yes';
}
return $package;
}
// }}}
// {{{ extractDownloadFileName($pkgfile, &$version)
function extractDownloadFileName($pkgfile, &$version)
{
if (@is_file($pkgfile)) {
return $pkgfile;
}
// regex defined in Common.php
if (preg_match(PEAR_COMMON_PACKAGE_DOWNLOAD_PREG, $pkgfile, $m)) {
$version = (isset($m[3])) ? $m[3] : null;
return $m[1];
}
$version = null;
return $pkgfile;
}
// }}}
// }}}
// {{{ getDownloadedPackages()
/**
* Retrieve a list of downloaded packages after a call to {@link download()}.
*
* Also resets the list of downloaded packages.
* @return array
*/
function getDownloadedPackages()
{
$ret = $this->_downloadedPackages;
$this->_downloadedPackages = array();
$this->_toDownload = array();
return $ret;
}
// }}}
// {{{ download()
/**
* Download any files and their dependencies, if necessary
*
* BC-compatible method name
* @param array a mixed list of package names, local files, or package.xml
*/
function download($packages)
{
return $this->doDownload($packages);
}
// }}}
// {{{ doDownload()
/**
* Download any files and their dependencies, if necessary
*
* @param array a mixed list of package names, local files, or package.xml
*/
function doDownload($packages)
{
$mywillinstall = array();
$state = $this->_preferredState;
// {{{ download files in this list if necessary
foreach($packages as $pkgfile) {
$need_download = false;
if (!is_file($pkgfile)) {
if (preg_match('#^(http|ftp)://#', $pkgfile)) {
$need_download = true;
}
$pkgfile = $this->_downloadNonFile($pkgfile);
if (PEAR::isError($pkgfile)) {
return $pkgfile;
}
if ($pkgfile === false) {
continue;
}
} // end is_file()
$tempinfo = $this->infoFromAny($pkgfile);
if ($need_download) {
$this->_toDownload[] = $tempinfo['package'];
}
if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) {
// ignore dependencies if there are any errors
if (!PEAR::isError($tempinfo)) {
$mywillinstall[strtolower($tempinfo['package'])] = @$tempinfo['release_deps'];
}
}
$this->_downloadedPackages[] = array('pkg' => $tempinfo['package'],
'file' => $pkgfile, 'info' => $tempinfo);
} // end foreach($packages)
// }}}
// {{{ extract dependencies from downloaded files and then download
// them if necessary
if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) {
$deppackages = array();
foreach ($mywillinstall as $package => $alldeps) {
if (!is_array($alldeps)) {
// there are no dependencies
continue;
}
$fail = false;
foreach ($alldeps as $info) {
if ($info['type'] != 'pkg') {
continue;
}
$ret = $this->_processDependency($package, $info, $mywillinstall);
if ($ret === false) {
continue;
}
if ($ret === 0) {
$fail = true;
continue;
}
if (PEAR::isError($ret)) {
return $ret;
}
$deppackages[] = $ret;
} // foreach($alldeps
if ($fail) {
$deppackages = array();
}
}
if (count($deppackages)) {
$this->doDownload($deppackages);
}
} // }}} if --alldeps or --onlyreqdeps
}
// }}}
// {{{ _downloadNonFile($pkgfile)
/**
* @return false|PEAR_Error|string false if loop should be broken out of,
* string if the file was downloaded,
* PEAR_Error on exception
* @access private
*/
function _downloadNonFile($pkgfile)
{
$origpkgfile = $pkgfile;
$state = null;
$pkgfile = $this->extractDownloadFileName($pkgfile, $version);
if (preg_match('#^(http|ftp)://#', $pkgfile)) {
return $this->_downloadFile($pkgfile, $version, $origpkgfile);
}
if (!$this->validPackageName($pkgfile)) {
return $this->raiseError("Package name '$pkgfile' not valid");
}
// ignore packages that are installed unless we are upgrading
$curinfo = $this->_registry->packageInfo($pkgfile);
if ($this->_registry->packageExists($pkgfile)
&& empty($this->_options['upgrade']) && empty($this->_options['force'])) {
$this->log(0, "Package '{$curinfo['package']}' already installed, skipping");
return false;
}
if (in_array($pkgfile, $this->_toDownload)) {
return false;
}
$releases = $this->_remote->call('package.info', $pkgfile, 'releases', true);
if (!count($releases)) {
return $this->raiseError("No releases found for package '$pkgfile'");
}
// Want a specific version/state
if ($version !== null) {
// Passed Foo-1.2
if ($this->validPackageVersion($version)) {
if (!isset($releases[$version])) {
return $this->raiseError("No release with version '$version' found for '$pkgfile'");
}
// Passed Foo-alpha
} elseif (in_array($version, $this->getReleaseStates())) {
$state = $version;
$version = 0;
foreach ($releases as $ver => $inf) {
if ($inf['state'] == $state && version_compare("$version", "$ver") < 0) {
$version = $ver;
break;
}
}
if ($version == 0) {
return $this->raiseError("No release with state '$state' found for '$pkgfile'");
}
// invalid suffix passed
} else {
return $this->raiseError("Invalid suffix '-$version', be sure to pass a valid PEAR ".
"version number or release state");
}
// Guess what to download
} else {
$states = $this->betterStates($this->_preferredState, true);
$possible = false;
$version = 0;
$higher_version = 0;
$prev_hi_ver = 0;
foreach ($releases as $ver => $inf) {
if (in_array($inf['state'], $states) && version_compare("$version", "$ver") < 0) {
$version = $ver;
break;
} else {
$ver = (string)$ver;
if (version_compare($prev_hi_ver, $ver) < 0) {
$prev_hi_ver = $higher_version = $ver;
}
}
}
if ($version === 0 && !isset($this->_options['force'])) {
return $this->raiseError('No release with state equal to: \'' . implode(', ', $states) .
"' found for '$pkgfile'");
} elseif ($version === 0) {
$this->log(0, "Warning: $pkgfile is state '" . $releases[$higher_version]['state'] . "' which is less stable " .
"than state '$this->_preferredState'");
}
}
// Check if we haven't already the version
if (empty($this->_options['force']) && !is_null($curinfo)) {
if ($curinfo['version'] == $version) {
$this->log(0, "Package '{$curinfo['package']}-{$curinfo['version']}' already installed, skipping");
return false;
} elseif (version_compare("$version", "{$curinfo['version']}") < 0) {
$this->log(0, "Package '{$curinfo['package']}' version '{$curinfo['version']}' " .
" is installed and {$curinfo['version']} is > requested '$version', skipping");
return false;
}
}
$this->_toDownload[] = $pkgfile;
return $this->_downloadFile($pkgfile, $version, $origpkgfile, $state);
}
// }}}
// {{{ _processDependency($package, $info, $mywillinstall)
/**
* Process a dependency, download if necessary
* @param array dependency information from PEAR_Remote call
* @param array packages that will be installed in this iteration
* @return false|string|PEAR_Error
* @access private
* @todo Add test for relation 'lt'/'le' -> make sure that the dependency requested is
* in fact lower than the required value. This will be very important for BC dependencies
*/
function _processDependency($package, $info, $mywillinstall)
{
$state = $this->_preferredState;
if (!isset($this->_options['alldeps']) && isset($info['optional']) &&
$info['optional'] == 'yes') {
// skip optional deps
$this->log(0, "skipping Package '$package' optional dependency '$info[name]'");
return false;
}
// {{{ get releases
$releases = $this->_remote->call('package.info', $info['name'], 'releases', true);
if (PEAR::isError($releases)) {
return $releases;
}
if (!count($releases)) {
if (!isset($this->_installed[strtolower($info['name'])])) {
$this->pushError("Package '$package' dependency '$info[name]' ".
"has no releases");
}
return false;
}
$found = false;
$save = $releases;
while(count($releases) && !$found) {
if (!empty($state) && $state != 'any') {
list($release_version, $release) = each($releases);
if ($state != $release['state'] &&
!in_array($release['state'], $this->betterStates($state)))
{
// drop this release - it ain't stable enough
array_shift($releases);
} else {
$found = true;
}
} else {
$found = true;
}
}
if (!count($releases) && !$found) {
$get = array();
foreach($save as $release) {
$get = array_merge($get,
$this->betterStates($release['state'], true));
}
$savestate = array_shift($get);
$this->pushError( "Release for '$package' dependency '$info[name]' " .
"has state '$savestate', requires '$state'");
return 0;
}
if (in_array(strtolower($info['name']), $this->_toDownload) ||
isset($mywillinstall[strtolower($info['name'])])) {
// skip upgrade check for packages we will install
return false;
}
if (!isset($this->_installed[strtolower($info['name'])])) {
// check to see if we can install the specific version required
if ($info['rel'] == 'eq') {
return $info['name'] . '-' . $info['version'];
}
// skip upgrade check for packages we don't have installed
return $info['name'];
}
// }}}
// {{{ see if a dependency must be upgraded
$inst_version = $this->_registry->packageInfo($info['name'], 'version');
if (!isset($info['version'])) {
// this is a rel='has' dependency, check against latest
if (version_compare($release_version, $inst_version, 'le')) {
return false;
} else {
return $info['name'];
}
}
if (version_compare($info['version'], $inst_version, 'le')) {
// installed version is up-to-date
return false;
}
return $info['name'];
}
// }}}
// {{{ _downloadCallback()
function _downloadCallback($msg, $params = null)
{
switch ($msg) {
case 'saveas':
$this->log(1, "downloading $params ...");
break;
case 'done':
$this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes');
break;
case 'bytesread':
static $bytes;
if (empty($bytes)) {
$bytes = 0;
}
if (!($bytes % 10240)) {
$this->log(1, '.', false);
}
$bytes += $params;
break;
case 'start':
$this->log(1, "Starting to download {$params[0]} (".number_format($params[1], 0, '', ',')." bytes)");
break;
}
if (method_exists($this->ui, '_downloadCallback'))
$this->ui->_downloadCallback($msg, $params);
}
// }}}
// {{{ _prependPath($path, $prepend)
function _prependPath($path, $prepend)
{
if (strlen($prepend) > 0) {
if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
$path = $prepend . substr($path, 2);
} else {
$path = $prepend . $path;
}
}
return $path;
}
// }}}
// {{{ pushError($errmsg, $code)
/**
* @param string
* @param integer
*/
function pushError($errmsg, $code = -1)
{
array_push($this->_errorStack, array($errmsg, $code));
}
// }}}
// {{{ getErrorMsgs()
function getErrorMsgs()
{
$msgs = array();
$errs = $this->_errorStack;
foreach ($errs as $err) {
$msgs[] = $err[0];
}
$this->_errorStack = array();
return $msgs;
}
// }}}
}
// }}}
?>

981
inc/PEAR/ErrorStack.php Executable file
View File

@ -0,0 +1,981 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Gregory Beaver <cellog@php.net> |
// | |
// +----------------------------------------------------------------------+
//
// $Id: ErrorStack.php,v 1.7.2.5 2005/01/01 21:26:51 cellog Exp $
/**
* Error Stack Implementation
*
* This is an incredibly simple implementation of a very complex error handling
* facility. It contains the ability
* to track multiple errors from multiple packages simultaneously. In addition,
* it can track errors of many levels, save data along with the error, context
* information such as the exact file, line number, class and function that
* generated the error, and if necessary, it can raise a traditional PEAR_Error.
* It has built-in support for PEAR::Log, to log errors as they occur
*
* Since version 0.2alpha, it is also possible to selectively ignore errors,
* through the use of an error callback, see {@link pushCallback()}
*
* Since version 0.3alpha, it is possible to specify the exception class
* returned from {@link push()}
*
* Since version PEAR1.3.2, ErrorStack no longer instantiates an exception class. This can
* still be done quite handily in an error callback or by manipulating the returned array
* @author Greg Beaver <cellog@php.net>
* @version PEAR1.3.2 (beta)
* @package PEAR_ErrorStack
* @category Debugging
* @license http://www.php.net/license/3_0.txt PHP License v3.0
*/
/**
* Singleton storage
*
* Format:
* <pre>
* array(
* 'package1' => PEAR_ErrorStack object,
* 'package2' => PEAR_ErrorStack object,
* ...
* )
* </pre>
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_SINGLETON']
*/
$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] = array();
/**
* Global error callback (default)
*
* This is only used if set to non-false. * is the default callback for
* all packages, whereas specific packages may set a default callback
* for all instances, regardless of whether they are a singleton or not.
*
* To exclude non-singletons, only set the local callback for the singleton
* @see PEAR_ErrorStack::setDefaultCallback()
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']
*/
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] = array(
'*' => false,
);
/**
* Global Log object (default)
*
* This is only used if set to non-false. Use to set a default log object for
* all stacks, regardless of instantiation order or location
* @see PEAR_ErrorStack::setDefaultLogger()
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
*/
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = false;
/**
* Global Overriding Callback
*
* This callback will override any error callbacks that specific loggers have set.
* Use with EXTREME caution
* @see PEAR_ErrorStack::staticPushCallback()
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
*/
$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
/**#@+
* One of four possible return values from the error Callback
* @see PEAR_ErrorStack::_errorCallback()
*/
/**
* If this is returned, then the error will be both pushed onto the stack
* and logged.
*/
define('PEAR_ERRORSTACK_PUSHANDLOG', 1);
/**
* If this is returned, then the error will only be pushed onto the stack,
* and not logged.
*/
define('PEAR_ERRORSTACK_PUSH', 2);
/**
* If this is returned, then the error will only be logged, but not pushed
* onto the error stack.
*/
define('PEAR_ERRORSTACK_LOG', 3);
/**
* If this is returned, then the error is completely ignored.
*/
define('PEAR_ERRORSTACK_IGNORE', 4);
/**
* If this is returned, then the error is logged and die() is called.
*/
define('PEAR_ERRORSTACK_DIE', 5);
/**#@-*/
/**
* Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in
* the singleton method.
*/
define('PEAR_ERRORSTACK_ERR_NONCLASS', 1);
/**
* Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()}
* that has no __toString() method
*/
define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2);
/**
* Error Stack Implementation
*
* Usage:
* <code>
* // global error stack
* $global_stack = &PEAR_ErrorStack::singleton('MyPackage');
* // local error stack
* $local_stack = new PEAR_ErrorStack('MyPackage');
* </code>
* @copyright 2004 Gregory Beaver
* @package PEAR_ErrorStack
* @license http://www.php.net/license/3_0.txt PHP License
*/
class PEAR_ErrorStack {
/**
* Errors are stored in the order that they are pushed on the stack.
* @since 0.4alpha Errors are no longer organized by error level.
* This renders pop() nearly unusable, and levels could be more easily
* handled in a callback anyway
* @var array
* @access private
*/
var $_errors = array();
/**
* Storage of errors by level.
*
* Allows easy retrieval and deletion of only errors from a particular level
* @since PEAR 1.4.0dev
* @var array
* @access private
*/
var $_errorsByLevel = array();
/**
* Package name this error stack represents
* @var string
* @access protected
*/
var $_package;
/**
* Determines whether a PEAR_Error is thrown upon every error addition
* @var boolean
* @access private
*/
var $_compat = false;
/**
* If set to a valid callback, this will be used to generate the error
* message from the error code, otherwise the message passed in will be
* used
* @var false|string|array
* @access private
*/
var $_msgCallback = false;
/**
* If set to a valid callback, this will be used to generate the error
* context for an error. For PHP-related errors, this will be a file
* and line number as retrieved from debug_backtrace(), but can be
* customized for other purposes. The error might actually be in a separate
* configuration file, or in a database query.
* @var false|string|array
* @access protected
*/
var $_contextCallback = false;
/**
* If set to a valid callback, this will be called every time an error
* is pushed onto the stack. The return value will be used to determine
* whether to allow an error to be pushed or logged.
*
* The return value must be one an PEAR_ERRORSTACK_* constant
* @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
* @var false|string|array
* @access protected
*/
var $_errorCallback = array();
/**
* PEAR::Log object for logging errors
* @var false|Log
* @access protected
*/
var $_logger = false;
/**
* Error messages - designed to be overridden
* @var array
* @abstract
*/
var $_errorMsgs = array();
/**
* Set up a new error stack
*
* @param string $package name of the package this error stack represents
* @param callback $msgCallback callback used for error message generation
* @param callback $contextCallback callback used for context generation,
* defaults to {@link getFileLine()}
* @param boolean $throwPEAR_Error
*/
function PEAR_ErrorStack($package, $msgCallback = false, $contextCallback = false,
$throwPEAR_Error = false)
{
$this->_package = $package;
$this->setMessageCallback($msgCallback);
$this->setContextCallback($contextCallback);
$this->_compat = $throwPEAR_Error;
}
/**
* Return a single error stack for this package.
*
* Note that all parameters are ignored if the stack for package $package
* has already been instantiated
* @param string $package name of the package this error stack represents
* @param callback $msgCallback callback used for error message generation
* @param callback $contextCallback callback used for context generation,
* defaults to {@link getFileLine()}
* @param boolean $throwPEAR_Error
* @param string $stackClass class to instantiate
* @static
* @return PEAR_ErrorStack
*/
function &singleton($package, $msgCallback = false, $contextCallback = false,
$throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack')
{
if (isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
}
if (!class_exists($stackClass)) {
if (function_exists('debug_backtrace')) {
$trace = debug_backtrace();
}
PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS,
'exception', array('stackclass' => $stackClass),
'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)',
false, $trace);
}
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] =
&new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error);
}
/**
* Internal error handler for PEAR_ErrorStack class
*
* Dies if the error is an exception (and would have died anyway)
* @access private
*/
function _handleError($err)
{
if ($err['level'] == 'exception') {
$message = $err['message'];
if (isset($_SERVER['REQUEST_URI'])) {
echo '<br />';
} else {
echo "\n";
}
var_dump($err['context']);
die($message);
}
}
/**
* Set up a PEAR::Log object for all error stacks that don't have one
* @param Log $log
* @static
*/
function setDefaultLogger(&$log)
{
if (is_object($log) && method_exists($log, 'log') ) {
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
} elseif (is_callable($log)) {
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
}
}
/**
* Set up a PEAR::Log object for this error stack
* @param Log $log
*/
function setLogger(&$log)
{
if (is_object($log) && method_exists($log, 'log') ) {
$this->_logger = &$log;
} elseif (is_callable($log)) {
$this->_logger = &$log;
}
}
/**
* Set an error code => error message mapping callback
*
* This method sets the callback that can be used to generate error
* messages for any instance
* @param array|string Callback function/method
*/
function setMessageCallback($msgCallback)
{
if (!$msgCallback) {
$this->_msgCallback = array(&$this, 'getErrorMessage');
} else {
if (is_callable($msgCallback)) {
$this->_msgCallback = $msgCallback;
}
}
}
/**
* Get an error code => error message mapping callback
*
* This method returns the current callback that can be used to generate error
* messages
* @return array|string|false Callback function/method or false if none
*/
function getMessageCallback()
{
return $this->_msgCallback;
}
/**
* Sets a default callback to be used by all error stacks
*
* This method sets the callback that can be used to generate error
* messages for a singleton
* @param array|string Callback function/method
* @param string Package name, or false for all packages
* @static
*/
function setDefaultCallback($callback = false, $package = false)
{
if (!is_callable($callback)) {
$callback = false;
}
$package = $package ? $package : '*';
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$package] = $callback;
}
/**
* Set a callback that generates context information (location of error) for an error stack
*
* This method sets the callback that can be used to generate context
* information for an error. Passing in NULL will disable context generation
* and remove the expensive call to debug_backtrace()
* @param array|string|null Callback function/method
*/
function setContextCallback($contextCallback)
{
if ($contextCallback === null) {
return $this->_contextCallback = false;
}
if (!$contextCallback) {
$this->_contextCallback = array(&$this, 'getFileLine');
} else {
if (is_callable($contextCallback)) {
$this->_contextCallback = $contextCallback;
}
}
}
/**
* Set an error Callback
* If set to a valid callback, this will be called every time an error
* is pushed onto the stack. The return value will be used to determine
* whether to allow an error to be pushed or logged.
*
* The return value must be one of the ERRORSTACK_* constants.
*
* This functionality can be used to emulate PEAR's pushErrorHandling, and
* the PEAR_ERROR_CALLBACK mode, without affecting the integrity of
* the error stack or logging
* @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
* @see popCallback()
* @param string|array $cb
*/
function pushCallback($cb)
{
array_push($this->_errorCallback, $cb);
}
/**
* Remove a callback from the error callback stack
* @see pushCallback()
* @return array|string|false
*/
function popCallback()
{
if (!count($this->_errorCallback)) {
return false;
}
return array_pop($this->_errorCallback);
}
/**
* Set a temporary overriding error callback for every package error stack
*
* Use this to temporarily disable all existing callbacks (can be used
* to emulate the @ operator, for instance)
* @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
* @see staticPopCallback(), pushCallback()
* @param string|array $cb
* @static
*/
function staticPushCallback($cb)
{
array_push($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'], $cb);
}
/**
* Remove a temporary overriding error callback
* @see staticPushCallback()
* @return array|string|false
* @static
*/
function staticPopCallback()
{
$ret = array_pop($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK']);
if (!is_array($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'])) {
$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
}
return $ret;
}
/**
* Add an error to the stack
*
* If the message generator exists, it is called with 2 parameters.
* - the current Error Stack object
* - an array that is in the same format as an error. Available indices
* are 'code', 'package', 'time', 'params', 'level', and 'context'
*
* Next, if the error should contain context information, this is
* handled by the context grabbing method.
* Finally, the error is pushed onto the proper error stack
* @param int $code Package-specific error code
* @param string $level Error level. This is NOT spell-checked
* @param array $params associative array of error parameters
* @param string $msg Error message, or a portion of it if the message
* is to be generated
* @param array $repackage If this error re-packages an error pushed by
* another package, place the array returned from
* {@link pop()} in this parameter
* @param array $backtrace Protected parameter: use this to pass in the
* {@link debug_backtrace()} that should be used
* to find error context
* @return PEAR_Error|array|Exception
* if compatibility mode is on, a PEAR_Error is also
* thrown. If the class Exception exists, then one
* is returned to allow code like:
* <code>
* throw ($stack->push(MY_ERROR_CODE, 'error', array('username' => 'grob')));
* </code>
*
* The errorData property of the exception class will be set to the array
* that would normally be returned. If a PEAR_Error is returned, the userinfo
* property is set to the array
*
* Otherwise, an array is returned in this format:
* <code>
* array(
* 'code' => $code,
* 'params' => $params,
* 'package' => $this->_package,
* 'level' => $level,
* 'time' => time(),
* 'context' => $context,
* 'message' => $msg,
* //['repackage' => $err] repackaged error array/Exception class
* );
* </code>
*/
function push($code, $level = 'error', $params = array(), $msg = false,
$repackage = false, $backtrace = false)
{
$context = false;
// grab error context
if ($this->_contextCallback) {
if (!$backtrace) {
$backtrace = debug_backtrace();
}
$context = call_user_func($this->_contextCallback, $code, $params, $backtrace);
}
// save error
$time = explode(' ', microtime());
$time = $time[1] + $time[0];
$err = array(
'code' => $code,
'params' => $params,
'package' => $this->_package,
'level' => $level,
'time' => $time,
'context' => $context,
'message' => $msg,
);
// set up the error message, if necessary
if ($this->_msgCallback) {
$msg = call_user_func_array($this->_msgCallback,
array(&$this, $err));
$err['message'] = $msg;
}
if ($repackage) {
$err['repackage'] = $repackage;
}
$push = $log = true;
$die = false;
// try the overriding callback first
$callback = $this->staticPopCallback();
if ($callback) {
$this->staticPushCallback($callback);
}
if (!is_callable($callback)) {
// try the local callback next
$callback = $this->popCallback();
if (is_callable($callback)) {
$this->pushCallback($callback);
} else {
// try the default callback
$callback = isset($GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package]) ?
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package] :
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']['*'];
}
}
if (is_callable($callback)) {
switch(call_user_func($callback, $err)){
case PEAR_ERRORSTACK_IGNORE:
return $err;
break;
case PEAR_ERRORSTACK_PUSH:
$log = false;
break;
case PEAR_ERRORSTACK_LOG:
$push = false;
break;
case PEAR_ERRORSTACK_DIE:
$die = true;
break;
// anything else returned has the same effect as pushandlog
}
}
if ($push) {
array_unshift($this->_errors, $err);
$this->_errorsByLevel[$err['level']][] = &$this->_errors[0];
}
if ($log) {
if ($this->_logger || $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']) {
$this->_log($err);
}
}
if ($die) {
die();
}
if ($this->_compat && $push) {
return $this->raiseError($msg, $code, null, null, $err);
}
return $err;
}
/**
* Static version of {@link push()}
*
* @param string $package Package name this error belongs to
* @param int $code Package-specific error code
* @param string $level Error level. This is NOT spell-checked
* @param array $params associative array of error parameters
* @param string $msg Error message, or a portion of it if the message
* is to be generated
* @param array $repackage If this error re-packages an error pushed by
* another package, place the array returned from
* {@link pop()} in this parameter
* @param array $backtrace Protected parameter: use this to pass in the
* {@link debug_backtrace()} that should be used
* to find error context
* @return PEAR_Error|null|Exception
* if compatibility mode is on, a PEAR_Error is also
* thrown. If the class Exception exists, then one
* is returned to allow code like:
* <code>
* throw ($stack->push(MY_ERROR_CODE, 'error', array('username' => 'grob')));
* </code>
* @static
*/
function staticPush($package, $code, $level = 'error', $params = array(),
$msg = false, $repackage = false, $backtrace = false)
{
$s = &PEAR_ErrorStack::singleton($package);
if ($s->_contextCallback) {
if (!$backtrace) {
if (function_exists('debug_backtrace')) {
$backtrace = debug_backtrace();
}
}
}
return $s->push($code, $level, $params, $msg, $repackage, $backtrace);
}
/**
* Log an error using PEAR::Log
* @param array $err Error array
* @param array $levels Error level => Log constant map
* @access protected
*/
function _log($err)
{
if ($this->_logger) {
$logger = &$this->_logger;
} else {
$logger = &$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'];
}
if (is_a($logger, 'Log')) {
$levels = array(
'exception' => PEAR_LOG_CRIT,
'alert' => PEAR_LOG_ALERT,
'critical' => PEAR_LOG_CRIT,
'error' => PEAR_LOG_ERR,
'warning' => PEAR_LOG_WARNING,
'notice' => PEAR_LOG_NOTICE,
'info' => PEAR_LOG_INFO,
'debug' => PEAR_LOG_DEBUG);
if (isset($levels[$err['level']])) {
$level = $levels[$err['level']];
} else {
$level = PEAR_LOG_INFO;
}
$logger->log($err['message'], $level, $err);
} else { // support non-standard logs
call_user_func($logger, $err);
}
}
/**
* Pop an error off of the error stack
*
* @return false|array
* @since 0.4alpha it is no longer possible to specify a specific error
* level to return - the last error pushed will be returned, instead
*/
function pop()
{
return @array_shift($this->_errors);
}
/**
* Determine whether there are any errors on the stack
* @param string|array Level name. Use to determine if any errors
* of level (string), or levels (array) have been pushed
* @return boolean
*/
function hasErrors($level = false)
{
if ($level) {
return isset($this->_errorsByLevel[$level]);
}
return count($this->_errors);
}
/**
* Retrieve all errors since last purge
*
* @param boolean set in order to empty the error stack
* @param string level name, to return only errors of a particular severity
* @return array
*/
function getErrors($purge = false, $level = false)
{
if (!$purge) {
if ($level) {
if (!isset($this->_errorsByLevel[$level])) {
return array();
} else {
return $this->_errorsByLevel[$level];
}
} else {
return $this->_errors;
}
}
if ($level) {
$ret = $this->_errorsByLevel[$level];
foreach ($this->_errorsByLevel[$level] as $i => $unused) {
// entries are references to the $_errors array
$this->_errorsByLevel[$level][$i] = false;
}
// array_filter removes all entries === false
$this->_errors = array_filter($this->_errors);
unset($this->_errorsByLevel[$level]);
return $ret;
}
$ret = $this->_errors;
$this->_errors = array();
$this->_errorsByLevel = array();
return $ret;
}
/**
* Determine whether there are any errors on a single error stack, or on any error stack
*
* The optional parameter can be used to test the existence of any errors without the need of
* singleton instantiation
* @param string|false Package name to check for errors
* @param string Level name to check for a particular severity
* @return boolean
* @static
*/
function staticHasErrors($package = false, $level = false)
{
if ($package) {
if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
return false;
}
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->hasErrors($level);
}
foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
if ($obj->hasErrors($level)) {
return true;
}
}
return false;
}
/**
* Get a list of all errors since last purge, organized by package
* @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be
* @param boolean $purge Set to purge the error stack of existing errors
* @param string $level Set to a level name in order to retrieve only errors of a particular level
* @param boolean $merge Set to return a flat array, not organized by package
* @param array $sortfunc Function used to sort a merged array - default
* sorts by time, and should be good for most cases
* @static
* @return array
*/
function staticGetErrors($purge = false, $level = false, $merge = false,
$sortfunc = array('PEAR_ErrorStack', '_sortErrors'))
{
$ret = array();
if (!is_callable($sortfunc)) {
$sortfunc = array('PEAR_ErrorStack', '_sortErrors');
}
foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
$test = $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->getErrors($purge, $level);
if ($test) {
if ($merge) {
$ret = array_merge($ret, $test);
} else {
$ret[$package] = $test;
}
}
}
if ($merge) {
usort($ret, $sortfunc);
}
return $ret;
}
/**
* Error sorting function, sorts by time
* @access private
*/
function _sortErrors($a, $b)
{
if ($a['time'] == $b['time']) {
return 0;
}
if ($a['time'] < $b['time']) {
return 1;
}
return -1;
}
/**
* Standard file/line number/function/class context callback
*
* This function uses a backtrace generated from {@link debug_backtrace()}
* and so will not work at all in PHP < 4.3.0. The frame should
* reference the frame that contains the source of the error.
* @return array|false either array('file' => file, 'line' => line,
* 'function' => function name, 'class' => class name) or
* if this doesn't work, then false
* @param unused
* @param integer backtrace frame.
* @param array Results of debug_backtrace()
* @static
*/
function getFileLine($code, $params, $backtrace = null)
{
if ($backtrace === null) {
return false;
}
$frame = 0;
$functionframe = 1;
if (!isset($backtrace[1])) {
$functionframe = 0;
} else {
while (isset($backtrace[$functionframe]['function']) &&
$backtrace[$functionframe]['function'] == 'eval' &&
isset($backtrace[$functionframe + 1])) {
$functionframe++;
}
}
if (isset($backtrace[$frame])) {
if (!isset($backtrace[$frame]['file'])) {
$frame++;
}
$funcbacktrace = $backtrace[$functionframe];
$filebacktrace = $backtrace[$frame];
$ret = array('file' => $filebacktrace['file'],
'line' => $filebacktrace['line']);
// rearrange for eval'd code or create function errors
if (strpos($filebacktrace['file'], '(') &&
preg_match(';^(.*?)\((\d+)\) : (.*?)$;', $filebacktrace['file'],
$matches)) {
$ret['file'] = $matches[1];
$ret['line'] = $matches[2] + 0;
}
if (isset($funcbacktrace['function']) && isset($backtrace[1])) {
if ($funcbacktrace['function'] != 'eval') {
if ($funcbacktrace['function'] == '__lambda_func') {
$ret['function'] = 'create_function() code';
} else {
$ret['function'] = $funcbacktrace['function'];
}
}
}
if (isset($funcbacktrace['class']) && isset($backtrace[1])) {
$ret['class'] = $funcbacktrace['class'];
}
return $ret;
}
return false;
}
/**
* Standard error message generation callback
*
* This method may also be called by a custom error message generator
* to fill in template values from the params array, simply
* set the third parameter to the error message template string to use
*
* The special variable %__msg% is reserved: use it only to specify
* where a message passed in by the user should be placed in the template,
* like so:
*
* Error message: %msg% - internal error
*
* If the message passed like so:
*
* <code>
* $stack->push(ERROR_CODE, 'error', array(), 'server error 500');
* </code>
*
* The returned error message will be "Error message: server error 500 -
* internal error"
* @param PEAR_ErrorStack
* @param array
* @param string|false Pre-generated error message template
* @static
* @return string
*/
function getErrorMessage(&$stack, $err, $template = false)
{
if ($template) {
$mainmsg = $template;
} else {
$mainmsg = $stack->getErrorMessageTemplate($err['code']);
}
$mainmsg = str_replace('%__msg%', $err['message'], $mainmsg);
if (count($err['params'])) {
foreach ($err['params'] as $name => $val) {
if (is_array($val)) {
// @ is needed in case $val is a multi-dimensional array
$val = @implode(', ', $val);
}
if (is_object($val)) {
if (method_exists($val, '__toString')) {
$val = $val->__toString();
} else {
PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING,
'warning', array('obj' => get_class($val)),
'object %obj% passed into getErrorMessage, but has no __toString() method');
$val = 'Object';
}
}
$mainmsg = str_replace('%' . $name . '%', $val, $mainmsg);
}
}
return $mainmsg;
}
/**
* Standard Error Message Template generator from code
* @return string
*/
function getErrorMessageTemplate($code)
{
if (!isset($this->_errorMsgs[$code])) {
return '%__msg%';
}
return $this->_errorMsgs[$code];
}
/**
* Set the Error Message Template array
*
* The array format must be:
* <pre>
* array(error code => 'message template',...)
* </pre>
*
* Error message parameters passed into {@link push()} will be used as input
* for the error message. If the template is 'message %foo% was %bar%', and the
* parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will
* be 'message one was six'
* @return string
*/
function setErrorMessageTemplate($template)
{
$this->_errorMsgs = $template;
}
/**
* emulate PEAR::raiseError()
*
* @return PEAR_Error
*/
function raiseError()
{
require_once 'PEAR.php';
$args = func_get_args();
return call_user_func_array(array('PEAR', 'raiseError'), $args);
}
}
$stack = &PEAR_ErrorStack::singleton('PEAR_ErrorStack');
$stack->pushCallback(array('PEAR_ErrorStack', '_handleError'));
?>

359
inc/PEAR/Exception.php Executable file
View File

@ -0,0 +1,359 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
// +----------------------------------------------------------------------+
// | PEAR_Exception |
// +----------------------------------------------------------------------+
// | Copyright (c) 2004 The PEAR Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Tomas V.V.Cox <cox@idecnet.com> |
// | Hans Lellelid <hans@velum.net> |
// | Bertrand Mansion <bmansion@mamasam.com> |
// | Greg Beaver <cellog@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Exception.php,v 1.4.2.2 2004/12/31 19:01:52 cellog Exp $
/**
* Base PEAR_Exception Class
*
* WARNING: This code should be considered stable, but the API is
* subject to immediate and drastic change, so API stability is
* at best alpha
*
* 1) Features:
*
* - Nestable exceptions (throw new PEAR_Exception($msg, $prev_exception))
* - Definable triggers, shot when exceptions occur
* - Pretty and informative error messages
* - Added more context info available (like class, method or cause)
* - cause can be a PEAR_Exception or an array of mixed
* PEAR_Exceptions/PEAR_ErrorStack warnings
* - callbacks for specific exception classes and their children
*
* 2) Ideas:
*
* - Maybe a way to define a 'template' for the output
*
* 3) Inherited properties from PHP Exception Class:
*
* protected $message
* protected $code
* protected $line
* protected $file
* private $trace
*
* 4) Inherited methods from PHP Exception Class:
*
* __clone
* __construct
* getMessage
* getCode
* getFile
* getLine
* getTraceSafe
* getTraceSafeAsString
* __toString
*
* 5) Usage example
*
* <code>
* require_once 'PEAR/Exception.php';
*
* class Test {
* function foo() {
* throw new PEAR_Exception('Error Message', ERROR_CODE);
* }
* }
*
* function myLogger($pear_exception) {
* echo $pear_exception->getMessage();
* }
* // each time a exception is thrown the 'myLogger' will be called
* // (its use is completely optional)
* PEAR_Exception::addObserver('myLogger');
* $test = new Test;
* try {
* $test->foo();
* } catch (PEAR_Exception $e) {
* print $e;
* }
* </code>
*
* @since PHP 5
* @package PEAR
* @version $Revision: 1.4.2.2 $
* @author Tomas V.V.Cox <cox@idecnet.com>
* @author Hans Lellelid <hans@velum.net>
* @author Bertrand Mansion <bmansion@mamasam.com>
*
*/
class PEAR_Exception extends Exception
{
const OBSERVER_PRINT = -2;
const OBSERVER_TRIGGER = -4;
const OBSERVER_DIE = -8;
protected $cause;
private static $_observers = array();
private static $_uniqueid = 0;
private $_trace;
/**
* Supported signatures:
* PEAR_Exception(string $message);
* PEAR_Exception(string $message, int $code);
* PEAR_Exception(string $message, Exception $cause);
* PEAR_Exception(string $message, Exception $cause, int $code);
* PEAR_Exception(string $message, array $causes);
* PEAR_Exception(string $message, array $causes, int $code);
*/
public function __construct($message, $p2 = null, $p3 = null)
{
if (is_int($p2)) {
$code = $p2;
$this->cause = null;
} elseif ($p2 instanceof Exception || is_array($p2)) {
$code = $p3;
if (is_array($p2) && isset($p2['message'])) {
// fix potential problem of passing in a single warning
$p2 = array($p2);
}
$this->cause = $p2;
} else {
$code = null;
$this->cause = null;
}
parent::__construct($message, $code);
$this->signal();
}
/**
* @param mixed $callback - A valid php callback, see php func is_callable()
* - A PEAR_Exception::OBSERVER_* constant
* - An array(const PEAR_Exception::OBSERVER_*,
* mixed $options)
* @param string $label The name of the observer. Use this if you want
* to remove it later with removeObserver()
*/
public static function addObserver($callback, $label = 'default')
{
self::$_observers[$label] = $callback;
}
public static function removeObserver($label = 'default')
{
unset(self::$_observers[$label]);
}
/**
* @return int unique identifier for an observer
*/
public static function getUniqueId()
{
return self::$_uniqueid++;
}
private function signal()
{
foreach (self::$_observers as $func) {
if (is_callable($func)) {
call_user_func($func, $this);
continue;
}
settype($func, 'array');
switch ($func[0]) {
case self::OBSERVER_PRINT :
$f = (isset($func[1])) ? $func[1] : '%s';
printf($f, $this->getMessage());
break;
case self::OBSERVER_TRIGGER :
$f = (isset($func[1])) ? $func[1] : E_USER_NOTICE;
trigger_error($this->getMessage(), $f);
break;
case self::OBSERVER_DIE :
$f = (isset($func[1])) ? $func[1] : '%s';
die(printf($f, $this->getMessage()));
break;
default:
trigger_error('invalid observer type', E_USER_WARNING);
}
}
}
/**
* Return specific error information that can be used for more detailed
* error messages or translation.
*
* This method may be overridden in child exception classes in order
* to add functionality not present in PEAR_Exception and is a placeholder
* to define API
*
* The returned array must be an associative array of parameter => value like so:
* <pre>
* array('name' => $name, 'context' => array(...))
* </pre>
* @return array
*/
public function getErrorData()
{
return array();
}
/**
* Returns the exception that caused this exception to be thrown
* @access public
* @return Exception|array The context of the exception
*/
public function getCause()
{
return $this->cause;
}
/**
* Function must be public to call on caused exceptions
* @param array
*/
public function getCauseMessage(&$causes)
{
$trace = $this->getTraceSafe();
$cause = array('class' => get_class($this),
'message' => $this->message,
'file' => 'unknown',
'line' => 'unknown');
if (isset($trace[0])) {
if (isset($trace[0]['file'])) {
$cause['file'] = $trace[0]['file'];
$cause['line'] = $trace[0]['line'];
}
}
if ($this->cause instanceof PEAR_Exception) {
$this->cause->getCauseMessage($causes);
}
if (is_array($this->cause)) {
foreach ($this->cause as $cause) {
if ($cause instanceof PEAR_Exception) {
$cause->getCauseMessage($causes);
} elseif (is_array($cause) && isset($cause['message'])) {
// PEAR_ErrorStack warning
$causes[] = array(
'class' => $cause['package'],
'message' => $cause['message'],
'file' => isset($cause['context']['file']) ?
$cause['context']['file'] :
'unknown',
'line' => isset($cause['context']['line']) ?
$cause['context']['line'] :
'unknown',
);
}
}
}
}
public function getTraceSafe()
{
if (!isset($this->_trace)) {
$this->_trace = $this->getTrace();
if (empty($this->_trace)) {
$backtrace = debug_backtrace();
$this->_trace = array($backtrace[count($backtrace)-1]);
}
}
return $this->_trace;
}
public function getErrorClass()
{
$trace = $this->getTraceSafe();
return $trace[0]['class'];
}
public function getErrorMethod()
{
$trace = $this->getTraceSafe();
return $trace[0]['function'];
}
public function __toString()
{
if (isset($_SERVER['REQUEST_URI'])) {
return $this->toHtml();
}
return $this->toText();
}
public function toHtml()
{
$trace = $this->getTraceSafe();
$causes = array();
$this->getCauseMessage($causes);
$html = '<table border="1" cellspacing="0">' . "\n";
foreach ($causes as $i => $cause) {
$html .= '<tr><td colspan="3" bgcolor="#ff9999">'
. str_repeat('-', $i) . ' <b>' . $cause['class'] . '</b>: '
. htmlspecialchars($cause['message']) . ' in <b>' . $cause['file'] . '</b> '
. 'on line <b>' . $cause['line'] . '</b>'
. "</td></tr>\n";
}
$html .= '<tr><td colspan="3" bgcolor="#aaaaaa" align="center"><b>Exception trace</b></td></tr>' . "\n"
. '<tr><td align="center" bgcolor="#cccccc" width="20"><b>#</b></td>'
. '<td align="center" bgcolor="#cccccc"><b>Function</b></td>'
. '<td align="center" bgcolor="#cccccc"><b>Location</b></td></tr>' . "\n";
foreach ($trace as $k => $v) {
$html .= '<tr><td align="center">' . $k . '</td>'
. '<td>';
if (!empty($v['class'])) {
$html .= $v['class'] . $v['type'];
}
$html .= $v['function'];
$args = array();
if (!empty($v['args'])) {
foreach ($v['args'] as $arg) {
if (is_null($arg)) $args[] = 'null';
elseif (is_array($arg)) $args[] = 'Array';
elseif (is_object($arg)) $args[] = 'Object('.get_class($arg).')';
elseif (is_bool($arg)) $args[] = $arg ? 'true' : 'false';
elseif (is_int($arg) || is_double($arg)) $args[] = $arg;
else {
$arg = (string)$arg;
$str = htmlspecialchars(substr($arg, 0, 16));
if (strlen($arg) > 16) $str .= '&hellip;';
$args[] = "'" . $str . "'";
}
}
}
$html .= '(' . implode(', ',$args) . ')'
. '</td>'
. '<td>' . $v['file'] . ':' . $v['line'] . '</td></tr>' . "\n";
}
$html .= '<tr><td align="center">' . ($k+1) . '</td>'
. '<td>{main}</td>'
. '<td>&nbsp;</td></tr>' . "\n"
. '</table>';
return $html;
}
public function toText()
{
$causes = array();
$this->getCauseMessage($causes);
$causeMsg = '';
foreach ($causes as $i => $cause) {
$causeMsg .= str_repeat(' ', $i) . $cause['class'] . ': '
. $cause['message'] . ' in ' . $cause['file']
. ' on line ' . $cause['line'] . "\n";
}
return $causeMsg . $this->getTraceAsString();
}
}
?>

509
inc/PEAR/Frontend/CLI.php Executable file
View File

@ -0,0 +1,509 @@
<?php
/*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2004 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.0 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_0.txt. |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Stig Sæther Bakken <ssb@php.net> |
+----------------------------------------------------------------------+
$Id: CLI.php,v 1.41 2004/02/17 05:49:16 ssb Exp $
*/
require_once "PEAR.php";
class PEAR_Frontend_CLI extends PEAR
{
// {{{ properties
/**
* What type of user interface this frontend is for.
* @var string
* @access public
*/
var $type = 'CLI';
var $lp = ''; // line prefix
var $params = array();
var $term = array(
'bold' => '',
'normal' => '',
);
// }}}
// {{{ constructor
function PEAR_Frontend_CLI()
{
parent::PEAR();
$term = getenv('TERM'); //(cox) $_ENV is empty for me in 4.1.1
if (function_exists('posix_isatty') && !posix_isatty(1)) {
// output is being redirected to a file or through a pipe
} elseif ($term) {
// XXX can use ncurses extension here, if available
if (preg_match('/^(xterm|vt220|linux)/', $term)) {
$this->term['bold'] = sprintf("%c%c%c%c", 27, 91, 49, 109);
$this->term['normal']=sprintf("%c%c%c", 27, 91, 109);
} elseif (preg_match('/^vt100/', $term)) {
$this->term['bold'] = sprintf("%c%c%c%c%c%c", 27, 91, 49, 109, 0, 0);
$this->term['normal']=sprintf("%c%c%c%c%c", 27, 91, 109, 0, 0);
}
} elseif (OS_WINDOWS) {
// XXX add ANSI codes here
}
}
// }}}
// {{{ displayLine(text)
function displayLine($text)
{
trigger_error("PEAR_Frontend_CLI::displayLine deprecated", E_USER_ERROR);
}
function _displayLine($text)
{
print "$this->lp$text\n";
}
// }}}
// {{{ display(text)
function display($text)
{
trigger_error("PEAR_Frontend_CLI::display deprecated", E_USER_ERROR);
}
function _display($text)
{
print $text;
}
// }}}
// {{{ displayError(eobj)
/**
* @param object PEAR_Error object
*/
function displayError($eobj)
{
return $this->_displayLine($eobj->getMessage());
}
// }}}
// {{{ displayFatalError(eobj)
/**
* @param object PEAR_Error object
*/
function displayFatalError($eobj)
{
$this->displayError($eobj);
exit(1);
}
// }}}
// {{{ displayHeading(title)
function displayHeading($title)
{
trigger_error("PEAR_Frontend_CLI::displayHeading deprecated", E_USER_ERROR);
}
function _displayHeading($title)
{
print $this->lp.$this->bold($title)."\n";
print $this->lp.str_repeat("=", strlen($title))."\n";
}
// }}}
// {{{ userDialog(prompt, [type], [default])
function userDialog($command, $prompts, $types = array(), $defaults = array())
{
$result = array();
if (is_array($prompts)) {
$fp = fopen("php://stdin", "r");
foreach ($prompts as $key => $prompt) {
$type = $types[$key];
$default = @$defaults[$key];
if ($type == 'password') {
system('stty -echo');
}
print "$this->lp$prompt ";
if ($default) {
print "[$default] ";
}
print ": ";
$line = fgets($fp, 2048);
if ($type == 'password') {
system('stty echo');
print "\n";
}
if ($default && trim($line) == "") {
$result[$key] = $default;
} else {
$result[$key] = $line;
}
}
fclose($fp);
}
return $result;
}
// }}}
// {{{ userConfirm(prompt, [default])
function userConfirm($prompt, $default = 'yes')
{
trigger_error("PEAR_Frontend_CLI::userConfirm not yet converted", E_USER_ERROR);
static $positives = array('y', 'yes', 'on', '1');
static $negatives = array('n', 'no', 'off', '0');
print "$this->lp$prompt [$default] : ";
$fp = fopen("php://stdin", "r");
$line = fgets($fp, 2048);
fclose($fp);
$answer = strtolower(trim($line));
if (empty($answer)) {
$answer = $default;
}
if (in_array($answer, $positives)) {
return true;
}
if (in_array($answer, $negatives)) {
return false;
}
if (in_array($default, $positives)) {
return true;
}
return false;
}
// }}}
// {{{ startTable([params])
function startTable($params = array())
{
trigger_error("PEAR_Frontend_CLI::startTable deprecated", E_USER_ERROR);
}
function _startTable($params = array())
{
$params['table_data'] = array();
$params['widest'] = array(); // indexed by column
$params['highest'] = array(); // indexed by row
$params['ncols'] = 0;
$this->params = $params;
}
// }}}
// {{{ tableRow(columns, [rowparams], [colparams])
function tableRow($columns, $rowparams = array(), $colparams = array())
{
trigger_error("PEAR_Frontend_CLI::tableRow deprecated", E_USER_ERROR);
}
function _tableRow($columns, $rowparams = array(), $colparams = array())
{
$highest = 1;
for ($i = 0; $i < sizeof($columns); $i++) {
$col = &$columns[$i];
if (isset($colparams[$i]) && !empty($colparams[$i]['wrap'])) {
$col = wordwrap($col, $colparams[$i]['wrap'], "\n", 0);
}
if (strpos($col, "\n") !== false) {
$multiline = explode("\n", $col);
$w = 0;
foreach ($multiline as $n => $line) {
if (strlen($line) > $w) {
$w = strlen($line);
}
}
$lines = sizeof($multiline);
} else {
$w = strlen($col);
}
if ($w > @$this->params['widest'][$i]) {
$this->params['widest'][$i] = $w;
}
$tmp = count_chars($columns[$i], 1);
// handle unix, mac and windows formats
$lines = (isset($tmp[10]) ? $tmp[10] : @$tmp[13]) + 1;
if ($lines > $highest) {
$highest = $lines;
}
}
if (sizeof($columns) > $this->params['ncols']) {
$this->params['ncols'] = sizeof($columns);
}
$new_row = array(
'data' => $columns,
'height' => $highest,
'rowparams' => $rowparams,
'colparams' => $colparams,
);
$this->params['table_data'][] = $new_row;
}
// }}}
// {{{ endTable()
function endTable()
{
trigger_error("PEAR_Frontend_CLI::endTable deprecated", E_USER_ERROR);
}
function _endTable()
{
extract($this->params);
if (!empty($caption)) {
$this->_displayHeading($caption);
}
if (count($table_data) == 0) {
return;
}
if (!isset($width)) {
$width = $widest;
} else {
for ($i = 0; $i < $ncols; $i++) {
if (!isset($width[$i])) {
$width[$i] = $widest[$i];
}
}
}
$border = false;
if (empty($border)) {
$cellstart = '';
$cellend = ' ';
$rowend = '';
$padrowend = false;
$borderline = '';
} else {
$cellstart = '| ';
$cellend = ' ';
$rowend = '|';
$padrowend = true;
$borderline = '+';
foreach ($width as $w) {
$borderline .= str_repeat('-', $w + strlen($cellstart) + strlen($cellend) - 1);
$borderline .= '+';
}
}
if ($borderline) {
$this->_displayLine($borderline);
}
for ($i = 0; $i < sizeof($table_data); $i++) {
extract($table_data[$i]);
if (!is_array($rowparams)) {
$rowparams = array();
}
if (!is_array($colparams)) {
$colparams = array();
}
$rowlines = array();
if ($height > 1) {
for ($c = 0; $c < sizeof($data); $c++) {
$rowlines[$c] = preg_split('/(\r?\n|\r)/', $data[$c]);
if (sizeof($rowlines[$c]) < $height) {
$rowlines[$c] = array_pad($rowlines[$c], $height, '');
}
}
} else {
for ($c = 0; $c < sizeof($data); $c++) {
$rowlines[$c] = array($data[$c]);
}
}
for ($r = 0; $r < $height; $r++) {
$rowtext = '';
for ($c = 0; $c < sizeof($data); $c++) {
if (isset($colparams[$c])) {
$attribs = array_merge($rowparams, $colparams);
} else {
$attribs = $rowparams;
}
$w = isset($width[$c]) ? $width[$c] : 0;
//$cell = $data[$c];
$cell = $rowlines[$c][$r];
$l = strlen($cell);
if ($l > $w) {
$cell = substr($cell, 0, $w);
}
if (isset($attribs['bold'])) {
$cell = $this->bold($cell);
}
if ($l < $w) {
// not using str_pad here because we may
// add bold escape characters to $cell
$cell .= str_repeat(' ', $w - $l);
}
$rowtext .= $cellstart . $cell . $cellend;
}
if (!$border) {
$rowtext = rtrim($rowtext);
}
$rowtext .= $rowend;
$this->_displayLine($rowtext);
}
}
if ($borderline) {
$this->_displayLine($borderline);
}
}
// }}}
// {{{ outputData()
function outputData($data, $command = '_default')
{
switch ($command) {
case 'install':
case 'upgrade':
case 'upgrade-all':
if (isset($data['release_warnings'])) {
$this->_displayLine('');
$this->_startTable(array(
'border' => false,
'caption' => 'Release Warnings'
));
$this->_tableRow(array($data['release_warnings']), null, array(1 => array('wrap' => 55)));
$this->_endTable();
$this->_displayLine('');
}
$this->_displayLine($data['data']);
break;
case 'search':
$this->_startTable($data);
if (isset($data['headline']) && is_array($data['headline'])) {
$this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55)));
}
foreach($data['data'] as $category) {
foreach($category as $pkg) {
$this->_tableRow($pkg, null, array(1 => array('wrap' => 55)));
}
};
$this->_endTable();
break;
case 'list-all':
$this->_startTable($data);
if (isset($data['headline']) && is_array($data['headline'])) {
$this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55)));
}
foreach($data['data'] as $category) {
foreach($category as $pkg) {
unset($pkg[3]);
unset($pkg[4]);
$this->_tableRow($pkg, null, array(1 => array('wrap' => 55)));
}
};
$this->_endTable();
break;
case 'config-show':
$data['border'] = false;
$opts = array(0 => array('wrap' => 30),
1 => array('wrap' => 20),
2 => array('wrap' => 35));
$this->_startTable($data);
if (isset($data['headline']) && is_array($data['headline'])) {
$this->_tableRow($data['headline'],
array('bold' => true),
$opts);
}
foreach($data['data'] as $group) {
foreach($group as $value) {
if ($value[2] == '') {
$value[2] = "<not set>";
}
$this->_tableRow($value, null, $opts);
}
}
$this->_endTable();
break;
case 'remote-info':
$data = array(
'caption' => 'Package details:',
'border' => false,
'data' => array(
array("Latest", $data['stable']),
array("Installed", $data['installed']),
array("Package", $data['name']),
array("License", $data['license']),
array("Category", $data['category']),
array("Summary", $data['summary']),
array("Description", $data['description']),
),
);
default: {
if (is_array($data)) {
$this->_startTable($data);
$count = count($data['data'][0]);
if ($count == 2) {
$opts = array(0 => array('wrap' => 25),
1 => array('wrap' => 48)
);
} elseif ($count == 3) {
$opts = array(0 => array('wrap' => 30),
1 => array('wrap' => 20),
2 => array('wrap' => 35)
);
} else {
$opts = null;
}
if (isset($data['headline']) && is_array($data['headline'])) {
$this->_tableRow($data['headline'],
array('bold' => true),
$opts);
}
foreach($data['data'] as $row) {
$this->_tableRow($row, null, $opts);
}
$this->_endTable();
} else {
$this->_displayLine($data);
}
}
}
}
// }}}
// {{{ log(text)
function log($text, $append_crlf = true)
{
if ($append_crlf) {
return $this->_displayLine($text);
}
return $this->_display($text);
}
// }}}
// {{{ bold($text)
function bold($text)
{
if (empty($this->term['bold'])) {
return strtoupper($text);
}
return $this->term['bold'] . $text . $this->term['normal'];
}
// }}}
}
?>

1068
inc/PEAR/Installer.php Executable file

File diff suppressed because it is too large Load Diff

165
inc/PEAR/Packager.php Executable file
View File

@ -0,0 +1,165 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Stig Bakken <ssb@php.net> |
// | Tomas V.V.Cox <cox@idecnet.com> |
// +----------------------------------------------------------------------+
//
// $Id: Packager.php,v 1.53 2004/06/13 14:06:01 pajoye Exp $
require_once 'PEAR/Common.php';
require_once 'System.php';
/**
* Administration class used to make a PEAR release tarball.
*
* TODO:
* - add an extra param the dir where to place the created package
*
* @since PHP 4.0.2
* @author Stig Bakken <ssb@php.net>
*/
class PEAR_Packager extends PEAR_Common
{
// {{{ constructor
function PEAR_Packager()
{
parent::PEAR_Common();
}
// }}}
// {{{ destructor
function _PEAR_Packager()
{
parent::_PEAR_Common();
}
// }}}
// {{{ package()
function package($pkgfile = null, $compress = true)
{
// {{{ validate supplied package.xml file
if (empty($pkgfile)) {
$pkgfile = 'package.xml';
}
// $this->pkginfo gets populated inside
$pkginfo = $this->infoFromDescriptionFile($pkgfile);
if (PEAR::isError($pkginfo)) {
return $this->raiseError($pkginfo);
}
$pkgdir = dirname(realpath($pkgfile));
$pkgfile = basename($pkgfile);
$errors = $warnings = array();
$this->validatePackageInfo($pkginfo, $errors, $warnings, $pkgdir);
foreach ($warnings as $w) {
$this->log(1, "Warning: $w");
}
foreach ($errors as $e) {
$this->log(0, "Error: $e");
}
if (sizeof($errors) > 0) {
return $this->raiseError('Errors in package');
}
// }}}
$pkgver = $pkginfo['package'] . '-' . $pkginfo['version'];
// {{{ Create the package file list
$filelist = array();
$i = 0;
foreach ($pkginfo['filelist'] as $fname => $atts) {
$file = $pkgdir . DIRECTORY_SEPARATOR . $fname;
if (!file_exists($file)) {
return $this->raiseError("File does not exist: $fname");
} else {
$filelist[$i++] = $file;
if (empty($pkginfo['filelist'][$fname]['md5sum'])) {
$md5sum = md5_file($file);
$pkginfo['filelist'][$fname]['md5sum'] = $md5sum;
}
$this->log(2, "Adding file $fname");
}
}
// }}}
// {{{ regenerate package.xml
$new_xml = $this->xmlFromInfo($pkginfo);
if (PEAR::isError($new_xml)) {
return $this->raiseError($new_xml);
}
if (!($tmpdir = System::mktemp(array('-d')))) {
return $this->raiseError("PEAR_Packager: mktemp failed");
}
$newpkgfile = $tmpdir . DIRECTORY_SEPARATOR . 'package.xml';
$np = @fopen($newpkgfile, 'wb');
if (!$np) {
return $this->raiseError("PEAR_Packager: unable to rewrite $pkgfile as $newpkgfile");
}
fwrite($np, $new_xml);
fclose($np);
// }}}
// {{{ TAR the Package -------------------------------------------
$ext = $compress ? '.tgz' : '.tar';
$dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext;
$tar =& new Archive_Tar($dest_package, $compress);
$tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors
// ----- Creates with the package.xml file
$ok = $tar->createModify(array($newpkgfile), '', $tmpdir);
if (PEAR::isError($ok)) {
return $this->raiseError($ok);
} elseif (!$ok) {
return $this->raiseError('PEAR_Packager: tarball creation failed');
}
// ----- Add the content of the package
if (!$tar->addModify($filelist, $pkgver, $pkgdir)) {
return $this->raiseError('PEAR_Packager: tarball creation failed');
}
$this->log(1, "Package $dest_package done");
if (file_exists("$pkgdir/CVS/Root")) {
$cvsversion = preg_replace('/[^a-z0-9]/i', '_', $pkginfo['version']);
$cvstag = "RELEASE_$cvsversion";
$this->log(1, "Tag the released code with `pear cvstag $pkgfile'");
$this->log(1, "(or set the CVS tag $cvstag by hand)");
}
// }}}
return $dest_package;
}
// }}}
}
// {{{ md5_file() utility function
if (!function_exists('md5_file')) {
function md5_file($file) {
if (!$fd = @fopen($file, 'r')) {
return false;
}
$md5 = md5(fread($fd, filesize($file)));
fclose($fd);
return $md5;
}
}
// }}}
?>

538
inc/PEAR/Registry.php Executable file
View File

@ -0,0 +1,538 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Stig Bakken <ssb@php.net> |
// | Tomas V.V.Cox <cox@idecnet.com> |
// | |
// +----------------------------------------------------------------------+
//
// $Id: Registry.php,v 1.50.4.3 2004/10/26 19:19:56 cellog Exp $
/*
TODO:
- Transform into singleton()
- Add application level lock (avoid change the registry from the cmdline
while using the GTK interface, for ex.)
*/
require_once "System.php";
require_once "PEAR.php";
define('PEAR_REGISTRY_ERROR_LOCK', -2);
define('PEAR_REGISTRY_ERROR_FORMAT', -3);
define('PEAR_REGISTRY_ERROR_FILE', -4);
/**
* Administration class used to maintain the installed package database.
*/
class PEAR_Registry extends PEAR
{
// {{{ properties
/** Directory where registry files are stored.
* @var string
*/
var $statedir = '';
/** File where the file map is stored
* @var string
*/
var $filemap = '';
/** Name of file used for locking the registry
* @var string
*/
var $lockfile = '';
/** File descriptor used during locking
* @var resource
*/
var $lock_fp = null;
/** Mode used during locking
* @var int
*/
var $lock_mode = 0; // XXX UNUSED
/** Cache of package information. Structure:
* array(
* 'package' => array('id' => ... ),
* ... )
* @var array
*/
var $pkginfo_cache = array();
/** Cache of file map. Structure:
* array( '/path/to/file' => 'package', ... )
* @var array
*/
var $filemap_cache = array();
// }}}
// {{{ constructor
/**
* PEAR_Registry constructor.
*
* @param string (optional) PEAR install directory (for .php files)
*
* @access public
*/
function PEAR_Registry($pear_install_dir = PEAR_INSTALL_DIR)
{
parent::PEAR();
$ds = DIRECTORY_SEPARATOR;
$this->install_dir = $pear_install_dir;
$this->statedir = $pear_install_dir.$ds.'.registry';
$this->filemap = $pear_install_dir.$ds.'.filemap';
$this->lockfile = $pear_install_dir.$ds.'.lock';
// XXX Compatibility code should be removed in the future
// rename all registry files if any to lowercase
if (!OS_WINDOWS && $handle = @opendir($this->statedir)) {
$dest = $this->statedir . DIRECTORY_SEPARATOR;
while (false !== ($file = readdir($handle))) {
if (preg_match('/^.*[A-Z].*\.reg$/', $file)) {
rename($dest . $file, $dest . strtolower($file));
}
}
closedir($handle);
}
if (!file_exists($this->filemap)) {
$this->rebuildFileMap();
}
}
// }}}
// {{{ destructor
/**
* PEAR_Registry destructor. Makes sure no locks are forgotten.
*
* @access private
*/
function _PEAR_Registry()
{
parent::_PEAR();
if (is_resource($this->lock_fp)) {
$this->_unlock();
}
}
// }}}
// {{{ _assertStateDir()
/**
* Make sure the directory where we keep registry files exists.
*
* @return bool TRUE if directory exists, FALSE if it could not be
* created
*
* @access private
*/
function _assertStateDir()
{
if (!@is_dir($this->statedir)) {
if (!System::mkdir(array('-p', $this->statedir))) {
return $this->raiseError("could not create directory '{$this->statedir}'");
}
}
return true;
}
// }}}
// {{{ _packageFileName()
/**
* Get the name of the file where data for a given package is stored.
*
* @param string package name
*
* @return string registry file name
*
* @access public
*/
function _packageFileName($package)
{
return $this->statedir . DIRECTORY_SEPARATOR . strtolower($package) . '.reg';
}
// }}}
// {{{ _openPackageFile()
function _openPackageFile($package, $mode)
{
$this->_assertStateDir();
$file = $this->_packageFileName($package);
$fp = @fopen($file, $mode);
if (!$fp) {
return null;
}
return $fp;
}
// }}}
// {{{ _closePackageFile()
function _closePackageFile($fp)
{
fclose($fp);
}
// }}}
// {{{ rebuildFileMap()
function rebuildFileMap()
{
$packages = $this->listPackages();
$files = array();
foreach ($packages as $package) {
$version = $this->packageInfo($package, 'version');
$filelist = $this->packageInfo($package, 'filelist');
if (!is_array($filelist)) {
continue;
}
foreach ($filelist as $name => $attrs) {
if (isset($attrs['role']) && $attrs['role'] != 'php') {
continue;
}
if (isset($attrs['baseinstalldir'])) {
$file = $attrs['baseinstalldir'].DIRECTORY_SEPARATOR.$name;
} else {
$file = $name;
}
$file = preg_replace(',^/+,', '', $file);
$files[$file] = $package;
}
}
$this->_assertStateDir();
$fp = @fopen($this->filemap, 'wb');
if (!$fp) {
return false;
}
$this->filemap_cache = $files;
fwrite($fp, serialize($files));
fclose($fp);
return true;
}
// }}}
// {{{ readFileMap()
function readFileMap()
{
$fp = @fopen($this->filemap, 'r');
if (!$fp) {
return $this->raiseError('PEAR_Registry: could not open filemap', PEAR_REGISTRY_ERROR_FILE, null, null, $php_errormsg);
}
$fsize = filesize($this->filemap);
$rt = get_magic_quotes_runtime();
set_magic_quotes_runtime(0);
$data = fread($fp, $fsize);
set_magic_quotes_runtime($rt);
fclose($fp);
$tmp = unserialize($data);
if (!$tmp && $fsize > 7) {
return $this->raiseError('PEAR_Registry: invalid filemap data', PEAR_REGISTRY_ERROR_FORMAT, null, null, $data);
}
$this->filemap_cache = $tmp;
return true;
}
// }}}
// {{{ _lock()
/**
* Lock the registry.
*
* @param integer lock mode, one of LOCK_EX, LOCK_SH or LOCK_UN.
* See flock manual for more information.
*
* @return bool TRUE on success, FALSE if locking failed, or a
* PEAR error if some other error occurs (such as the
* lock file not being writable).
*
* @access private
*/
function _lock($mode = LOCK_EX)
{
if (!eregi('Windows 9', php_uname())) {
if ($mode != LOCK_UN && is_resource($this->lock_fp)) {
// XXX does not check type of lock (LOCK_SH/LOCK_EX)
return true;
}
if (PEAR::isError($err = $this->_assertStateDir())) {
return $err;
}
$open_mode = 'w';
// XXX People reported problems with LOCK_SH and 'w'
if ($mode === LOCK_SH || $mode === LOCK_UN) {
if (@!is_file($this->lockfile)) {
touch($this->lockfile);
}
$open_mode = 'r';
}
if (!is_resource($this->lock_fp)) {
$this->lock_fp = @fopen($this->lockfile, $open_mode);
}
if (!is_resource($this->lock_fp)) {
return $this->raiseError("could not create lock file" .
(isset($php_errormsg) ? ": " . $php_errormsg : ""));
}
if (!(int)flock($this->lock_fp, $mode)) {
switch ($mode) {
case LOCK_SH: $str = 'shared'; break;
case LOCK_EX: $str = 'exclusive'; break;
case LOCK_UN: $str = 'unlock'; break;
default: $str = 'unknown'; break;
}
return $this->raiseError("could not acquire $str lock ($this->lockfile)",
PEAR_REGISTRY_ERROR_LOCK);
}
}
return true;
}
// }}}
// {{{ _unlock()
function _unlock()
{
$ret = $this->_lock(LOCK_UN);
if (is_resource($this->lock_fp)) {
fclose($this->lock_fp);
}
$this->lock_fp = null;
return $ret;
}
// }}}
// {{{ _packageExists()
function _packageExists($package)
{
return file_exists($this->_packageFileName($package));
}
// }}}
// {{{ _packageInfo()
function _packageInfo($package = null, $key = null)
{
if ($package === null) {
return array_map(array($this, '_packageInfo'),
$this->_listPackages());
}
$fp = $this->_openPackageFile($package, 'r');
if ($fp === null) {
return null;
}
$rt = get_magic_quotes_runtime();
set_magic_quotes_runtime(0);
$data = fread($fp, filesize($this->_packageFileName($package)));
set_magic_quotes_runtime($rt);
$this->_closePackageFile($fp);
$data = unserialize($data);
if ($key === null) {
return $data;
}
if (isset($data[$key])) {
return $data[$key];
}
return null;
}
// }}}
// {{{ _listPackages()
function _listPackages()
{
$pkglist = array();
$dp = @opendir($this->statedir);
if (!$dp) {
return $pkglist;
}
while ($ent = readdir($dp)) {
if ($ent{0} == '.' || substr($ent, -4) != '.reg') {
continue;
}
$pkglist[] = substr($ent, 0, -4);
}
return $pkglist;
}
// }}}
// {{{ packageExists()
function packageExists($package)
{
if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
return $e;
}
$ret = $this->_packageExists($package);
$this->_unlock();
return $ret;
}
// }}}
// {{{ packageInfo()
function packageInfo($package = null, $key = null)
{
if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
return $e;
}
$ret = $this->_packageInfo($package, $key);
$this->_unlock();
return $ret;
}
// }}}
// {{{ listPackages()
function listPackages()
{
if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
return $e;
}
$ret = $this->_listPackages();
$this->_unlock();
return $ret;
}
// }}}
// {{{ addPackage()
function addPackage($package, $info)
{
if ($this->packageExists($package)) {
return false;
}
if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
return $e;
}
$fp = $this->_openPackageFile($package, 'wb');
if ($fp === null) {
$this->_unlock();
return false;
}
$info['_lastmodified'] = time();
fwrite($fp, serialize($info));
$this->_closePackageFile($fp);
$this->_unlock();
return true;
}
// }}}
// {{{ deletePackage()
function deletePackage($package)
{
if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
return $e;
}
$file = $this->_packageFileName($package);
$ret = @unlink($file);
$this->rebuildFileMap();
$this->_unlock();
return $ret;
}
// }}}
// {{{ updatePackage()
function updatePackage($package, $info, $merge = true)
{
$oldinfo = $this->packageInfo($package);
if (empty($oldinfo)) {
return false;
}
if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
return $e;
}
$fp = $this->_openPackageFile($package, 'w');
if ($fp === null) {
$this->_unlock();
return false;
}
$info['_lastmodified'] = time();
if ($merge) {
fwrite($fp, serialize(array_merge($oldinfo, $info)));
} else {
fwrite($fp, serialize($info));
}
$this->_closePackageFile($fp);
if (isset($info['filelist'])) {
$this->rebuildFileMap();
}
$this->_unlock();
return true;
}
// }}}
// {{{ checkFileMap()
/**
* Test whether a file belongs to a package.
*
* @param string $path file path, absolute or relative to the pear
* install dir
*
* @return string which package the file belongs to, or an empty
* string if the file does not belong to an installed package
*
* @access public
*/
function checkFileMap($path)
{
if (is_array($path)) {
static $notempty;
if (empty($notempty)) {
$notempty = create_function('$a','return !empty($a);');
}
$pkgs = array();
foreach ($path as $name => $attrs) {
if (is_array($attrs) && isset($attrs['baseinstalldir'])) {
$name = $attrs['baseinstalldir'].DIRECTORY_SEPARATOR.$name;
}
$pkgs[$name] = $this->checkFileMap($name);
}
return array_filter($pkgs, $notempty);
}
if (empty($this->filemap_cache) && PEAR::isError($this->readFileMap())) {
return $err;
}
if (isset($this->filemap_cache[$path])) {
return $this->filemap_cache[$path];
}
$l = strlen($this->install_dir);
if (substr($path, 0, $l) == $this->install_dir) {
$path = preg_replace('!^'.DIRECTORY_SEPARATOR.'+!', '', substr($path, $l));
}
if (isset($this->filemap_cache[$path])) {
return $this->filemap_cache[$path];
}
return '';
}
// }}}
}
?>

394
inc/PEAR/Remote.php Executable file
View File

@ -0,0 +1,394 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Stig Bakken <ssb@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Remote.php,v 1.50 2004/06/08 18:03:46 cellog Exp $
require_once 'PEAR.php';
require_once 'PEAR/Config.php';
/**
* This is a class for doing remote operations against the central
* PEAR database.
*
* @nodep XML_RPC_Value
* @nodep XML_RPC_Message
* @nodep XML_RPC_Client
*/
class PEAR_Remote extends PEAR
{
// {{{ properties
var $config = null;
var $cache = null;
// }}}
// {{{ PEAR_Remote(config_object)
function PEAR_Remote(&$config)
{
$this->PEAR();
$this->config = &$config;
}
// }}}
// {{{ getCache()
function getCache($args)
{
$id = md5(serialize($args));
$cachedir = $this->config->get('cache_dir');
$filename = $cachedir . DIRECTORY_SEPARATOR . 'xmlrpc_cache_' . $id;
if (!file_exists($filename)) {
return null;
};
$fp = fopen($filename, 'rb');
if (!$fp) {
return null;
}
$content = fread($fp, filesize($filename));
fclose($fp);
$result = array(
'age' => time() - filemtime($filename),
'lastChange' => filemtime($filename),
'content' => unserialize($content),
);
return $result;
}
// }}}
// {{{ saveCache()
function saveCache($args, $data)
{
$id = md5(serialize($args));
$cachedir = $this->config->get('cache_dir');
if (!file_exists($cachedir)) {
System::mkdir(array('-p', $cachedir));
}
$filename = $cachedir.'/xmlrpc_cache_'.$id;
$fp = @fopen($filename, "wb");
if ($fp) {
fwrite($fp, serialize($data));
fclose($fp);
};
}
// }}}
// {{{ call(method, [args...])
function call($method)
{
$_args = $args = func_get_args();
$this->cache = $this->getCache($args);
$cachettl = $this->config->get('cache_ttl');
// If cache is newer than $cachettl seconds, we use the cache!
if ($this->cache !== null && $this->cache['age'] < $cachettl) {
return $this->cache['content'];
};
if (extension_loaded("xmlrpc")) {
$result = call_user_func_array(array(&$this, 'call_epi'), $args);
if (!PEAR::isError($result)) {
$this->saveCache($_args, $result);
};
return $result;
}
if (!@include_once("XML/RPC.php")) {
return $this->raiseError("For this remote PEAR operation you need to install the XML_RPC package");
}
array_shift($args);
$server_host = $this->config->get('master_server');
$username = $this->config->get('username');
$password = $this->config->get('password');
$eargs = array();
foreach($args as $arg) $eargs[] = $this->_encode($arg);
$f = new XML_RPC_Message($method, $eargs);
if ($this->cache !== null) {
$maxAge = '?maxAge='.$this->cache['lastChange'];
} else {
$maxAge = '';
};
$proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';
if ($proxy = parse_url($this->config->get('http_proxy'))) {
$proxy_host = @$proxy['host'];
$proxy_port = @$proxy['port'];
$proxy_user = @urldecode(@$proxy['user']);
$proxy_pass = @urldecode(@$proxy['pass']);
}
$c = new XML_RPC_Client('/xmlrpc.php'.$maxAge, $server_host, 80, $proxy_host, $proxy_port, $proxy_user, $proxy_pass);
if ($username && $password) {
$c->setCredentials($username, $password);
}
if ($this->config->get('verbose') >= 3) {
$c->setDebug(1);
}
$r = $c->send($f);
if (!$r) {
return $this->raiseError("XML_RPC send failed");
}
$v = $r->value();
if ($e = $r->faultCode()) {
if ($e == $GLOBALS['XML_RPC_err']['http_error'] && strstr($r->faultString(), '304 Not Modified') !== false) {
return $this->cache['content'];
}
return $this->raiseError($r->faultString(), $e);
}
$result = XML_RPC_decode($v);
$this->saveCache($_args, $result);
return $result;
}
// }}}
// {{{ call_epi(method, [args...])
function call_epi($method)
{
do {
if (extension_loaded("xmlrpc")) {
break;
}
if (OS_WINDOWS) {
$ext = 'dll';
} elseif (PHP_OS == 'HP-UX') {
$ext = 'sl';
} elseif (PHP_OS == 'AIX') {
$ext = 'a';
} else {
$ext = 'so';
}
$ext = OS_WINDOWS ? 'dll' : 'so';
@dl("xmlrpc-epi.$ext");
if (extension_loaded("xmlrpc")) {
break;
}
@dl("xmlrpc.$ext");
if (extension_loaded("xmlrpc")) {
break;
}
return $this->raiseError("unable to load xmlrpc extension");
} while (false);
$params = func_get_args();
array_shift($params);
$method = str_replace("_", ".", $method);
$request = xmlrpc_encode_request($method, $params);
$server_host = $this->config->get("master_server");
if (empty($server_host)) {
return $this->raiseError("PEAR_Remote::call: no master_server configured");
}
$server_port = 80;
if ($http_proxy = $this->config->get('http_proxy')) {
$proxy = parse_url($http_proxy);
$proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';
$proxy_host = @$proxy['host'];
$proxy_port = @$proxy['port'];
$proxy_user = @urldecode(@$proxy['user']);
$proxy_pass = @urldecode(@$proxy['pass']);
$fp = @fsockopen($proxy_host, $proxy_port);
$use_proxy = true;
} else {
$use_proxy = false;
$fp = @fsockopen($server_host, $server_port);
}
if (!$fp && $http_proxy) {
return $this->raiseError("PEAR_Remote::call: fsockopen(`$proxy_host', $proxy_port) failed");
} elseif (!$fp) {
return $this->raiseError("PEAR_Remote::call: fsockopen(`$server_host', $server_port) failed");
}
$len = strlen($request);
$req_headers = "Host: $server_host:$server_port\r\n" .
"Content-type: text/xml\r\n" .
"Content-length: $len\r\n";
$username = $this->config->get('username');
$password = $this->config->get('password');
if ($username && $password) {
$req_headers .= "Cookie: PEAR_USER=$username; PEAR_PW=$password\r\n";
$tmp = base64_encode("$username:$password");
$req_headers .= "Authorization: Basic $tmp\r\n";
}
if ($this->cache !== null) {
$maxAge = '?maxAge='.$this->cache['lastChange'];
} else {
$maxAge = '';
};
if ($use_proxy && $proxy_host != '' && $proxy_user != '') {
$req_headers .= 'Proxy-Authorization: Basic '
.base64_encode($proxy_user.':'.$proxy_pass)
."\r\n";
}
if ($this->config->get('verbose') > 3) {
print "XMLRPC REQUEST HEADERS:\n";
var_dump($req_headers);
print "XMLRPC REQUEST BODY:\n";
var_dump($request);
}
if ($use_proxy && $proxy_host != '') {
$post_string = "POST http://".$server_host;
if ($proxy_port > '') {
$post_string .= ':'.$server_port;
}
} else {
$post_string = "POST ";
}
fwrite($fp, ($post_string."/xmlrpc.php$maxAge HTTP/1.0\r\n$req_headers\r\n$request"));
$response = '';
$line1 = fgets($fp, 2048);
if (!preg_match('!^HTTP/[0-9\.]+ (\d+) (.*)!', $line1, $matches)) {
return $this->raiseError("PEAR_Remote: invalid HTTP response from XML-RPC server");
}
switch ($matches[1]) {
case "200": // OK
break;
case "304": // Not Modified
return $this->cache['content'];
case "401": // Unauthorized
if ($username && $password) {
return $this->raiseError("PEAR_Remote: authorization failed", 401);
} else {
return $this->raiseError("PEAR_Remote: authorization required, please log in first", 401);
}
default:
return $this->raiseError("PEAR_Remote: unexpected HTTP response", (int)$matches[1], null, null, "$matches[1] $matches[2]");
}
while (trim(fgets($fp, 2048)) != ''); // skip rest of headers
while ($chunk = fread($fp, 10240)) {
$response .= $chunk;
}
fclose($fp);
if ($this->config->get('verbose') > 3) {
print "XMLRPC RESPONSE:\n";
var_dump($response);
}
$ret = xmlrpc_decode($response);
if (is_array($ret) && isset($ret['__PEAR_TYPE__'])) {
if ($ret['__PEAR_TYPE__'] == 'error') {
if (isset($ret['__PEAR_CLASS__'])) {
$class = $ret['__PEAR_CLASS__'];
} else {
$class = "PEAR_Error";
}
if ($ret['code'] === '') $ret['code'] = null;
if ($ret['message'] === '') $ret['message'] = null;
if ($ret['userinfo'] === '') $ret['userinfo'] = null;
if (strtolower($class) == 'db_error') {
$ret = $this->raiseError(PEAR::errorMessage($ret['code']),
$ret['code'], null, null,
$ret['userinfo']);
} else {
$ret = $this->raiseError($ret['message'], $ret['code'],
null, null, $ret['userinfo']);
}
}
} elseif (is_array($ret) && sizeof($ret) == 1 && isset($ret[0])
&& is_array($ret[0]) &&
!empty($ret[0]['faultString']) &&
!empty($ret[0]['faultCode'])) {
extract($ret[0]);
$faultString = "XML-RPC Server Fault: " .
str_replace("\n", " ", $faultString);
return $this->raiseError($faultString, $faultCode);
}
return $ret;
}
// }}}
// {{{ _encode
// a slightly extended version of XML_RPC_encode
function _encode($php_val)
{
global $XML_RPC_Boolean, $XML_RPC_Int, $XML_RPC_Double;
global $XML_RPC_String, $XML_RPC_Array, $XML_RPC_Struct;
$type = gettype($php_val);
$xmlrpcval = new XML_RPC_Value;
switch($type) {
case "array":
reset($php_val);
$firstkey = key($php_val);
end($php_val);
$lastkey = key($php_val);
if ($firstkey === 0 && is_int($lastkey) &&
($lastkey + 1) == count($php_val)) {
$is_continuous = true;
reset($php_val);
$size = count($php_val);
for ($expect = 0; $expect < $size; $expect++, next($php_val)) {
if (key($php_val) !== $expect) {
$is_continuous = false;
break;
}
}
if ($is_continuous) {
reset($php_val);
$arr = array();
while (list($k, $v) = each($php_val)) {
$arr[$k] = $this->_encode($v);
}
$xmlrpcval->addArray($arr);
break;
}
}
// fall though if not numerical and continuous
case "object":
$arr = array();
while (list($k, $v) = each($php_val)) {
$arr[$k] = $this->_encode($v);
}
$xmlrpcval->addStruct($arr);
break;
case "integer":
$xmlrpcval->addScalar($php_val, $XML_RPC_Int);
break;
case "double":
$xmlrpcval->addScalar($php_val, $XML_RPC_Double);
break;
case "string":
case "NULL":
$xmlrpcval->addScalar($php_val, $XML_RPC_String);
break;
case "boolean":
$xmlrpcval->addScalar($php_val, $XML_RPC_Boolean);
break;
case "unknown type":
default:
return null;
}
return $xmlrpcval;
}
// }}}
}
?>

363
inc/PEAR/RunTest.php Executable file
View File

@ -0,0 +1,363 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Tomas V.V.Cox <cox@idecnet.com> |
// | Greg Beaver <cellog@php.net> |
// | |
// +----------------------------------------------------------------------+
//
// $Id: RunTest.php,v 1.3.2.4 2005/02/17 17:47:55 cellog Exp $
//
/**
* Simplified version of PHP's test suite
* -- EXPERIMENTAL --
Try it with:
$ php -r 'include "../PEAR/RunTest.php"; $t=new PEAR_RunTest; $o=$t->run("./pear_system.phpt");print_r($o);'
TODO:
Actually finish the development and testing
*/
require_once 'PEAR.php';
require_once 'PEAR/Config.php';
define('DETAILED', 1);
putenv("PHP_PEAR_RUNTESTS=1");
class PEAR_RunTest
{
var $_logger;
/**
* An object that supports the PEAR_Common->log() signature, or null
* @param PEAR_Common|null
*/
function PEAR_RunTest($logger = null)
{
$this->_logger = $logger;
}
//
// Run an individual test case.
//
function run($file, $ini_settings = '')
{
$cwd = getcwd();
$conf = &PEAR_Config::singleton();
$php = $conf->get('php_bin');
//var_dump($php);exit;
global $log_format, $info_params, $ini_overwrites;
$info_params = '';
$log_format = 'LEOD';
// Load the sections of the test file.
$section_text = array(
'TEST' => '(unnamed test)',
'SKIPIF' => '',
'GET' => '',
'ARGS' => '',
);
if (!is_file($file) || !$fp = fopen($file, "r")) {
return PEAR::raiseError("Cannot open test file: $file");
}
$section = '';
while (!feof($fp)) {
$line = fgets($fp);
// Match the beginning of a section.
if (ereg('^--([A-Z]+)--',$line,$r)) {
$section = $r[1];
$section_text[$section] = '';
continue;
}
// Add to the section text.
$section_text[$section] .= $line;
}
fclose($fp);
$shortname = str_replace($cwd.'/', '', $file);
$tested = trim($section_text['TEST'])." [$shortname]";
$tmp = realpath(dirname($file));
$tmp_skipif = $tmp . uniqid('/phpt.');
$tmp_file = ereg_replace('\.phpt$','.php',$file);
$tmp_post = $tmp . uniqid('/phpt.');
@unlink($tmp_skipif);
@unlink($tmp_file);
@unlink($tmp_post);
// unlink old test results
@unlink(ereg_replace('\.phpt$','.diff',$file));
@unlink(ereg_replace('\.phpt$','.log',$file));
@unlink(ereg_replace('\.phpt$','.exp',$file));
@unlink(ereg_replace('\.phpt$','.out',$file));
// Check if test should be skipped.
$info = '';
$warn = false;
if (array_key_exists('SKIPIF', $section_text)) {
if (trim($section_text['SKIPIF'])) {
$this->save_text($tmp_skipif, $section_text['SKIPIF']);
//$extra = substr(PHP_OS, 0, 3) !== "WIN" ?
// "unset REQUEST_METHOD;": "";
//$output = `$extra $php $info_params -f $tmp_skipif`;
$output = `$php $info_params -f $tmp_skipif`;
unlink($tmp_skipif);
if (eregi("^skip", trim($output))) {
$skipreason = "SKIP $tested";
$reason = (eregi("^skip[[:space:]]*(.+)\$", trim($output))) ? eregi_replace("^skip[[:space:]]*(.+)\$", "\\1", trim($output)) : FALSE;
if ($reason) {
$skipreason .= " (reason: $reason)";
}
$this->_logger->log(0, $skipreason);
if (isset($old_php)) {
$php = $old_php;
}
return 'SKIPPED';
}
if (eregi("^info", trim($output))) {
$reason = (ereg("^info[[:space:]]*(.+)\$", trim($output))) ? ereg_replace("^info[[:space:]]*(.+)\$", "\\1", trim($output)) : FALSE;
if ($reason) {
$info = " (info: $reason)";
}
}
if (eregi("^warn", trim($output))) {
$reason = (ereg("^warn[[:space:]]*(.+)\$", trim($output))) ? ereg_replace("^warn[[:space:]]*(.+)\$", "\\1", trim($output)) : FALSE;
if ($reason) {
$warn = true; /* only if there is a reason */
$info = " (warn: $reason)";
}
}
}
}
// We've satisfied the preconditions - run the test!
$this->save_text($tmp_file,$section_text['FILE']);
$args = $section_text['ARGS'] ? ' -- '.$section_text['ARGS'] : '';
$cmd = "$php$ini_settings -f $tmp_file$args 2>&1";
if (isset($this->_logger)) {
$this->_logger->log(2, 'Running command "' . $cmd . '"');
}
$savedir = getcwd(); // in case the test moves us around
if (isset($section_text['RETURNS'])) {
ob_start();
system($cmd, $return_value);
$out = ob_get_contents();
ob_end_clean();
@unlink($tmp_post);
$section_text['RETURNS'] = (int) trim($section_text['RETURNS']);
$returnfail = ($return_value != $section_text['RETURNS']);
} else {
$out = `$cmd`;
$returnfail = false;
}
chdir($savedir);
// Does the output match what is expected?
$output = trim($out);
$output = preg_replace('/\r\n/', "\n", $output);
if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) {
if (isset($section_text['EXPECTF'])) {
$wanted = trim($section_text['EXPECTF']);
} else {
$wanted = trim($section_text['EXPECTREGEX']);
}
$wanted_re = preg_replace('/\r\n/',"\n",$wanted);
if (isset($section_text['EXPECTF'])) {
$wanted_re = preg_quote($wanted_re, '/');
// Stick to basics
$wanted_re = str_replace("%s", ".+?", $wanted_re); //not greedy
$wanted_re = str_replace("%i", "[+\-]?[0-9]+", $wanted_re);
$wanted_re = str_replace("%d", "[0-9]+", $wanted_re);
$wanted_re = str_replace("%x", "[0-9a-fA-F]+", $wanted_re);
$wanted_re = str_replace("%f", "[+\-]?\.?[0-9]+\.?[0-9]*(E-?[0-9]+)?", $wanted_re);
$wanted_re = str_replace("%c", ".", $wanted_re);
// %f allows two points "-.0.0" but that is the best *simple* expression
}
/* DEBUG YOUR REGEX HERE
var_dump($wanted_re);
print(str_repeat('=', 80) . "\n");
var_dump($output);
*/
if (!$returnfail && preg_match("/^$wanted_re\$/s", $output)) {
@unlink($tmp_file);
$this->_logger->log(0, "PASS $tested$info");
if (isset($old_php)) {
$php = $old_php;
}
return 'PASSED';
}
} else {
$wanted = trim($section_text['EXPECT']);
$wanted = preg_replace('/\r\n/',"\n",$wanted);
// compare and leave on success
$ok = (0 == strcmp($output,$wanted));
if (!$returnfail && $ok) {
@unlink($tmp_file);
$this->_logger->log(0, "PASS $tested$info");
if (isset($old_php)) {
$php = $old_php;
}
return 'PASSED';
}
}
// Test failed so we need to report details.
if ($warn) {
$this->_logger->log(0, "WARN $tested$info");
} else {
$this->_logger->log(0, "FAIL $tested$info");
}
if (isset($section_text['RETURNS'])) {
$GLOBALS['__PHP_FAILED_TESTS__'][] = array(
'name' => $file,
'test_name' => $tested,
'output' => ereg_replace('\.phpt$','.log', $file),
'diff' => ereg_replace('\.phpt$','.diff', $file),
'info' => $info,
'return' => $return_value
);
} else {
$GLOBALS['__PHP_FAILED_TESTS__'][] = array(
'name' => $file,
'test_name' => $tested,
'output' => ereg_replace('\.phpt$','.log', $file),
'diff' => ereg_replace('\.phpt$','.diff', $file),
'info' => $info,
);
}
// write .exp
if (strpos($log_format,'E') !== FALSE) {
$logname = ereg_replace('\.phpt$','.exp',$file);
if (!$log = fopen($logname,'w')) {
return PEAR::raiseError("Cannot create test log - $logname");
}
fwrite($log,$wanted);
fclose($log);
}
// write .out
if (strpos($log_format,'O') !== FALSE) {
$logname = ereg_replace('\.phpt$','.out',$file);
if (!$log = fopen($logname,'w')) {
return PEAR::raiseError("Cannot create test log - $logname");
}
fwrite($log,$output);
fclose($log);
}
// write .diff
if (strpos($log_format,'D') !== FALSE) {
$logname = ereg_replace('\.phpt$','.diff',$file);
if (!$log = fopen($logname,'w')) {
return PEAR::raiseError("Cannot create test log - $logname");
}
fwrite($log, $this->generate_diff($wanted, $output,
isset($section_text['RETURNS']) ? array(trim($section_text['RETURNS']),
$return_value) : null));
fclose($log);
}
// write .log
if (strpos($log_format,'L') !== FALSE) {
$logname = ereg_replace('\.phpt$','.log',$file);
if (!$log = fopen($logname,'w')) {
return PEAR::raiseError("Cannot create test log - $logname");
}
fwrite($log,"
---- EXPECTED OUTPUT
$wanted
---- ACTUAL OUTPUT
$output
---- FAILED
");
if ($returnfail) {
fwrite($log,"
---- EXPECTED RETURN
$section_text[RETURNS]
---- ACTUAL RETURN
$return_value
");
}
fclose($log);
//error_report($file,$logname,$tested);
}
if (isset($old_php)) {
$php = $old_php;
}
return $warn ? 'WARNED' : 'FAILED';
}
function generate_diff($wanted, $output, $return_value)
{
$w = explode("\n", $wanted);
$o = explode("\n", $output);
$w1 = array_diff_assoc($w,$o);
$o1 = array_diff_assoc($o,$w);
$w2 = array();
$o2 = array();
foreach($w1 as $idx => $val) $w2[sprintf("%03d<",$idx)] = sprintf("%03d- ", $idx+1).$val;
foreach($o1 as $idx => $val) $o2[sprintf("%03d>",$idx)] = sprintf("%03d+ ", $idx+1).$val;
$diff = array_merge($w2, $o2);
ksort($diff);
if ($return_value) {
$extra = "##EXPECTED: $return_value[0]\r\n##RETURNED: $return_value[1]";
} else {
$extra = '';
}
return implode("\r\n", $diff) . $extra;
}
//
// Write the given text to a temporary file, and return the filename.
//
function save_text($filename, $text)
{
if (!$fp = fopen($filename, 'w')) {
return PEAR::raiseError("Cannot open file '" . $filename . "' (save_text)");
}
fwrite($fp,$text);
fclose($fp);
if (1 < DETAILED) echo "
FILE $filename {{{
$text
}}}
";
}
}
?>

83
inc/PHPUnit.php Executable file
View File

@ -0,0 +1,83 @@
<?php
//
// +------------------------------------------------------------------------+
// | PEAR :: PHPUnit |
// +------------------------------------------------------------------------+
// | Copyright (c) 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>. |
// +------------------------------------------------------------------------+
// | This source file is subject to version 3.00 of the PHP License, |
// | that is available at http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +------------------------------------------------------------------------+
//
// $Id: PHPUnit.php,v 1.14 2004/12/22 08:06:11 sebastian Exp $
//
require_once 'PHPUnit/TestCase.php';
require_once 'PHPUnit/TestResult.php';
require_once 'PHPUnit/TestSuite.php';
/**
* PHPUnit runs a TestSuite and returns a TestResult object.
*
* Here is an example:
*
* <code>
* <?php
* require_once 'PHPUnit.php';
*
* class MathTest extends PHPUnit_TestCase {
* var $fValue1;
* var $fValue2;
*
* function MathTest($name) {
* $this->PHPUnit_TestCase($name);
* }
*
* function setUp() {
* $this->fValue1 = 2;
* $this->fValue2 = 3;
* }
*
* function testAdd() {
* $this->assertTrue($this->fValue1 + $this->fValue2 == 5);
* }
* }
*
* $suite = new PHPUnit_TestSuite();
* $suite->addTest(new MathTest('testAdd'));
*
* $result = PHPUnit::run($suite);
* print $result->toHTML();
* ?>
* </code>
*
* Alternatively, you can pass a class name to the PHPUnit_TestSuite()
* constructor and let it automatically add all methods of that class
* that start with 'test' to the suite:
*
* <code>
* <?php
* $suite = new PHPUnit_TestSuite('MathTest');
* $result = PHPUnit::run($suite);
* print $result->toHTML();
* ?>
* </code>
*
* @author Sebastian Bergmann <sb@sebastian-bergmann.de>
* @copyright Copyright &copy; 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>
* @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0
* @category Testing
* @package PHPUnit
*/
class PHPUnit {
function &run(&$suite) {
$result = new PHPUnit_TestResult();
$suite->run($result);
return $result;
}
}
?>

384
inc/PHPUnit/Assert.php Executable file
View File

@ -0,0 +1,384 @@
<?php
//
// +------------------------------------------------------------------------+
// | PEAR :: PHPUnit |
// +------------------------------------------------------------------------+
// | Copyright (c) 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>. |
// +------------------------------------------------------------------------+
// | This source file is subject to version 3.00 of the PHP License, |
// | that is available at http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +------------------------------------------------------------------------+
//
// $Id: Assert.php,v 1.25 2005/01/31 04:57:16 sebastian Exp $
//
/**
* A set of assert methods.
*
* @author Sebastian Bergmann <sb@sebastian-bergmann.de>
* @copyright Copyright &copy; 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>
* @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0
* @category Testing
* @package PHPUnit
*/
class PHPUnit_Assert {
/**
* @var boolean
* @access private
*/
var $_looselyTyped = FALSE;
/**
* Asserts that a haystack contains a needle.
*
* @param mixed
* @param mixed
* @param string
* @access public
* @since 1.1.0
*/
function assertContains($needle, $haystack, $message = '') {
if (is_string($needle) && is_string($haystack)) {
$this->assertTrue(strpos($haystack, $needle) !== FALSE ? TRUE : FALSE);
}
else if (is_array($haystack) && !is_object($needle)) {
$this->assertTrue(in_array($needle, $haystack), $message);
}
else {
$this->fail('Unsupported parameter passed to assertContains().');
}
}
/**
* Asserts that a haystack does not contain a needle.
*
* @param mixed
* @param mixed
* @param string
* @access public
* @since 1.1.0
*/
function assertNotContains($needle, $haystack, $message = '') {
if (is_string($needle) && is_string($haystack)) {
$this->assertFalse(strpos($haystack, $needle) !== FALSE ? TRUE : FALSE);
}
else if (is_array($haystack) && !is_object($needle)) {
$this->assertFalse(in_array($needle, $haystack), $message);
}
else {
$this->fail('Unsupported parameter passed to assertNotContains().');
}
}
/**
* Asserts that two variables are equal.
*
* @param mixed
* @param mixed
* @param string
* @param mixed
* @access public
*/
function assertEquals($expected, $actual, $message = '', $delta = 0) {
if ((is_array($actual) && is_array($expected)) ||
(is_object($actual) && is_object($expected))) {
if (is_array($actual) && is_array($expected)) {
ksort($actual);
ksort($expected);
}
if ($this->_looselyTyped) {
$actual = $this->_convertToString($actual);
$expected = $this->_convertToString($expected);
}
$actual = serialize($actual);
$expected = serialize($expected);
$message = sprintf(
'%sexpected %s, actual %s',
!empty($message) ? $message . ' ' : '',
$expected,
$actual
);
if ($actual !== $expected) {
return $this->fail($message);
}
}
elseif (is_numeric($actual) && is_numeric($expected)) {
$message = sprintf(
'%sexpected %s%s, actual %s',
!empty($message) ? $message . ' ' : '',
$expected,
($delta != 0) ? ('+/- ' . $delta) : '',
$actual
);
if (!($actual >= ($expected - $delta) && $actual <= ($expected + $delta))) {
return $this->fail($message);
}
}
else {
$message = sprintf(
'%sexpected %s, actual %s',
!empty($message) ? $message . ' ' : '',
$expected,
$actual
);
if ($actual !== $expected) {
return $this->fail($message);
}
}
}
/**
* Asserts that two variables reference the same object.
* This requires the Zend Engine 2 to work.
*
* @param object
* @param object
* @param string
* @access public
* @deprecated
*/
function assertSame($expected, $actual, $message = '') {
if (!version_compare(phpversion(), '5.0.0', '>=')) {
$this->fail('assertSame() only works with PHP >= 5.0.0.');
}
if ((is_object($expected) || is_null($expected)) &&
(is_object($actual) || is_null($actual))) {
$message = sprintf(
'%sexpected two variables to reference the same object',
!empty($message) ? $message . ' ' : ''
);
if ($expected !== $actual) {
return $this->fail($message);
}
} else {
$this->fail('Unsupported parameter passed to assertSame().');
}
}
/**
* Asserts that two variables do not reference the same object.
* This requires the Zend Engine 2 to work.
*
* @param object
* @param object
* @param string
* @access public
* @deprecated
*/
function assertNotSame($expected, $actual, $message = '') {
if (!version_compare(phpversion(), '5.0.0', '>=')) {
$this->fail('assertNotSame() only works with PHP >= 5.0.0.');
}
if ((is_object($expected) || is_null($expected)) &&
(is_object($actual) || is_null($actual))) {
$message = sprintf(
'%sexpected two variables to reference different objects',
!empty($message) ? $message . ' ' : ''
);
if ($expected === $actual) {
return $this->fail($message);
}
} else {
$this->fail('Unsupported parameter passed to assertNotSame().');
}
}
/**
* Asserts that a variable is not NULL.
*
* @param mixed
* @param string
* @access public
*/
function assertNotNull($actual, $message = '') {
$message = sprintf(
'%sexpected NOT NULL, actual NULL',
!empty($message) ? $message . ' ' : ''
);
if (is_null($actual)) {
return $this->fail($message);
}
}
/**
* Asserts that a variable is NULL.
*
* @param mixed
* @param string
* @access public
*/
function assertNull($actual, $message = '') {
$message = sprintf(
'%sexpected NULL, actual NOT NULL',
!empty($message) ? $message . ' ' : ''
);
if (!is_null($actual)) {
return $this->fail($message);
}
}
/**
* Asserts that a condition is true.
*
* @param boolean
* @param string
* @access public
*/
function assertTrue($condition, $message = '') {
$message = sprintf(
'%sexpected TRUE, actual FALSE',
!empty($message) ? $message . ' ' : ''
);
if (!$condition) {
return $this->fail($message);
}
}
/**
* Asserts that a condition is false.
*
* @param boolean
* @param string
* @access public
*/
function assertFalse($condition, $message = '') {
$message = sprintf(
'%sexpected FALSE, actual TRUE',
!empty($message) ? $message . ' ' : ''
);
if ($condition) {
return $this->fail($message);
}
}
/**
* Asserts that a string matches a given regular expression.
*
* @param string
* @param string
* @param string
* @access public
*/
function assertRegExp($pattern, $string, $message = '') {
$message = sprintf(
'%s"%s" does not match pattern "%s"',
!empty($message) ? $message . ' ' : '',
$string,
$pattern
);
if (!preg_match($pattern, $string)) {
return $this->fail($message);
}
}
/**
* Asserts that a string does not match a given regular expression.
*
* @param string
* @param string
* @param string
* @access public
* @since 1.1.0
*/
function assertNotRegExp($pattern, $string, $message = '') {
$message = sprintf(
'%s"%s" matches pattern "%s"',
!empty($message) ? $message . ' ' : '',
$string,
$pattern
);
if (preg_match($pattern, $string)) {
return $this->fail($message);
}
}
/**
* Asserts that a variable is of a given type.
*
* @param string $expected
* @param mixed $actual
* @param optional string $message
* @access public
*/
function assertType($expected, $actual, $message = '') {
return $this->assertEquals(
$expected,
gettype($actual),
$message
);
}
/**
* Converts a value to a string.
*
* @param mixed $value
* @access private
*/
function _convertToString($value) {
foreach ($value as $k => $v) {
if (is_array($v)) {
$value[$k] = $this->_convertToString($value[$k]);
} else {
settype($value[$k], 'string');
}
}
return $value;
}
/**
* @param boolean $looselyTyped
* @access public
*/
function setLooselyTyped($looselyTyped) {
if (is_bool($looselyTyped)) {
$this->_looselyTyped = $looselyTyped;
}
}
/**
* Fails a test with the given message.
*
* @param string
* @access protected
* @abstract
*/
function fail($message = '') { /* abstract */ }
}
?>

698
inc/PHPUnit/GUI/Gtk.php Executable file
View File

@ -0,0 +1,698 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 2004 Scott Mattocks |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Scott Mattocks <scottmattocks@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Gtk.php,v 1.2 2004/11/25 09:06:55 sebastian Exp $
/**
* GTK GUI interface for PHPUnit.
*
* This class is a PHP port of junit.awtui.testrunner. Documentation
* for junit.awtui.testrunner can be found at
* http://junit.sourceforge.net
*
* Due to the limitations of PHP4 and PHP-Gtk, this class can not
* duplicate all of the functionality of the JUnit GUI. Some of the
* things this class cannot do include:
* - Reloading the class for each run
* - Stopping the test in progress
*
* To use simply intantiate the class and call main()
* $gtk =& new PHPUnit_GUI_Gtk;
* $gtk->main();
*
* Once the window has finished loading, you can enter the name of
* a class that has been loaded (include/require some where in your
* code, or you can pass the name of the file containing the class.
*
* You can also load classes using the SetupDecorator class.
* require_once 'PHPUnit/GUI/SetupDecorator.php';
* require_once 'PHPUnit/GUI/Gtk.php';
* $gui = new PHPUnit_GUI_SetupDecorator(new PHPUnit_GUI_Gtk());
* $gui->getSuitesFromDir('/path/to/test','.*\.php$',array('index.php','sql.php'));
* $gui->show();
*
* @todo Allow file drop. (Gtk_FileDrop)
*
* @author Scott Mattocks
* @copyright Copyright &copy; 2004 Scott Mattocks
* @license PHP 3.0
* @version @VER@
* @category PHP
* @package PHPUnit
* @subpackage GUI
*/
class PHPUnit_GUI_Gtk {
/**
* The main gtk window
* @var object
*/
var $gui;
/**
* The text entry that contains the name of the
* file that holds the test(s)/suite(s).
* @var object
*/
var $suiteField;
/**
* The label that shows the number of tests that
* were run.
* @var object
*/
var $numberOfRuns;
/**
* The label that shows the number of errors that
* were encountered.
* @var object
*/
var $numberOfErrors;
/**
* The label that shows the number of failures
* that were encountered.
* @var object
*/
var $numberOfFailures;
/**
* The label for reporting user messages.
* @var object
*/
var $statusLine;
/**
* The text area for reporting messages from successful
* test runs. (not necessarily successful tests)
* @var object
*/
var $reportArea;
/**
* The text area for reporting errors encountered when
* running tests.
* @var object
*/
var $dumpArea;
/**
* The progress bar indicator. Shows the percentage of
* passed tests.
* @var object
*/
var $progress;
/**
* A checkbox for the user to indicate whether or not they
* would like to see results from all tests or just failures.
* @object
*/
var $showPassed;
/**
* Constructor.
*
* The constructor checks for the gtk extension and loads it
* if needed. Then it creates the GUI. The GUI is not shown
* nor is the main gtk loop started until main() is called.
*
* @access public
* @param none
* @return void
*/
function PHPUnit_GUI_Gtk()
{
// Check for php-gtk extension.
if (!extension_loaded('gtk')) {
dl( 'php_gtk.' . PHP_SHLIB_SUFFIX);
}
// Create the interface but don't start the loop
$this->_createUI();
}
/**
* Start the main gtk loop.
*
* main() first sets the default state of the showPassed
* check box. Next all widgets that are part of the GUI
* are shown. Finally the main gtk loop is started.
*
* @access public
* @param boolean $showPassed
* @return void
*/
function main($showPassed = true)
{
$this->showPassed->set_active($showPassed);
$this->gui->show_all();
gtk::main();
}
/**
* Create the user interface.
*
* The user interface is pretty simple. It consists of a
* menu, text entry, run button, some labels, a progress
* indicator, and a couple of areas for notification of
* any messages.
*
* @access private
* @param none
* @return void
*/
function _createUI()
{
// Create a window.
$window =& new GtkWindow;
$window->set_title('PHPUnit Gtk');
$window->set_usize(400, -1);
// Create the main box.
$mainBox =& new GtkVBox;
$window->add($mainBox);
// Start with the menu.
$mainBox->pack_start($this->_createMenu());
// Then add the suite field entry.
$mainBox->pack_start($this->_createSuiteEntry());
// Then add the report labels.
$mainBox->pack_start($this->_createReportLabels());
// Next add the progress bar.
$mainBox->pack_start($this->_createProgressBar());
// Then add the report area and the dump area.
$mainBox->pack_start($this->_createReportAreas());
// Finish off with the status line.
$mainBox->pack_start($this->_createStatusLine());
// Connect the destroy signal.
$window->connect_object('destroy', array('gtk', 'main_quit'));
// Assign the member.
$this->gui =& $window;
}
/**
* Create the menu.
*
* The menu is very simple. It an exit menu item, which exits
* the application, and an about menu item, which shows some
* basic information about the application itself.
*
* @access private
* @param none
* @return &object The GtkMenuBar
*/
function &_createMenu()
{
// Create the menu bar.
$menuBar =& new GtkMenuBar;
// Create the main (only) menu item.
$phpHeader =& new GtkMenuItem('PHPUnit');
// Add the menu item to the menu bar.
$menuBar->append($phpHeader);
// Create the PHPUnit menu.
$phpMenu =& new GtkMenu;
// Add the menu items
$about =& new GtkMenuItem('About...');
$about->connect('activate', array(&$this, 'about'));
$phpMenu->append($about);
$exit =& new GtkMenuItem('Exit');
$exit->connect_object('activate', array('gtk', 'main_quit'));
$phpMenu->append($exit);
// Complete the menu.
$phpHeader->set_submenu($phpMenu);
return $menuBar;
}
/**
* Create the suite entry and related widgets.
*
* The suite entry has some supporting components such as a
* label, the show passed check box and the run button. All
* of these items are packed into two nested boxes.
*
* @access private
* @param none
* @return &object A box that contains all of the suite entry pieces.
*/
function &_createSuiteEntry()
{
// Create the outermost box.
$outerBox =& new GtkVBox;
// Create the suite label, box, and field.
$suiteLabel =& new GtkLabel('Test class name:');
$suiteBox =& new GtkHBox;
$this->suiteField =& new GtkEntry;
$this->suiteField->set_text($suiteName != NULL ? $suiteName : '');
// Create the button the user will use to start the test.
$runButton =& new GtkButton('Run');
$runButton->connect_object('clicked', array(&$this, 'run'));
// Create the check box that lets the user show only failures.
$this->showPassed =& new GtkCheckButton('Show passed tests');
// Add the components to their respective boxes.
$suiteLabel->set_alignment(0, 0);
$outerBox->pack_start($suiteLabel);
$outerBox->pack_start($suiteBox);
$outerBox->pack_start($this->showPassed);
$suiteBox->pack_start($this->suiteField);
$suiteBox->pack_start($runButton);
return $outerBox;
}
/**
* Create the labels that tell the user what has happened.
*
* There are three labels, one each for total runs, errors and
* failures. There is also one label for each of these that
* describes what the label is. It could be done with one label
* instead of two but that would make updates much harder.
*
* @access private
* @param none
* @return &object A box containing the labels.
*/
function &_createReportLabels()
{
// Create a box to hold everything.
$labelBox =& new GtkHBox;
// Create the non-updated labels.
$numberOfRuns =& new GtkLabel('Runs:');
$numberOfErrors =& new GtkLabel('Errors:');
$numberOfFailures =& new GtkLabel('Failures:');
// Create the labels that will be updated.
// These are asssigned to members to make it easier to
// set their values later.
$this->numberOfRuns =& new GtkLabel(0);
$this->numberOfErrors =& new GtkLabel(0);
$this->numberOfFailures =& new GtkLabel(0);
// Pack everything in.
$labelBox->pack_start($numberOfRuns);
$labelBox->pack_start($this->numberOfRuns);
$labelBox->pack_start($numberOfErrors);
$labelBox->pack_start($this->numberOfErrors);
$labelBox->pack_start($numberOfFailures);
$labelBox->pack_start($this->numberOfFailures);
return $labelBox;
}
/**
* Create the success/failure indicator.
*
* A GtkProgressBar is used to visually indicate how many
* tests were successful compared to how many were not. The
* progress bar shows the percentage of success and will
* change from green to red if there are any failures.
*
* @access private
* @param none
* @return &object The progress bar
*/
function &_createProgressBar()
{
// Create the progress bar.
$this->progress =& new GtkProgressBar(new GtkAdjustment(0, 0, 1, .1, 1, 0));
// Set the progress bar to print the percentage.
$this->progress->set_show_text(true);
return $this->progress;
}
/**
* Create the report text areas.
*
* The report area consists of one text area for failures, one
* text area for errors and one label for identification purposes.
* All three widgets are packed into a box.
*
* @access private
* @param none
* @return &object The box containing the report areas.
*/
function &_createReportAreas()
{
// Create the containing box.
$reportBox =& new GtkVBox;
// Create the identification label
$reportLabel =& new GtkLabel('Errors and Failures:');
$reportLabel->set_alignment(0, 0);
// Create the scrolled windows for the text areas.
$reportScroll =& new GtkScrolledWindow;
$dumpScroll =& new GtkScrolledWindow;
// Make the scroll areas big enough.
$reportScroll->set_usize(-1, 150);
$dumpScroll->set_usize(-1, 150);
// Only show the vertical scroll bar when needed.
// Never show the horizontal scroll bar.
$reportScroll->set_policy(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
$dumpScroll->set_policy(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
// Create the text areas.
$this->reportArea =& new GtkText;
$this->dumpArea =& new GtkText;
// Don't let words get broken.
$this->reportArea->set_word_wrap(true);
$this->dumpArea->set_word_wrap(true);
// Pack everything in.
$reportBox->pack_start($reportLabel);
$reportScroll->add($this->reportArea);
$reportBox->pack_start($reportScroll);
$dumpScroll->add($this->dumpArea);
$reportBox->pack_start($dumpScroll);
return $reportBox;
}
/**
* Create a status label.
*
* A status line at the bottom of the application is used
* to notify the user of non-test related messages such as
* failures loading a test suite.
*
* @access private
* @param none
* @return &object The status label.
*/
function &_createStatusLine()
{
// Create the status label.
$this->statusLine =& new GtkLabel('');
$this->statusLine->set_alignment(0, 0);
return $this->statusLine;
}
/**
* Show a popup with information about the application.
*
* The popup should show information about the version,
* the author, the license, where to get the latest
* version and a short description.
*
* @access public
* @param none
* @return void
*/
function about()
{
// Create the new window.
$about =& new GtkWindow;
$about->set_title('About PHPUnit GUI Gtk');
$about->set_usize(250, -1);
// Put two vboxes in the hbox.
$vBox =& new GtkVBox;
$about->add($vBox);
// Create the labels.
$version =& new GtkLabel(" Version: 1.0");
$license =& new GtkLabel(" License: PHP License v3.0");
$where =& new GtkLabel(" Download from: http://pear.php.net/PHPUnit/");
$unitAuth =& new GtkLabel(" PHPUnit Author: Sebastian Bergman");
$gtkAuth =& new GtkLabel(" Gtk GUI Author: Scott Mattocks");
// Align everything to the left
$where->set_alignment(0, .5);
$version->set_alignment(0, .5);
$license->set_alignment(0, .5);
$gtkAuth->set_alignment(0, .5);
$unitAuth->set_alignment(0, .5);
// Pack everything into the vBox;
$vBox->pack_start($version);
$vBox->pack_start($license);
$vBox->pack_start($where);
$vBox->pack_start($unitAuth);
$vBox->pack_start($gtkAuth);
// Connect the destroy signal.
$about->connect('destroy', array('gtk', 'true'));
// Show the goods.
$about->show_all();
}
/**
* Load the test suite.
*
* This method tries to load test suite based on the user
* info. If the user passes the name of a tests suite, it
* is instantiated and a new object is returned. If the
* user passes a file that contains a test suite, the class
* is instantiated and a new object is returned. If the user
* passes a file that contains a test case, the test case is
* passed to a new test suite and the new suite object is
* returned.
*
* @access public
* @param string The file that contains a test case/suite or the classname.
* @return &object The new test suite.
*/
function &loadTest(&$file)
{
// Check to see if a class name was given.
if (is_a($file, 'PHPUnit_TestSuite')) {
return $file;
} elseif (class_exists($file)) {
require_once 'PHPUnit/TestSuite.php';
return new PHPUnit_TestSuite($file);
}
// Check that the file exists.
if (!@is_readable($file)) {
$this->_showStatus('Cannot find file: ' . $file);
return false;
}
$this->_showStatus('Loading test suite...');
// Instantiate the class.
// If the path is /path/to/test/TestClass.php
// the class name should be test_TestClass
require_once $file;
$className = str_replace(DIRECTORY_SEPARATOR, '_', $file);
$className = substr($className, 0, strpos($className, '.'));
require_once 'PHPUnit/TestSuite.php';
return new PHPUnit_TestSuite($className);
}
/**
* Run the test suite.
*
* This method runs the test suite and updates the messages
* for the user. When finished it changes the status line
* to 'Test Complete'
*
* @access public
* @param none
* @return void
*/
function runTest()
{
// Notify the user that the test is running.
$this->_showStatus('Running Test...');
// Run the test.
$result = PHPUnit::run($this->suite);
// Update the labels.
$this->_setLabelValue($this->numberOfRuns, $result->runCount());
$this->_setLabelValue($this->numberOfErrors, $result->errorCount());
$this->_setLabelValue($this->numberOfFailures, $result->failureCount());
// Update the progress bar.
$this->_updateProgress($result->runCount(),
$result->errorCount(),
$result->failureCount()
);
// Show the errors.
$this->_showFailures($result->errors(), $this->dumpArea);
// Show the messages from the tests.
if ($this->showPassed->get_active()) {
// Show failures and success.
$this->_showAll($result, $this->reportArea);
} else {
// Show only failures.
$this->_showFailures($result->failures(), $this->reportArea);
}
// Update the status message.
$this->_showStatus('Test complete');
}
/**
* Set the text of a label.
*
* Change the text of a given label.
*
* @access private
* @param widget &$label The label whose value is to be changed.
* @param string $value The new text of the label.
* @return void
*/
function _setLabelValue(&$label, $value)
{
$label->set_text($value);
}
/**
* The main work of the application.
*
* Load the test suite and then execute the tests.
*
* @access public
* @param none
* @return void
*/
function run()
{
// Load the test suite.
$this->suite =& $this->loadTest($this->suiteField->get_text());
// Check to make sure the suite was loaded properly.
if (!is_object($this->suite)) {
// Raise an error.
$this->_showStatus('Could not load test suite.');
return false;
}
// Run the tests.
$this->runTest();
}
/**
* Update the status message.
*
* @access private
* @param string $status The new message.
* @return void
*/
function _showStatus($status)
{
$this->statusLine->set_text($status);
}
/**
* Alias for main()
*
* @see main
*/
function show($showPassed = true)
{
$this->main($showPassed);
}
/**
* Add a suite to the tests.
*
* This method is require by SetupDecorator. It adds a
* suite to the the current set of suites.
*
* @access public
* @param object $testSuite The suite to add.
* @return void
*/
function addSuites($testSuite)
{
if (!is_array($testSuite)) {
settype($testSuite, 'array');
}
foreach ($testSuite as $suite) {
if (is_a($this->suite, 'PHPUnit_TestSuite')) {
$this->suite->addTestSuite($suite->getName());
} else {
$this->suite =& $this->loadTest($suite);
}
// Set the suite field.
$text = $this->suiteField->get_text();
if (empty($text)) {
$this->suiteField->set_text($this->suite->getName());
}
}
}
/**
* Show all test messages.
*
* @access private
* @param object The TestResult from the test suite.
* @return void
*/
function _showAll(&$result)
{
// Clear the area first.
$this->reportArea->delete_text(0, -1);
$this->reportArea->insert_text($result->toString(), 0);
}
/**
* Show failure/error messages in the given text area.
*
* @access private
* @param object &$results The results of the test.
* @param widget &$area The area to show the results in.
*/
function _showFailures(&$results, &$area)
{
$area->delete_text(0, -1);
foreach (array_reverse($results, true) as $result) {
$area->insert_text($result->toString(), 0);
}
}
/**
* Update the progress indicator.
*
* @access private
* @param integer $runs
* @param integer $errors
* @param integer $failures
* @return void
*/
function _updateProgress($runs, $errors, $failures)
{
$percentage = 1 - (($errors + $failures) / $runs);
$this->progress->set_percentage($percentage);
}
}
?>

210
inc/PHPUnit/GUI/HTML.php Executable file
View File

@ -0,0 +1,210 @@
<?php
//
// +------------------------------------------------------------------------+
// | PEAR :: PHPUnit |
// +------------------------------------------------------------------------+
// | Copyright (c) 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>. |
// +------------------------------------------------------------------------+
// | This source file is subject to version 3.00 of the PHP License, |
// | that is available at http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +------------------------------------------------------------------------+
//
// $Id: HTML.php,v 1.17 2005/01/07 07:34:05 sebastian Exp $
//
/**
* This class provides a HTML GUI.
*
* @author Wolfram Kriesing <wolfram@kriesing.de>
* @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0
* @category Testing
* @package PHPUnit
* @subpackage GUI
*/
class PHPUnit_GUI_HTML
{
var $_suites = array();
/**
* the current implementation of PHPUnit is designed
* this way that adding a suite to another suite only
* grabs all the tests and adds them to the suite, so you
* have no chance to find out which test goes with which suite
* therefore you can simply pass an array of suites to this constructor here
*
* @param array The suites to be tested. If not given, then you might
* be using the SetupDecorator, which detects them automatically
* when calling getSuitesFromDir()
*/
function PHPUnit_GUI_HTML($suites = array())
{
if (!is_array($suites)) {
$this->_suites = array($suites);
} else {
$this->_suites = $suites;
}
}
/**
* Add suites to the GUI
*
* @param object this should be an instance of PHPUnit_TestSuite
*/
function addSuites($suites)
{
$this->_suites = array_merge($this->_suites,$suites);
}
/**
* this prints the HTML code straight out
*
*/
function show()
{
$request = $_REQUEST;
$showPassed = FALSE;
$submitted = @$request['submitted'];
if ($submitted) {
$showPassed = @$request['showOK'] ? TRUE : FALSE;
}
$suiteResults = array();
foreach ($this->_suites as $aSuite) {
$aSuiteResult = array();
// remove the first directory's name from the test-suite name, since it
// mostly is something like 'tests' or alike
$removablePrefix = explode('_',$aSuite->getName());
$aSuiteResult['name'] = str_replace($removablePrefix[0].'_', '', $aSuite->getName());
if ($submitted && isset($request[$aSuiteResult['name']])) {
$result = PHPUnit::run($aSuite);
$aSuiteResult['counts']['run'] = $result->runCount();
$aSuiteResult['counts']['error'] = $result->errorCount();
$aSuiteResult['counts']['failure'] = $result->failureCount();
$aSuiteResult['results'] = $this->_prepareResult($result,$showPassed);
$per = 100/$result->runCount();
$failed = ($per*$result->errorCount())+($per*$result->failureCount());
$aSuiteResult['percent'] = round(100-$failed,2);
} else {
$aSuiteResult['addInfo'] = 'NOT EXECUTED';
}
$suiteResults[] = $aSuiteResult;
}
$final['name'] = 'OVERALL RESULT';
$final['counts'] = array();
$final['percent'] = 0;
$numExecutedTests = 0;
foreach ($suiteResults as $aSuiteResult) {
if (sizeof(@$aSuiteResult['counts'])) {
foreach ($aSuiteResult['counts'] as $key=>$aCount) {
if (!isset($final['counts'][$key])) {
$final['counts'][$key] = 0;
}
$final['counts'][$key] += $aCount;
}
}
}
if (isset($final['counts']['run'])) {
$per = 100/$final['counts']['run'];
$failed = ($per*$final['counts']['error'])+($per*$final['counts']['failure']);
$final['percent'] = round(100-$failed,2);
} else {
$final['percent'] = 0;
}
array_unshift($suiteResults,$final);
include 'PHPUnit/GUI/HTML.tpl';
}
function _prepareResult($result,$showPassed)
{
$ret = array();
$failures = $result->failures();
foreach($failures as $aFailure) {
$ret['failures'][] = $this->_prepareFailure($aFailure);
}
$errors = $result->errors();
foreach($errors as $aError) {
$ret['errors'][] = $this->_prepareErrors($aError);
}
if ($showPassed) {
$passed = $result->passedTests();
foreach($passed as $aPassed) {
$ret['passed'][] = $this->_preparePassedTests($aPassed);
}
}
return $ret;
}
function _prepareFailure($failure)
{
$test = $failure->failedTest();
$ret['testName'] = $test->getName();
$exception = $failure->thrownException();
// a serialized string starts with a 'character:decimal:{'
// if so we try to unserialize it
// this piece of the regular expression is for detecting a serialized
// type like 'a:3:' for an array with three element or an object i.e. 'O:12:"class":3'
$serialized = '(\w:\d+:(?:"[^"]+":\d+:)?\{.*\})';
// Spaces might make a diff, so we shall show them properly (since a
// user agent ignores them).
if (preg_match('/^(.*)expected ' . $serialized . ', actual ' . $serialized . '$/sU', $exception, $matches)) {
ob_start();
print_r(unserialize($matches[2]));
$ret['expected'] = htmlspecialchars($matches[1]) . "<pre>" . htmlspecialchars(rtrim(ob_get_contents())) . "</pre>";
// Improved compatibility, ob_clean() would be PHP >= 4.2.0 only.
ob_end_clean();
ob_start();
print_r(unserialize($matches[3]));
$ret['actual'] = htmlspecialchars($matches[1]) . "<pre>" . htmlspecialchars(rtrim(ob_get_contents())) . "</pre>";
ob_end_clean();
}
else if (preg_match('/^(.*)expected (.*), actual (.*)$/sU', $exception, $matches)) {
$ret['expected'] = nl2br(str_replace(" ", "&nbsp;", htmlspecialchars($matches[1] . $matches[2])));
$ret['actual'] = nl2br(str_replace(" ", "&nbsp;", htmlspecialchars($matches[1] . $matches[3])));
} else {
$ret['message'] = nl2br(str_replace(" ", "&nbsp;", htmlspecialchars($exception)));
}
return $ret;
}
function _preparePassedTests($passed)
{
$ret['testName'] = $passed->getName();
return $ret;
}
function _prepareError($error)
{
$ret['testName'] = $error->getName();
$ret['message'] = $error->toString();
return $ret;
}
}
?>

View File

@ -0,0 +1,167 @@
<?php
//
// +------------------------------------------------------------------------+
// | PEAR :: PHPUnit |
// +------------------------------------------------------------------------+
// | Copyright (c) 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>. |
// +------------------------------------------------------------------------+
// | This source file is subject to version 3.00 of the PHP License, |
// | that is available at http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +------------------------------------------------------------------------+
//
// $Id: SetupDecorator.php,v 1.12 2005/05/14 05:58:38 sebastian Exp $
//
/**
* This decorator actually just adds the functionality to read the
* test-suite classes from a given directory and instanciate them
* automatically, use it as given in the example below.
*
* <code>
* <?php
* $gui = new PHPUnit_GUI_SetupDecorator(new PHPUnit_GUI_HTML());
* $gui->getSuitesFromDir('/path/to/dir/tests','.*\.php$',array('index.php','sql.php'));
* $gui->show();
* ?>
* </code>
*
* The example calls this class and tells it to:
*
* - find all file under the directory /path/to/dir/tests
* - for files, which end with '.php' (this is a piece of a regexp, that's why the . is escaped)
* - and to exclude the files 'index.php' and 'sql.php'
* - and include all the files that are left in the tests.
*
* Given that the path (the first parameter) ends with 'tests' it will be assumed
* that the classes are named tests_* where * is the directory plus the filename,
* according to PEAR standards.
*
* So that:
*
* - 'testMe.php' in the dir 'tests' bill be assumed to contain a class tests_testMe
* - '/moretests/aTest.php' should contain a class 'tests_moretests_aTest'
*
* @author Wolfram Kriesing <wolfram@kriesing.de>
* @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0
* @category Testing
* @package PHPUnit
* @subpackage GUI
*/
class PHPUnit_GUI_SetupDecorator
{
/**
*
*
*/
function PHPUnit_GUI_SetupDecorator(&$gui)
{
$this->_gui = &$gui;
}
/**
* just forwarding the action to the decorated class.
*
*/
function show($showPassed=TRUE)
{
$this->_gui->show($showPassed);
}
/**
* Setup test suites that can be found in the given directory
* Using the second parameter you can also choose a subsets of the files found
* in the given directory. I.e. only all the files that contain '_UnitTest_',
* in order to do this simply call it like this:
* <code>getSuitesFromDir($dir,'.*_UnitTest_.*')</code>.
* There you can already see that the pattern is built for the use within a regular expression.
*
* @param string the directory where to search for test-suite files
* @param string the pattern (a regexp) by which to find the files
* @param array an array of file names that shall be excluded
*/
function getSuitesFromDir($dir, $filenamePattern = '', $exclude = array())
{
if ($dir{strlen($dir)-1} == DIRECTORY_SEPARATOR) {
$dir = substr($dir, 0, -1);
}
$files = $this->_getFiles($dir, $filenamePattern, $exclude, realpath($dir . '/..'));
asort($files);
foreach ($files as $className => $aFile) {
include_once($aFile);
if (class_exists($className)) {
$suites[] =& new PHPUnit_TestSuite($className);
} else {
trigger_error("$className could not be found in $dir$aFile!");
}
}
$this->_gui->addSuites($suites);
}
/**
* This method searches recursively through the directories
* to find all the files that shall be added to the be visible.
*
* @param string the path where find the files
* @param srting the string pattern by which to find the files
* @param string the file names to be excluded
* @param string the root directory, which serves as the prefix to the fully qualified filename
* @access private
*/
function _getFiles($dir, $filenamePattern, $exclude, $rootDir)
{
$files = array();
if ($dp = opendir($dir)) {
while (FALSE !== ($file = readdir($dp))) {
$filename = $dir . DIRECTORY_SEPARATOR . $file;
$match = TRUE;
if ($filenamePattern && !preg_match("~$filenamePattern~", $file)) {
$match = FALSE;
}
if (sizeof($exclude)) {
foreach ($exclude as $aExclude) {
if (strpos($file, $aExclude) !== FALSE) {
$match = FALSE;
break;
}
}
}
if (is_file($filename) && $match) {
$tmp = str_replace($rootDir, '', $filename);
if (strpos($tmp, DIRECTORY_SEPARATOR) === 0) {
$tmp = substr($tmp, 1);
}
if (strpos($tmp, '/') === 0) {
$tmp = substr($tmp, 1);
}
$className = str_replace(DIRECTORY_SEPARATOR, '_', $tmp);
$className = basename($className, '.php');
$files[$className] = $filename;
}
if ($file != '.' && $file != '..' && is_dir($filename)) {
$files = array_merge($files, $this->_getFiles($filename, $filenamePattern, $exclude, $rootDir));
}
}
closedir($dp);
}
return $files;
}
}
?>

112
inc/PHPUnit/RepeatedTest.php Executable file
View File

@ -0,0 +1,112 @@
<?php
//
// +------------------------------------------------------------------------+
// | PEAR :: PHPUnit |
// +------------------------------------------------------------------------+
// | Copyright (c) 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>. |
// +------------------------------------------------------------------------+
// | This source file is subject to version 3.00 of the PHP License, |
// | that is available at http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +------------------------------------------------------------------------+
//
// $Id: RepeatedTest.php,v 1.10 2004/12/22 08:06:11 sebastian Exp $
//
require_once 'PHPUnit/TestDecorator.php';
/**
* A Decorator that runs a test repeatedly.
*
* Here is an example:
*
* <code>
* <?php
* require_once 'PHPUnit.php';
* require_once 'PHPUnit/RepeatedTest.php';
*
* class MathTest extends PHPUnit_TestCase {
* var $fValue1;
* var $fValue2;
*
* function MathTest($name) {
* $this->PHPUnit_TestCase($name);
* }
*
* function setUp() {
* $this->fValue1 = 2;
* $this->fValue2 = 3;
* }
*
* function testAdd() {
* $this->assertTrue($this->fValue1 + $this->fValue2 == 5);
* }
* }
*
* $suite = new PHPUnit_TestSuite;
*
* $suite->addTest(
* new PHPUnit_RepeatedTest(
* new MathTest('testAdd'),
* 10
* )
* );
*
* $result = PHPUnit::run($suite);
* print $result->toString();
* ?>
* </code>
*
* @author Sebastian Bergmann <sb@sebastian-bergmann.de>
* @copyright Copyright &copy; 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>
* @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0
* @category Testing
* @package PHPUnit
*/
class PHPUnit_RepeatedTest extends PHPUnit_TestDecorator {
/**
* @var integer
* @access private
*/
var $_timesRepeat = 1;
/**
* Constructor.
*
* @param object
* @param integer
* @access public
*/
function PHPUnit_RepeatedTest(&$test, $timesRepeat = 1) {
$this->PHPUnit_TestDecorator($test);
$this->_timesRepeat = $timesRepeat;
}
/**
* Counts the number of test cases that
* will be run by this test.
*
* @return integer
* @access public
*/
function countTestCases() {
return $this->_timesRepeat * $this->_test->countTestCases();
}
/**
* Runs the decorated test and collects the
* result in a TestResult.
*
* @param object
* @access public
* @abstract
*/
function run(&$result) {
for ($i = 0; $i < $this->_timesRepeat; $i++) {
$this->_test->run($result);
}
}
}
?>

406
inc/PHPUnit/Skeleton.php Executable file
View File

@ -0,0 +1,406 @@
<?php
//
// +------------------------------------------------------------------------+
// | PEAR :: PHPUnit |
// +------------------------------------------------------------------------+
// | Copyright (c) 2002-2004 Sebastian Bergmann <sb@sebastian-bergmann.de>. |
// +------------------------------------------------------------------------+
// | This source file is subject to version 3.00 of the PHP License, |
// | that is available at http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +------------------------------------------------------------------------+
//
// $Id: Skeleton.php,v 1.6 2004/12/22 08:06:11 sebastian Exp $
//
/**
* Class for creating a PHPUnit_TestCase skeleton file.
*
* This class will take a classname as a parameter on construction and will
* create a PHP file that contains the skeleton of a PHPUnit_TestCase
* subclass. The test case will contain a test foreach method of the class.
* Methods of the parent class will, by default, be excluded from the test
* class. Passing and optional construction parameter will include them.
*
* Example
*
* <?php
* require_once 'PHPUnit/Skeleton.php';
* $ps = new PHPUnit_Skeleton('PHPUnit_Skeleton', 'PHPUnit/Skeleton.php');
*
* // Generate the test class.
* // Default settings will not include any parent class methods, but
* // will include private methods.
* $ps->createTestClass();
*
* // Write the new test class to file.
* // By default, code to run the test will be included.
* $ps->writeTestClass();
* ?>
*
* Now open the skeleton class and fill in the details.
* If you run the test as is, all tests will fail and
* you will see plenty of undefined constant errors.
*
* @author Scott Mattocks <scott@crisscott.com>
* @copyright Copyright &copy; 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>
* @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0
* @category Testing
* @package PHPUnit
*/
class PHPUnit_Skeleton {
/**
* Path to the class file to create a skeleton for.
* @var string
*/
var $classPath;
/**
* The name of the class
* @var string
*/
var $className;
/**
* Path to the configuration file needed by class to test.
* @var string
*/
var $configFile;
/**
* Whether or not to include the methods of the parent class when testing.
* @var boolean
*/
var $includeParents;
/**
* Whether or not to test private methods.
* @var boolean
*/
var $includePrivate;
/**
* The test class that will be created.
* @var string
*/
var $testClass;
/**
* Constructor. Sets the class members and check that the class
* to test is accessible.
*
* @access public
* @param string $className
* @param string $classPath
* @param boolean $includeParents Wheter to include the parent's methods in the test.
* @return void
*/
function PHPUnit_Skeleton($className, $classPath, $includeParents = FALSE, $includePrivate = TRUE) {
// Set up the members.
if (@is_readable($classPath)) {
$this->className = $className;
$this->classPath = $classPath;
} else {
$this->_handleErrors($classPath . ' is not readable. Cannot create test class.');
}
// Do we want to include parent methods?
$this->includeParents = $includeParents;
// Do we want to allow private methods?
$this->includePrivate = $includePrivate;
}
/**
* The class to test may require a special config file before it can be
* instantiated. This method lets you set that file.
*
* @access public
* @param string $configPath
* @return void
*/
function setConfigFile($configFile) {
// Check that the file is readable
if (@is_readable($configFile)) {
$this->configFile = $configFile;
} else {
$this->_handleErrors($configFile . ' is not readable. Cannot create test class.');
}
}
/**
* Create the code that will be the skeleton of the test case.
*
* The test case must have a clss definition, one var, a constructor
* setUp, tearDown, and methods. Optionally and by default the code
* to run the test is added when the class is written to file.
*
* @access public
* @param none
* @return void
*/
function createTestClass() {
// Instantiate the object.
if (isset($this->configFile)) {
require_once $this->configFile;
}
require_once $this->classPath;
// Get the methods.
$classMethods = get_class_methods($this->className);
// Remove the parent methods if needed.
if (!$this->includeParents) {
$parentMethods = get_class_methods(get_parent_class($this->className));
if (count($parentMethods)) {
$classMethods = array_diff($classMethods, $parentMethods);
}
}
// Create the class definition, constructor, setUp and tearDown.
$this->_createDefinition();
$this->_createConstructor();
$this->_createSetUpTearDown();
if (count($classMethods)) {
// Foreach method create a test case.
foreach ($classMethods as $method) {
// Unless it is the constructor.
if (strcasecmp($this->className, $method) !== 0) {
// Check for private methods.
if (!$this->includePrivate && strpos($method, '_') === 0) {
continue;
} else {
$this->_createMethod($method);
}
}
}
}
// Finis off the class.
$this->_finishClass();
}
/**
* Create the class definition.
*
* The definition consist of a header comment, require statment
* for getting the PHPUnit file, the actual class definition,
* and the definition of the class member variable.
*
* All of the code needed for the new class is stored in the
* testClass member.
*
* @access private
* @param none
* @return void
*/
function _createDefinition() {
// Create header comment.
$this->testClass =
"/**\n" .
" * PHPUnit test case for " . $this->className . "\n" .
" * \n" .
" * The method skeletons below need to be filled in with \n" .
" * real data so that the tests will run correctly. Replace \n" .
" * all EXPECTED_VAL and PARAM strings with real data. \n" .
" * \n" .
" * Created with PHPUnit_Skeleton on " . date('Y-m-d') . "\n" .
" */\n";
// Add the require statements.
$this->testClass .= "require_once 'PHPUnit.php';\n";
// Add the class definition and variable definition.
$this->testClass .=
"class " . $this->className . "Test extends PHPUnit_TestCase {\n\n" .
" var \$" . $this->className . ";\n\n";
}
/**
* Create the class constructor. (PHP4 style)
*
* The constructor simply calls the PHPUnit_TestCase method.
* This code is taken from the PHPUnit documentation.
*
* All of the code needed for the new class is stored in the
* testClass member.
*
* @access private
* @param none
* @return void
*/
function _createConstructor() {
// Create the test class constructor.
$this->testClass.=
" function " . $this->className . "Test(\$name)\n" .
" {\n" .
" \$this->PHPUnit_TestCase(\$name);\n" .
" }\n\n";
}
/**
* Create setUp and tearDown methods.
*
* The setUp method creates the instance of the object to test.
* The tearDown method releases the instance.
* This code is taken from the PHPUnit documentation.
*
* All of the code needed for the new class is stored in the
* testClass member.
*
* @access private
* @param none
* @return void
*/
function _createSetUpTearDown() {
// Create the setUp method.
$this->testClass .=
" function setUp()\n" .
" {\n";
if (isset($this->configFile)) {
$this->testClass .=
" require_once '" . $this->configFile . "';\n";
}
$this->testClass .=
" require_once '" . $this->classPath . "';\n" .
" \$this->" . $this->className . " =& new " . $this->className . "(PARAM);\n" .
" }\n\n";
// Create the tearDown method.
$this->testClass .=
" function tearDown()\n" .
" {\n" .
" unset(\$this->" . $this->className . ");\n" .
" }\n\n";
}
/**
* Create a basic skeleton for test methods.
*
* This code is taken from the PHPUnit documentation.
*
* All of the code needed for the new class is stored in the
* testClass member.
*
* @access private
* @param none
* @return void
*/
function _createMethod($methodName) {
// Create a test method.
$this->testClass .=
" function test" . $methodName . "()\n" .
" {\n" .
" \$result = \$this->" . $this->className . "->" . $methodName . "(PARAM);\n" .
" \$expected = EXPECTED_VAL;\n" .
" \$this->assertEquals(\$expected, \$result);\n" .
" }\n\n";
}
/**
* Add the closing brace needed for a proper class definition.
*
* All of the code needed for the new class is stored in the
* testClass member.
*
* @access private
* @param none
* @return void
*/
function _finishClass() {
// Close off the class.
$this->testClass.= "}\n";
}
/**
* Create the code that will actually run the test.
*
* This code is added by default so that the test can be run
* just by running the file. To have it not added pass false
* as the second parameter to the writeTestClass method.
* This code is taken from the PHPUnit documentation.
*
* All of the code needed for the new class is stored in the
* testClass member.
*
* @access private
* @param none
* @return void
*/
function _createTest() {
// Create a call to the test.
$test =
"// Running the test.\n" .
"\$suite = new PHPUnit_TestSuite('" . $this->className . "Test');\n" .
"\$result = PHPUnit::run(\$suite);\n" .
"echo \$result->toString();\n";
return $test;
}
/**
* Write the test class to file.
*
* This will write the test class created using the createTestClass
* method to a file called <className>Test.php. By default the file
* is written to the current directory and will have code to run
* the test appended to the bottom of the file.
*
* @access public
* @param string $destination The directory to write the file to.
* @param boolean $addTest Wheter to add the test running code.
* @return void
*/
function writeTestClass($destination = './', $addTest = TRUE) {
// Check for something to write to file.
if (!isset($this->testClass)) {
$this->_handleErrors('Noting to write.', PHPUS_WARNING);
return;
}
// Open the destination file.
$fp = fopen($destination . $this->className . 'Test.php', 'w');
fwrite($fp, "<?php\n");
// Write the test class.
fwrite($fp, $this->testClass);
// Add the call to test the class in the file if we were asked to.
if ($addTest) {
fwrite($fp, $this->_createTest());
}
// Close the file.
fwrite($fp, "?>\n");
fclose($fp);
}
/**
* Error handler.
*
* This method should be rewritten to use the prefered error
* handling method. (PEAR_ErrorStack)
*
* @access private
* @param string $message The error message.
* @param integer $type An indication of the severity of the error.
* @return void Code may cause PHP to exit.
*/
function _handleErrors($message, $type = E_USER_ERROR) {
// For now just echo the message.
echo $message;
// Check to see if we should quit.
if ($type == E_USER_ERROR) {
exit;
}
}
}
?>

237
inc/PHPUnit/TestCase.php Executable file
View File

@ -0,0 +1,237 @@
<?php
//
// +------------------------------------------------------------------------+
// | PEAR :: PHPUnit |
// +------------------------------------------------------------------------+
// | Copyright (c) 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>. |
// +------------------------------------------------------------------------+
// | This source file is subject to version 3.00 of the PHP License, |
// | that is available at http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +------------------------------------------------------------------------+
//
// $Id: TestCase.php,v 1.16 2004/12/22 08:06:11 sebastian Exp $
//
require_once 'PHPUnit/Assert.php';
require_once 'PHPUnit/TestResult.php';
/**
* A TestCase defines the fixture to run multiple tests.
*
* To define a TestCase
*
* 1) Implement a subclass of PHPUnit_TestCase.
* 2) Define instance variables that store the state of the fixture.
* 3) Initialize the fixture state by overriding setUp().
* 4) Clean-up after a test by overriding tearDown().
*
* Each test runs in its own fixture so there can be no side effects
* among test runs.
*
* Here is an example:
*
* <code>
* <?php
* class MathTest extends PHPUnit_TestCase {
* var $fValue1;
* var $fValue2;
*
* function MathTest($name) {
* $this->PHPUnit_TestCase($name);
* }
*
* function setUp() {
* $this->fValue1 = 2;
* $this->fValue2 = 3;
* }
* }
* ?>
* </code>
*
* For each test implement a method which interacts with the fixture.
* Verify the expected results with assertions specified by calling
* assert with a boolean.
*
* <code>
* <?php
* function testPass() {
* $this->assertTrue($this->fValue1 + $this->fValue2 == 5);
* }
* ?>
* </code>
*
* @author Sebastian Bergmann <sb@sebastian-bergmann.de>
* @copyright Copyright &copy; 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>
* @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0
* @category Testing
* @package PHPUnit
*/
class PHPUnit_TestCase extends PHPUnit_Assert {
/**
* @var boolean
* @access private
*/
var $_failed = FALSE;
/**
* The name of the test case.
*
* @var string
* @access private
*/
var $_name = '';
/**
* PHPUnit_TestResult object
*
* @var object
* @access private
*/
var $_result;
/**
* Constructs a test case with the given name.
*
* @param string
* @access public
*/
function PHPUnit_TestCase($name = FALSE) {
if ($name !== FALSE) {
$this->setName($name);
}
}
/**
* Counts the number of test cases executed by run(TestResult result).
*
* @return integer
* @access public
*/
function countTestCases() {
return 1;
}
/**
* Gets the name of a TestCase.
*
* @return string
* @access public
*/
function getName() {
return $this->_name;
}
/**
* Runs the test case and collects the results in a given TestResult object.
*
* @param object
* @return object
* @access public
*/
function run(&$result) {
$this->_result = &$result;
$this->_result->run($this);
return $this->_result;
}
/**
* Runs the bare test sequence.
*
* @access public
*/
function runBare() {
$this->setUp();
$this->runTest();
$this->tearDown();
$this->pass();
}
/**
* Override to run the test and assert its state.
*
* @access protected
*/
function runTest() {
call_user_func(
array(
&$this,
$this->_name
)
);
}
/**
* Sets the name of a TestCase.
*
* @param string
* @access public
*/
function setName($name) {
$this->_name = $name;
}
/**
* Returns a string representation of the test case.
*
* @return string
* @access public
*/
function toString() {
return '';
}
/**
* Creates a default TestResult object.
*
* @return object
* @access protected
*/
function &createResult() {
return new PHPUnit_TestResult;
}
/**
* Fails a test with the given message.
*
* @param string
* @access protected
*/
function fail($message = '') {
$this->_result->addFailure($this, $message);
$this->_failed = TRUE;
}
/**
* Passes a test.
*
* @access protected
*/
function pass() {
if (!$this->_failed) {
$this->_result->addPassedTest($this);
}
}
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
*
* @access protected
* @abstract
*/
function setUp() { /* abstract */ }
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed.
*
* @access protected
* @abstract
*/
function tearDown() { /* abstract */ }
}
?>

110
inc/PHPUnit/TestDecorator.php Executable file
View File

@ -0,0 +1,110 @@
<?php
//
// +------------------------------------------------------------------------+
// | PEAR :: PHPUnit |
// +------------------------------------------------------------------------+
// | Copyright (c) 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>. |
// +------------------------------------------------------------------------+
// | This source file is subject to version 3.00 of the PHP License, |
// | that is available at http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +------------------------------------------------------------------------+
//
// $Id: TestDecorator.php,v 1.12 2005/05/14 05:58:38 sebastian Exp $
//
require_once 'PHPUnit/TestCase.php';
require_once 'PHPUnit/TestSuite.php';
/**
* A Decorator for Tests.
*
* Use TestDecorator as the base class for defining new
* test decorators. Test decorator subclasses can be introduced
* to add behaviour before or after a test is run.
*
* @author Sebastian Bergmann <sb@sebastian-bergmann.de>
* @copyright Copyright &copy; 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>
* @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0
* @category Testing
* @package PHPUnit
*/
class PHPUnit_TestDecorator {
/**
* The Test to be decorated.
*
* @var object
* @access protected
*/
var $_test = NULL;
/**
* Constructor.
*
* @param object
* @access public
*/
function PHPUnit_TestDecorator(&$test) {
if (is_object($test) &&
(is_a($test, 'PHPUnit_TestCase') ||
is_a($test, 'PHPUnit_TestSuite'))) {
$this->_test = &$test;
}
}
/**
* Runs the test and collects the
* result in a TestResult.
*
* @param object
* @access public
*/
function basicRun(&$result) {
$this->_test->run($result);
}
/**
* Counts the number of test cases that
* will be run by this test.
*
* @return integer
* @access public
*/
function countTestCases() {
return $this->_test->countTestCases();
}
/**
* Returns the test to be run.
*
* @return object
* @access public
*/
function &getTest() {
return $this->_test;
}
/**
* Runs the decorated test and collects the
* result in a TestResult.
*
* @param object
* @access public
* @abstract
*/
function run(&$result) { /* abstract */ }
/**
* Returns a string representation of the test.
*
* @return string
* @access public
*/
function toString() {
return $this->_test->toString();
}
}
?>

88
inc/PHPUnit/TestFailure.php Executable file
View File

@ -0,0 +1,88 @@
<?php
//
// +------------------------------------------------------------------------+
// | PEAR :: PHPUnit |
// +------------------------------------------------------------------------+
// | Copyright (c) 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>. |
// +------------------------------------------------------------------------+
// | This source file is subject to version 3.00 of the PHP License, |
// | that is available at http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +------------------------------------------------------------------------+
//
// $Id: TestFailure.php,v 1.10 2005/05/14 05:58:38 sebastian Exp $
//
/**
* A TestFailure collects a failed test together with the caught exception.
*
* @author Sebastian Bergmann <sb@sebastian-bergmann.de>
* @copyright Copyright &copy; 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>
* @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0
* @category Testing
* @package PHPUnit
*/
class PHPUnit_TestFailure {
/**
* @var object
* @access private
*/
var $_failedTest;
/**
* @var string
* @access private
*/
var $_thrownException;
/**
* Constructs a TestFailure with the given test and exception.
*
* @param object
* @param string
* @access public
*/
function PHPUnit_TestFailure(&$failedTest, &$thrownException) {
$this->_failedTest = &$failedTest;
$this->_thrownException = &$thrownException;
}
/**
* Gets the failed test.
*
* @return object
* @access public
*/
function &failedTest() {
return $this->_failedTest;
}
/**
* Gets the thrown exception.
*
* @return object
* @access public
*/
function &thrownException() {
return $this->_thrownException;
}
/**
* Returns a short description of the failure.
*
* @return string
* @access public
*/
function toString() {
return sprintf(
"TestCase %s->%s() failed: %s\n",
get_class($this->_failedTest),
$this->_failedTest->getName(),
$this->_thrownException
);
}
}
?>

120
inc/PHPUnit/TestListener.php Executable file
View File

@ -0,0 +1,120 @@
<?php
//
// +------------------------------------------------------------------------+
// | PEAR :: PHPUnit |
// +------------------------------------------------------------------------+
// | Copyright (c) 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>. |
// +------------------------------------------------------------------------+
// | This source file is subject to version 3.00 of the PHP License, |
// | that is available at http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +------------------------------------------------------------------------+
//
// $Id: TestListener.php,v 1.9 2004/12/22 08:06:11 sebastian Exp $
//
/**
* A Listener for test progress.
*
* Here is an example:
*
* <code>
* <?php
* require_once 'PHPUnit.php';
* require_once 'PHPUnit/TestListener.php';
*
* class MathTest extends PHPUnit_TestCase {
* var $fValue1;
* var $fValue2;
*
* function MathTest($name) {
* $this->PHPUnit_TestCase($name);
* }
*
* function setUp() {
* $this->fValue1 = 2;
* $this->fValue2 = 3;
* }
*
* function testAdd() {
* $this->assertTrue($this->fValue1 + $this->fValue2 == 4);
* }
* }
*
* class MyListener extends PHPUnit_TestListener {
* function addError(&$test, &$t) {
* print "MyListener::addError() called.\n";
* }
*
* function addFailure(&$test, &$t) {
* print "MyListener::addFailure() called.\n";
* }
*
* function endTest(&$test) {
* print "MyListener::endTest() called.\n";
* }
*
* function startTest(&$test) {
* print "MyListener::startTest() called.\n";
* }
* }
*
* $suite = new PHPUnit_TestSuite;
* $suite->addTest(new MathTest('testAdd'));
*
* $result = new PHPUnit_TestResult;
* $result->addListener(new MyListener);
*
* $suite->run($result);
* print $result->toString();
* ?>
* </code>
*
* @author Sebastian Bergmann <sb@sebastian-bergmann.de>
* @copyright Copyright &copy; 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>
* @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0
* @category Testing
* @package PHPUnit
*/
class PHPUnit_TestListener {
/**
* An error occurred.
*
* @param object
* @param object
* @access public
* @abstract
*/
function addError(&$test, &$t) { /*abstract */ }
/**
* A failure occurred.
*
* @param object
* @param object
* @access public
* @abstract
*/
function addFailure(&$test, &$t) { /*abstract */ }
/**
* A test ended.
*
* @param object
* @access public
* @abstract
*/
function endTest(&$test) { /*abstract */ }
/**
* A test started.
*
* @param object
* @access public
* @abstract
*/
function startTest(&$test) { /*abstract */ }
}
?>

300
inc/PHPUnit/TestResult.php Executable file
View File

@ -0,0 +1,300 @@
<?php
//
// +------------------------------------------------------------------------+
// | PEAR :: PHPUnit |
// +------------------------------------------------------------------------+
// | Copyright (c) 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>. |
// +------------------------------------------------------------------------+
// | This source file is subject to version 3.00 of the PHP License, |
// | that is available at http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +------------------------------------------------------------------------+
//
// $Id: TestResult.php,v 1.13 2005/05/14 05:58:38 sebastian Exp $
//
require_once 'PHPUnit/TestFailure.php';
require_once 'PHPUnit/TestListener.php';
/**
* A TestResult collects the results of executing a test case.
*
* @author Sebastian Bergmann <sb@sebastian-bergmann.de>
* @copyright Copyright &copy; 2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>
* @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0
* @category Testing
* @package PHPUnit
*/
class PHPUnit_TestResult {
/**
* @var array
* @access protected
*/
var $_errors = array();
/**
* @var array
* @access protected
*/
var $_failures = array();
/**
* @var array
* @access protected
*/
var $_listeners = array();
/**
* @var array
* @access protected
*/
var $_passedTests = array();
/**
* @var integer
* @access protected
*/
var $_runTests = 0;
/**
* @var boolean
* @access private
*/
var $_stop = FALSE;
/**
* Adds an error to the list of errors.
* The passed in exception caused the error.
*
* @param object
* @param object
* @access public
*/
function addError(&$test, &$t) {
$this->_errors[] = new PHPUnit_TestFailure($test, $t);
for ($i = 0; $i < sizeof($this->_listeners); $i++) {
$this->_listeners[$i]->addError($test, $t);
}
}
/**
* Adds a failure to the list of failures.
* The passed in exception caused the failure.
*
* @param object
* @param object
* @access public
*/
function addFailure(&$test, &$t) {
$this->_failures[] = new PHPUnit_TestFailure($test, $t);
for ($i = 0; $i < sizeof($this->_listeners); $i++) {
$this->_listeners[$i]->addFailure($test, $t);
}
}
/**
* Registers a TestListener.
*
* @param object
* @access public
*/
function addListener(&$listener) {
if (is_object($listener) &&
is_a($listener, 'PHPUnit_TestListener')) {
$this->_listeners[] = &$listener;
}
}
/**
* Adds a passed test to the list of passed tests.
*
* @param object
* @access public
*/
function addPassedTest(&$test) {
$this->_passedTests[] = &$test;
}
/**
* Informs the result that a test was completed.
*
* @param object
* @access public
*/
function endTest(&$test) {
for ($i = 0; $i < sizeof($this->_listeners); $i++) {
$this->_listeners[$i]->endTest($test);
}
}
/**
* Gets the number of detected errors.
*
* @return integer
* @access public
*/
function errorCount() {
return sizeof($this->_errors);
}
/**
* Returns an Enumeration for the errors.
*
* @return array
* @access public
*/
function &errors() {
return $this->_errors;
}
/**
* Gets the number of detected failures.
*
* @return integer
* @access public
*/
function failureCount() {
return sizeof($this->_failures);
}
/**
* Returns an Enumeration for the failures.
*
* @return array
* @access public
*/
function &failures() {
return $this->_failures;
}
/**
* Returns an Enumeration for the passed tests.
*
* @return array
* @access public
*/
function &passedTests() {
return $this->_passedTests;
}
/**
* Unregisters a TestListener.
* This requires the Zend Engine 2 (to work properly).
*
* @param object
* @access public
*/
function removeListener(&$listener) {
for ($i = 0; $i < sizeof($this->_listeners); $i++) {
if ($this->_listeners[$i] === $listener) {
unset($this->_listeners[$i]);
}
}
}
/**
* Runs a TestCase.
*
* @param object
* @access public
*/
function run(&$test) {
$this->startTest($test);
$this->_runTests++;
$test->runBare();
$this->endTest($test);
}
/**
* Gets the number of run tests.
*
* @return integer
* @access public
*/
function runCount() {
return $this->_runTests;
}
/**
* Checks whether the test run should stop.
*
* @access public
*/
function shouldStop() {
return $this->_stop;
}
/**
* Informs the result that a test will be started.
*
* @param object
* @access public
*/
function startTest(&$test) {
for ($i = 0; $i < sizeof($this->_listeners); $i++) {
$this->_listeners[$i]->startTest($test);
}
}
/**
* Marks that the test run should stop.
*
* @access public
*/
function stop() {
$this->_stop = TRUE;
}
/**
* Returns a HTML representation of the test result.
*
* @return string
* @access public
*/
function toHTML() {
return '<pre>' . htmlspecialchars($this->toString()) . '</pre>';
}
/**
* Returns a text representation of the test result.
*
* @return string
* @access public
*/
function toString() {
$result = '';
foreach ($this->_passedTests as $passedTest) {
$result .= sprintf(
"TestCase %s->%s() passed\n",
get_class($passedTest),
$passedTest->getName()
);
}
foreach ($this->_failures as $failedTest) {
$result .= $failedTest->toString();
}
return $result;
}
/**
* Returns whether the entire test was successful or not.
*
* @return boolean
* @access public
*/
function wasSuccessful() {
if (empty($this->_errors) && empty($this->_failures)) {
return TRUE;
} else {
return FALSE;
}
}
}
?>

Some files were not shown because too many files have changed in this diff Show More