Subversion Repositories Applications.papyrus

Compare Revisions

Ignore whitespace Rev 1172 → Rev 1173

/trunk/api/pear/PEAR.php
1,26 → 1,33
<?php
//
// +--------------------------------------------------------------------+
// | PEAR, the PHP Extension and Application Repository |
// +--------------------------------------------------------------------+
// | 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: Sterling Hughes <sterling@php.net> |
// | Stig Bakken <ssb@php.net> |
// | Tomas V.V.Cox <cox@idecnet.com> |
// +--------------------------------------------------------------------+
//
// $Id: PEAR.php,v 1.1 2005-03-30 08:50:19 jpm Exp $
//
/**
* PEAR, the PHP Extension and Application Repository
*
* PEAR class and PEAR_Error class
*
* 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 pear
* @package PEAR
* @author Sterling Hughes <sterling@php.net>
* @author Stig Bakken <ssb@php.net>
* @author Tomas V.V.Cox <cox@idecnet.com>
* @author Greg Beaver <cellog@php.net>
* @copyright 1997-2006 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: PEAR.php,v 1.2 2006-12-14 15:04:29 jp_milcent Exp $
* @link http://pear.php.net/package/PEAR
* @since File available since Release 0.1
*/
 
/**#@+
* ERROR constants
*/
define('PEAR_ERROR_RETURN', 1);
define('PEAR_ERROR_PRINT', 2);
define('PEAR_ERROR_TRIGGER', 4);
31,6 → 38,7
* @deprecated
*/
define('PEAR_ERROR_EXCEPTION', 32);
/**#@-*/
define('PEAR_ZE2', (function_exists('version_compare') &&
version_compare(zend_version(), "2-dev", "ge")));
 
78,9 → 86,18
* IMPORTANT! To use the emulated destructors you need to create the
* objects by reference: $obj =& new PEAR_child;
*
* @since PHP 4.0.2
* @author Stig Bakken <ssb@php.net>
* @see http://pear.php.net/manual/
* @category pear
* @package PEAR
* @author Stig Bakken <ssb@php.net>
* @author Tomas V.V. Cox <cox@idecnet.com>
* @author Greg Beaver <cellog@php.net>
* @copyright 1997-2006 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.4.11
* @link http://pear.php.net/package/PEAR
* @see PEAR_Error
* @since Class available since PHP 4.0.2
* @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear
*/
class PEAR
{
230,6 → 247,12
*/
function registerShutdownFunc($func, $args = array())
{
// if we are called statically, there is a potential
// that no shutdown func is registered. Bug #6445
if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
register_shutdown_function("_PEAR_call_destructors");
$GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
}
$GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args);
}
 
492,7 → 515,7
* @see PEAR::setErrorHandling
* @since PHP 4.0.5
*/
function raiseError($message = null,
function &raiseError($message = null,
$code = null,
$mode = null,
$options = null,
537,9 → 560,11
$ec = 'PEAR_Error';
}
if ($skipmsg) {
return new $ec($code, $mode, $options, $userinfo);
$a = &new $ec($code, $mode, $options, $userinfo);
return $a;
} else {
return new $ec($message, $code, $mode, $options, $userinfo);
$a = &new $ec($message, $code, $mode, $options, $userinfo);
return $a;
}
}
 
553,14 → 578,16
* @param string $message
*
*/
function throwError($message = null,
function &throwError($message = null,
$code = null,
$userinfo = null)
{
if (isset($this) && is_a($this, 'PEAR')) {
return $this->raiseError($message, $code, null, null, $userinfo);
$a = &$this->raiseError($message, $code, null, null, $userinfo);
return $a;
} else {
return PEAR::raiseError($message, $code, null, null, $userinfo);
$a = &PEAR::raiseError($message, $code, null, null, $userinfo);
return $a;
}
}
 
769,7 → 796,23
}
 
// }}}
 
/**
* Standard PEAR error class for PHP 4
*
* This class is supserseded by {@link PEAR_Exception} in PHP 5
*
* @category pear
* @package PEAR
* @author Stig Bakken <ssb@php.net>
* @author Tomas V.V. Cox <cox@idecnet.com>
* @author Gregory Beaver <cellog@php.net>
* @copyright 1997-2006 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.4.11
* @link http://pear.php.net/manual/en/core.pear.pear-error.php
* @see PEAR::raiseError(), PEAR::throwError()
* @since Class available since PHP 4.0.2
*/
class PEAR_Error
{
// {{{ properties
859,8 → 902,8
}
}
if ($this->mode & PEAR_ERROR_EXCEPTION) {
trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_ErrorStack for exceptions", E_USER_WARNING);
eval('$e = new Exception($this->message, $this->code);$e->PEAR_Error = $this;throw($e);');
trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING);
eval('$e = new Exception($this->message, $this->code);throw($e);');
}
}
 
975,6 → 1018,9
*/
function getBacktrace($frame = null)
{
if (defined('PEAR_IGNORE_BACKTRACE')) {
return null;
}
if ($frame === null) {
return $this->backtrace;
}
/trunk/api/pear/Auth/Anonymous.php
New file
0,0 → 1,138
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Anonymous authentication support
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Yavor Shahpasov <yavo@netsmart.com.cy>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: Anonymous.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @since File available since Release 1.3.0
*/
 
/**
* Include Auth package
*/
require_once 'Auth.php';
 
/**
* Anonymous Authentication
*
* This class provides anonymous authentication if username and password
* were not supplied
*
* @category Authentication
* @package Auth
* @author Yavor Shahpasov <yavo@netsmart.com.cy>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.1 $
* @link http://pear.php.net/package/Auth
* @since Class available since Release 1.3.0
*/
class Auth_Anonymous extends Auth
{
 
// {{{ properties
 
/**
* Whether to allow anonymous authentication
*
* @var boolean
*/
var $allow_anonymous = true;
 
/**
* Username to use for anonymous user
*
* @var string
*/
var $anonymous_username = 'anonymous';
 
// }}}
// {{{ Auth_Anonymous() [constructor]
/**
* Pass all parameters to Parent Auth class
*
* Set up the storage driver.
*
* @param string Type of the storage driver
* @param mixed Additional options for the storage driver
* (example: if you are using DB as the storage
* driver, you have to pass the dsn string here)
*
* @param string Name of the function that creates the login form
* @param boolean Should the login form be displayed if neccessary?
* @return void
* @see Auth::Auth()
*/
function Auth_Anonymous($storageDriver, $options = '', $loginFunction = '', $showLogin = true) {
parent::Auth($storageDriver, $options, $loginFunction, $showLogin);
}
 
// }}}
// {{{ login()
/**
* Login function
*
* If no username & password is passed then login as the username
* provided in $this->anonymous_username else call standard login()
* function.
*
* @return void
* @access private
* @see Auth::login()
*/
function login() {
if ( $this->allow_anonymous
&& empty($this->username)
&& empty($this->password) ) {
$this->setAuth($this->anonymous_username);
if (is_callable($this->loginCallback)) {
call_user_func_array($this->loginCallback, array($this->username, $this) );
}
} else {
// Call normal login system
parent::login();
}
}
 
// }}}
// {{{ forceLogin()
/**
* Force the user to login
*
* Calling this function forces the user to provide a real username and
* password before continuing.
*
* @return void
*/
function forceLogin() {
$this->allow_anonymous = false;
if( !empty($this->session['username']) && $this->session['username'] == $this->anonymous_username ) {
$this->logout();
}
}
 
// }}}
 
}
 
?>
/trunk/api/pear/Auth/Frontend/Html.php
New file
0,0 → 1,142
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Standard Html Login form
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Martin Jansen <mj@php.net>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: Html.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @since File available since Release 1.3.0
*/
 
/**
* Standard Html Login form
*
* @category Authentication
* @package Auth
* @author Yavor Shahpasov <yavo@netsmart.com.cy>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.1 $
* @link http://pear.php.net/package/Auth
* @since Class available since Release 1.3.0
*/
class Auth_Frontend_Html {
// {{{ render()
 
/**
* Displays the login form
*
* @param object The calling auth instance
* @param string The previously used username
* @return void
*/
function render(&$caller, $username = '') {
$loginOnClick = 'return true;';
// Try To Use Challene response
// TODO javascript might need some improvement for work on other browsers
if($caller->advancedsecurity && $caller->storage->supportsChallengeResponse() ) {
 
// Init the secret cookie
$caller->session['loginchallenege'] = md5(microtime());
 
print "\n";
print '<script language="JavaScript">'."\n";
 
include 'Auth/Frontend/md5.js';
 
print "\n";
print ' function securePassword() { '."\n";
print ' var pass = document.getElementById(\''.$caller->getPostPasswordField().'\');'."\n";
print ' var secret = document.getElementById(\'authsecret\')'."\n";
//print ' alert(pass);alert(secret); '."\n";
 
// If using md5 for password storage md5 the password before
// we hash it with the secret
// print ' alert(pass.value);';
if ($caller->storage->getCryptType() == 'md5' ) {
print ' pass.value = hex_md5(pass.value); '."\n";
#print ' alert(pass.value);';
}
 
print ' pass.value = hex_md5(pass.value+\''.$caller->session['loginchallenege'].'\'); '."\n";
// print ' alert(pass.value);';
print ' secret.value = 1;'."\n";
print ' var doLogin = document.getElementById(\'doLogin\')'."\n";
print ' doLogin.disabled = true;'."\n";
print ' return true;';
print ' } '."\n";
print '</script>'."\n";;
print "\n";
 
$loginOnClick = ' return securePassword(); ';
}
 
print '<center>'."\n";
 
$status = '';
if (!empty($caller->status) && $caller->status == AUTH_EXPIRED) {
$status = '<i>Your session has expired. Please login again!</i>'."\n";
} else if (!empty($caller->status) && $caller->status == AUTH_IDLED) {
$status = '<i>You have been idle for too long. Please login again!</i>'."\n";
} else if (!empty ($caller->status) && $caller->status == AUTH_WRONG_LOGIN) {
$status = '<i>Wrong login data!</i>'."\n";
} else if (!empty ($caller->status) && $caller->status == AUTH_SECURITY_BREACH) {
$status = '<i>Security problem detected. </i>'."\n";
}
print '<form method="post" action="'.$caller->server['PHP_SELF'].'" '
.'onSubmit="'.$loginOnClick.'">'."\n";
print '<table border="0" cellpadding="2" cellspacing="0" '
.'summary="login form" align="center" >'."\n";
print '<tr>'."\n";
print ' <td colspan="2" bgcolor="#eeeeee"><strong>Login </strong>'
.$status.'</td>'."\n";
print '</tr>'."\n";
print '<tr>'."\n";
print ' <td>Username:</td>'."\n";
print ' <td><input type="text" id="'.$caller->getPostUsernameField()
.'" name="'.$caller->getPostUsernameField().'" value="' . $username
.'" /></td>'."\n";
print '</tr>'."\n";
print '<tr>'."\n";
print ' <td>Password:</td>'."\n";
print ' <td><input type="password" id="'.$caller->getPostPasswordField()
.'" name="'.$caller->getPostPasswordField().'" /></td>'."\n";
print '</tr>'."\n";
print '<tr>'."\n";
//onClick=" '.$loginOnClick.' "
print ' <td colspan="2" bgcolor="#eeeeee"><input value="Login" '
.'id="doLogin" name="doLogin" type="submit" /></td>'."\n";
print '</tr>'."\n";
print '</table>'."\n";
 
// Might be a good idea to make the variable name variable
print '<input type="hidden" id="authsecret" name="authsecret" value="" />';
print '</form>'."\n";
print '</center>'."\n";
}
 
// }}}
}
 
?>
/trunk/api/pear/Auth/Frontend/md5.js
New file
0,0 → 1,256
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
 
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
 
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
 
/*
* Perform a simple self-test to see if the VM is working
*/
function md5_vm_test()
{
return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
}
 
/*
* Calculate the MD5 of an array of little-endian words, and a bit length
*/
function core_md5(x, len)
{
/* append padding */
x[len >> 5] |= 0x80 << ((len) % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;
 
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
 
for(var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
 
a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
 
a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
 
a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
 
a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
 
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}
return Array(a, b, c, d);
 
}
 
/*
* These functions implement the four basic operations the algorithm uses.
*/
function md5_cmn(q, a, b, x, s, t)
{
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}
 
/*
* Calculate the HMAC-MD5, of a key and some data
*/
function core_hmac_md5(key, data)
{
var bkey = str2binl(key);
if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
 
var ipad = Array(16), opad = Array(16);
for(var i = 0; i < 16; i++)
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
 
var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
return core_md5(opad.concat(hash), 512 + 128);
}
 
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
 
/*
* Bitwise rotate a 32-bit number to the left.
*/
function bit_rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
 
/*
* Convert a string to an array of little-endian words
* If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
*/
function str2binl(str)
{
var bin = Array();
var mask = (1 << chrsz) - 1;
for(var i = 0; i < str.length * chrsz; i += chrsz)
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
return bin;
}
 
/*
* Convert an array of little-endian words to a string
*/
function binl2str(bin)
{
var str = "";
var mask = (1 << chrsz) - 1;
for(var i = 0; i < bin.length * 32; i += chrsz)
str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
return str;
}
 
/*
* Convert an array of little-endian words to a hex string.
*/
function binl2hex(binarray)
{
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for(var i = 0; i < binarray.length * 4; i++)
{
str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF);
}
return str;
}
 
/*
* Convert an array of little-endian words to a base-64 string
*/
function binl2b64(binarray)
{
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var str = "";
for(var i = 0; i < binarray.length * 4; i += 3)
{
var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16)
| (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
| ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
for(var j = 0; j < 4; j++)
{
if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
}
}
return str;
}
/trunk/api/pear/Auth/Container/KADM5.php
New file
0,0 → 1,170
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for Authentication on a Kerberos V server.
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Andrew Teixeira <ateixeira@gmail.com>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: KADM5.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @since File available since Release 1.4.0
*/
 
/**
* Include Auth_Container base class
*/
require_once 'Auth/Container.php';
/**
* Include PEAR for error handling
*/
require_once 'PEAR.php';
 
/**
* Storage driver for Authentication on a Kerberos V server.
*
* Available options:
* hostname: The hostname of the kerberos server
* realm: The Kerberos V realm
* timeout: The timeout for checking the server
* checkServer: Set to true to check if the server is running when
* constructing the object
*
* @category Authentication
* @package Auth
* @author Andrew Teixeira <ateixeira@gmail.com>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.1 $
* @link http://pear.php.net/package/Auth
* @since Class available since Release 1.4.0
*/
class Auth_Container_KADM5 extends Auth_Container {
 
// {{{ properties
 
/**
* Options for the class
* @var string
*/
var $options = array();
 
// }}}
// {{{ Auth_Container_KADM5()
 
/**
* Constructor of the container class
*
* $options can have these keys:
* 'hostname' The hostname of the kerberos server
* 'realm' The Kerberos V realm
* 'timeout' The timeout for checking the server
* 'checkServer' Set to true to check if the server is running when
* constructing the object
*
* @param $options associative array
* @return object Returns an error object if something went wrong
*/
function Auth_Container_KADM5($options) {
if (!extension_loaded('kadm5')) {
return PEAR::raiseError("Cannot use Kerberos V authentication, KADM5 extension not loaded!", 41, PEAR_ERROR_DIE);
}
$this->_setDefaults();
if (isset($options['hostname'])) {
$this->options['hostname'] = $options['hostname'];
}
if (isset($options['realm'])) {
$this->options['realm'] = $options['realm'];
}
if (isset($options['timeout'])) {
$this->options['timeout'] = $options['timeout'];
}
if (isset($options['checkServer'])) {
$this->options['checkServer'] = $options['checkServer'];
}
if ($this->options['checkServer']) {
$this->_checkServer();
}
}
 
// }}}
// {{{ fetchData()
/**
* Try to login to the KADM5 server
*
* @param string Username
* @param string Password
* @return boolean
*/
function fetchData($username, $password) {
if ( ($username == NULL) || ($password == NULL) ) {
return false;
}
$server = $this->options['hostname'];
$realm = $this->options['realm'];
$check = @kadm5_init_with_password($server, $realm, $username, $password);
if ($check == false) {
return false;
} else {
return true;
}
}
// }}}
// {{{ _setDefaults()
/**
* Set some default options
*
* @access private
*/
function _setDefaults() {
$this->options['hostname'] = 'localhost';
$this->options['realm'] = NULL;
$this->options['timeout'] = 10;
$this->options['checkServer'] = false;
}
// }}}
// {{{ _checkServer()
/**
* Check if the given server and port are reachable
*
* @access private
*/
function _checkServer() {
$fp = @fsockopen ($this->options['hostname'], 88, $errno, $errstr, $this->options['timeout']);
if (is_resource($fp)) {
@fclose($fp);
} else {
$message = "Error connecting to Kerberos V server "
.$this->options['hostname'].":".$this->options['port'];
return PEAR::raiseError($message, 41, PEAR_ERROR_DIE);
}
}
// }}}
 
}
 
?>
/trunk/api/pear/Auth/Container/MDB.php
1,392 → 1,573
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | |
// +----------------------------------------------------------------------+
// | 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: Lorenzo Alberton <l.alberton@quipo.it> |
// +----------------------------------------------------------------------+
//
// $Id: MDB.php,v 1.1 2005-03-30 08:50:33 jpm Exp $
//
 
require_once 'Auth/Container.php';
require_once 'MDB.php';
 
/**
* Storage driver for fetching login data from a database
*
* This storage driver can use all databases which are supported
* by the PEAR MDB abstraction layer to fetch login data.
*
* @author Lorenzo Alberton <l.alberton@quipo.it>
* @package Auth
* @version $Revision: 1.1 $
*/
class Auth_Container_MDB extends Auth_Container
{
 
/**
* Additional options for the storage container
* @var array
*/
var $options = array();
 
/**
* DB object
* @var object
*/
var $db = null;
var $dsn = '';
 
/**
* User that is currently selected from the DB.
* @var string
*/
var $activeUser = '';
 
// {{{ Constructor
 
/**
* Constructor of the container class
*
* Initate connection to the database via PEAR::DB
*
* @param string Connection data or DB object
* @return object Returns an error object if something went wrong
*/
function Auth_Container_MDB($dsn)
{
$this->_setDefaults();
 
if (is_array($dsn)) {
$this->_parseOptions($dsn);
if (empty($this->options['dsn'])) {
PEAR::raiseError('No connection parameters specified!');
}
} else {
$this->options['dsn'] = $dsn;
}
}
 
// }}}
// {{{ _connect()
 
/**
* Connect to database by using the given DSN string
*
* @access private
* @param string DSN string
* @return mixed Object on error, otherwise bool
*/
function _connect($dsn)
{
if (is_string($dsn) || is_array($dsn)) {
$this->db =& MDB::Connect($dsn);
} elseif (get_parent_class($dsn) == "mdb_common") {
$this->db = $dsn;
} elseif (is_object($dsn) && MDB::isError($dsn)) {
return PEAR::raiseError($dsn->getMessage(), $dsn->code);
} else {
return PEAR::raiseError('The given dsn was not valid in file ' . __FILE__ . ' at line ' . __LINE__,
41,
PEAR_ERROR_RETURN,
null,
null
);
 
}
 
if (MDB::isError($this->db) || PEAR::isError($this->db)) {
return PEAR::raiseError($this->db->getMessage(), $this->db->code);
} else {
return true;
}
}
 
// }}}
// {{{ _prepare()
 
/**
* Prepare database connection
*
* This function checks if we have already opened a connection to
* the database. If that's not the case, a new connection is opened.
*
* @access private
* @return mixed True or a DB error object.
*/
function _prepare()
{
return $this->_connect($this->options['dsn']);
}
 
// }}}
// {{{ query()
 
/**
* Prepare query to the database
*
* This function checks if we have already opened a connection to
* the database. If that's not the case, a new connection is opened.
* After that the query is passed to the database.
*
* @access public
* @param string Query string
* @return mixed a MDB_result object or MDB_OK on success, a MDB
* or PEAR error on failure
*/
function query($query)
{
$err = $this->_prepare();
if ($err !== true) {
return $err;
}
return $this->db->query($query);
}
 
// }}}
// {{{ _setDefaults()
 
/**
* Set some default options
*
* @access private
* @return void
*/
function _setDefaults()
{
$this->options['table'] = 'auth';
$this->options['usernamecol'] = 'username';
$this->options['passwordcol'] = 'password';
$this->options['dsn'] = '';
$this->options['db_fields'] = '';
$this->options['cryptType'] = 'md5';
}
 
// }}}
// {{{ _parseOptions()
 
/**
* Parse options passed to the container class
*
* @access private
* @param array
*/
function _parseOptions($array)
{
foreach ($array as $key => $value) {
if (isset($this->options[$key])) {
$this->options[$key] = $value;
}
}
 
// Include additional fields if they exist
if (!empty($this->options['db_fields'])) {
if (is_array($this->options['db_fields'])) {
$this->options['db_fields'] = join($this->options['db_fields'], ', ');
}
$this->options['db_fields'] = ', ' . $this->options['db_fields'];
}
 
}
 
// }}}
// {{{ fetchData()
 
/**
* Get user information from database
*
* This function uses the given username to fetch
* the corresponding login data from the database
* table. If an account that matches the passed username
* and password is found, the function returns true.
* Otherwise it returns false.
*
* @param string Username
* @param string Password
* @return mixed Error object or boolean
*/
function fetchData($username, $password)
{
// Prepare for a database query
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
// Find if db_fileds contains a *, i so assume all col are selected
if (strstr($this->options['db_fields'], '*')) {
$sql_from = '*';
} else{
$sql_from = $this->options['usernamecol'] . ', '. $this->options['passwordcol'] . $this->options['db_fields'];
}
 
$query = sprintf("SELECT %s FROM %s WHERE %s = %s",
$sql_from,
$this->options['table'],
$this->options['usernamecol'],
$this->db->getTextValue($username)
);
 
$res = $this->db->getRow($query, null, null, null, MDB_FETCHMODE_ASSOC);
 
if (MDB::isError($res) || PEAR::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
}
if (!is_array($res)) {
$this->activeUser = '';
return false;
}
if ($this->verifyPassword(trim($password, "\r\n"),
trim($res[$this->options['passwordcol']], "\r\n"),
$this->options['cryptType'])) {
// Store additional field values in the session
foreach ($res as $key => $value) {
if ($key == $this->options['passwordcol'] ||
$key == $this->options['usernamecol']) {
continue;
}
// Use reference to the auth object if exists
// This is because the auth session variable can change so a static call to setAuthData does not make sence
if(is_object($this->_auth_obj)){
$this->_auth_obj->setAuthData($key, $value);
} else {
Auth::setAuthData($key, $value);
}
}
 
return true;
}
 
$this->activeUser = $res[$this->options['usernamecol']];
return false;
}
 
// }}}
// {{{ listUsers()
 
function listUsers()
{
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
$retVal = array();
 
// Find if db_fileds contains a *, i so assume all col are selected
if (strstr($this->options['db_fields'], '*')) {
$sql_from = '*';
} else{
$sql_from = $this->options['db_fields'];
}
 
$query = sprintf('SELECT %s FROM %s',
$sql_from,
$this->options['table']
);
 
$res = $this->db->getAll($query, null, null, null, MDB_FETCHMODE_ASSOC);
 
if (MDB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
} else {
foreach ($res as $user) {
$user['username'] = $user[$this->options['usernamecol']];
$retVal[] = $user;
}
}
return $retVal;
}
 
// }}}
// {{{ addUser()
 
/**
* Add user to the storage container
*
* @access public
* @param string Username
* @param string Password
* @param mixed Additional information that are stored in the DB
*
* @return mixed True on success, otherwise error object
*/
function addUser($username, $password, $additional = "")
{
if (function_exists($this->options['cryptType'])) {
$cryptFunction = $this->options['cryptType'];
} else {
$cryptFunction = 'md5';
}
 
$additional_key = '';
$additional_value = '';
 
if (is_array($additional)) {
foreach ($additional as $key => $value) {
$additional_key .= ', ' . $key;
$additional_value .= ', ' . $this->db->getTextValue($value);
}
}
 
$query = sprintf("INSERT INTO %s (%s, %s%s) VALUES (%s, %s%s)",
$this->options['table'],
$this->options['usernamecol'],
$this->options['passwordcol'],
$additional_key,
$this->db->getTextValue($username),
$this->db->getTextValue($cryptFunction($password)),
$additional_value
);
 
$res = $this->query($query);
 
if (MDB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->code);
} else {
return true;
}
}
 
// }}}
// {{{ removeUser()
 
/**
* Remove user from the storage container
*
* @access public
* @param string Username
*
* @return mixed True on success, otherwise error object
*/
function removeUser($username)
{
$query = sprintf("DELETE FROM %s WHERE %s = %s",
$this->options['table'],
$this->options['usernamecol'],
$this->db->getTextValue($username)
);
 
$res = $this->query($query);
 
if (MDB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->code);
} else {
return true;
}
}
 
// }}}
}
?>
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for use against PEAR MDB
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Lorenzo Alberton <l.alberton@quipo.it>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: MDB.php,v 1.2 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @since File available since Release 1.2.3
*/
 
/**
* Include Auth_Container base class
*/
require_once 'Auth/Container.php';
/**
* Include PEAR MDB package
*/
require_once 'MDB.php';
 
/**
* Storage driver for fetching login data from a database
*
* This storage driver can use all databases which are supported
* by the PEAR MDB abstraction layer to fetch login data.
*
* @category Authentication
* @package Auth
* @author Lorenzo Alberton <l.alberton@quipo.it>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.2 $
* @link http://pear.php.net/package/Auth
* @since Class available since Release 1.2.3
*/
class Auth_Container_MDB extends Auth_Container
{
 
// {{{ properties
 
/**
* Additional options for the storage container
* @var array
*/
var $options = array();
 
/**
* MDB object
* @var object
*/
var $db = null;
var $dsn = '';
 
/**
* User that is currently selected from the DB.
* @var string
*/
var $activeUser = '';
 
// }}}
// {{{ Auth_Container_MDB() [constructor]
 
/**
* Constructor of the container class
*
* Initate connection to the database via PEAR::MDB
*
* @param string Connection data or MDB object
* @return object Returns an error object if something went wrong
*/
function Auth_Container_MDB($dsn)
{
$this->_setDefaults();
 
if (is_array($dsn)) {
$this->_parseOptions($dsn);
if (empty($this->options['dsn'])) {
PEAR::raiseError('No connection parameters specified!');
}
} else {
$this->options['dsn'] = $dsn;
}
}
 
// }}}
// {{{ _connect()
 
/**
* Connect to database by using the given DSN string
*
* @access private
* @param mixed DSN string | array | mdb object
* @return mixed Object on error, otherwise bool
*/
function _connect($dsn)
{
if (is_string($dsn) || is_array($dsn)) {
$this->db =& MDB::connect($dsn, $this->options['db_options']);
} elseif (is_subclass_of($dsn, 'mdb_common')) {
$this->db = $dsn;
} elseif (is_object($dsn) && MDB::isError($dsn)) {
return PEAR::raiseError($dsn->getMessage(), $dsn->code);
} else {
return PEAR::raiseError('The given dsn was not valid in file ' . __FILE__ . ' at line ' . __LINE__,
41,
PEAR_ERROR_RETURN,
null,
null
);
 
}
 
if (MDB::isError($this->db) || PEAR::isError($this->db)) {
return PEAR::raiseError($this->db->getMessage(), $this->db->code);
}
 
if ($this->options['auto_quote']) {
$this->options['final_table'] = $this->db->quoteIdentifier($this->options['table']);
$this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol']);
$this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol']);
} else {
$this->options['final_table'] = $this->options['table'];
$this->options['final_usernamecol'] = $this->options['usernamecol'];
$this->options['final_passwordcol'] = $this->options['passwordcol'];
}
 
return true;
}
 
// }}}
// {{{ _prepare()
 
/**
* Prepare database connection
*
* This function checks if we have already opened a connection to
* the database. If that's not the case, a new connection is opened.
*
* @access private
* @return mixed True or a MDB error object.
*/
function _prepare()
{
if (is_subclass_of($this->db, 'mdb_common')) {
return true;
}
return $this->_connect($this->options['dsn']);
}
 
// }}}
// {{{ query()
 
/**
* Prepare query to the database
*
* This function checks if we have already opened a connection to
* the database. If that's not the case, a new connection is opened.
* After that the query is passed to the database.
*
* @access public
* @param string Query string
* @return mixed a MDB_result object or MDB_OK on success, a MDB
* or PEAR error on failure
*/
function query($query)
{
$err = $this->_prepare();
if ($err !== true) {
return $err;
}
return $this->db->query($query);
}
 
// }}}
// {{{ _setDefaults()
 
/**
* Set some default options
*
* @access private
* @return void
*/
function _setDefaults()
{
$this->options['table'] = 'auth';
$this->options['usernamecol'] = 'username';
$this->options['passwordcol'] = 'password';
$this->options['dsn'] = '';
$this->options['db_fields'] = '';
$this->options['cryptType'] = 'md5';
$this->options['db_options'] = array();
$this->options['auto_quote'] = true;
}
 
// }}}
// {{{ _parseOptions()
 
/**
* Parse options passed to the container class
*
* @access private
* @param array
*/
function _parseOptions($array)
{
foreach ($array as $key => $value) {
if (isset($this->options[$key])) {
$this->options[$key] = $value;
}
}
}
 
// }}}
// {{{ _quoteDBFields()
 
/**
* Quote the db_fields option to avoid the possibility of SQL injection.
*
* @access private
* @return string A properly quoted string that can be concatenated into a
* SELECT clause.
*/
function _quoteDBFields()
{
if (isset($this->options['db_fields'])) {
if (is_array($this->options['db_fields'])) {
if ($this->options['auto_quote']) {
$fields = array();
foreach ($this->options['db_fields'] as $field) {
$fields[] = $this->db->quoteIdentifier($field);
}
return implode(', ', $fields);
} else {
return implode(', ', $this->options['db_fields']);
}
} else {
if (strlen($this->options['db_fields']) > 0) {
if ($this->options['auto_quote']) {
return $this->db->quoteIdentifier($this->options['db_fields']);
} else {
return $this->options['db_fields'];
}
}
}
}
 
return '';
}
// }}}
// {{{ fetchData()
 
/**
* Get user information from database
*
* This function uses the given username to fetch
* the corresponding login data from the database
* table. If an account that matches the passed username
* and password is found, the function returns true.
* Otherwise it returns false.
*
* @param string Username
* @param string Password
* @param boolean If true password is secured using a md5 hash
* the frontend and auth are responsible for making sure the container supports
* challenge response password authentication
* @return mixed Error object or boolean
*/
function fetchData($username, $password, $isChallengeResponse=false)
{
// Prepare for a database query
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
//Check if db_fields contains a *, if so assume all columns are selected
if (is_string($this->options['db_fields'])
&& strstr($this->options['db_fields'], '*')) {
$sql_from = '*';
} else {
$sql_from = $this->options['final_usernamecol'].
", ".$this->options['final_passwordcol'];
 
if (strlen($fields = $this->_quoteDBFields()) > 0) {
$sql_from .= ', '.$fields;
}
}
 
$query = sprintf("SELECT %s FROM %s WHERE %s = %s",
$sql_from,
$this->options['final_table'],
$this->options['final_usernamecol'],
$this->db->getTextValue($username)
);
 
$res = $this->db->getRow($query, null, null, null, MDB_FETCHMODE_ASSOC);
 
if (MDB::isError($res) || PEAR::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
}
if (!is_array($res)) {
$this->activeUser = '';
return false;
}
 
// Perform trimming here before the hashing
$password = trim($password, "\r\n");
$res[$this->options['passwordcol']] = trim($res[$this->options['passwordcol']], "\r\n");
// If using Challenge Response md5 the pass with the secret
if ($isChallengeResponse) {
$res[$this->options['passwordcol']] =
md5($res[$this->options['passwordcol']].$this->_auth_obj->session['loginchallenege']);
// UGLY cannot avoid without modifying verifyPassword
if ($this->options['cryptType'] == 'md5') {
$res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]);
}
}
if ($this->verifyPassword($password,
$res[$this->options['passwordcol']],
$this->options['cryptType'])) {
// Store additional field values in the session
foreach ($res as $key => $value) {
if ($key == $this->options['passwordcol'] ||
$key == $this->options['usernamecol']) {
continue;
}
// Use reference to the auth object if exists
// This is because the auth session variable can change so a static
// call to setAuthData does not make sense
$this->_auth_obj->setAuthData($key, $value);
}
return true;
}
 
$this->activeUser = $res[$this->options['usernamecol']];
return false;
}
 
// }}}
// {{{ listUsers()
 
/**
* Returns a list of users from the container
*
* @return mixed array|PEAR_Error
* @access public
*/
function listUsers()
{
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
$retVal = array();
 
//Check if db_fields contains a *, if so assume all columns are selected
if ( is_string($this->options['db_fields'])
&& strstr($this->options['db_fields'], '*')) {
$sql_from = '*';
} else {
$sql_from = $this->options['final_usernamecol']
.', '.$this->options['final_passwordcol'];
if (strlen($fields = $this->_quoteDBFields()) > 0) {
$sql_from .= ', '.$fields;
}
}
 
$query = sprintf('SELECT %s FROM %s',
$sql_from,
$this->options['final_table']
);
 
$res = $this->db->getAll($query, null, null, null, MDB_FETCHMODE_ASSOC);
 
if (MDB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
} else {
foreach ($res as $user) {
$user['username'] = $user[$this->options['usernamecol']];
$retVal[] = $user;
}
}
return $retVal;
}
 
// }}}
// {{{ addUser()
 
/**
* Add user to the storage container
*
* @access public
* @param string Username
* @param string Password
* @param mixed Additional information that are stored in the DB
*
* @return mixed True on success, otherwise error object
*/
function addUser($username, $password, $additional = "")
{
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') {
$cryptFunction = 'strval';
} elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) {
$cryptFunction = $this->options['cryptType'];
} else {
$cryptFunction = 'md5';
}
 
$password = $cryptFunction($password);
 
$additional_key = '';
$additional_value = '';
 
if (is_array($additional)) {
foreach ($additional as $key => $value) {
if ($this->options['auto_quote']) {
$additional_key .= ', ' . $this->db->quoteIdentifier($key);
} else {
$additional_key .= ', ' . $key;
}
$additional_value .= ', ' . $this->db->getTextValue($value);
}
}
 
$query = sprintf("INSERT INTO %s (%s, %s%s) VALUES (%s, %s%s)",
$this->options['final_table'],
$this->options['final_usernamecol'],
$this->options['final_passwordcol'],
$additional_key,
$this->db->getTextValue($username),
$this->db->getTextValue($password),
$additional_value
);
 
$res = $this->query($query);
 
if (MDB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->code);
}
return true;
}
 
// }}}
// {{{ removeUser()
 
/**
* Remove user from the storage container
*
* @access public
* @param string Username
*
* @return mixed True on success, otherwise error object
*/
function removeUser($username)
{
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
$query = sprintf("DELETE FROM %s WHERE %s = %s",
$this->options['final_table'],
$this->options['final_usernamecol'],
$this->db->getTextValue($username)
);
 
$res = $this->query($query);
 
if (MDB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->code);
}
return true;
}
 
// }}}
// {{{ changePassword()
 
/**
* Change password for user in the storage container
*
* @param string Username
* @param string The new password (plain text)
*/
function changePassword($username, $password)
{
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') {
$cryptFunction = 'strval';
} elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) {
$cryptFunction = $this->options['cryptType'];
} else {
$cryptFunction = 'md5';
}
 
$password = $cryptFunction($password);
 
$query = sprintf("UPDATE %s SET %s = %s WHERE %s = %s",
$this->options['final_table'],
$this->options['final_passwordcol'],
$this->db->getTextValue($password),
$this->options['final_usernamecol'],
$this->db->getTextValue($username)
);
 
$res = $this->query($query);
 
if (MDB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->code);
}
return true;
}
 
// }}}
// {{{ supportsChallengeResponse()
 
/**
* Determine if this container supports
* password authentication with challenge response
*
* @return bool
* @access public
*/
function supportsChallengeResponse()
{
return in_array($this->options['cryptType'], array('md5', 'none', ''));
}
 
// }}}
// {{{ getCryptType()
 
/**
* Returns the selected crypt type for this container
*
* @return string Function used to crypt the password
*/
function getCryptType()
{
return $this->options['cryptType'];
}
 
// }}}
 
}
?>
/trunk/api/pear/Auth/Container/SOAP.php
1,170 → 1,228
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 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: Bruno Pedro <bpedro@co.sapo.pt> |
// +----------------------------------------------------------------------+
//
// $Id: SOAP.php,v 1.1 2005-03-30 08:50:33 jpm Exp $
//
 
require_once "Auth/Container.php";
require_once "PEAR.php";
require_once 'SOAP/Client.php';
 
/**
* Storage driver for fetching login data from SOAP
*
* This class takes one parameter (options), where
* you specify the following fields: endpoint, namespace,
* method, encoding, usernamefield and passwordfield.
*
* You can use specify features of your SOAP service
* by providing its parameters in an associative manner by
* using the '_features' array through the options parameter.
*
* The 'matchpassword' option should be set to false if your
* webservice doesn't return (username,password) pairs, but
* instead returns error when the login is invalid.
*
* Example usage:
*
* <?php
*
* ...
*
* $options = array (
* 'endpoint' => 'http://your.soap.service/endpoint',
* 'namespace' => 'urn:/Your/Namespace',
* 'method' => 'get',
* 'encoding' => 'UTF-8',
* 'usernamefield' => 'login',
* 'passwordfield' => 'password',
* 'matchpasswords' => false,
* '_features' => array (
* 'example_feature' => 'example_value',
* 'another_example' => ''
* )
* );
* $auth = new Auth('SOAP', $options, 'loginFunction');
* $auth->start();
*
* ...
*
* ?>
*
* @author Bruno Pedro <bpedro@co.sapo.pt>
* @package Auth
* @version $Revision: 1.1 $
*/
class Auth_Container_SOAP extends Auth_Container
{
 
/**
* Required options for the class
* @var array
* @access private
*/
var $_requiredOptions = array('endpoint', 'namespace', 'method', 'encoding', 'usernamefield', 'passwordfield');
 
/**
* Options for the class
* @var array
* @access private
*/
var $_options = array();
 
/**
* Optional SOAP features
* @var array
* @access private
*/
var $_features = array();
 
/**
* The SOAP response
* @var array
* @access public
*/
var $soapResponse = array();
 
/**
* Constructor of the container class
*
* @param $options, associative array with endpoint, namespace, method,
* usernamefield, passwordfield and optional features
*/
function Auth_Container_SOAP($options)
{
$this->_options = $options;
if (!isset($this->_options['matchpasswords'])) {
$this->_options['matchpasswords'] = true;
}
if (!empty($this->_options['_features'])) {
$this->_features = $this->_options['_features'];
unset($this->_options['_features']);
}
}
 
/**
* Fetch data from SOAP service
*
* Requests the SOAP service for the given username/password
* combination.
*
* @param string Username
* @param string Password
* @return mixed Returns the SOAP response or false if something went wrong
*/
function fetchData($username, $password)
{
// check if all required options are set
if (array_intersect($this->_requiredOptions, array_keys($this->_options)) != $this->_requiredOptions) {
return false;
} else {
// create a SOAP client and set encoding
$soapClient = new SOAP_Client($this->_options['endpoint']);
$soapClient->setEncoding($this->_options['encoding']);
}
// assign username and password fields
$usernameField = new SOAP_Value($this->_options['usernamefield'],'string', $username);
$passwordField = new SOAP_Value($this->_options['passwordfield'],'string', $password);
$SOAPParams = array($usernameField, $passwordField);
// assign optional features
foreach ($this->_features as $fieldName => $fieldValue) {
$SOAPParams[] = new SOAP_Value($fieldName, 'string', $fieldValue);
}
// make SOAP call
$this->soapResponse = $soapClient->call(
$this->_options['method'],
$SOAPParams,
array('namespace' => $this->_options['namespace'])
);
if (!PEAR::isError($this->soapResponse)) {
if ($this->_options['matchpasswords']) {
// check if passwords match
if ($password == $this->soapResponse->{$this->_options['passwordfield']}) {
return true;
} else {
return false;
}
} else {
return true;
}
} else {
return false;
}
}
}
?>
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for use against a SOAP service
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Bruno Pedro <bpedro@co.sapo.pt>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: SOAP.php,v 1.2 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @since File available since Release 1.2.0
*/
 
/**
* Include Auth_Container base class
*/
require_once "Auth/Container.php";
/**
* Include PEAR package for error handling
*/
require_once "PEAR.php";
/**
* Include PEAR SOAP_Client
*/
require_once 'SOAP/Client.php';
 
/**
* Storage driver for fetching login data from SOAP
*
* This class takes one parameter (options), where
* you specify the following fields: endpoint, namespace,
* method, encoding, usernamefield and passwordfield.
*
* You can use specify features of your SOAP service
* by providing its parameters in an associative manner by
* using the '_features' array through the options parameter.
*
* The 'matchpassword' option should be set to false if your
* webservice doesn't return (username,password) pairs, but
* instead returns error when the login is invalid.
*
* Example usage:
*
* <?php
*
* ...
*
* $options = array (
* 'endpoint' => 'http://your.soap.service/endpoint',
* 'namespace' => 'urn:/Your/Namespace',
* 'method' => 'get',
* 'encoding' => 'UTF-8',
* 'usernamefield' => 'login',
* 'passwordfield' => 'password',
* 'matchpasswords' => false,
* '_features' => array (
* 'example_feature' => 'example_value',
* 'another_example' => ''
* )
* );
* $auth = new Auth('SOAP', $options, 'loginFunction');
* $auth->start();
*
* ...
*
* ?>
*
* @category Authentication
* @package Auth
* @author Bruno Pedro <bpedro@co.sapo.pt>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.2 $
* @link http://pear.php.net/package/Auth
* @since Class available since Release 1.2.0
*/
class Auth_Container_SOAP extends Auth_Container
{
 
// {{{ properties
 
/**
* Required options for the class
* @var array
* @access private
*/
var $_requiredOptions = array(
'endpoint',
'namespace',
'method',
'encoding',
'usernamefield',
'passwordfield',
);
 
/**
* Options for the class
* @var array
* @access private
*/
var $_options = array();
 
/**
* Optional SOAP features
* @var array
* @access private
*/
var $_features = array();
 
/**
* The SOAP response
* @var array
* @access public
*/
var $soapResponse = array();
 
/**
* The SOAP client
* @var mixed
* @access public
*/
var $soapClient = null;
 
// }}}
// {{{ Auth_Container_SOAP() [constructor]
 
/**
* Constructor of the container class
*
* @param $options, associative array with endpoint, namespace, method,
* usernamefield, passwordfield and optional features
*/
function Auth_Container_SOAP($options)
{
$this->_options = $options;
if (!isset($this->_options['matchpasswords'])) {
$this->_options['matchpasswords'] = true;
}
if (!empty($this->_options['_features'])) {
$this->_features = $this->_options['_features'];
unset($this->_options['_features']);
}
}
 
// }}}
// {{{ fetchData()
 
/**
* Fetch data from SOAP service
*
* Requests the SOAP service for the given username/password
* combination.
*
* @param string Username
* @param string Password
* @return mixed Returns the SOAP response or false if something went wrong
*/
function fetchData($username, $password)
{
// check if all required options are set
if (array_intersect($this->_requiredOptions, array_keys($this->_options)) != $this->_requiredOptions) {
return false;
} else {
// create a SOAP client and set encoding
$this->soapClient = new SOAP_Client($this->_options['endpoint']);
$this->soapClient->setEncoding($this->_options['encoding']);
}
 
// set the trace option if requested
if (isset($this->_options['trace'])) {
$this->soapClient->__options['trace'] = true;
}
 
// set the timeout option if requested
if (isset($this->_options['timeout'])) {
$this->soapClient->__options['timeout'] = $this->_options['timeout'];
}
 
// assign username and password fields
$usernameField = new SOAP_Value($this->_options['usernamefield'],'string', $username);
$passwordField = new SOAP_Value($this->_options['passwordfield'],'string', $password);
$SOAPParams = array($usernameField, $passwordField);
 
// assign optional features
foreach ($this->_features as $fieldName => $fieldValue) {
$SOAPParams[] = new SOAP_Value($fieldName, 'string', $fieldValue);
}
 
// make SOAP call
$this->soapResponse = $this->soapClient->call(
$this->_options['method'],
$SOAPParams,
array('namespace' => $this->_options['namespace'])
);
 
if (!PEAR::isError($this->soapResponse)) {
if ($this->_options['matchpasswords']) {
// check if passwords match
if ($password == $this->soapResponse->{$this->_options['passwordfield']}) {
return true;
} else {
return false;
}
} else {
return true;
}
} else {
return false;
}
}
 
// }}}
 
}
?>
/trunk/api/pear/Auth/Container/SMBPasswd.php
1,134 → 1,177
<?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 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: Michael Bretterklieber <michael@bretterklieber.com> |
// +----------------------------------------------------------------------+
//
// $Id: SMBPasswd.php,v 1.1 2005-03-30 08:50:33 jpm Exp $
//
 
require_once "File/SMBPasswd.php";
require_once "Auth/Container.php";
require_once "PEAR.php";
 
/**
* Storage driver for fetching login data from an SAMBA smbpasswd file.
*
* This storage container can handle SAMBA smbpasswd files.
*
* Example:
* $a = new Auth("SMBPasswd", '/usr/local/private/smbpasswd');
* $a->start();
* if ($a->getAuth()) {
* printf ("AUTH OK<br>\n");
* $a->logout();
* }
*
* @author Michael Bretterklieber <michael@bretterklieber.com>
* @package Auth
* @version $Revision: 1.1 $
*/
class Auth_Container_SMBPasswd extends Auth_Container
{
/**
* File_SMBPasswd object
* @var object
*/
var $pwfile;
 
// {{{ Constructor
 
/**
* Constructor of the container class
*
* @param $filename string filename for a passwd type file
* @return object Returns an error object if something went wrong
*/
function Auth_Container_SMBPasswd($filename)
{
$this->pwfile = new File_SMBPasswd($filename,0);
 
if (!$this->pwfile->load()) {
PEAR::raiseError("Error while reading file contents.", 41, PEAR_ERROR_DIE);
return;
}
 
}
 
// }}}
// {{{ fetchData()
 
/**
* Get user information from pwfile
*
* @param string Username
* @param string Password
* @return boolean
*/
function fetchData($username, $password)
{
return $this->pwfile->verifyAccount($username, $password);
}
 
// }}}
// {{{ listUsers()
function listUsers()
{
return $this->pwfile->getAccounts();
}
 
// }}}
// {{{ addUser()
 
/**
* Add a new user to the storage container
*
* @param string Username
* @param string Password
* @param array Additional information
*
* @return boolean
*/
function addUser($username, $password, $additional = '')
{
$res = $this->pwfile->addUser($user, $additional['userid'], $pass);
if ($res === true) {
return $this->pwfile->save();
}
return $res;
}
 
// }}}
// {{{ removeUser()
 
/**
* Remove user from the storage container
*
* @param string Username
*/
function removeUser($username)
{
$res = $this->pwfile->delUser($username);
if ($res === true) {
return $this->pwfile->save();
}
return $res;
}
 
// }}}
 
}
?>
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for use against Samba password files
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Michael Bretterklieber <michael@bretterklieber.com>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: SMBPasswd.php,v 1.2 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @since File available since Release 1.2.3
*/
 
/**
* Include PEAR File_SMBPasswd
*/
require_once "File/SMBPasswd.php";
/**
* Include Auth_Container Base file
*/
require_once "Auth/Container.php";
/**
* Include PEAR class for error handling
*/
require_once "PEAR.php";
 
/**
* Storage driver for fetching login data from an SAMBA smbpasswd file.
*
* This storage container can handle SAMBA smbpasswd files.
*
* Example:
* $a = new Auth("SMBPasswd", '/usr/local/private/smbpasswd');
* $a->start();
* if ($a->getAuth()) {
* printf ("AUTH OK<br>\n");
* $a->logout();
* }
*
* @category Authentication
* @package Auth
* @author Michael Bretterklieber <michael@bretterklieber.com>
* @author Adam Ashley <aashley@php.net>
* @package Auth
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.2 $
* @link http://pear.php.net/package/Auth
* @since Class available since Release 1.2.3
*/
class Auth_Container_SMBPasswd extends Auth_Container
{
 
// {{{ properties
 
/**
* File_SMBPasswd object
* @var object
*/
var $pwfile;
 
// }}}
 
// {{{ Auth_Container_SMBPasswd() [constructor]
 
/**
* Constructor of the container class
*
* @param $filename string filename for a passwd type file
* @return object Returns an error object if something went wrong
*/
function Auth_Container_SMBPasswd($filename)
{
$this->pwfile = new File_SMBPasswd($filename,0);
 
if (!$this->pwfile->load()) {
PEAR::raiseError("Error while reading file contents.", 41, PEAR_ERROR_DIE);
return;
}
 
}
 
// }}}
// {{{ fetchData()
 
/**
* Get user information from pwfile
*
* @param string Username
* @param string Password
* @return boolean
*/
function fetchData($username, $password)
{
return $this->pwfile->verifyAccount($username, $password);
}
 
// }}}
// {{{ listUsers()
function listUsers()
{
return $this->pwfile->getAccounts();
}
 
// }}}
// {{{ addUser()
 
/**
* Add a new user to the storage container
*
* @param string Username
* @param string Password
* @param array Additional information
*
* @return boolean
*/
function addUser($username, $password, $additional = '')
{
$res = $this->pwfile->addUser($user, $additional['userid'], $pass);
if ($res === true) {
return $this->pwfile->save();
}
return $res;
}
 
// }}}
// {{{ removeUser()
 
/**
* Remove user from the storage container
*
* @param string Username
*/
function removeUser($username)
{
$res = $this->pwfile->delUser($username);
if ($res === true) {
return $this->pwfile->save();
}
return $res;
}
 
// }}}
// {{{ changePassword()
 
/**
* Change password for user in the storage container
*
* @param string Username
* @param string The new password
*/
function changePassword($username, $password)
{
$res = $this->pwfile->modUser($username, '', $password);
if ($res === true) {
return $this->pwfile->save();
}
return $res;
}
 
// }}}
 
}
?>
/trunk/api/pear/Auth/Container/DBLite.php
New file
0,0 → 1,298
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Reduced storage driver for use against PEAR DB
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Martin Jansen <mj@php.net>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: DBLite.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @since File available since Release 1.3.0
*/
 
/**
* Include Auth_Container base class
*/
require_once 'Auth/Container.php';
/**
* Include PEAR DB package
*/
require_once 'DB.php';
 
/**
* A lighter storage driver for fetching login data from a database
*
* This driver is derived from the DB storage container but
* with the user manipulation function removed for smaller file size
* by the PEAR DB abstraction layer to fetch login data.
*
* @category Authentication
* @package Auth
* @author Martin Jansen <mj@php.net>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.1 $
* @link http://pear.php.net/package/Auth
* @since Class available since Release 1.3.0
*/
class Auth_Container_DBLite extends Auth_Container
{
 
// {{{ properties
 
/**
* Additional options for the storage container
* @var array
*/
var $options = array();
 
/**
* DB object
* @var object
*/
var $db = null;
var $dsn = '';
 
/**
* User that is currently selected from the DB.
* @var string
*/
var $activeUser = '';
 
// }}}
// {{{ Auth_Container_DBLite() [constructor]
 
/**
* Constructor of the container class
*
* Initate connection to the database via PEAR::DB
*
* @param string Connection data or DB object
* @return object Returns an error object if something went wrong
*/
function Auth_Container_DBLite($dsn)
{
$this->options['table'] = 'auth';
$this->options['usernamecol'] = 'username';
$this->options['passwordcol'] = 'password';
$this->options['dsn'] = '';
$this->options['db_fields'] = '';
$this->options['cryptType'] = 'md5';
$this->options['db_options'] = array();
$this->options['auto_quote'] = true;
 
if (is_array($dsn)) {
$this->_parseOptions($dsn);
if (empty($this->options['dsn'])) {
PEAR::raiseError('No connection parameters specified!');
}
} else {
$this->options['dsn'] = $dsn;
}
}
 
// }}}
// {{{ _connect()
 
/**
* Connect to database by using the given DSN string
*
* @access private
* @param string DSN string
* @return mixed Object on error, otherwise bool
*/
function _connect(&$dsn)
{
if (is_string($dsn) || is_array($dsn)) {
$this->db =& DB::connect($dsn, $this->options['db_options']);
} elseif (is_subclass_of($dsn, "db_common")) {
$this->db =& $dsn;
} else {
return PEAR::raiseError("Invalid dsn or db object given");
}
 
if (DB::isError($this->db) || PEAR::isError($this->db)) {
return PEAR::raiseError($this->db->getMessage(), $this->db->getCode());
} else {
return true;
}
}
 
// }}}
// {{{ _prepare()
 
/**
* Prepare database connection
*
* This function checks if we have already opened a connection to
* the database. If that's not the case, a new connection is opened.
*
* @access private
* @return mixed True or a DB error object.
*/
function _prepare()
{
if (!DB::isConnection($this->db)) {
$res = $this->_connect($this->options['dsn']);
if (DB::isError($res) || PEAR::isError($res)) {
return $res;
}
}
if ($this->options['auto_quote'] && $this->db->dsn['phptype'] != 'sqlite') {
$this->options['final_table'] = $this->db->quoteIdentifier($this->options['table']);
$this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol']);
$this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol']);
} else {
$this->options['final_table'] = $this->options['table'];
$this->options['final_usernamecol'] = $this->options['usernamecol'];
$this->options['final_passwordcol'] = $this->options['passwordcol'];
}
return true;
}
 
// }}}
// {{{ _parseOptions()
 
/**
* Parse options passed to the container class
*
* @access private
* @param array
*/
function _parseOptions($array)
{
foreach ($array as $key => $value) {
if (isset($this->options[$key])) {
$this->options[$key] = $value;
}
}
}
 
// }}}
// {{{ _quoteDBFields()
 
/**
* Quote the db_fields option to avoid the possibility of SQL injection.
*
* @access private
* @return string A properly quoted string that can be concatenated into a
* SELECT clause.
*/
function _quoteDBFields()
{
if (isset($this->options['db_fields'])) {
if (is_array($this->options['db_fields'])) {
if ($this->options['auto_quote']) {
$fields = array();
foreach ($this->options['db_fields'] as $field) {
$fields[] = $this->db->quoteIdentifier($field);
}
return implode(', ', $fields);
} else {
return implode(', ', $this->options['db_fields']);
}
} else {
if (strlen($this->options['db_fields']) > 0) {
if ($this->options['auto_quote']) {
return $this->db->quoteIdentifier($this->options['db_fields']);
} else {
$this->options['db_fields'];
}
}
}
}
 
return '';
}
// }}}
// {{{ fetchData()
 
/**
* Get user information from database
*
* This function uses the given username to fetch
* the corresponding login data from the database
* table. If an account that matches the passed username
* and password is found, the function returns true.
* Otherwise it returns false.
*
* @param string Username
* @param string Password
* @return mixed Error object or boolean
*/
function fetchData($username, $password)
{
// Prepare for a database query
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
// Find if db_fields contains a *, if so assume all col are selected
if (is_string($this->options['db_fields'])
&& strstr($this->options['db_fields'], '*')) {
$sql_from = "*";
} else {
$sql_from = $this->options['final_usernamecol'].
", ".$this->options['final_passwordcol'];
 
if (strlen($fields = $this->_quoteDBFields()) > 0) {
$sql_from .= ', '.$fields;
}
}
$query = "SELECT ".$sql_from.
" FROM ".$this->options['final_table'].
" WHERE ".$this->options['final_usernamecol']." = ".$this->db->quoteSmart($username);
$res = $this->db->getRow($query, null, DB_FETCHMODE_ASSOC);
 
if (DB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
}
if (!is_array($res)) {
$this->activeUser = '';
return false;
}
if ($this->verifyPassword(trim($password, "\r\n"),
trim($res[$this->options['passwordcol']], "\r\n"),
$this->options['cryptType'])) {
// Store additional field values in the session
foreach ($res as $key => $value) {
if ($key == $this->options['passwordcol'] ||
$key == $this->options['usernamecol']) {
continue;
}
// Use reference to the auth object if exists
// This is because the auth session variable can change so a static call to setAuthData does not make sence
if (is_object($this->_auth_obj)) {
$this->_auth_obj->setAuthData($key, $value);
} else {
Auth::setAuthData($key, $value);
}
}
$this->activeUser = $res[$this->options['usernamecol']];
return true;
}
$this->activeUser = $res[$this->options['usernamecol']];
return false;
}
 
// }}}
 
}
?>
/trunk/api/pear/Auth/Container/Array.php
New file
0,0 → 1,159
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for use against a PHP Array
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author georg_1 at have2 dot com
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: Array.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
* @since File available since Release 1.4.0
*/
 
/**
* Include Auth_Container base class
*/
require_once "Auth/Container.php";
/**
* Include PEAR package for error handling
*/
require_once "PEAR.php";
 
/**
* Storage driver for fetching authentication data from a PHP Array
*
* This container takes two options when configuring:
*
* cryptType: The crypt used to store the password. Currently recognised
* are: none, md5 and crypt. default: none
* users: A named array of usernames and passwords.
* Ex:
* array(
* 'guest' => '084e0343a0486ff05530df6c705c8bb4', // password guest
* 'georg' => 'fc77dba827fcc88e0243404572c51325' // password georg
* )
*
* Usage Example:
* <?php
* $AuthOptions = array(
* 'users' => array(
* 'guest' => '084e0343a0486ff05530df6c705c8bb4', // password guest
* 'georg' => 'fc77dba827fcc88e0243404572c51325' // password georg
* ),
* 'cryptType'=>'md5',
* );
*
* $auth = new Auth("Array", $AuthOptions);
* ?>
*
* @category Authentication
* @package Auth
* @author georg_1 at have2 dot com
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.1 $
* @since File available since Release 1.4.0
*/
 
class Auth_Container_Array extends Auth_Container {
 
// {{{ properties
 
/**
* The users and their password to authenticate against
*
* @var array $users
*/
var $users;
 
/**
* The cryptType used on the passwords
*
* @var string $cryptType
*/
var $cryptType = 'none';
 
// }}}
// {{{ Auth_Container_Array()
 
/**
* Constructor for Array Container
*
* @param array $data Options for the container
* @return void
*/
function Auth_Container_Array($data)
{
if (!is_array($data)) {
PEAR::raiseError('The options for Auth_Container_Array must be an array');
}
if (isset($data['users']) && is_array($data['users'])) {
$this->users = $data['users'];
} else {
$this->users = array();
PEAR::raiseError('Auth_Container_Array: no user data found inoptions array');
}
if (isset($data['cryptType'])) {
$this->cryptType = $data['cryptType'];
}
}
 
// }}}
// {{{ fetchData()
 
/**
* Get user information from array
*
* This function uses the given username to fetch the corresponding
* login data from the array. If an account that matches the passed
* username and password is found, the function returns true.
* Otherwise it returns false.
*
* @param string Username
* @param string Password
* @return boolean|PEAR_Error Error object or boolean
*/
function fetchData($user, $pass)
{
if ( isset($this->users[$user])
&& $this->verifyPassword($pass, $this->users[$user], $this->cryptType)) {
return true;
}
return false;
}
 
// }}}
// {{{ listUsers()
 
/**
* Returns a list of users available within the container
*
* @return array
*/
function listUsers()
{
$ret = array();
foreach ($this->users as $username => $password) {
$ret[]['username'] = $username;
}
return $ret;
}
 
// }}}
 
}
 
?>
/trunk/api/pear/Auth/Container/File.php
1,200 → 1,305
<?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 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: Stefan Ekman <stekman@sedata.org> |
// | Martin Jansen <mj@php.net> |
// | Mika Tuupola <tuupola@appelsiini.net> |
// +----------------------------------------------------------------------+
//
// $Id: File.php,v 1.1 2005-03-30 08:50:33 jpm Exp $
//
 
require_once "File/Passwd.php";
require_once "Auth/Container.php";
require_once "PEAR.php";
 
/**
* Storage driver for fetching login data from an encrypted password file.
*
* This storage container can handle CVS pserver style passwd files.
*
* @author Stefan Ekman <stekman@sedata.org>
* @author Michael Wallner <mike@php.net>
* @package Auth
* @version $Revision: 1.1 $
*/
class Auth_Container_File extends Auth_Container
{
/**
* Path to passwd file
*
* @var string
*/
var $pwfile = '';
 
// {{{ Constructor
 
/**
* Constructor of the container class
*
* @param string $filename path to passwd file
* @return object Auth_Container_File new Auth_Container_File object
*/
function Auth_Container_File($filename)
{
$this->pwfile = $filename;
}
 
// }}}
// {{{ fetchData()
 
/**
* Authenticate an user
*
* @param string username
* @param string password
* @return mixed boolean|PEAR_Error
*/
function fetchData($user, $pass)
{
return File_Passwd::staticAuth('Cvs', $this->pwfile, $user, $pass);
}
 
// }}}
// {{{ listUsers()
/**
* List all available users
*
* @return array
*/
function listUsers()
{
$pw_obj = &$this->_load();
if (PEAR::isError($pw_obj)) {
return array();
}
 
$users = $pw_obj->listUser();
if (!is_array($users)) {
return array();
}
 
foreach ($users as $key => $value) {
$retVal[] = array("username" => $key,
"password" => $value['passwd'],
"cvsuser" => $value['system']);
}
 
return $retVal;
}
 
// }}}
// {{{ addUser()
 
/**
* Add a new user to the storage container
*
* @param string username
* @param string password
* @param mixed CVS username
*
* @return boolean
*/
function addUser($user, $pass, $additional='')
{
$cvs = (string) (is_array($additional) && isset($additional['cvsuser'])) ?
$additional['cvsuser'] : $additional;
 
$pw_obj = &$this->_load();
if (PEAR::isError($pw_obj)) {
return false;
}
$res = $pw_obj->addUser($user, $pass, $cvs);
if(PEAR::isError($res)){
return false;
}
$res = $pw_obj->save();
if (PEAR::isError($res)) {
return false;
}
return true;
}
 
// }}}
// {{{ removeUser()
 
/**
* Remove user from the storage container
*
* @param string Username
* @return boolean
*/
function removeUser($user)
{
$pw_obj = &$this->_load();
if (PEAR::isError($pw_obj)) {
return false;
}
$res = $pw_obj->delUser($user);
if(PEAR::isError($res)){
return false;
}
$res = $pw_obj->save();
if (PEAR::isError($res)) {
return false;
}
return true;
}
 
// }}}
// {{{ _load()
/**
* Load and initialize the File_Passwd object
*
* @return object File_Passwd_Cvs|PEAR_Error
*/
function &_load()
{
static $pw_obj;
if (!isset($pw_obj)) {
$pw_obj = File_Passwd::factory('Cvs');
if (PEAR::isError($pw_obj)) {
return $pw_obj;
}
$pw_obj->setFile($this->pwfile);
$res = $pw_obj->load();
if (PEAR::isError($res)) {
return $res;
}
}
return $pw_obj;
}
 
// }}}
}
?>
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for use against a generic password file
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Stefan Ekman <stekman@sedata.org>
* @author Martin Jansen <mj@php.net>
* @author Mika Tuupola <tuupola@appelsiini.net>
* @author Michael Wallner <mike@php.net>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: File.php,v 1.2 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
*/
 
/**
* Include PEAR File_Passwd package
*/
require_once "File/Passwd.php";
/**
* Include Auth_Container base class
*/
require_once "Auth/Container.php";
/**
* Include PEAR package for error handling
*/
require_once "PEAR.php";
 
/**
* Storage driver for fetching login data from an encrypted password file.
*
* This storage container can handle CVS pserver style passwd files.
*
* @category Authentication
* @package Auth
* @author Stefan Ekman <stekman@sedata.org>
* @author Martin Jansen <mj@php.net>
* @author Mika Tuupola <tuupola@appelsiini.net>
* @author Michael Wallner <mike@php.net>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.2 $
* @link http://pear.php.net/package/Auth
*/
class Auth_Container_File extends Auth_Container
{
 
// {{{ properties
 
/**
* Path to passwd file
*
* @var string
*/
var $pwfile = '';
 
/**
* Options for container
*
* @var array
*/
var $options = array();
 
// }}}
// {{{ Auth_Container_File() [constructor]
 
/**
* Constructor of the container class
*
* @param string $filename path to passwd file
* @return object Auth_Container_File new Auth_Container_File object
*/
function Auth_Container_File($filename) {
$this->_setDefaults();
// Only file is a valid option here
if(is_array($filename)) {
$this->pwfile = $filename['file'];
$this->_parseOptions($filename);
} else {
$this->pwfile = $filename;
}
}
 
// }}}
// {{{ fetchData()
 
/**
* Authenticate an user
*
* @param string username
* @param string password
* @return mixed boolean|PEAR_Error
*/
function fetchData($user, $pass)
{
return File_Passwd::staticAuth($this->options['type'], $this->pwfile, $user, $pass);
}
 
// }}}
// {{{ listUsers()
/**
* List all available users
*
* @return array
*/
function listUsers()
{
$pw_obj = &$this->_load();
if (PEAR::isError($pw_obj)) {
return array();
}
 
$users = $pw_obj->listUser();
if (!is_array($users)) {
return array();
}
 
foreach ($users as $key => $value) {
$retVal[] = array("username" => $key,
"password" => $value['passwd'],
"cvsuser" => $value['system']);
}
 
return $retVal;
}
 
// }}}
// {{{ addUser()
 
/**
* Add a new user to the storage container
*
* @param string username
* @param string password
* @param mixed Additional parameters to File_Password_*::addUser()
*
* @return boolean
*/
function addUser($user, $pass, $additional='')
{
$params = array($user, $pass);
if (is_array($additional)) {
foreach ($additional as $item) {
$params[] = $item;
}
} else {
$params[] = $additional;
}
 
$pw_obj = &$this->_load();
if (PEAR::isError($pw_obj)) {
return false;
}
$res = call_user_func_array(array(&$pw_obj, 'addUser'), $params);
if (PEAR::isError($res)) {
return false;
}
$res = $pw_obj->save();
if (PEAR::isError($res)) {
return false;
}
return true;
}
 
// }}}
// {{{ removeUser()
 
/**
* Remove user from the storage container
*
* @param string Username
* @return boolean
*/
function removeUser($user)
{
$pw_obj = &$this->_load();
if (PEAR::isError($pw_obj)) {
return false;
}
$res = $pw_obj->delUser($user);
if (PEAR::isError($res)) {
return false;
}
$res = $pw_obj->save();
if (PEAR::isError($res)) {
return false;
}
return true;
}
 
// }}}
// {{{ changePassword()
 
/**
* Change password for user in the storage container
*
* @param string Username
* @param string The new password
*/
function changePassword($username, $password)
{
$pw_obj = &$this->_load();
if (PEAR::isError($pw_obj)) {
return false;
}
$res = $pw_obj->changePasswd($username, $password);
if (PEAR::isError($res)) {
return false;
}
$res = $pw_obj->save();
if (PEAR::isError($res)) {
return false;
}
return true;
}
 
// }}}
// {{{ _load()
/**
* Load and initialize the File_Passwd object
*
* @return object File_Passwd_Cvs|PEAR_Error
*/
function &_load()
{
static $pw_obj;
if (!isset($pw_obj)) {
$pw_obj = File_Passwd::factory($this->options['type']);
if (PEAR::isError($pw_obj)) {
return $pw_obj;
}
$pw_obj->setFile($this->pwfile);
$res = $pw_obj->load();
if (PEAR::isError($res)) {
return $res;
}
}
return $pw_obj;
}
 
// }}}
// {{{ _setDefaults()
 
/**
* Set some default options
*
* @access private
* @return void
*/
function _setDefaults()
{
$this->options['type'] = 'Cvs';
}
 
// }}}
// {{{ _parseOptions()
 
/**
* Parse options passed to the container class
*
* @access private
* @param array
*/
function _parseOptions($array)
{
foreach ($array as $key => $value) {
if (isset($this->options[$key])) {
$this->options[$key] = $value;
}
}
}
 
// }}}
 
}
?>
/trunk/api/pear/Auth/Container/LDAP.php
1,472 → 1,773
<?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: Jan Wagner <wagner@netsols.de> |
// +----------------------------------------------------------------------+
//
// $Id: LDAP.php,v 1.1 2005-03-30 08:50:33 jpm Exp $
//
 
require_once "Auth/Container.php";
require_once "PEAR.php";
 
/**
* Storage driver for fetching login data from LDAP
*
* This class is heavily based on the DB and File containers. By default it
* connects to localhost:389 and searches for uid=$username with the scope
* "sub". If no search base is specified, it will try to determine it via
* the namingContexts attribute. It takes its parameters in a hash, connects
* to the ldap server, binds anonymously, searches for the user, and tries
* to bind as the user with the supplied password. When a group was set, it
* will look for group membership of the authenticated user. If all goes
* well the authentication was successful.
*
* Parameters:
*
* host: localhost (default), ldap.netsols.de or 127.0.0.1
* port: 389 (default) or 636 or whereever your server runs
* url: ldap://localhost:389/
* useful for ldaps://, works only with openldap2 ?
* it will be preferred over host and port
* binddn: If set, searching for user will be done after binding
* as this user, if not set the bind will be anonymous.
* This is reported to make the container work with MS
* Active Directory, but should work with any server that
* is configured this way.
* This has to be a complete dn for now (basedn and
* userdn will not be appended).
* bindpw: The password to use for binding with binddn
* scope: one, sub (default), or base
* basedn: the base dn of your server
* userdn: gets prepended to basedn when searching for user
* userattr: the user attribute to search for (default: uid)
* useroc: objectclass of user (for the search filter)
* (default: posixAccount)
* groupdn: gets prepended to basedn when searching for group
* groupattr : the group attribute to search for (default: cn)
* groupoc : objectclass of group (for the search filter)
* (default: groupOfUniqueNames)
* memberattr : the attribute of the group object where the user dn
* may be found (default: uniqueMember)
* memberisdn: whether the memberattr is the dn of the user (default)
* or the value of userattr (usually uid)
* group: the name of group to search for
* debug: Enable/Disable debugging output (default: false)
*
* To use this storage container, you have to use the following syntax:
*
* <?php
* ...
*
* $a = new Auth("LDAP", array(
* 'host' => 'localhost',
* 'port' => '389',
* 'basedn' => 'o=netsols,c=de',
* 'userattr' => 'uid'
* 'binddn' => 'cn=admin,o=netsols,c=de',
* 'bindpw' => 'password'));
*
* $a2 = new Auth('LDAP', array(
* 'url' => 'ldaps://ldap.netsols.de',
* 'basedn' => 'o=netsols,c=de',
* 'scope' => 'one',
* 'userdn' => 'ou=People',
* 'groupdn' => 'ou=Groups',
* 'groupoc' => 'posixGroup',
* 'memberattr' => 'memberUid',
* 'memberisdn' => false,
* 'group' => 'admin'
* ));
*
* $a3 = new Auth('LDAP', array(
* 'host' => 'ad.netsols.de',
* 'basedn' => 'dc=netsols,dc=de',
* 'userdn' => 'ou=Users',
* 'binddn' => 'cn=Jan Wagner,ou=Users,dc=netsols,dc=de',
* 'bindpw' => '*******',
* 'userattr' => 'samAccountName',
* 'useroc' => 'user',
* 'debug' => true
* ));
*
* The parameter values have to correspond
* to the ones for your LDAP server of course.
*
* When talking to a Microsoft ActiveDirectory server you have to
* use 'samaccountname' as the 'userattr' and follow special rules
* to translate the ActiveDirectory directory names into 'basedn'.
* The 'basedn' for the default 'Users' folder on an ActiveDirectory
* server for the ActiveDirectory Domain (which is not related to
* its DNS name) "win2000.example.org" would be:
* "CN=Users, DC=win2000, DC=example, DC=org'
* where every component of the domain name becomes a DC attribute
* of its own. If you want to use a custom users folder you have to
* replace "CN=Users" with a sequence of "OU" attributes that specify
* the path to your custom folder in reverse order.
* So the ActiveDirectory folder
* "win2000.example.org\Custom\Accounts"
* would become
* "OU=Accounts, OU=Custom, DC=win2000, DC=example, DC=org'
*
* It seems that binding anonymously to an Active Directory
* is not allowed, so you have to set binddn and bindpw for
* user searching,
*
* Example a3 shows a tested example for connenction to Windows 2000
* Active Directory
*
* @author Jan Wagner <wagner@netsols.de>
* @package Auth
* @version $Revision: 1.1 $
*/
class Auth_Container_LDAP extends Auth_Container
{
/**
* Options for the class
* @var array
*/
var $options = array();
 
/**
* Connection ID of LDAP Link
* @var string
*/
var $conn_id = false;
 
/**
* LDAP search function to use
* @var string
*/
var $ldap_search_func;
 
/**
* Constructor of the container class
*
* @param $params, associative hash with host,port,basedn and userattr key
* @return object Returns an error object if something went wrong
*/
function Auth_Container_LDAP($params)
{
$this->_setDefaults();
 
if (is_array($params)) {
$this->_parseOptions($params);
}
}
 
// }}}
// {{{ _connect()
 
/**
* Connect to the LDAP server using the global options
*
* @access private
* @return object Returns a PEAR error object if an error occurs.
*/
function _connect()
{
// connect
if (isset($this->options['url']) && $this->options['url'] != '') {
$this->_debug('Connecting with URL', __LINE__);
$conn_params = array($this->options['url']);
} else {
$this->_debug('Connecting with host:port', __LINE__);
$conn_params = array($this->options['host'], $this->options['port']);
}
 
if(($this->conn_id = @call_user_func_array('ldap_connect', $conn_params)) === false) {
return PEAR::raiseError('Auth_Container_LDAP: Could not connect to server.', 41, PEAR_ERROR_DIE);
}
$this->_debug('Successfully connected to server', __LINE__);
 
// try switchig to LDAPv3
$ver = 0;
if(@ldap_get_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $ver) && $ver >= 2) {
$this->_debug('Switching to LDAPv3', __LINE__);
@ldap_set_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, 3);
}
 
// bind with credentials or anonymously
if($this->options['binddn'] && $this->options['bindpw']) {
$this->_debug('Binding with credentials', __LINE__);
$bind_params = array($this->conn_id, $this->options['binddn'], $this->options['bindpw']);
} else {
$this->_debug('Binding anonymously', __LINE__);
$bind_params = array($this->conn_id);
}
// bind for searching
if ((@call_user_func_array('ldap_bind', $bind_params)) == false) {
$this->_debug();
$this->_disconnect();
return PEAR::raiseError("Auth_Container_LDAP: Could not bind to LDAP server.", 41, PEAR_ERROR_DIE);
}
$this->_debug('Binding was successful', __LINE__);
}
 
/**
* Disconnects (unbinds) from ldap server
*
* @access private
*/
function _disconnect()
{
if($this->_isValidLink()) {
$this->_debug('disconnecting from server');
@ldap_unbind($this->conn_id);
}
}
 
/**
* Tries to find Basedn via namingContext Attribute
*
* @access private
*/
function _getBaseDN()
{
if ($this->options['basedn'] == "" && $this->_isValidLink()) {
$this->_debug("basedn not set, searching via namingContexts.", __LINE__);
 
$result_id = @ldap_read($this->conn_id, "", "(objectclass=*)", array("namingContexts"));
if (ldap_count_entries($this->conn_id, $result_id) == 1) {
$this->_debug("got result for namingContexts", __LINE__);
$entry_id = ldap_first_entry($this->conn_id, $result_id);
$attrs = ldap_get_attributes($this->conn_id, $entry_id);
$basedn = $attrs['namingContexts'][0];
 
if ($basedn != "") {
$this->_debug("result for namingContexts was $basedn", __LINE__);
$this->options['basedn'] = $basedn;
}
}
ldap_free_result($result_id);
}
 
// if base ist still not set, raise error
if ($this->options['basedn'] == "") {
return PEAR::raiseError("Auth_Container_LDAP: LDAP search base not specified!", 41, PEAR_ERROR_DIE);
}
return true;
}
 
/**
* determines whether there is a valid ldap conenction or not
*
* @accessd private
* @return boolean
*/
function _isValidLink()
{
if(is_resource($this->conn_id)) {
if(get_resource_type($this->conn_id) == 'ldap link') {
return true;
}
}
return false;
}
 
/**
* Set some default options
*
* @access private
*/
function _setDefaults()
{
$this->options['host'] = 'localhost';
$this->options['port'] = '389';
$this->options['binddn'] = '';
$this->options['bindpw'] = '';
$this->options['scope'] = 'sub';
$this->options['basedn'] = '';
$this->options['userdn'] = '';
$this->options['userattr'] = "uid";
$this->options['useroc'] = 'posixAccount';
$this->options['groupdn'] = '';
$this->options['groupattr'] = 'cn';
$this->options['groupoc'] = 'groupOfUniqueNames';
$this->options['memberattr'] = 'uniqueMember';
$this->options['memberisdn'] = true;
$this->options['debug'] = false;
}
 
/**
* Parse options passed to the container class
*
* @access private
* @param array
*/
function _parseOptions($array)
{
foreach ($array as $key => $value) {
$this->options[$key] = $value;
}
 
// get the according search function for selected scope
switch($this->options['scope']) {
case 'one':
$this->ldap_search_func = 'ldap_list';
break;
case 'base':
$this->ldap_search_func = 'ldap_read';
break;
default:
$this->ldap_search_func = 'ldap_search';
break;
}
$this->_debug("LDAP search function will be: {$this->ldap_search_func}", __LINE__);
}
 
/**
* Fetch data from LDAP server
*
* Searches the LDAP server for the given username/password
* combination.
*
* @param string Username
* @param string Password
* @return boolean
*/
function fetchData($username, $password)
{
 
$this->_connect();
$this->_getBaseDN();
// make search filter
$filter = sprintf('(&(objectClass=%s)(%s=%s))', $this->options['useroc'], $this->options['userattr'], $username);
 
// make search base dn
$search_basedn = $this->options['userdn'];
if ($search_basedn != '' && substr($search_basedn, -1) != ',') {
$search_basedn .= ',';
}
$search_basedn .= $this->options['basedn'];
// make functions params array
$func_params = array($this->conn_id, $search_basedn, $filter, array($this->options['userattr']));
 
$this->_debug("Searching with $filter in $search_basedn", __LINE__);
 
// search
if (($result_id = @call_user_func_array($this->ldap_search_func, $func_params)) == false) {
$this->_debug('User not found', __LINE__);
} elseif (ldap_count_entries($this->conn_id, $result_id) == 1) { // did we get just one entry?
 
$this->_debug('User was found', __LINE__);
// then get the user dn
$entry_id = ldap_first_entry($this->conn_id, $result_id);
$user_dn = ldap_get_dn($this->conn_id, $entry_id);
 
ldap_free_result($result_id);
 
// need to catch an empty password as openldap seems to return TRUE
// if anonymous binding is allowed
if ($password != "") {
$this->_debug("Bind as $user_dn", __LINE__);
 
// try binding as this user with the supplied password
if (@ldap_bind($this->conn_id, $user_dn, $password)) {
$this->_debug('Bind successful', __LINE__);
 
// check group if appropiate
if(isset($this->options['group'])) {
// decide whether memberattr value is a dn or the username
$this->_debug('Checking group membership', __LINE__);
return $this->checkGroup(($this->options['memberisdn']) ? $user_dn : $username);
} else {
$this->_debug('Authenticated', __LINE__);
$this->_disconnect();
return true; // user authenticated
} // checkGroup
} // bind
} // non-empty password
} // one entry
// default
$this->_debug('NOT authenticated!', __LINE__);
$this->_disconnect();
return false;
}
 
/**
* Validate group membership
*
* Searches the LDAP server for group membership of the
* authenticated user
*
* @param string Distinguished Name of the authenticated User
* @return boolean
*/
function checkGroup($user)
{
// make filter
$filter = sprintf('(&(%s=%s)(objectClass=%s)(%s=%s))',
$this->options['groupattr'],
$this->options['group'],
$this->options['groupoc'],
$this->options['memberattr'],
$user
);
 
// make search base dn
$search_basedn = $this->options['groupdn'];
if($search_basedn != '' && substr($search_basedn, -1) != ',') {
$search_basedn .= ',';
}
$search_basedn .= $this->options['basedn'];
$func_params = array($this->conn_id, $search_basedn, $filter, array($this->options['memberattr']));
 
$this->_debug("Searching with $filter in $search_basedn", __LINE__);
// search
if(($result_id = @call_user_func_array($this->ldap_search_func, $func_params)) != false) {
if(ldap_count_entries($this->conn_id, $result_id) == 1) {
ldap_free_result($result_id);
$this->_debug('User is member of group', __LINE__);
$this->_disconnect();
return true;
}
}
 
// default
$this->_debug('User is NOT member of group', __LINE__);
$this->_disconnect();
return false;
}
 
/**
* Outputs debugging messages
*
* @access private
* @param string Debugging Message
* @param integer Line number
*/
function _debug($msg = '', $line = 0)
{
if($this->options['debug'] === true) {
if($msg == '' && $this->_isValidLink()) {
$msg = 'LDAP_Error: ' . @ldap_err2str(@ldap_errno($this->_conn_id));
}
print("$line: $msg <br />");
}
}
}
 
?>
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for use against an LDAP server
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Jan Wagner <wagner@netsols.de>
* @author Adam Ashley <aashley@php.net>
* @author Hugues Peeters <hugues.peeters@claroline.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: LDAP.php,v 1.2 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
*/
 
/**
* Include Auth_Container base class
*/
require_once "Auth/Container.php";
/**
* Include PEAR package for error handling
*/
require_once "PEAR.php";
 
/**
* Storage driver for fetching login data from LDAP
*
* This class is heavily based on the DB and File containers. By default it
* connects to localhost:389 and searches for uid=$username with the scope
* "sub". If no search base is specified, it will try to determine it via
* the namingContexts attribute. It takes its parameters in a hash, connects
* to the ldap server, binds anonymously, searches for the user, and tries
* to bind as the user with the supplied password. When a group was set, it
* will look for group membership of the authenticated user. If all goes
* well the authentication was successful.
*
* Parameters:
*
* host: localhost (default), ldap.netsols.de or 127.0.0.1
* port: 389 (default) or 636 or whereever your server runs
* url: ldap://localhost:389/
* useful for ldaps://, works only with openldap2 ?
* it will be preferred over host and port
* version: LDAP version to use, ususally 2 (default) or 3,
* must be an integer!
* referrals: If set, determines whether the LDAP library automatically
* follows referrals returned by LDAP servers or not. Possible
* values are true (default) or false.
* binddn: If set, searching for user will be done after binding
* as this user, if not set the bind will be anonymous.
* This is reported to make the container work with MS
* Active Directory, but should work with any server that
* is configured this way.
* This has to be a complete dn for now (basedn and
* userdn will not be appended).
* bindpw: The password to use for binding with binddn
* basedn: the base dn of your server
* userdn: gets prepended to basedn when searching for user
* userscope: Scope for user searching: one, sub (default), or base
* userattr: the user attribute to search for (default: uid)
* userfilter: filter that will be added to the search filter
* this way: (&(userattr=username)(userfilter))
* default: (objectClass=posixAccount)
* attributes: array of additional attributes to fetch from entry.
* these will added to auth data and can be retrieved via
* Auth::getAuthData(). An empty array will fetch all attributes,
* array('') will fetch no attributes at all (default)
* If you add 'dn' as a value to this array, the users DN that was
* used for binding will be added to auth data as well.
* attrformat: The returned format of the additional data defined in the
* 'attributes' option. Two formats are available.
* LDAP returns data formatted in a
* multidimensional array where each array starts with a
* 'count' element providing the number of attributes in the
* entry, or the number of values for attributes. When set
* to this format, the only way to retrieve data from the
* Auth object is by calling getAuthData('attributes').
* AUTH returns data formatted in a
* structure more compliant with other Auth Containers,
* where each attribute element can be directly called by
* getAuthData() method from Auth.
* For compatibily with previous LDAP container versions,
* the default format is LDAP.
* groupdn: gets prepended to basedn when searching for group
* groupattr: the group attribute to search for (default: cn)
* groupfilter: filter that will be added to the search filter when
* searching for a group:
* (&(groupattr=group)(memberattr=username)(groupfilter))
* default: (objectClass=groupOfUniqueNames)
* memberattr : the attribute of the group object where the user dn
* may be found (default: uniqueMember)
* memberisdn: whether the memberattr is the dn of the user (default)
* or the value of userattr (usually uid)
* group: the name of group to search for
* groupscope: Scope for group searching: one, sub (default), or base
* start_tls: enable/disable the use of START_TLS encrypted connection
* (default: false)
* debug: Enable/Disable debugging output (default: false)
* try_all: Whether to try all user accounts returned from the search
* or just the first one. (default: false)
*
* To use this storage container, you have to use the following syntax:
*
* <?php
* ...
*
* $a1 = new Auth("LDAP", array(
* 'host' => 'localhost',
* 'port' => '389',
* 'version' => 3,
* 'basedn' => 'o=netsols,c=de',
* 'userattr' => 'uid'
* 'binddn' => 'cn=admin,o=netsols,c=de',
* 'bindpw' => 'password'));
*
* $a2 = new Auth('LDAP', array(
* 'url' => 'ldaps://ldap.netsols.de',
* 'basedn' => 'o=netsols,c=de',
* 'userscope' => 'one',
* 'userdn' => 'ou=People',
* 'groupdn' => 'ou=Groups',
* 'groupfilter' => '(objectClass=posixGroup)',
* 'memberattr' => 'memberUid',
* 'memberisdn' => false,
* 'group' => 'admin'
* ));
*
* $a3 = new Auth('LDAP', array(
* 'host' => 'ldap.netsols.de',
* 'port' => 389,
* 'version' => 3,
* 'referrals' => false,
* 'basedn' => 'dc=netsols,dc=de',
* 'binddn' => 'cn=Jan Wagner,cn=Users,dc=netsols,dc=de',
* 'bindpw' => 'password',
* 'userattr' => 'samAccountName',
* 'userfilter' => '(objectClass=user)',
* 'attributes' => array(''),
* 'group' => 'testing',
* 'groupattr' => 'samAccountName',
* 'groupfilter' => '(objectClass=group)',
* 'memberattr' => 'member',
* 'memberisdn' => true,
* 'groupdn' => 'cn=Users',
* 'groupscope' => 'one',
* 'debug' => true);
*
* The parameter values have to correspond
* to the ones for your LDAP server of course.
*
* When talking to a Microsoft ActiveDirectory server you have to
* use 'samaccountname' as the 'userattr' and follow special rules
* to translate the ActiveDirectory directory names into 'basedn'.
* The 'basedn' for the default 'Users' folder on an ActiveDirectory
* server for the ActiveDirectory Domain (which is not related to
* its DNS name) "win2000.example.org" would be:
* "CN=Users, DC=win2000, DC=example, DC=org'
* where every component of the domain name becomes a DC attribute
* of its own. If you want to use a custom users folder you have to
* replace "CN=Users" with a sequence of "OU" attributes that specify
* the path to your custom folder in reverse order.
* So the ActiveDirectory folder
* "win2000.example.org\Custom\Accounts"
* would become
* "OU=Accounts, OU=Custom, DC=win2000, DC=example, DC=org'
*
* It seems that binding anonymously to an Active Directory
* is not allowed, so you have to set binddn and bindpw for
* user searching.
*
* LDAP Referrals need to be set to false for AD to work sometimes.
*
* Example a3 shows a full blown and tested example for connection to
* Windows 2000 Active Directory with group mebership checking
*
* Note also that if you want an encrypted connection to an MS LDAP
* server, then, on your webserver, you must specify
* TLS_REQCERT never
* in /etc/ldap/ldap.conf or in the webserver user's ~/.ldaprc (which
* may or may not be read depending on your configuration).
*
*
* @category Authentication
* @package Auth
* @author Jan Wagner <wagner@netsols.de>
* @author Adam Ashley <aashley@php.net>
* @author Hugues Peeters <hugues.peeters@claroline.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.2 $
* @link http://pear.php.net/package/Auth
*/
class Auth_Container_LDAP extends Auth_Container
{
 
// {{{ properties
 
/**
* Options for the class
* @var array
*/
var $options = array();
 
/**
* Connection ID of LDAP Link
* @var string
*/
var $conn_id = false;
 
// }}}
 
// {{{ Auth_Container_LDAP() [constructor]
 
/**
* Constructor of the container class
*
* @param $params, associative hash with host,port,basedn and userattr key
* @return object Returns an error object if something went wrong
*/
function Auth_Container_LDAP($params)
{
if (false === extension_loaded('ldap')) {
return PEAR::raiseError('Auth_Container_LDAP: LDAP Extension not loaded',
41, PEAR_ERROR_DIE);
}
 
$this->_setDefaults();
 
if (is_array($params)) {
$this->_parseOptions($params);
}
}
 
// }}}
// {{{ _prepare()
 
/**
* Prepare LDAP connection
*
* This function checks if we have already opened a connection to
* the LDAP server. If that's not the case, a new connection is opened.
*
* @access private
* @return mixed True or a PEAR error object.
*/
function _prepare()
{
if (!$this->_isValidLink()) {
$res = $this->_connect();
if (PEAR::isError($res)) {
return $res;
}
}
return true;
}
 
// }}}
// {{{ _connect()
 
/**
* Connect to the LDAP server using the global options
*
* @access private
* @return object Returns a PEAR error object if an error occurs.
*/
function _connect()
{
// connect
if (isset($this->options['url']) && $this->options['url'] != '') {
$this->_debug('Connecting with URL', __LINE__);
$conn_params = array($this->options['url']);
} else {
$this->_debug('Connecting with host:port', __LINE__);
$conn_params = array($this->options['host'], $this->options['port']);
}
 
if (($this->conn_id = @call_user_func_array('ldap_connect', $conn_params)) === false) {
return PEAR::raiseError('Auth_Container_LDAP: Could not connect to server.', 41);
}
$this->_debug('Successfully connected to server', __LINE__);
 
// switch LDAP version
if (is_numeric($this->options['version']) && $this->options['version'] > 2) {
$this->_debug("Switching to LDAP version {$this->options['version']}", __LINE__);
@ldap_set_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $this->options['version']);
// start TLS if available
if (isset($this->options['start_tls']) && $this->options['start_tls']) {
$this->_debug("Starting TLS session", __LINE__);
if (@ldap_start_tls($this->conn_id) === false) {
return PEAR::raiseError('Auth_Container_LDAP: Could not start tls.', 41);
}
}
}
 
// switch LDAP referrals
if (is_bool($this->options['referrals'])) {
$this->_debug("Switching LDAP referrals to " . (($this->options['referrals']) ? 'true' : 'false'), __LINE__);
@ldap_set_option($this->conn_id, LDAP_OPT_REFERRALS, $this->options['referrals']);
}
 
// bind with credentials or anonymously
if (strlen($this->options['binddn']) && strlen($this->options['bindpw'])) {
$this->_debug('Binding with credentials', __LINE__);
$bind_params = array($this->conn_id, $this->options['binddn'], $this->options['bindpw']);
} else {
$this->_debug('Binding anonymously', __LINE__);
$bind_params = array($this->conn_id);
}
 
// bind for searching
if ((@call_user_func_array('ldap_bind', $bind_params)) === false) {
$this->_debug();
$this->_disconnect();
return PEAR::raiseError("Auth_Container_LDAP: Could not bind to LDAP server.", 41);
}
$this->_debug('Binding was successful', __LINE__);
 
return true;
}
 
// }}}
// {{{ _disconnect()
 
/**
* Disconnects (unbinds) from ldap server
*
* @access private
*/
function _disconnect()
{
if ($this->_isValidLink()) {
$this->_debug('disconnecting from server');
@ldap_unbind($this->conn_id);
}
}
 
// }}}
// {{{ _getBaseDN()
 
/**
* Tries to find Basedn via namingContext Attribute
*
* @access private
*/
function _getBaseDN()
{
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
if ($this->options['basedn'] == "" && $this->_isValidLink()) {
$this->_debug("basedn not set, searching via namingContexts.", __LINE__);
 
$result_id = @ldap_read($this->conn_id, "", "(objectclass=*)", array("namingContexts"));
 
if (@ldap_count_entries($this->conn_id, $result_id) == 1) {
 
$this->_debug("got result for namingContexts", __LINE__);
 
$entry_id = @ldap_first_entry($this->conn_id, $result_id);
$attrs = @ldap_get_attributes($this->conn_id, $entry_id);
$basedn = $attrs['namingContexts'][0];
 
if ($basedn != "") {
$this->_debug("result for namingContexts was $basedn", __LINE__);
$this->options['basedn'] = $basedn;
}
}
@ldap_free_result($result_id);
}
 
// if base ist still not set, raise error
if ($this->options['basedn'] == "") {
return PEAR::raiseError("Auth_Container_LDAP: LDAP search base not specified!", 41);
}
return true;
}
 
// }}}
// {{{ _isValidLink()
 
/**
* determines whether there is a valid ldap conenction or not
*
* @accessd private
* @return boolean
*/
function _isValidLink()
{
if (is_resource($this->conn_id)) {
if (get_resource_type($this->conn_id) == 'ldap link') {
return true;
}
}
return false;
}
 
// }}}
// {{{ _setDefaults()
 
/**
* Set some default options
*
* @access private
*/
function _setDefaults()
{
$this->options['url'] = '';
$this->options['host'] = 'localhost';
$this->options['port'] = '389';
$this->options['version'] = 2;
$this->options['referrals'] = true;
$this->options['binddn'] = '';
$this->options['bindpw'] = '';
$this->options['basedn'] = '';
$this->options['userdn'] = '';
$this->options['userscope'] = 'sub';
$this->options['userattr'] = 'uid';
$this->options['userfilter'] = '(objectClass=posixAccount)';
$this->options['attributes'] = array(''); // no attributes
// $this->options['attrformat'] = 'LDAP'; // returns attribute array as PHP LDAP functions return it
$this->options['attrformat'] = 'AUTH'; // returns attribute like other Auth containers
$this->options['group'] = '';
$this->options['groupdn'] = '';
$this->options['groupscope'] = 'sub';
$this->options['groupattr'] = 'cn';
$this->options['groupfilter'] = '(objectClass=groupOfUniqueNames)';
$this->options['memberattr'] = 'uniqueMember';
$this->options['memberisdn'] = true;
$this->options['start_tls'] = false;
$this->options['debug'] = false;
$this->options['try_all'] = false; // Try all user ids returned not just the first one
}
 
// }}}
// {{{ _parseOptions()
 
/**
* Parse options passed to the container class
*
* @access private
* @param array
*/
function _parseOptions($array)
{
$array = $this->_setV12OptionsToV13($array);
 
foreach ($array as $key => $value) {
if (array_key_exists($key, $this->options)) {
if ($key == 'attributes') {
if (is_array($value)) {
$this->options[$key] = $value;
} else {
$this->options[$key] = explode(',', $value);
}
} else {
$this->options[$key] = $value;
}
}
}
}
 
// }}}
// {{{ _setV12OptionsToV13()
 
/**
* Adapt deprecated options from Auth 1.2 LDAP to Auth 1.3 LDAP
*
* @author Hugues Peeters <hugues.peeters@claroline.net>
* @access private
* @param array
* @return array
*/
function _setV12OptionsToV13($array)
{
if (isset($array['useroc']))
$array['userfilter'] = "(objectClass=".$array['useroc'].")";
if (isset($array['groupoc']))
$array['groupfilter'] = "(objectClass=".$array['groupoc'].")";
if (isset($array['scope']))
$array['userscope'] = $array['scope'];
 
return $array;
}
 
// }}}
// {{{ _scope2function()
 
/**
* Get search function for scope
*
* @param string scope
* @return string ldap search function
*/
function _scope2function($scope)
{
switch($scope) {
case 'one':
$function = 'ldap_list';
break;
case 'base':
$function = 'ldap_read';
break;
default:
$function = 'ldap_search';
break;
}
return $function;
}
 
// }}}
// {{{ fetchData()
 
/**
* Fetch data from LDAP server
*
* Searches the LDAP server for the given username/password
* combination. Escapes all LDAP meta characters in username
* before performing the query.
*
* @param string Username
* @param string Password
* @return boolean
*/
function fetchData($username, $password)
{
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
$err = $this->_getBaseDN();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
// UTF8 Encode username for LDAPv3
if (@ldap_get_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $ver) && $ver == 3) {
$this->_debug('UTF8 encoding username for LDAPv3', __LINE__);
$username = utf8_encode($username);
}
 
// make search filter
$filter = sprintf('(&(%s=%s)%s)',
$this->options['userattr'],
$this->_quoteFilterString($username),
$this->options['userfilter']);
 
// make search base dn
$search_basedn = $this->options['userdn'];
if ($search_basedn != '' && substr($search_basedn, -1) != ',') {
$search_basedn .= ',';
}
$search_basedn .= $this->options['basedn'];
 
// attributes
$attributes = $this->options['attributes'];
 
// make functions params array
$func_params = array($this->conn_id, $search_basedn, $filter, $attributes);
 
// search function to use
$func_name = $this->_scope2function($this->options['userscope']);
 
$this->_debug("Searching with $func_name and filter $filter in $search_basedn", __LINE__);
 
// search
if (($result_id = @call_user_func_array($func_name, $func_params)) === false) {
$this->_debug('User not found', __LINE__);
} elseif (@ldap_count_entries($this->conn_id, $result_id) >= 1) { // did we get some possible results?
 
$this->_debug('User(s) found', __LINE__);
 
$first = true;
$entry_id = null;
 
do {
// then get the user dn
if ($first) {
$entry_id = @ldap_first_entry($this->conn_id, $result_id);
$first = false;
} else {
$entry_id = @ldap_next_entry($this->conn_id, $entry_id);
if ($entry_id === false)
break;
}
$user_dn = @ldap_get_dn($this->conn_id, $entry_id);
 
// as the dn is not fetched as an attribute, we save it anyway
if (is_array($attributes) && in_array('dn', $attributes)) {
$this->_debug('Saving DN to AuthData', __LINE__);
$this->_auth_obj->setAuthData('dn', $user_dn);
}
// fetch attributes
if ($attributes = @ldap_get_attributes($this->conn_id, $entry_id)) {
 
if (is_array($attributes) && isset($attributes['count']) &&
$attributes['count'] > 0) {
 
// ldap_get_attributes() returns a specific multi dimensional array
// format containing all the attributes and where each array starts
// with a 'count' element providing the number of attributes in the
// entry, or the number of values for attribute. For compatibility
// reasons, it remains the default format returned by LDAP container
// setAuthData().
// The code below optionally returns attributes in another format,
// more compliant with other Auth containers, where each attribute
// element are directly set in the 'authData' list. This option is
// enabled by setting 'attrformat' to
// 'AUTH' in the 'options' array.
// eg. $this->options['attrformat'] = 'AUTH'
 
if ( strtoupper($this->options['attrformat']) == 'AUTH' ) {
$this->_debug('Saving attributes to Auth data in AUTH format', __LINE__);
unset ($attributes['count']);
foreach ($attributes as $attributeName => $attributeValue ) {
if (is_int($attributeName)) continue;
if (is_array($attributeValue) && isset($attributeValue['count'])) {
unset ($attributeValue['count']);
}
if (count($attributeValue)<=1) $attributeValue = $attributeValue[0];
$this->_auth_obj->setAuthData($attributeName, $attributeValue);
}
}
else
{
$this->_debug('Saving attributes to Auth data in LDAP format', __LINE__);
$this->_auth_obj->setAuthData('attributes', $attributes);
}
}
}
@ldap_free_result($result_id);
 
// need to catch an empty password as openldap seems to return TRUE
// if anonymous binding is allowed
if ($password != "") {
$this->_debug("Bind as $user_dn", __LINE__);
 
// try binding as this user with the supplied password
if (@ldap_bind($this->conn_id, $user_dn, $password)) {
$this->_debug('Bind successful', __LINE__);
 
// check group if appropiate
if (strlen($this->options['group'])) {
// decide whether memberattr value is a dn or the username
$this->_debug('Checking group membership', __LINE__);
$return = $this->checkGroup(($this->options['memberisdn']) ? $user_dn : $username);
$this->_disconnect();
return $return;
} else {
$this->_debug('Authenticated', __LINE__);
$this->_disconnect();
return true; // user authenticated
} // checkGroup
} // bind
} // non-empty password
} while ($this->options['try_all'] == true); // interate through entries
} // get results
// default
$this->_debug('NOT authenticated!', __LINE__);
$this->_disconnect();
return false;
}
 
// }}}
// {{{ checkGroup()
 
/**
* Validate group membership
*
* Searches the LDAP server for group membership of the
* supplied username. Quotes all LDAP filter meta characters in
* the user name before querying the LDAP server.
*
* @param string Distinguished Name of the authenticated User
* @return boolean
*/
function checkGroup($user)
{
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
// make filter
$filter = sprintf('(&(%s=%s)(%s=%s)%s)',
$this->options['groupattr'],
$this->options['group'],
$this->options['memberattr'],
$this->_quoteFilterString($user),
$this->options['groupfilter']);
 
// make search base dn
$search_basedn = $this->options['groupdn'];
if ($search_basedn != '' && substr($search_basedn, -1) != ',') {
$search_basedn .= ',';
}
$search_basedn .= $this->options['basedn'];
 
$func_params = array($this->conn_id, $search_basedn, $filter,
array($this->options['memberattr']));
$func_name = $this->_scope2function($this->options['groupscope']);
 
$this->_debug("Searching with $func_name and filter $filter in $search_basedn", __LINE__);
 
// search
if (($result_id = @call_user_func_array($func_name, $func_params)) != false) {
if (@ldap_count_entries($this->conn_id, $result_id) == 1) {
@ldap_free_result($result_id);
$this->_debug('User is member of group', __LINE__);
return true;
}
}
// default
$this->_debug('User is NOT member of group', __LINE__);
return false;
}
 
// }}}
// {{{ _debug()
 
/**
* Outputs debugging messages
*
* @access private
* @param string Debugging Message
* @param integer Line number
*/
function _debug($msg = '', $line = 0)
{
if ($this->options['debug'] == true) {
if ($msg == '' && $this->_isValidLink()) {
$msg = 'LDAP_Error: ' . @ldap_err2str(@ldap_errno($this->_conn_id));
}
print("$line: $msg <br />");
}
}
 
// }}}
// {{{ _quoteFilterString()
 
/**
* Escapes LDAP filter special characters as defined in RFC 2254.
*
* @access private
* @param string Filter String
*/
function _quoteFilterString($filter_str)
{
$metas = array( '\\', '*', '(', ')', "\x00");
$quoted_metas = array('\\\\', '\*', '\(', '\)', "\\\x00");
return str_replace($metas, $quoted_metas, $filter_str);
}
 
// }}}
 
}
 
?>
/trunk/api/pear/Auth/Container/POP3.php
1,107 → 1,143
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 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: Stefan Ekman <stekman@sedata.org> |
// | Martin Jansen <mj@php.net> |
// | Mika Tuupola <tuupola@appelsiini.net> |
// +----------------------------------------------------------------------+
//
// $Id: POP3.php,v 1.1 2005-03-30 08:50:33 jpm Exp $
//
 
 
require_once('Auth/Container.php');
require_once('PEAR.php');
require_once('Net/POP3.php');
 
/**
* Storage driver for Authentication on a POP3 server.
*
* @author Yavor Shahpasov <yavo@netsmart.com.cy>
* @package Auth
* @version $Revision: 1.1 $
*/
class Auth_Container_POP3 extends Auth_Container
{
/**
* POP3 Server
* @var string
*/
var $server='localhost';
 
/**
* POP3 Server port
* @var string
*/
var $port='110';
 
// {{{ Constructor
 
/**
* Constructor of the container class
*
* @param $server string server or server:port combination
* @return object Returns an error object if something went wrong
*/
function Auth_Container_POP3($server=null)
{
if(isset($server)){
if(is_array($server)){
if(isset($server['host'])){
$this->server = $server['host'];
}
if(isset($server['port'])){
$this->port = $server['port'];
}
}
else{
if(strstr($server, ':')){
$serverparts = explode(':', trim($server));
$this->server = $serverparts[0];
$this->port = $serverparts[1];
}
else
{
$this->server = $server;
}
}
}
}
 
// }}}
// {{{ fetchData()
 
/**
* Try to login to the POP3 server
*
* @param string Username
* @param string Password
* @return boolean
*/
function fetchData($username, $password)
{
$pop3 =& new Net_POP3();
$res = $pop3->connect($this->server, $this->port);
if(!$res){
return($res);
}
$result = $pop3->login($username, $password);
$pop3->disconnect();
return $result;
}
 
// }}}
}
?>
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for use against a POP3 server
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Stefan Ekman <stekman@sedata.org>
* @author Martin Jansen <mj@php.net>
* @author Mika Tuupola <tuupola@appelsiini.net>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: POP3.php,v 1.2 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @since File available since Release 1.2.0
*/
 
/**
* Include Auth_Container base class
*/
require_once 'Auth/Container.php';
/**
* Include PEAR package for error handling
*/
require_once 'PEAR.php';
/**
* Include PEAR Net_POP3 package
*/
require_once 'Net/POP3.php';
 
/**
* Storage driver for Authentication on a POP3 server.
*
* @category Authentication
* @package Auth
* @author Martin Jansen <mj@php.net>
* @author Mika Tuupola <tuupola@appelsiini.net>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.2 $
* @link http://pear.php.net/package/Auth
* @since Class available since Release 1.2.0
*/
class Auth_Container_POP3 extends Auth_Container
{
 
// {{{ properties
 
/**
* POP3 Server
* @var string
*/
var $server='localhost';
 
/**
* POP3 Server port
* @var string
*/
var $port='110';
 
/**
* POP3 Authentication method
*
* Prefered POP3 authentication method. Acceptable values:
* Boolean TRUE - Use Net_POP3's autodetection
* String 'DIGEST-MD5','CRAM-MD5','LOGIN','PLAIN','APOP','USER'
* - Attempt this authentication style first
* then fallback to autodetection.
* @var mixed
*/
var $method=true;
 
// }}}
// {{{ Auth_Container_POP3() [constructor]
 
/**
* Constructor of the container class
*
* @param $server string server or server:port combination
* @return object Returns an error object if something went wrong
*/
function Auth_Container_POP3($server=null)
{
if (isset($server) && !is_null($server)) {
if (is_array($server)) {
if (isset($server['host'])) {
$this->server = $server['host'];
}
if (isset($server['port'])) {
$this->port = $server['port'];
}
if (isset($server['method'])) {
$this->method = $server['method'];
}
} else {
if (strstr($server, ':')) {
$serverparts = explode(':', trim($server));
$this->server = $serverparts[0];
$this->port = $serverparts[1];
} else {
$this->server = $server;
}
}
}
}
 
// }}}
// {{{ fetchData()
 
/**
* Try to login to the POP3 server
*
* @param string Username
* @param string Password
* @return boolean
*/
function fetchData($username, $password)
{
$pop3 =& new Net_POP3();
$res = $pop3->connect($this->server, $this->port, $this->method);
if (!$res) {
return $res;
}
$result = $pop3->login($username, $password);
$pop3->disconnect();
return $result;
}
 
// }}}
 
}
?>
/trunk/api/pear/Auth/Container/SAP.php
New file
0,0 → 1,177
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for use against a SAP system using the SAPRFC PHP extension.
*
* Requires the SAPRFC ext available at http://saprfc.sourceforge.net/
*
* PHP version 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Stoyan Stefanov <ssttoo@gmail.com>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: SAP.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @since File available since Release 1.4.0
*/
 
/**
* Include Auth_Container base class
*/
require_once 'Auth/Container.php';
/**
* Include PEAR for error handling
*/
require_once 'PEAR.php';
 
/**
* Performs authentication against a SAP system using the SAPRFC PHP extension.
*
* When the option GETSSO2 is TRUE (default)
* the Single Sign-On (SSO) ticket is retrieved
* and stored as an Auth attribute called 'sap'
* in order to be reused for consecutive connections.
*
* @category Authentication
* @package Auth
* @author Stoyan Stefanov <ssttoo@gmail.com>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.1 $
* @since Class available since Release 1.4.0
*/
class Auth_Container_SAP extends Auth_Container {
 
// {{{ properties
/**
* @var array Default options
*/
var $options = array(
'CLIENT' => '000',
'LANG' => 'EN',
'GETSSO2' => true,
);
 
// }}}
// {{{ Auth_Container_SAP()
 
/**
* Class constructor. Checks that required options
* are present and that the SAPRFC extension is loaded
*
* Options that can be passed and their defaults:
* <pre>
* array(
* 'ASHOST' => "",
* 'SYSNR' => "",
* 'CLIENT' => "000",
* 'GWHOST' =>"",
* 'GWSERV' =>"",
* 'MSHOST' =>"",
* 'R3NAME' =>"",
* 'GROUP' =>"",
* 'LANG' =>"EN",
* 'TRACE' =>"",
* 'GETSSO2'=> true
* )
* </pre>
*
* @param array array of options.
* @return void
*/
function Auth_Container_SAP($options)
{
$saprfc_loaded = PEAR::loadExtension('saprfc');
if (!$saprfc_loaded) {
return PEAR::raiseError('Cannot use SAP authentication, '
.'SAPRFC extension not loaded!');
}
if (empty($options['R3NAME']) && empty($options['ASHOST'])) {
return PEAR::raiseError('R3NAME or ASHOST required for authentication');
}
$this->options = array_merge($this->options, $options);
}
 
// }}}
// {{{ fetchData()
 
/**
* Performs username and password check
*
* @param string Username
* @param string Password
* @return boolean TRUE on success (valid user), FALSE otherwise
*/
function fetchData($username, $password)
{
$connection_options = $this->options;
$connection_options['USER'] = $username;
$connection_options['PASSWD'] = $password;
$rfc = saprfc_open($connection_options);
if (!$rfc) {
$message = "Couldn't connect to the SAP system.";
$error = $this->getError();
if ($error['message']) {
$message .= ': ' . $error['message'];
}
PEAR::raiseError($message, null, null, null, @$erorr['all']);
return false;
} else {
if (!empty($this->options['GETSSO2'])) {
if ($ticket = @saprfc_get_ticket($rfc)) {
$this->options['MYSAPSSO2'] = $ticket;
unset($this->options['GETSSO2']);
$this->_auth_obj->setAuthData('sap', $this->options);
} else {
PEAR::raiseError("SSO ticket retrieval failed");
}
}
@saprfc_close($rfc);
return true;
}
}
 
// }}}
// {{{ getError()
 
/**
* Retrieves the last error from the SAP connection
* and returns it as an array.
*
* @return array Array of error information
*/
function getError()
{
 
$error = array();
$sap_error = saprfc_error();
if (empty($err)) {
return $error;
}
$err = explode("n", $sap_error);
foreach ($err AS $line) {
$item = split(':', $line);
$error[strtolower(trim($item[0]))] = trim($item[1]);
}
$error['all'] = $sap_error;
return $error;
}
 
// }}}
 
}
 
?>
/trunk/api/pear/Auth/Container/MDB2.php
New file
0,0 → 1,571
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for use against PEAR MDB2
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Lorenzo Alberton <l.alberton@quipo.it>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: MDB2.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @since File available since Release 1.3.0
*/
 
/**
* Include Auth_Container base class
*/
require_once 'Auth/Container.php';
/**
* Include PEAR MDB2 package
*/
require_once 'MDB2.php';
 
/**
* Storage driver for fetching login data from a database
*
* This storage driver can use all databases which are supported
* by the PEAR MDB2 abstraction layer to fetch login data.
*
* @category Authentication
* @package Auth
* @author Lorenzo Alberton <l.alberton@quipo.it>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.1 $
* @link http://pear.php.net/package/Auth
* @since Class available since Release 1.3.0
*/
class Auth_Container_MDB2 extends Auth_Container
{
 
// {{{ properties
 
/**
* Additional options for the storage container
* @var array
*/
var $options = array();
 
/**
* MDB object
* @var object
*/
var $db = null;
var $dsn = '';
 
/**
* User that is currently selected from the DB.
* @var string
*/
var $activeUser = '';
 
// }}}
// {{{ Auth_Container_MDB2() [constructor]
 
/**
* Constructor of the container class
*
* Initate connection to the database via PEAR::MDB2
*
* @param string Connection data or MDB2 object
* @return object Returns an error object if something went wrong
*/
function Auth_Container_MDB2($dsn)
{
$this->_setDefaults();
 
if (is_array($dsn)) {
$this->_parseOptions($dsn);
if (empty($this->options['dsn'])) {
PEAR::raiseError('No connection parameters specified!');
}
} else {
$this->options['dsn'] = $dsn;
}
}
 
// }}}
// {{{ _connect()
 
/**
* Connect to database by using the given DSN string
*
* @access private
* @param mixed DSN string | array | mdb object
* @return mixed Object on error, otherwise bool
*/
function _connect($dsn)
{
if (is_string($dsn) || is_array($dsn)) {
$this->db =& MDB2::connect($dsn, $this->options['db_options']);
} elseif (is_subclass_of($dsn, 'MDB2_Driver_Common')) {
$this->db = $dsn;
} elseif (is_object($dsn) && MDB2::isError($dsn)) {
return PEAR::raiseError($dsn->getMessage(), $dsn->code);
} else {
return PEAR::raiseError('The given dsn was not valid in file ' . __FILE__ . ' at line ' . __LINE__,
41,
PEAR_ERROR_RETURN,
null,
null
);
 
}
 
if (MDB2::isError($this->db) || PEAR::isError($this->db)) {
return PEAR::raiseError($this->db->getMessage(), $this->db->code);
}
if ($this->options['auto_quote']) {
$this->options['final_table'] = $this->db->quoteIdentifier($this->options['table'], true);
$this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol'], true);
$this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol'], true);
} else {
$this->options['final_table'] = $this->options['table'];
$this->options['final_usernamecol'] = $this->options['usernamecol'];
$this->options['final_passwordcol'] = $this->options['passwordcol'];
}
return true;
}
 
// }}}
// {{{ _prepare()
 
/**
* Prepare database connection
*
* This function checks if we have already opened a connection to
* the database. If that's not the case, a new connection is opened.
*
* @access private
* @return mixed True or a MDB error object.
*/
function _prepare()
{
if (is_subclass_of($this->db, 'MDB2_Driver_Common')) {
return true;
}
return $this->_connect($this->options['dsn']);
}
 
// }}}
// {{{ query()
 
/**
* Prepare query to the database
*
* This function checks if we have already opened a connection to
* the database. If that's not the case, a new connection is opened.
* After that the query is passed to the database.
*
* @access public
* @param string Query string
* @return mixed a MDB_result object or MDB_OK on success, a MDB
* or PEAR error on failure
*/
function query($query)
{
$err = $this->_prepare();
if ($err !== true) {
return $err;
}
return $this->db->exec($query);
}
 
// }}}
// {{{ _setDefaults()
 
/**
* Set some default options
*
* @access private
* @return void
*/
function _setDefaults()
{
$this->options['table'] = 'auth';
$this->options['usernamecol'] = 'username';
$this->options['passwordcol'] = 'password';
$this->options['dsn'] = '';
$this->options['db_fields'] = '';
$this->options['cryptType'] = 'md5';
$this->options['db_options'] = array();
$this->options['auto_quote'] = true;
}
 
// }}}
// {{{ _parseOptions()
 
/**
* Parse options passed to the container class
*
* @access private
* @param array
*/
function _parseOptions($array)
{
foreach ($array as $key => $value) {
if (isset($this->options[$key])) {
$this->options[$key] = $value;
}
}
}
 
// }}}
// {{{ _quoteDBFields()
 
/**
* Quote the db_fields option to avoid the possibility of SQL injection.
*
* @access private
* @return string A properly quoted string that can be concatenated into a
* SELECT clause.
*/
function _quoteDBFields()
{
if (isset($this->options['db_fields'])) {
if (is_array($this->options['db_fields'])) {
if ($this->options['auto_quote']) {
$fields = array();
foreach ($this->options['db_fields'] as $field) {
$fields[] = $this->db->quoteIdentifier($field, true);
}
return implode(', ', $fields);
} else {
return implode(', ', $this->options['db_fields']);
}
} else {
if (strlen($this->options['db_fields']) > 0) {
if ($this->options['auto_quote']) {
return $this->db->quoteIdentifier($this->options['db_fields'], true);
} else {
return $this->options['db_fields'];
}
}
}
}
 
return '';
}
// }}}
// {{{ fetchData()
 
/**
* Get user information from database
*
* This function uses the given username to fetch
* the corresponding login data from the database
* table. If an account that matches the passed username
* and password is found, the function returns true.
* Otherwise it returns false.
*
* @param string Username
* @param string Password
* @param boolean If true password is secured using a md5 hash
* the frontend and auth are responsible for making sure the container supports
* challenge response password authentication
* @return mixed Error object or boolean
*/
function fetchData($username, $password, $isChallengeResponse=false)
{
// Prepare for a database query
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
//Check if db_fields contains a *, if so assume all columns are selected
if (is_string($this->options['db_fields'])
&& strstr($this->options['db_fields'], '*')) {
$sql_from = '*';
} else {
$sql_from = $this->options['final_usernamecol'].
", ".$this->options['final_passwordcol'];
 
if (strlen($fields = $this->_quoteDBFields()) > 0) {
$sql_from .= ', '.$fields;
}
}
$query = sprintf("SELECT %s FROM %s WHERE %s = %s",
$sql_from,
$this->options['final_table'],
$this->options['final_usernamecol'],
$this->db->quote($username, 'text')
);
 
$res = $this->db->queryRow($query, null, MDB2_FETCHMODE_ASSOC);
if (MDB2::isError($res) || PEAR::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
}
if (!is_array($res)) {
$this->activeUser = '';
return false;
}
 
// Perform trimming here before the hashing
$password = trim($password, "\r\n");
$res[$this->options['passwordcol']] = trim($res[$this->options['passwordcol']], "\r\n");
// If using Challenge Response md5 the pass with the secret
if ($isChallengeResponse) {
$res[$this->options['passwordcol']] =
md5($res[$this->options['passwordcol']].$this->_auth_obj->session['loginchallenege']);
// UGLY cannot avoid without modifying verifyPassword
if ($this->options['cryptType'] == 'md5') {
$res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]);
}
}
if ($this->verifyPassword($password,
$res[$this->options['passwordcol']],
$this->options['cryptType'])) {
// Store additional field values in the session
foreach ($res as $key => $value) {
if ($key == $this->options['passwordcol'] ||
$key == $this->options['usernamecol']) {
continue;
}
// Use reference to the auth object if exists
// This is because the auth session variable can change so a static call to setAuthData does not make sense
$this->_auth_obj->setAuthData($key, $value);
}
return true;
}
 
$this->activeUser = $res[$this->options['usernamecol']];
return false;
}
 
// }}}
// {{{ listUsers()
 
/**
* Returns a list of users from the container
*
* @return mixed array|PEAR_Error
* @access public
*/
function listUsers()
{
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
$retVal = array();
 
//Check if db_fields contains a *, if so assume all columns are selected
if ( is_string($this->options['db_fields'])
&& strstr($this->options['db_fields'], '*')) {
$sql_from = '*';
} else {
$sql_from = $this->options['final_usernamecol'].
", ".$this->options['final_passwordcol'];
 
if (strlen($fields = $this->_quoteDBFields()) > 0) {
$sql_from .= ', '.$fields;
}
}
 
$query = sprintf('SELECT %s FROM %s',
$sql_from,
$this->options['final_table']
);
 
$res = $this->db->queryAll($query, null, MDB2_FETCHMODE_ASSOC);
if (MDB2::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
} else {
foreach ($res as $user) {
$user['username'] = $user[$this->options['usernamecol']];
$retVal[] = $user;
}
}
return $retVal;
}
 
// }}}
// {{{ addUser()
 
/**
* Add user to the storage container
*
* @access public
* @param string Username
* @param string Password
* @param mixed Additional information that are stored in the DB
*
* @return mixed True on success, otherwise error object
*/
function addUser($username, $password, $additional = "")
{
 
// Prepare for a database query
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') {
$cryptFunction = 'strval';
} elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) {
$cryptFunction = $this->options['cryptType'];
} else {
$cryptFunction = 'md5';
}
 
$password = $cryptFunction($password);
 
$additional_key = '';
$additional_value = '';
 
if (is_array($additional)) {
foreach ($additional as $key => $value) {
if ($this->options['auto_quote']) {
$additional_key .= ', ' . $this->db->quoteIdentifier($key, true);
} else {
$additional_key .= ', ' . $key;
}
$additional_value .= ', ' . $this->db->quote($value, 'text');
}
}
 
$query = sprintf("INSERT INTO %s (%s, %s%s) VALUES (%s, %s%s)",
$this->options['final_table'],
$this->options['final_usernamecol'],
$this->options['final_passwordcol'],
$additional_key,
$this->db->quote($username, 'text'),
$this->db->quote($password, 'text'),
$additional_value
);
 
$res = $this->query($query);
 
if (MDB2::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->code);
}
return true;
}
 
// }}}
// {{{ removeUser()
 
/**
* Remove user from the storage container
*
* @access public
* @param string Username
*
* @return mixed True on success, otherwise error object
*/
function removeUser($username)
{
// Prepare for a database query
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
$query = sprintf("DELETE FROM %s WHERE %s = %s",
$this->options['final_table'],
$this->options['final_usernamecol'],
$this->db->quote($username, 'text')
);
 
$res = $this->query($query);
 
if (MDB2::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->code);
}
return true;
}
 
// }}}
// {{{ changePassword()
 
/**
* Change password for user in the storage container
*
* @param string Username
* @param string The new password (plain text)
*/
function changePassword($username, $password)
{
// Prepare for a database query
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') {
$cryptFunction = 'strval';
} elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) {
$cryptFunction = $this->options['cryptType'];
} else {
$cryptFunction = 'md5';
}
 
$password = $cryptFunction($password);
 
$query = sprintf("UPDATE %s SET %s = %s WHERE %s = %s",
$this->options['final_table'],
$this->options['final_passwordcol'],
$this->db->quote($password, 'text'),
$this->options['final_usernamecol'],
$this->db->quote($username, 'text')
);
 
$res = $this->query($query);
 
if (MDB2::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->code);
}
return true;
}
 
// }}}
// {{{ supportsChallengeResponse()
 
/**
* Determine if this container supports
* password authentication with challenge response
*
* @return bool
* @access public
*/
function supportsChallengeResponse()
{
return in_array($this->options['cryptType'], array('md5', 'none', ''));
}
 
// }}}
// {{{ getCryptType()
 
/**
* Returns the selected crypt type for this container
*
* @return string Function used to crypt the password
*/
function getCryptType()
{
return $this->options['cryptType'];
}
 
// }}}
 
}
?>
/trunk/api/pear/Auth/Container/DB.php
1,409 → 1,578
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | |
// +----------------------------------------------------------------------+
// | 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: Martin Jansen <mj@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: DB.php,v 1.1 2005-03-30 08:50:33 jpm Exp $
//
 
require_once 'Auth/Container.php';
require_once 'DB.php';
 
/**
* Storage driver for fetching login data from a database
*
* This storage driver can use all databases which are supported
* by the PEAR DB abstraction layer to fetch login data.
*
* @author Martin Jansen <mj@php.net>
* @package Auth
* @version $Revision: 1.1 $
*/
class Auth_Container_DB extends Auth_Container
{
 
/**
* Additional options for the storage container
* @var array
*/
var $options = array();
 
/**
* DB object
* @var object
*/
var $db = null;
var $dsn = '';
 
/**
* User that is currently selected from the DB.
* @var string
*/
var $activeUser = '';
 
// {{{ Constructor
 
/**
* Constructor of the container class
*
* Initate connection to the database via PEAR::DB
*
* @param string Connection data or DB object
* @return object Returns an error object if something went wrong
*/
function Auth_Container_DB($dsn)
{
$this->_setDefaults();
 
if (is_array($dsn)) {
$this->_parseOptions($dsn);
 
if (empty($this->options['dsn'])) {
PEAR::raiseError('No connection parameters specified!');
}
} else {
$this->options['dsn'] = $dsn;
}
}
 
// }}}
// {{{ _connect()
 
/**
* Connect to database by using the given DSN string
*
* @access private
* @param string DSN string
* @return mixed Object on error, otherwise bool
*/
function _connect($dsn)
{
if (is_string($dsn) || is_array($dsn)) {
$this->db = DB::Connect($dsn);
} elseif (get_parent_class($dsn) == "db_common") {
$this->db = $dsn;
} elseif (DB::isError($dsn)) {
return PEAR::raiseError($dsn->getMessage(), $dsn->getCode());
} else {
return PEAR::raiseError('The given dsn was not valid in file ' . __FILE__ . ' at line ' . __LINE__,
41,
PEAR_ERROR_RETURN,
null,
null
);
}
 
if (DB::isError($this->db) || PEAR::isError($this->db)) {
return PEAR::raiseError($this->db->getMessage(), $this->db->getCode());
} else {
return true;
}
}
 
// }}}
// {{{ _prepare()
 
/**
* Prepare database connection
*
* This function checks if we have already opened a connection to
* the database. If that's not the case, a new connection is opened.
*
* @access private
* @return mixed True or a DB error object.
*/
function _prepare()
{
if (!DB::isConnection($this->db)) {
$res = $this->_connect($this->options['dsn']);
if(DB::isError($res) || PEAR::isError($res)){
return $res;
}
}
return true;
}
 
// }}}
// {{{ query()
 
/**
* Prepare query to the database
*
* This function checks if we have already opened a connection to
* the database. If that's not the case, a new connection is opened.
* After that the query is passed to the database.
*
* @access public
* @param string Query string
* @return mixed a DB_result object or DB_OK on success, a DB
* or PEAR error on failure
*/
function query($query)
{
$err = $this->_prepare();
if ($err !== true) {
return $err;
}
return $this->db->query($query);
}
 
// }}}
// {{{ _setDefaults()
 
/**
* Set some default options
*
* @access private
* @return void
*/
function _setDefaults()
{
$this->options['table'] = 'auth';
$this->options['usernamecol'] = 'username';
$this->options['passwordcol'] = 'password';
$this->options['dsn'] = '';
$this->options['db_fields'] = '';
$this->options['cryptType'] = 'md5';
}
 
// }}}
// {{{ _parseOptions()
 
/**
* Parse options passed to the container class
*
* @access private
* @param array
*/
function _parseOptions($array)
{
foreach ($array as $key => $value) {
if (isset($this->options[$key])) {
$this->options[$key] = $value;
}
}
 
/* Include additional fields if they exist */
if(!empty($this->options['db_fields'])){
if(is_array($this->options['db_fields'])){
$this->options['db_fields'] = join($this->options['db_fields'], ', ');
}
$this->options['db_fields'] = ', '.$this->options['db_fields'];
}
}
 
// }}}
// {{{ fetchData()
 
/**
* Get user information from database
*
* This function uses the given username to fetch
* the corresponding login data from the database
* table. If an account that matches the passed username
* and password is found, the function returns true.
* Otherwise it returns false.
*
* @param string Username
* @param string Password
* @return mixed Error object or boolean
*/
function fetchData($username, $password)
{
// Prepare for a database query
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
// Find if db_fileds contains a *, i so assume all col are selected
if(strstr($this->options['db_fields'], '*')){
$sql_from = "*";
}
else{
$sql_from = $this->options['usernamecol'] . ", ".$this->options['passwordcol'].$this->options['db_fields'];
}
/**
Old Style, removed to go around the oci8
problem
See bug 206
http://pear.php.net/bugs/bug.php?id=206
$query = "SELECT ! FROM ! WHERE ! = ?";
$query_params = array(
$sql_from,
$this->options['table'],
$this->options['usernamecol'],
$username
);
*/
$query = "SELECT ".$sql_from.
" FROM ".$this->options['table'].
" WHERE ".$this->options['usernamecol']." = '".$this->db->quoteString($username)."'";
$res = $this->db->getRow($query, null, DB_FETCHMODE_ASSOC);
 
if (DB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
}
if (!is_array($res)) {
$this->activeUser = '';
return false;
}
if ($this->verifyPassword(trim($password, "\r\n"),
trim($res[$this->options['passwordcol']], "\r\n"),
$this->options['cryptType'])) {
// Store additional field values in the session
foreach ($res as $key => $value) {
if ($key == $this->options['passwordcol'] ||
$key == $this->options['usernamecol']) {
continue;
}
// Use reference to the auth object if exists
// This is because the auth session variable can change so a static call to setAuthData does not make sence
if(is_object($this->_auth_obj)){
$this->_auth_obj->setAuthData($key, $value);
} else {
Auth::setAuthData($key, $value);
}
}
 
return true;
}
 
$this->activeUser = $res[$this->options['usernamecol']];
return false;
}
 
// }}}
// {{{ listUsers()
 
function listUsers()
{
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
$retVal = array();
 
// Find if db_fileds contains a *, i so assume all col are selected
if(strstr($this->options['db_fields'], '*')){
$sql_from = "*";
}
else{
$sql_from = $this->options['usernamecol'] . ", ".$this->options['passwordcol'].$this->options['db_fields'];
}
 
$query = sprintf("SELECT %s FROM %s",
$sql_from,
$this->options['table']
);
$res = $this->db->getAll($query, null, DB_FETCHMODE_ASSOC);
 
if (DB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
} else {
foreach ($res as $user) {
$user['username'] = $user[$this->options['usernamecol']];
$retVal[] = $user;
}
}
return $retVal;
}
 
// }}}
// {{{ addUser()
 
/**
* Add user to the storage container
*
* @access public
* @param string Username
* @param string Password
* @param mixed Additional information that are stored in the DB
*
* @return mixed True on success, otherwise error object
*/
function addUser($username, $password, $additional = "")
{
if (function_exists($this->options['cryptType'])) {
$cryptFunction = $this->options['cryptType'];
} else {
$cryptFunction = 'md5';
}
 
$additional_key = '';
$additional_value = '';
 
if (is_array($additional)) {
foreach ($additional as $key => $value) {
$additional_key .= ', ' . $key;
$additional_value .= ", '" . $value . "'";
}
}
 
$query = sprintf("INSERT INTO %s (%s, %s%s) VALUES ('%s', '%s'%s)",
$this->options['table'],
$this->options['usernamecol'],
$this->options['passwordcol'],
$additional_key,
$username,
$cryptFunction($password),
$additional_value
);
 
$res = $this->query($query);
 
if (DB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
} else {
return true;
}
}
 
// }}}
// {{{ removeUser()
 
/**
* Remove user from the storage container
*
* @access public
* @param string Username
*
* @return mixed True on success, otherwise error object
*/
function removeUser($username)
{
$query = sprintf("DELETE FROM %s WHERE %s = '%s'",
$this->options['table'],
$this->options['usernamecol'],
$username
);
 
$res = $this->query($query);
 
if (DB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
} else {
return true;
}
}
 
// }}}
}
?>
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for use against PEAR DB
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Martin Jansen <mj@php.net>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: DB.php,v 1.2 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
*/
 
/**
* Include Auth_Container base class
*/
require_once 'Auth/Container.php';
/**
* Include PEAR DB
*/
require_once 'DB.php';
 
/**
* Storage driver for fetching login data from a database
*
* This storage driver can use all databases which are supported
* by the PEAR DB abstraction layer to fetch login data.
*
* @category Authentication
* @package Auth
* @author Martin Jansen <mj@php.net>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.2 $
* @link http://pear.php.net/package/Auth
*/
class Auth_Container_DB extends Auth_Container
{
 
// {{{ properties
 
/**
* Additional options for the storage container
* @var array
*/
var $options = array();
 
/**
* DB object
* @var object
*/
var $db = null;
var $dsn = '';
 
/**
* User that is currently selected from the DB.
* @var string
*/
var $activeUser = '';
 
// }}}
// {{{ Auth_Container_DB [constructor]
 
/**
* Constructor of the container class
*
* Save the initial options passed to the container. Initiation of the DB
* connection is no longer performed here and is only done when needed.
*
* @param string Connection data or DB object
* @return object Returns an error object if something went wrong
*/
function Auth_Container_DB($dsn)
{
$this->_setDefaults();
 
if (is_array($dsn)) {
$this->_parseOptions($dsn);
 
if (empty($this->options['dsn'])) {
PEAR::raiseError('No connection parameters specified!');
}
} else {
$this->options['dsn'] = $dsn;
}
}
 
// }}}
// {{{ _connect()
 
/**
* Connect to database by using the given DSN string
*
* @access private
* @param string DSN string
* @return mixed Object on error, otherwise bool
*/
function _connect($dsn)
{
if (is_string($dsn) || is_array($dsn)) {
$this->db = DB::Connect($dsn, $this->options['db_options']);
} elseif (is_subclass_of($dsn, 'db_common')) {
$this->db = $dsn;
} elseif (DB::isError($dsn)) {
return PEAR::raiseError($dsn->getMessage(), $dsn->getCode());
} else {
return PEAR::raiseError('The given dsn was not valid in file ' . __FILE__ . ' at line ' . __LINE__,
41,
PEAR_ERROR_RETURN,
null,
null
);
}
 
if (DB::isError($this->db) || PEAR::isError($this->db)) {
return PEAR::raiseError($this->db->getMessage(), $this->db->getCode());
} else {
return true;
}
}
 
// }}}
// {{{ _prepare()
 
/**
* Prepare database connection
*
* This function checks if we have already opened a connection to
* the database. If that's not the case, a new connection is opened.
*
* @access private
* @return mixed True or a DB error object.
*/
function _prepare()
{
if (!DB::isConnection($this->db)) {
$res = $this->_connect($this->options['dsn']);
if (DB::isError($res) || PEAR::isError($res)) {
return $res;
}
}
if ($this->options['auto_quote'] && $this->db->dsn['phptype'] != 'sqlite') {
$this->options['final_table'] = $this->db->quoteIdentifier($this->options['table']);
$this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol']);
$this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol']);
} else {
$this->options['final_table'] = $this->options['table'];
$this->options['final_usernamecol'] = $this->options['usernamecol'];
$this->options['final_passwordcol'] = $this->options['passwordcol'];
}
return true;
}
 
// }}}
// {{{ query()
 
/**
* Prepare query to the database
*
* This function checks if we have already opened a connection to
* the database. If that's not the case, a new connection is opened.
* After that the query is passed to the database.
*
* @access public
* @param string Query string
* @return mixed a DB_result object or DB_OK on success, a DB
* or PEAR error on failure
*/
function query($query)
{
$err = $this->_prepare();
if ($err !== true) {
return $err;
}
return $this->db->query($query);
}
 
// }}}
// {{{ _setDefaults()
 
/**
* Set some default options
*
* @access private
* @return void
*/
function _setDefaults()
{
$this->options['table'] = 'auth';
$this->options['usernamecol'] = 'username';
$this->options['passwordcol'] = 'password';
$this->options['dsn'] = '';
$this->options['db_fields'] = '';
$this->options['cryptType'] = 'md5';
$this->options['db_options'] = array();
$this->options['auto_quote'] = true;
}
 
// }}}
// {{{ _parseOptions()
 
/**
* Parse options passed to the container class
*
* @access private
* @param array
*/
function _parseOptions($array)
{
foreach ($array as $key => $value) {
if (isset($this->options[$key])) {
$this->options[$key] = $value;
}
}
}
 
// }}}
// {{{ _quoteDBFields()
 
/**
* Quote the db_fields option to avoid the possibility of SQL injection.
*
* @access private
* @return string A properly quoted string that can be concatenated into a
* SELECT clause.
*/
function _quoteDBFields()
{
if (isset($this->options['db_fields'])) {
if (is_array($this->options['db_fields'])) {
if ($this->options['auto_quote']) {
$fields = array();
foreach ($this->options['db_fields'] as $field) {
$fields[] = $this->db->quoteIdentifier($field);
}
return implode(', ', $fields);
} else {
return implode(', ', $this->options['db_fields']);
}
} else {
if (strlen($this->options['db_fields']) > 0) {
if ($this->options['auto_quote']) {
return $this->db->quoteIdentifier($this->options['db_fields']);
} else {
return $this->options['db_fields'];
}
}
}
}
 
return '';
}
// }}}
// {{{ fetchData()
 
/**
* Get user information from database
*
* This function uses the given username to fetch
* the corresponding login data from the database
* table. If an account that matches the passed username
* and password is found, the function returns true.
* Otherwise it returns false.
*
* @param string Username
* @param string Password
* @param boolean If true password is secured using a md5 hash
* the frontend and auth are responsible for making sure the container supports
* challenge response password authentication
* @return mixed Error object or boolean
*/
function fetchData($username, $password, $isChallengeResponse=false)
{
// Prepare for a database query
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
// Find if db_fields contains a *, if so assume all columns are selected
if (is_string($this->options['db_fields'])
&& strstr($this->options['db_fields'], '*')) {
$sql_from = "*";
} else {
$sql_from = $this->options['final_usernamecol'].
", ".$this->options['final_passwordcol'];
 
if (strlen($fields = $this->_quoteDBFields()) > 0) {
$sql_from .= ', '.$fields;
}
}
 
$query = "SELECT ".$sql_from.
" FROM ".$this->options['final_table'].
" WHERE ".$this->options['final_usernamecol']." = ".$this->db->quoteSmart($username);
 
$res = $this->db->getRow($query, null, DB_FETCHMODE_ASSOC);
 
if (DB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
}
 
if (!is_array($res)) {
$this->activeUser = '';
return false;
}
 
// Perform trimming here before the hashihg
$password = trim($password, "\r\n");
$res[$this->options['passwordcol']] = trim($res[$this->options['passwordcol']], "\r\n");
 
// If using Challenge Response md5 the pass with the secret
if ($isChallengeResponse) {
$res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]
.$this->_auth_obj->session['loginchallenege']);
// UGLY cannot avoid without modifying verifyPassword
if ($this->options['cryptType'] == 'md5') {
$res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]);
}
//print " Hashed Password [{$res[$this->options['passwordcol']]}]<br/>\n";
}
 
if ($this->verifyPassword($password,
$res[$this->options['passwordcol']],
$this->options['cryptType'])) {
// Store additional field values in the session
foreach ($res as $key => $value) {
if ($key == $this->options['passwordcol'] ||
$key == $this->options['usernamecol']) {
continue;
}
// Use reference to the auth object if exists
// This is because the auth session variable can change so a
// static call to setAuthData does not make sence
$this->_auth_obj->setAuthData($key, $value);
}
return true;
}
$this->activeUser = $res[$this->options['usernamecol']];
return false;
}
 
// }}}
// {{{ listUsers()
 
/**
* Returns a list of users from the container
*
* @return mixed
* @access public
*/
function listUsers()
{
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
$retVal = array();
 
// Find if db_fields contains a *, if so assume all col are selected
if ( is_string($this->options['db_fields'])
&& strstr($this->options['db_fields'], '*')) {
$sql_from = "*";
} else {
$sql_from = $this->options['final_usernamecol'].
", ".$this->options['final_passwordcol'];
 
if (strlen($fields = $this->_quoteDBFields()) > 0) {
$sql_from .= ', '.$fields;
}
}
 
$query = sprintf("SELECT %s FROM %s",
$sql_from,
$this->options['final_table']
);
$res = $this->db->getAll($query, null, DB_FETCHMODE_ASSOC);
 
if (DB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
} else {
foreach ($res as $user) {
$user['username'] = $user[$this->options['usernamecol']];
$retVal[] = $user;
}
}
return $retVal;
}
 
// }}}
// {{{ addUser()
 
/**
* Add user to the storage container
*
* @access public
* @param string Username
* @param string Password
* @param mixed Additional information that are stored in the DB
*
* @return mixed True on success, otherwise error object
*/
function addUser($username, $password, $additional = "")
{
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
if ( isset($this->options['cryptType'])
&& $this->options['cryptType'] == 'none') {
$cryptFunction = 'strval';
} elseif ( isset($this->options['cryptType'])
&& function_exists($this->options['cryptType'])) {
$cryptFunction = $this->options['cryptType'];
} else {
$cryptFunction = 'md5';
}
 
$password = $cryptFunction($password);
 
$additional_key = '';
$additional_value = '';
 
if (is_array($additional)) {
foreach ($additional as $key => $value) {
if ($this->options['auto_quote']) {
$additional_key .= ', ' . $this->db->quoteIdentifier($key);
} else {
$additional_key .= ', ' . $key;
}
$additional_value .= ", " . $this->db->quoteSmart($value);
}
}
 
$query = sprintf("INSERT INTO %s (%s, %s%s) VALUES (%s, %s%s)",
$this->options['final_table'],
$this->options['final_usernamecol'],
$this->options['final_passwordcol'],
$additional_key,
$this->db->quoteSmart($username),
$this->db->quoteSmart($password),
$additional_value
);
 
$res = $this->query($query);
 
if (DB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
} else {
return true;
}
}
 
// }}}
// {{{ removeUser()
 
/**
* Remove user from the storage container
*
* @access public
* @param string Username
*
* @return mixed True on success, otherwise error object
*/
function removeUser($username)
{
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
$query = sprintf("DELETE FROM %s WHERE %s = %s",
$this->options['final_table'],
$this->options['final_usernamecol'],
$this->db->quoteSmart($username)
);
 
$res = $this->query($query);
 
if (DB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
} else {
return true;
}
}
 
// }}}
// {{{ changePassword()
 
/**
* Change password for user in the storage container
*
* @param string Username
* @param string The new password (plain text)
*/
function changePassword($username, $password)
{
$err = $this->_prepare();
if ($err !== true) {
return PEAR::raiseError($err->getMessage(), $err->getCode());
}
 
if ( isset($this->options['cryptType'])
&& $this->options['cryptType'] == 'none') {
$cryptFunction = 'strval';
} elseif ( isset($this->options['cryptType'])
&& function_exists($this->options['cryptType'])) {
$cryptFunction = $this->options['cryptType'];
} else {
$cryptFunction = 'md5';
}
 
$password = $cryptFunction($password);
 
$query = sprintf("UPDATE %s SET %s = %s WHERE %s = %s",
$this->options['final_table'],
$this->options['final_passwordcol'],
$this->db->quoteSmart($password),
$this->options['final_usernamecol'],
$this->db->quoteSmart($username)
);
 
$res = $this->query($query);
 
if (DB::isError($res)) {
return PEAR::raiseError($res->getMessage(), $res->getCode());
} else {
return true;
}
}
 
// }}}
// {{{ supportsChallengeResponse()
 
/**
* Determine if this container supports
* password authentication with challenge response
*
* @return bool
* @access public
*/
function supportsChallengeResponse()
{
return in_array($this->options['cryptType'], array('md5', 'none', ''));
}
 
// }}}
// {{{ getCryptType()
 
/**
* Returns the selected crypt type for this container
*/
function getCryptType()
{
return($this->options['cryptType']);
}
 
// }}}
 
}
?>
/trunk/api/pear/Auth/Container/IMAP.php
1,170 → 1,206
<?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: Jeroen Houben <jeroen@terena.nl> |
// +----------------------------------------------------------------------+
//
// $Id: IMAP.php,v 1.1 2005-03-30 08:50:33 jpm Exp $
//
 
require_once "Auth/Container.php";
require_once "PEAR.php";
 
/**
* Storage driver for fetching login data from an IMAP server
*
* This class is based on LDAP containers, but it very simple.
* By default it connects to localhost:143
* The constructor will first check if the host:port combination is
* actually reachable. This behaviour can be disabled.
* It then tries to create an IMAP stream (without opening a mailbox)
* If you wish to pass extended options to the connections, you may
* do so by specifying protocol options.
*
* To use this storage containers, you have to use the
* following syntax:
*
* <?php
* ...
* $params = array(
* 'host' => 'mail.example.com',
* 'port' => 143,
* );
* $myAuth = new Auth('IMAP', $params);
* ....
*
* By default we connect without any protocol options set. However, some
* servers require you to connect with the notls or norsh options set.
* To do this you need to add the following value to the params array:
* 'baseDSN' => '/imap/notls/norsh'
*
* To connect to an SSL IMAP server:
* 'baseDSN' => '/imap/ssl'
*
* To connect to an SSL IMAP server with a self-signed certificate:
* 'baseDSN' => '/imap/ssl/novalidate-cert'
*
* Further options may be available and can be found on the php site at
* http://www.php.net/manual/function.imap-open.php
*
*/
 
/*
*
* @author Jeroen Houben <jeroen@terena.nl>, Cipriano Groenendal <cipri@campai.nl>
* @package Auth
* @version $Revision: 1.1 $
*/
class Auth_Container_IMAP extends Auth_Container
{
/**
* Options for the class
* @var array
*/
var $options = array();
 
/**
* Constructor of the container class
*
* @param $params, associative hash with host,port,basedn and userattr key
* @param $params, associative array with host, port, baseDSN, checkServer key.
* @return object Returns an error object if something went wrong
*/
function Auth_Container_IMAP($params)
{
if (!extension_loaded('imap')) {
return PEAR::raiseError("Cannot use IMAP authentication, IMAP extension not loaded!",
41, PEAR_ERROR_DIE);
}
$this->_setDefaults();
// set parameters (if any)
if (is_array($params)) {
$this->_parseOptions($params);
}
if ($this->options['checkServer']) {
$this->_checkServer($this->options['timeout']);
}
return true;
}
 
/**
* Set some default options
*
* @access private
*/
function _setDefaults()
{
$this->options['host'] = 'localhost';
$this->options['port'] = 143;
$this->options['baseDSN'] = '';
$this->options['checkServer'] = true;
$this->options['timeout'] = 20;
}
 
 
/**
* Check if the given server and port are reachable
*
* @access private
*/
function _checkServer() {
$fp = @fsockopen ($this->options['host'], $this->options['port'],
$errno, $errstr, $timeout);
if (is_resource($fp)) {
@fclose($fp);
} else {
$message = "Error connecting to IMAP server "
. $this->options['host']
. ":" . $this->options['port'];
return PEAR::raiseError($message, 41, PEAR_ERROR_DIE);
}
}
 
/**
* Parse options passed to the container class
*
* @access private
* @param array
*/
function _parseOptions($array)
{
foreach ($array as $key => $value) {
$this->options[$key] = $value;
}
}
 
/**
* Try to open a IMAP stream using $username / $password
*
* @param string Username
* @param string Password
* @return boolean
*/
function fetchData($username, $password)
{
$dsn = '{'.$this->options['host'].':'.$this->options['port'].$this->options['baseDSN'].'}';
$conn = @imap_open ($dsn, $username, $password, OP_HALFOPEN);
if (is_resource($conn)){
$this->activeUser = $username;
@imap_close($conn);
return true;
} else {
$this->activeUser = '';
return false;
}
}
}
?>
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for use against IMAP servers
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Jeroen Houben <jeroen@terena.nl>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: IMAP.php,v 1.2 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @since File available since Release 1.2.0
*/
 
/**
* Include Auth_Container base class
*/
require_once "Auth/Container.php";
 
/**
* Include PEAR class for error handling
*/
require_once "PEAR.php";
 
/**
* Storage driver for fetching login data from an IMAP server
*
* This class is based on LDAP containers, but it very simple.
* By default it connects to localhost:143
* The constructor will first check if the host:port combination is
* actually reachable. This behaviour can be disabled.
* It then tries to create an IMAP stream (without opening a mailbox)
* If you wish to pass extended options to the connections, you may
* do so by specifying protocol options.
*
* To use this storage containers, you have to use the
* following syntax:
*
* <?php
* ...
* $params = array(
* 'host' => 'mail.example.com',
* 'port' => 143,
* );
* $myAuth = new Auth('IMAP', $params);
* ...
*
* By default we connect without any protocol options set. However, some
* servers require you to connect with the notls or norsh options set.
* To do this you need to add the following value to the params array:
* 'baseDSN' => '/imap/notls/norsh'
*
* To connect to an SSL IMAP server:
* 'baseDSN' => '/imap/ssl'
*
* To connect to an SSL IMAP server with a self-signed certificate:
* 'baseDSN' => '/imap/ssl/novalidate-cert'
*
* Further options may be available and can be found on the php site at
* http://www.php.net/manual/function.imap-open.php
*
* @category Authentication
* @package Auth
* @author Jeroen Houben <jeroen@terena.nl>
* @author Cipriano Groenendal <cipri@campai.nl>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.2 $
* @link http://pear.php.net/package/Auth
* @since Class available since Release 1.2.0
*/
class Auth_Container_IMAP extends Auth_Container
{
 
// {{{ properties
 
/**
* Options for the class
* @var array
*/
var $options = array();
 
// }}}
// {{{ Auth_Container_IMAP() [constructor]
 
/**
* Constructor of the container class
*
* @param $params associative array with host, port, baseDSN, checkServer
* and userattr key
* @return object Returns an error object if something went wrong
* @todo Use PEAR Net_IMAP if IMAP extension not loaded
*/
function Auth_Container_IMAP($params)
{
if (!extension_loaded('imap')) {
return PEAR::raiseError('Cannot use IMAP authentication, '
.'IMAP extension not loaded!', 41, PEAR_ERROR_DIE);
}
$this->_setDefaults();
 
// set parameters (if any)
if (is_array($params)) {
$this->_parseOptions($params);
}
 
if ($this->options['checkServer']) {
$this->_checkServer($this->options['timeout']);
}
return true;
}
 
// }}}
// {{{ _setDefaults()
 
/**
* Set some default options
*
* @access private
*/
function _setDefaults()
{
$this->options['host'] = 'localhost';
$this->options['port'] = 143;
$this->options['baseDSN'] = '';
$this->options['checkServer'] = true;
$this->options['timeout'] = 20;
}
 
// }}}
// {{{ _checkServer()
 
/**
* Check if the given server and port are reachable
*
* @access private
*/
function _checkServer() {
$fp = @fsockopen ($this->options['host'], $this->options['port'],
$errno, $errstr, $this->options['timeout']);
if (is_resource($fp)) {
@fclose($fp);
} else {
$message = "Error connecting to IMAP server "
. $this->options['host']
. ":" . $this->options['port'];
return PEAR::raiseError($message, 41);
}
}
 
// }}}
// {{{ _parseOptions()
 
/**
* Parse options passed to the container class
*
* @access private
* @param array
*/
function _parseOptions($array)
{
foreach ($array as $key => $value) {
$this->options[$key] = $value;
}
}
 
// }}}
// {{{ fetchData()
 
/**
* Try to open a IMAP stream using $username / $password
*
* @param string Username
* @param string Password
* @return boolean
*/
function fetchData($username, $password)
{
$dsn = '{'.$this->options['host'].':'.$this->options['port'].$this->options['baseDSN'].'}';
$conn = @imap_open ($dsn, $username, $password, OP_HALFOPEN);
if (is_resource($conn)) {
$this->activeUser = $username;
@imap_close($conn);
return true;
} else {
$this->activeUser = '';
return false;
}
}
 
// }}}
 
}
?>
/trunk/api/pear/Auth/Container/vpopmail.php
1,66 → 1,87
<?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 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: Stanislav Grozev <tacho@orbitel.bg> |
// +----------------------------------------------------------------------+
//
// $Id: vpopmail.php,v 1.1 2005-03-30 08:50:33 jpm Exp $
//
 
require_once "Auth/Container.php";
 
/**
* Storage driver for fetching login data from vpopmail
*
* @author Stanislav Grozev <tacho@orbitel.bg>
* @package Auth
* @version $Revision: 1.1 $
*/
class Auth_Container_vpopmail extends Auth_Container {
 
// {{{ Constructor
 
/**
* Constructor of the container class
*
* @return integer Always returns 1.
*/
function Auth_Container_vpopmail()
{
return 1;
}
 
// }}}
// {{{ fetchData()
 
/**
* Get user information from vpopmail
*
* @param string Username - has to be valid email address
* @param string Password
* @return boolean
*/
function fetchData($username, $password)
{
$userdata = array();
$userdata = preg_split("/@/", $username, 2);
$result = @vpopmail_auth_user($userdata[0], $userdata[1], $password);
 
return $result;
}
 
// }}}
}
?>
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for use against vpopmail setups
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Stanislav Grozev <tacho@orbitel.bg>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: vpopmail.php,v 1.2 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @since File available since Release 1.2.0
*/
 
/**
* Include Auth_Container base class
*/
require_once "Auth/Container.php";
/**
* Include PEAR package for error handling
*/
require_once "PEAR.php";
 
/**
* Storage driver for fetching login data from vpopmail
*
* @category Authentication
* @package Auth
* @author Stanislav Grozev <tacho@orbitel.bg>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.2 $
* @link http://pear.php.net/package/Auth
* @since Class available since Release 1.2.0
*/
class Auth_Container_vpopmail extends Auth_Container {
 
// {{{ Constructor
 
/**
* Constructor of the container class
*
* @return void
*/
function Auth_Container_vpopmail()
{
if (!extension_loaded('vpopmail')) {
return PEAR::raiseError('Cannot use VPOPMail authentication, '
.'VPOPMail extension not loaded!', 41, PEAR_ERROR_DIE);
}
}
 
// }}}
// {{{ fetchData()
 
/**
* Get user information from vpopmail
*
* @param string Username - has to be valid email address
* @param string Password
* @return boolean
*/
function fetchData($username, $password)
{
$userdata = array();
$userdata = preg_split("/@/", $username, 2);
$result = @vpopmail_auth_user($userdata[0], $userdata[1], $password);
 
return $result;
}
 
// }}}
 
}
?>
/trunk/api/pear/Auth/Container/PEAR.php
New file
0,0 → 1,103
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for use against PEAR website
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Yavor Shahpasov <yavo@netsmart.com.cy>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: PEAR.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @since File available since Release 1.3.0
*/
 
/**
* Include Auth_Container base class
*/
require_once 'Auth/Container.php';
/**
* Include PEAR XML_RPC
*/
require_once 'XML/RPC.php';
 
/**
* Storage driver for authenticating against PEAR website
*
* This driver provides a method for authenticating against the pear.php.net
* authentication system.
*
* @category Authentication
* @package Auth
* @author Yavor Shahpasov <yavo@netsmart.com.cy>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.1 $
* @link http://pear.php.net/package/Auth
* @since Class available since Release 1.3.0
*/
class Auth_Container_Pear extends Auth_Container
{
 
// {{{ Auth_Container_Pear() [constructor]
 
/**
* Constructor
*
* Currently does nothing
*
* @return void
*/
function Auth_Container_Pear()
{
}
 
// }}}
// {{{ fetchData()
/**
* Get user information from pear.php.net
*
* This function uses the given username and password to authenticate
* against the pear.php.net website
*
* @param string Username
* @param string Password
* @return mixed Error object or boolean
*/
function fetchData($username, $password)
{
$rpc = new XML_RPC_Client('/xmlrpc.php', 'pear.php.net');
$rpc_message = new XML_RPC_Message("user.info", array(new XML_RPC_Value($username, "string")) );
// Error Checking howto ???
$result = $rpc->send($rpc_message);
$value = $result->value();
$userinfo = xml_rpc_decode($value);
if ($userinfo['password'] == md5($password)) {
$this->activeUser = $userinfo['handle'];
foreach ($userinfo as $uk=>$uv) {
$this->_auth_obj->setAuthData($uk, $uv);
}
return true;
}
return false;
}
 
// }}}
}
?>
/trunk/api/pear/Auth/Container/RADIUS.php
1,154 → 1,180
<?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: Michael Bretterklieber <michael@bretterklieber.com> |
// +----------------------------------------------------------------------+
//
// $Id: RADIUS.php,v 1.1 2005-03-30 08:50:33 jpm Exp $
//
 
require_once "Auth/Container.php";
require_once "Auth/RADIUS.php";
 
/**
* Storage driver for authenticating users against RADIUS servers.
*
* @author Michael Bretterklieber <michael@bretterklieber.com>
* @access public
* @version $Revision: 1.1 $
*/
class Auth_Container_RADIUS extends Auth_Container
{
 
/**
* Contains a RADIUS object
* @var object
*/
var $radius;
/**
* Contains the authentication type
* @var string
*/
var $authtype;
 
/**
* Constructor of the container class.
*
* $options can have these keys:
* 'servers' an array containing an array: servername, port,
* sharedsecret, timeout, maxtries
* 'configfile' The filename of the configuration file
* 'authtype' The type of authentication, one of: PAP, CHAP_MD5,
* MSCHAPv1, MSCHAPv2, default is PAP
*
* @param $options associative array
* @return object Returns an error object if something went wrong
*/
function Auth_Container_RADIUS($options)
{
$this->authtype = 'PAP';
if (isset($options['authtype'])) {
$this->authtype = $options['authtype'];
}
$classname = 'Auth_RADIUS_' . $this->authtype;
if (!class_exists($classname)) {
PEAR::raiseError("Unknown Authtype, please use on of: PAP, CHAP_MD5, MSCHAPv1, MSCHAPv2!",
41, PEAR_ERROR_DIE);
}
$this->radius = new $classname;
 
if (isset($options['configfile'])) {
$this->radius->setConfigfile($options['configfile']);
}
 
$servers = $options['servers'];
if (is_array($servers)) {
foreach ($servers as $server) {
$servername = $server[0];
$port = isset($server[1]) ? $server[1] : 0;
$sharedsecret = isset($server[2]) ? $server[2] : 'testing123';
$timeout = isset($server[3]) ? $server[3] : 3;
$maxtries = isset($server[4]) ? $server[4] : 3;
$this->radius->addServer($servername, $port, $sharedsecret, $timeout, $maxtries);
}
}
if (!$this->radius->start()) {
PEAR::raiseError($this->radius->getError(), 41, PEAR_ERROR_DIE);
}
}
 
/**
* Authenticate
*
* @param string Username
* @param string Password
* @return bool true on success, false on reject
*/
function fetchData($username, $password, $challenge = null)
{
switch($this->authtype) {
case 'CHAP_MD5':
case 'MSCHAPv1':
if (isset($challenge)) {
echo $password;
$this->radius->challenge = $challenge;
$this->radius->chapid = 1;
$this->radius->response = pack('H*', $password);
} else {
require_once 'Crypt_CHAP/CHAP.php';
$classname = 'Crypt_' . $this->authtype;
$crpt = new $classname;
$crpt->password = $password;
$this->radius->challenge = $crpt->challenge;
$this->radius->chapid = $crpt->chapid;
$this->radius->response = $crpt->challengeResponse();
break;
}
 
case 'MSCHAPv2':
require_once 'Crypt_CHAP/CHAP.php';
$crpt = new Crypt_MSCHAPv2;
$crpt->username = $username;
$crpt->password = $password;
$this->radius->challenge = $crpt->authChallenge;
$this->radius->peerChallenge = $crpt->peerChallenge;
$this->radius->chapid = $crpt->chapid;
$this->radius->response = $crpt->challengeResponse();
break;
 
default:
$this->radius->password = $password;
break;
}
 
$this->radius->username = $username;
 
$this->radius->putAuthAttributes();
$result = $this->radius->send();
if (PEAR::isError($result)) {
return false;
}
 
$this->radius->getAttributes();
// just for debugging
// $this->radius->dumpAttributes();
 
return $result;
}
}
?>
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for use against RADIUS servers
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Michael Bretterklieber <michael@bretterklieber.com>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: RADIUS.php,v 1.2 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @since File available since Release 1.2.0
*/
 
/**
* Include Auth_Container base class
*/
require_once "Auth/Container.php";
/**
* Include PEAR Auth_RADIUS package
*/
require_once "Auth/RADIUS.php";
 
/**
* Storage driver for authenticating users against RADIUS servers.
*
* @category Authentication
* @package Auth
* @author Michael Bretterklieber <michael@bretterklieber.com>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.2 $
* @link http://pear.php.net/package/Auth
* @since Class available since Release 1.2.0
*/
class Auth_Container_RADIUS extends Auth_Container
{
 
// {{{ properties
 
/**
* Contains a RADIUS object
* @var object
*/
var $radius;
/**
* Contains the authentication type
* @var string
*/
var $authtype;
 
// }}}
// {{{ Auth_Container_RADIUS() [constructor]
 
/**
* Constructor of the container class.
*
* $options can have these keys:
* 'servers' an array containing an array: servername, port,
* sharedsecret, timeout, maxtries
* 'configfile' The filename of the configuration file
* 'authtype' The type of authentication, one of: PAP, CHAP_MD5,
* MSCHAPv1, MSCHAPv2, default is PAP
*
* @param $options associative array
* @return object Returns an error object if something went wrong
*/
function Auth_Container_RADIUS($options)
{
$this->authtype = 'PAP';
if (isset($options['authtype'])) {
$this->authtype = $options['authtype'];
}
$classname = 'Auth_RADIUS_' . $this->authtype;
if (!class_exists($classname)) {
PEAR::raiseError("Unknown Authtype, please use one of: "
."PAP, CHAP_MD5, MSCHAPv1, MSCHAPv2!", 41, PEAR_ERROR_DIE);
}
$this->radius = new $classname;
 
if (isset($options['configfile'])) {
$this->radius->setConfigfile($options['configfile']);
}
 
$servers = $options['servers'];
if (is_array($servers)) {
foreach ($servers as $server) {
$servername = $server[0];
$port = isset($server[1]) ? $server[1] : 0;
$sharedsecret = isset($server[2]) ? $server[2] : 'testing123';
$timeout = isset($server[3]) ? $server[3] : 3;
$maxtries = isset($server[4]) ? $server[4] : 3;
$this->radius->addServer($servername, $port, $sharedsecret, $timeout, $maxtries);
}
}
if (!$this->radius->start()) {
PEAR::raiseError($this->radius->getError(), 41, PEAR_ERROR_DIE);
}
}
 
// }}}
// {{{ fetchData()
 
/**
* Authenticate
*
* @param string Username
* @param string Password
* @return bool true on success, false on reject
*/
function fetchData($username, $password, $challenge = null)
{
switch($this->authtype) {
case 'CHAP_MD5':
case 'MSCHAPv1':
if (isset($challenge)) {
$this->radius->challenge = $challenge;
$this->radius->chapid = 1;
$this->radius->response = pack('H*', $password);
} else {
require_once 'Crypt/CHAP.php';
$classname = 'Crypt_' . $this->authtype;
$crpt = new $classname;
$crpt->password = $password;
$this->radius->challenge = $crpt->challenge;
$this->radius->chapid = $crpt->chapid;
$this->radius->response = $crpt->challengeResponse();
break;
}
 
case 'MSCHAPv2':
require_once 'Crypt/CHAP.php';
$crpt = new Crypt_MSCHAPv2;
$crpt->username = $username;
$crpt->password = $password;
$this->radius->challenge = $crpt->authChallenge;
$this->radius->peerChallenge = $crpt->peerChallenge;
$this->radius->chapid = $crpt->chapid;
$this->radius->response = $crpt->challengeResponse();
break;
 
default:
$this->radius->password = $password;
break;
}
 
$this->radius->username = $username;
 
$this->radius->putAuthAttributes();
$result = $this->radius->send();
if (PEAR::isError($result)) {
return false;
}
 
$this->radius->getAttributes();
// just for debugging
// $this->radius->dumpAttributes();
 
return $result;
}
 
// }}}
 
}
?>
/trunk/api/pear/Auth/Container/SOAP5.php
New file
0,0 → 1,267
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Storage driver for use against a SOAP service using PHP5 SoapClient
*
* PHP version 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Based upon Auth_Container_SOAP by Bruno Pedro <bpedro@co.sapo.pt>
* @author Marcel Oelke <puRe@rednoize.com>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: SOAP5.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
* @since File available since Release 1.4.0
*/
 
/**
* Include Auth_Container base class
*/
require_once "Auth/Container.php";
/**
* Include PEAR package for error handling
*/
require_once "PEAR.php";
 
/**
* Storage driver for fetching login data from SOAP using the PHP5 Builtin SOAP
* functions. This is a modification of the SOAP Storage driver from Bruno Pedro
* thats using the PEAR SOAP Package.
*
* This class takes one parameter (options), where
* you specify the following fields:
* * location and uri, or wsdl file
* * method to call on the SOAP service
* * usernamefield, the name of the parameter where the username is supplied
* * passwordfield, the name of the parameter where the password is supplied
* * matchpassword, whether to look for the password in the response from
* the function call or assume that no errors means user
* authenticated.
*
* See http://www.php.net/manual/en/ref.soap.php for further details
* on options for the PHP5 SoapClient which are passed through.
*
* Example usage without WSDL:
*
* <?php
*
* $options = array (
* 'wsdl' => NULL,
* 'location' => 'http://your.soap.service/endpoint',
* 'uri' => 'urn:/Your/Namespace',
* 'method' => 'checkAuth',
* 'usernamefield' => 'username',
* 'passwordfield' => 'password',
* 'matchpasswords' => false,
* '_features' => array (
* 'extra_parameter' => 'example_value',
* 'another_parameter' => 'foobar'
* )
* );
*
* $auth = new Auth('SOAP5', $options);
* $auth->start();
*
* ?>
*
* Example usage with WSDL:
*
* <?php
*
* $options = array (
* 'wsdl' => 'http://your.soap.service/wsdl',
* 'method' => 'checkAuth',
* 'usernamefield' => 'username',
* 'passwordfield' => 'password',
* 'matchpasswords' => false,
* '_features' => array (
* 'extra_parameter' => 'example_value',
* 'another_parameter' => 'foobar'
* )
* );
*
* $auth = new Auth('SOAP5', $options);
* $auth->start();
*
* ?>
*
* @category Authentication
* @package Auth
* @author Based upon Auth_Container_SOAP by Bruno Pedro <bpedro@co.sapo.pt>
* @author Marcel Oelke <puRe@rednoize.com>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.1 $
* @since Class available since Release 1.4.0
*/
class Auth_Container_SOAP5 extends Auth_Container
{
 
// {{{ properties
 
/**
* Required options for the class
* @var array
* @access private
*/
var $_requiredOptions = array(
'location',
'uri',
'method',
'usernamefield',
'passwordfield',
'wsdl',
);
 
/**
* Options for the class
* @var array
* @access private
*/
var $_options = array();
 
/**
* Optional SOAP features
* @var array
* @access private
*/
var $_features = array();
 
/**
* The SOAP response
* @var array
* @access public
*/
var $soapResponse = array();
// }}}
// {{{ Auth_Container_SOAP5()
 
/**
* Constructor of the container class
*
* @param $options, associative array with endpoint, namespace, method,
* usernamefield, passwordfield and optional features
*/
function Auth_Container_SOAP5($options)
{
$this->_setDefaults();
 
foreach ($options as $name => $value) {
$this->_options[$name] = $value;
}
 
if (!empty($this->_options['_features'])) {
$this->_features = $this->_options['_features'];
unset($this->_options['_features']);
}
}
 
// }}}
// {{{ fetchData()
 
/**
* Fetch data from SOAP service
*
* Requests the SOAP service for the given username/password
* combination.
*
* @param string Username
* @param string Password
* @return mixed Returns the SOAP response or false if something went wrong
*/
function fetchData($username, $password)
{
$result = $this->_validateOptions();
if (PEAR::isError($result))
return $result;
 
// create a SOAP client
$soapClient = new SoapClient($this->_options["wsdl"], $this->_options);
$params = array();
// first, assign the optional features
foreach ($this->_features as $fieldName => $fieldValue) {
$params[$fieldName] = $fieldValue;
}
// assign username and password ...
$params[$this->_options['usernamefield']] = $username;
$params[$this->_options['passwordfield']] = $password;
try {
$this->soapResponse = $soapClient->__soapCall($this->_options['method'], $params);
if ($this->_options['matchpasswords']) {
// check if passwords match
if ($password == $this->soapResponse[$this->_options['passwordfield']]) {
return true;
} else {
return false;
}
} else {
return true;
}
} catch (SoapFault $e) {
return PEAR::raiseError("Error retrieving authentication data. Received SOAP Fault: ".$e->faultstring, $e->faultcode);
}
}
 
// }}}
// {{{ _validateOptions()
/**
* Validate that the options passed to the container class are enough for us to proceed
*
* @access private
* @param array
*/
function _validateOptions($array)
{
if ( ( is_null($this->options['wsdl'])
&& is_null($this->options['location'])
&& is_null($this->options['uri']))
|| ( is_null($this->options['wsdl'])
&& ( is_null($this->options['location'])
|| is_null($this->options['uri'])))) {
return PEAR::raiseError('Either a WSDL file or a location/uri pair must be specified.');
}
if (is_null($this->options['method'])) {
return PEAR::raiseError('A method to call on the soap service must be specified.');
}
return true;
}
// }}}
// {{{ _setDefaults()
 
/**
* Set some default options
*
* @access private
* @return void
*/
function _setDefaults()
{
$this->options['wsdl'] = null;
$this->options['location'] = null;
$this->options['uri'] = null;
$this->options['method'] = null;
$this->options['usernamefield'] = 'username';
$this->options['passwordfield'] = 'password';
$this->options['matchpasswords'] = true;
}
 
// }}}
}
?>
/trunk/api/pear/Auth/SASL/Plain.php
New file
0,0 → 1,63
<?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@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id: Plain.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
 
/**
* Implmentation of PLAIN SASL mechanism
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
 
require_once('Auth/SASL/Common.php');
 
class Auth_SASL_Plain extends Auth_SASL_Common
{
/**
* Returns PLAIN response
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $authzid Autorization id
* @return string PLAIN Response
*/
function getResponse($authcid, $pass, $authzid = '')
{
return $authzid . chr(0) . $authcid . chr(0) . $pass;
}
}
?>
/trunk/api/pear/Auth/SASL/DigestMD5.php
New file
0,0 → 1,194
<?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@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id: DigestMD5.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
 
/**
* Implmentation of DIGEST-MD5 SASL mechanism
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
 
require_once('Auth/SASL/Common.php');
 
class Auth_SASL_DigestMD5 extends Auth_SASL_Common
{
/**
* Provides the (main) client response for DIGEST-MD5
* requires a few extra parameters than the other
* mechanisms, which are unavoidable.
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $challenge The digest challenge sent by the server
* @param string $hostname The hostname of the machine you're connecting to
* @param string $service The servicename (eg. imap, pop, acap etc)
* @param string $authzid Authorization id (username to proxy as)
* @return string The digest response (NOT base64 encoded)
* @access public
*/
function getResponse($authcid, $pass, $challenge, $hostname, $service, $authzid = '')
{
$challenge = $this->_parseChallenge($challenge);
$authzid_string = '';
if ($authzid != '') {
$authzid_string = ',authzid="' . $authzid . '"';
}
 
if (!empty($challenge)) {
$cnonce = $this->_getCnonce();
$digest_uri = sprintf('%s/%s', $service, $hostname);
$response_value = $this->_getResponseValue($authcid, $pass, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $authzid);
 
return sprintf('username="%s",realm="%s"' . $authzid_string . ',nonce="%s",cnonce="%s",nc="00000001",qop=auth,digest-uri="%s",response=%s,%d', $authcid, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']);
} else {
return PEAR::raiseError('Invalid digest challenge');
}
}
/**
* Parses and verifies the digest challenge*
*
* @param string $challenge The digest challenge
* @return array The parsed challenge as an assoc
* array in the form "directive => value".
* @access private
*/
function _parseChallenge($challenge)
{
$tokens = array();
while (preg_match('/^([a-z-]+)=("[^"]+(?<!\\\)"|[^,]+)/i', $challenge, $matches)) {
 
// Ignore these as per rfc2831
if ($matches[1] == 'opaque' OR $matches[1] == 'domain') {
$challenge = substr($challenge, strlen($matches[0]) + 1);
continue;
}
 
// Allowed multiple "realm" and "auth-param"
if (!empty($tokens[$matches[1]]) AND ($matches[1] == 'realm' OR $matches[1] == 'auth-param')) {
if (is_array($tokens[$matches[1]])) {
$tokens[$matches[1]][] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
} else {
$tokens[$matches[1]] = array($tokens[$matches[1]], preg_replace('/^"(.*)"$/', '\\1', $matches[2]));
}
 
// Any other multiple instance = failure
} elseif (!empty($tokens[$matches[1]])) {
$tokens = array();
break;
 
} else {
$tokens[$matches[1]] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
}
 
// Remove the just parsed directive from the challenge
$challenge = substr($challenge, strlen($matches[0]) + 1);
}
 
/**
* Defaults and required directives
*/
// Realm
if (empty($tokens['realm'])) {
$uname = posix_uname();
$tokens['realm'] = $uname['nodename'];
}
// Maxbuf
if (empty($tokens['maxbuf'])) {
$tokens['maxbuf'] = 65536;
}
// Required: nonce, algorithm
if (empty($tokens['nonce']) OR empty($tokens['algorithm'])) {
return array();
}
return $tokens;
}
 
/**
* Creates the response= part of the digest response
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $realm Realm as provided by the server
* @param string $nonce Nonce as provided by the server
* @param string $cnonce Client nonce
* @param string $digest_uri The digest-uri= value part of the response
* @param string $authzid Authorization id
* @return string The response= part of the digest response
* @access private
*/
function _getResponseValue($authcid, $pass, $realm, $nonce, $cnonce, $digest_uri, $authzid = '')
{
if ($authzid == '') {
$A1 = sprintf('%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce);
} else {
$A1 = sprintf('%s:%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce, $authzid);
}
$A2 = 'AUTHENTICATE:' . $digest_uri;
return md5(sprintf('%s:%s:00000001:%s:auth:%s', md5($A1), $nonce, $cnonce, md5($A2)));
}
 
/**
* Creates the client nonce for the response
*
* @return string The cnonce value
* @access private
*/
function _getCnonce()
{
if (file_exists('/dev/urandom')) {
return base64_encode(fread(fopen('/dev/urandom', 'r'), 32));
 
} elseif (file_exists('/dev/random')) {
return base64_encode(fread(fopen('/dev/random', 'r'), 32));
 
} else {
$str = '';
mt_srand((double)microtime()*10000000);
for ($i=0; $i<32; $i++) {
$str .= chr(mt_rand(0, 255));
}
return base64_encode($str);
}
}
}
?>
/trunk/api/pear/Auth/SASL/Anonymous.php
New file
0,0 → 1,71
<?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@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id: Anonymous.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
 
/**
* Implmentation of ANONYMOUS SASL mechanism
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
 
require_once('Auth/SASL/Common.php');
 
class Auth_SASL_Anonymous extends Auth_SASL_Common
{
/**
* Not much to do here except return the token supplied.
* No encoding, hashing or encryption takes place for this
* mechanism, simply one of:
* o An email address
* o An opaque string not containing "@" that can be interpreted
* by the sysadmin
* o Nothing
*
* We could have some logic here for the second option, but this
* would by no means create something interpretable.
*
* @param string $token Optional email address or string to provide
* as trace information.
* @return string The unaltered input token
*/
function getResponse($token = '')
{
return $token;
}
}
?>
/trunk/api/pear/Auth/SASL/Common.php
New file
0,0 → 1,74
<?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@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id: Common.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
 
/**
* Common functionality to SASL mechanisms
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
 
class Auth_SASL_Common
{
/**
* Function which implements HMAC MD5 digest
*
* @param string $key The secret key
* @param string $data The data to protect
* @return string The HMAC MD5 digest
*/
function _HMAC_MD5($key, $data)
{
if (strlen($key) > 64) {
$key = pack('H32', md5($key));
}
 
if (strlen($key) < 64) {
$key = str_pad($key, 64, chr(0));
}
 
$k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
$k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);
 
$inner = pack('H32', md5($k_ipad . $data));
$digest = md5($k_opad . $inner);
 
return $digest;
}
}
?>
/trunk/api/pear/Auth/SASL/CramMD5.php
New file
0,0 → 1,68
<?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@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id: CramMD5.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
 
/**
* Implmentation of CRAM-MD5 SASL mechanism
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
 
require_once('Auth/SASL/Common.php');
 
class Auth_SASL_CramMD5 extends Auth_SASL_Common
{
/**
* Implements the CRAM-MD5 SASL mechanism
* This DOES NOT base64 encode the return value,
* you will need to do that yourself.
*
* @param string $user Username
* @param string $pass Password
* @param string $challenge The challenge supplied by the server.
* this should be already base64_decoded.
*
* @return string The string to pass back to the server, of the form
* "<user> <digest>". This is NOT base64_encoded.
*/
function getResponse($user, $pass, $challenge)
{
return $user . ' ' . $this->_HMAC_MD5($pass, $challenge);
}
}
?>
/trunk/api/pear/Auth/SASL/Login.php
New file
0,0 → 1,65
<?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@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id: Login.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
 
/**
* This is technically not a SASL mechanism, however
* it's used by Net_Sieve, Net_Cyrus and potentially
* other protocols , so here is a good place to abstract
* it.
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
 
require_once('Auth/SASL/Common.php');
 
class Auth_SASL_Login extends Auth_SASL_Common
{
/**
* Pseudo SASL LOGIN mechanism
*
* @param string $user Username
* @param string $pass Password
* @return string LOGIN string
*/
function getResponse($user, $pass)
{
return sprintf('LOGIN %s %s', $user, $pass);
}
}
?>
/trunk/api/pear/Auth/HTTP.php
New file
0,0 → 1,795
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 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: Martin Jansen <mj@php.net> |
// | Rui Hirokawa <hirokawa@php.net> |
// | David Costa <gurugeek@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: HTTP.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
//
 
require_once "Auth/Auth.php";
 
define('AUTH_HTTP_NONCE_TIME_LEN', 16);
define('AUTH_HTTP_NONCE_HASH_LEN', 32);
 
// {{{ class Auth_HTTP
 
/**
* PEAR::Auth_HTTP
*
* The PEAR::Auth_HTTP class provides methods for creating an
* HTTP authentication system based on RFC-2617 using PHP.
*
* Instead of generating an HTML driven form like PEAR::Auth
* does, this class sends header commands to the clients which
* cause them to present a login box like they are e.g. used
* in Apache's .htaccess mechanism.
*
* This class requires the PEAR::Auth package.
*
* @notes The HTTP Digest Authentication part is based on
* authentication class written by Tom Pike <tom.pike@xiven.com>
*
* @author Martin Jansen <mj@php.net>
* @author Rui Hirokawa <hirokawa@php.net>
* @author David Costa <gurugeek@php.net>
* @package Auth_HTTP
* @extends Auth
* @version $Revision: 1.1 $
*/
class Auth_HTTP extends Auth
{
// {{{ properties
 
/**
* Authorization method: 'basic' or 'digest'
*
* @access public
* @var string
*/
var $authType = 'basic';
/**
* Name of the realm for Basic Authentication
*
* @access public
* @var string
* @see drawLogin()
*/
var $realm = "protected area";
 
/**
* Text to send if user hits cancel button
*
* @access public
* @var string
* @see drawLogin()
*/
var $CancelText = "Error 401 - Access denied";
 
/**
* option array
*
* @access public
* @var array
*/
var $options = array();
 
/**
* flag to indicate the nonce was stale.
*
* @access public
* @var bool
*/
var $stale = false;
 
/**
* opaque string for digest authentication
*
* @access public
* @var string
*/
var $opaque = 'dummy';
 
/**
* digest URI
*
* @access public
* @var string
*/
var $uri = '';
 
/**
* authorization info returned by the client
*
* @access public
* @var array
*/
var $auth = array();
 
/**
* next nonce value
*
* @access public
* @var string
*/
var $nextNonce = '';
 
/**
* nonce value
*
* @access public
* @var string
*/
var $nonce = '';
 
/**
* Holds a reference to the global server variable
* @var array
*/
var $server;
 
/**
* Holds a reference to the global post variable
* @var array
*/
var $post;
 
/**
* Holds a reference to the global cookie variable
* @var array
*/
var $cookie;
 
 
// }}}
// {{{ Constructor
 
/**
* Constructor
*
* @param string Type of the storage driver
* @param mixed Additional options for the storage driver
* (example: if you are using DB as the storage
* driver, you have to pass the dsn string here)
*
* @return void
*/
function Auth_HTTP($storageDriver, $options = '')
{
/* set default values for options */
$this->options = array('cryptType' => 'md5',
'algorithm' => 'MD5',
'qop' => 'auth-int,auth',
'opaquekey' => 'moo',
'noncekey' => 'moo',
'digestRealm' => 'protected area',
'forceDigestOnly' => false,
'nonceLife' => 300,
'sessionSharing' => true,
);
if (!empty($options['authType'])) {
$this->authType = strtolower($options['authType']);
}
if (is_array($options)) {
foreach($options as $key => $value) {
if (array_key_exists( $key, $this->options)) {
$this->options[$key] = $value;
}
}
if (!empty($this->options['opaquekey'])) {
$this->opaque = md5($this->options['opaquekey']);
}
}
$this->Auth($storageDriver, $options);
}
// }}}
// {{{ assignData()
 
/**
* Assign values from $PHP_AUTH_USER and $PHP_AUTH_PW or 'Authorization' header
* to internal variables and sets the session id based
* on them
*
* @access public
* @return void
*/
function assignData()
{
if (method_exists($this, '_importGlobalVariable')) {
$this->server = &$this->_importGlobalVariable('server');
}
if ($this->authType == 'basic') {
if (!empty($this->server['PHP_AUTH_USER'])) {
$this->username = $this->server['PHP_AUTH_USER'];
}
if (!empty($this->server['PHP_AUTH_PW'])) {
$this->password = $this->server['PHP_AUTH_PW'];
}
/**
* Try to get authentication information from IIS
*/
if (empty($this->username) && empty($this->password)) {
if (!empty($this->server['HTTP_AUTHORIZATION'])) {
list($this->username, $this->password) =
explode(':', base64_decode(substr($this->server['HTTP_AUTHORIZATION'], 6)));
}
}
} elseif ($this->authType == 'digest') {
$this->username = '';
$this->password = '';
 
$this->digest_header = null;
if (!empty($this->server['PHP_AUTH_DIGEST'])) {
$this->digest_header = substr($this->server['PHP_AUTH_DIGEST'],
strpos($this->server['PHP_AUTH_DIGEST'],' ')+1);
} else {
$headers = getallheaders();
if(isset($headers['Authorization']) && !empty($headers['Authorization'])) {
$this->digest_header = substr($headers['Authorization'],
strpos($headers['Authorization'],' ')+1);
}
}
 
if($this->digest_header) {
$authtemp = explode(',', $this->digest_header);
$auth = array();
foreach($authtemp as $key => $value) {
$value = trim($value);
if(strpos($value,'=') !== false) {
$lhs = substr($value,0,strpos($value,'='));
$rhs = substr($value,strpos($value,'=')+1);
if(substr($rhs,0,1) == '"' && substr($rhs,-1,1) == '"') {
$rhs = substr($rhs,1,-1);
}
$auth[$lhs] = $rhs;
}
}
}
if (!isset($auth['uri']) || !isset($auth['realm'])) {
return;
}
if ($this->selfURI() == $auth['uri']) {
$this->uri = $auth['uri'];
if (substr($headers['Authorization'],0,7) == 'Digest ') {
$this->authType = 'digest';
 
if (!isset($auth['nonce']) || !isset($auth['username']) ||
!isset($auth['response']) || !isset($auth['qop']) ||
!isset($auth['nc']) || !isset($auth['cnonce'])){
return;
}
 
if ($auth['qop'] != 'auth' && $auth['qop'] != 'auth-int') {
return;
}
$this->stale = $this->_judgeStale($auth['nonce']);
 
if ($this->nextNonce == false) {
return;
}
 
$this->username = $auth['username'];
$this->password = $auth['response'];
$this->auth['nonce'] = $auth['nonce'];
$this->auth['qop'] = $auth['qop'];
$this->auth['nc'] = $auth['nc'];
$this->auth['cnonce'] = $auth['cnonce'];
 
if (isset($auth['opaque'])) {
$this->auth['opaque'] = $auth['opaque'];
}
} elseif (substr($headers['Authorization'],0,6) == 'Basic ') {
if ($this->options['forceDigestOnly']) {
return; // Basic authentication is not allowed.
}
$this->authType = 'basic';
list($username, $password) =
explode(':',base64_decode(substr($headers['Authorization'],6)));
$this->username = $username;
$this->password = $password;
}
}
} else {
return PEAR::raiseError('authType is invalid.');
}
 
if ($this->options['sessionSharing'] &&
isset($this->username) && isset($this->password)) {
session_id(md5('Auth_HTTP' . $this->username . $this->password));
}
/**
* set sessionName for AUTH, so that the sessionName is different
* for distinct realms
*/
$this->_sessionName = "_authhttp".md5($this->realm);
}
 
// }}}
// {{{ login()
 
/**
* Login function
*
* @access private
* @return void
*/
function login()
{
$login_ok = false;
if (method_exists($this, '_loadStorage')) {
$this->_loadStorage();
}
$this->storage->_auth_obj->_sessionName =& $this->_sessionName;
 
/**
* When the user has already entered a username,
* we have to validate it.
*/
if (!empty($this->username) && !empty($this->password)) {
if ($this->authType == 'basic' && !$this->options['forceDigestOnly']) {
if (true === $this->storage->fetchData($this->username, $this->password)) {
$login_ok = true;
}
} else { /* digest authentication */
 
if (!$this->getAuth() || $this->getAuthData('a1') == null) {
/*
* note:
* - only PEAR::DB is supported as container.
* - password should be stored in container as plain-text
* (if $options['cryptType'] == 'none') or
* A1 hashed form (md5('username:realm:password'))
* (if $options['cryptType'] == 'md5')
*/
$dbs = $this->storage;
if (!DB::isConnection($dbs->db)) {
$dbs->_connect($dbs->options['dsn']);
}
$query = 'SELECT '.$dbs->options['passwordcol']." FROM ".$dbs->options['table'].
' WHERE '.$dbs->options['usernamecol']." = '".
$dbs->db->quoteString($this->username)."' ";
$pwd = $dbs->db->getOne($query); // password stored in container.
if (DB::isError($pwd)) {
return PEAR::raiseError($pwd->getMessage(), $pwd->getCode());
}
if ($this->options['cryptType'] == 'none') {
$a1 = md5($this->username.':'.$this->options['digestRealm'].':'.$pwd);
} else {
$a1 = $pwd;
}
$this->setAuthData('a1', $a1, true);
} else {
$a1 = $this->getAuthData('a1');
}
$login_ok = $this->validateDigest($this->password, $a1);
if ($this->nextNonce == false) {
$login_ok = false;
}
}
if (!$login_ok && is_callable($this->loginFailedCallback)) {
call_user_func($this->loginFailedCallback,$this->username, $this);
}
}
if (!empty($this->username) && $login_ok) {
$this->setAuth($this->username);
if (is_callable($this->loginCallback)) {
call_user_func($this->loginCallback,$this->username, $this);
}
}
/**
* If the login failed or the user entered no username,
* output the login screen again.
*/
if (!empty($this->username) && !$login_ok) {
$this->status = AUTH_WRONG_LOGIN;
}
if ((empty($this->username) || !$login_ok) && $this->showLogin) {
$this->drawLogin($this->storage->activeUser);
return;
}
 
if (!empty($this->username) && $login_ok && $this->authType == 'digest'
&& $this->auth['qop'] == 'auth') {
$this->authenticationInfo();
}
}
// }}}
// {{{ drawLogin()
 
/**
* Launch the login box
*
* @param string $username Username
* @return void
* @access private
*/
function drawLogin($username = "")
{
/**
* Send the header commands
*/
if ($this->authType == 'basic') {
header("WWW-Authenticate: Basic realm=\"".$this->realm."\"");
header('HTTP/1.0 401 Unauthorized');
} else if ($this->authType == 'digest') {
$this->nonce = $this->_getNonce();
 
$wwwauth = 'WWW-Authenticate: Digest ';
$wwwauth .= 'qop="'.$this->options['qop'].'", ';
$wwwauth .= 'algorithm='.$this->options['algorithm'].', ';
$wwwauth .= 'realm="'.$this->options['digestRealm'].'", ';
$wwwauth .= 'nonce="'.$this->nonce.'", ';
if ($this->stale) {
$wwwauth .= 'stale=true, ';
}
if (!empty($this->opaque)) {
$wwwauth .= 'opaque="'.$this->opaque.'"' ;
}
$wwwauth .= "\r\n";
if (!$this->options['forceDigestOnly']) {
$wwwauth .= 'WWW-Authenticate: Basic realm="'.$this->realm.'"';
}
header($wwwauth);
header('HTTP/1.0 401 Unauthorized');
}
 
/**
* This code is only executed if the user hits the cancel
* button or if he enters wrong data 3 times.
*/
if ($this->stale) {
echo 'Stale nonce value, please re-authenticate.';
} else {
echo $this->CancelText;
}
exit;
}
 
// }}}
// {{{ setRealm()
 
/**
* Set name of the current realm
*
* @access public
* @param string $realm Name of the realm
* @param string $digestRealm Name of the realm for digest authentication
* @return void
*/
function setRealm($realm, $digestRealm = '')
{
$this->realm = $realm;
if (!empty($digestRealm)) {
$this->options['digestRealm'] = $digestRealm;
}
}
 
// }}}
// {{{ setCancelText()
 
/**
* Set the text to send if user hits the cancel button
*
* @access public
* @param string $text Text to send
* @return void
*/
function setCancelText($text)
{
$this->CancelText = $text;
}
 
// }}}
// {{{ validateDigest()
/**
* judge if the client response is valid.
*
* @access private
* @param string $response client response
* @param string $a1 password or hashed password stored in container
* @return bool true if success, false otherwise
*/
function validateDigest($response, $a1)
{
if (method_exists($this, '_importGlobalVariable')) {
$this->server = &$this->_importGlobalVariable('server');
}
 
$a2unhashed = $this->server['REQUEST_METHOD'].":".$this->selfURI();
if($this->auth['qop'] == 'auth-int') {
if(isset($GLOBALS["HTTP_RAW_POST_DATA"])) {
// In PHP < 4.3 get raw POST data from this variable
$body = $GLOBALS["HTTP_RAW_POST_DATA"];
} else if($lines = @file('php://input')) {
// In PHP >= 4.3 get raw POST data from this file
$body = implode("\n", $lines);
} else {
if (method_exists($this, '_importGlobalVariable')) {
$this->post = &$this->_importGlobalVariable('post');
}
$body = '';
foreach($this->post as $key => $value) {
if($body != '') $body .= '&';
$body .= rawurlencode($key) . '=' . rawurlencode($value);
}
}
 
$a2unhashed .= ':'.md5($body);
}
$a2 = md5($a2unhashed);
$combined = $a1.':'.
$this->auth['nonce'].':'.
$this->auth['nc'].':'.
$this->auth['cnonce'].':'.
$this->auth['qop'].':'.
$a2;
$expectedResponse = md5($combined);
if(!isset($this->auth['opaque']) || $this->auth['opaque'] == $this->opaque) {
if($response == $expectedResponse) { // password is valid
if(!$this->stale) {
return true;
} else {
$this->drawLogin();
}
}
}
return false;
}
// }}}
// {{{ _judgeStale()
/**
* judge if nonce from client is stale.
*
* @access private
* @param string $nonce nonce value from client
* @return bool stale
*/
function _judgeStale($nonce)
{
$stale = false;
if(!$this->_decodeNonce($nonce, $time, $hash_cli)) {
$this->nextNonce = false;
$stale = true;
return $stale;
}
 
if ($time < time() - $this->options['nonceLife']) {
$this->nextNonce = $this->_getNonce();
$stale = true;
} else {
$this->nextNonce = $nonce;
}
 
return $stale;
}
// }}}
// {{{ _nonceDecode()
/**
* decode nonce string
*
* @access private
* @param string $nonce nonce value from client
* @param string $time decoded time
* @param string $hash decoded hash
* @return bool false if nonce is invalid
*/
function _decodeNonce($nonce, &$time, &$hash)
{
if (method_exists($this, '_importGlobalVariable')) {
$this->server = &$this->_importGlobalVariable('server');
}
 
if (strlen($nonce) != AUTH_HTTP_NONCE_TIME_LEN + AUTH_HTTP_NONCE_HASH_LEN) {
return false;
}
 
$time = base64_decode(substr($nonce, 0, AUTH_HTTP_NONCE_TIME_LEN));
$hash_cli = substr($nonce, AUTH_HTTP_NONCE_TIME_LEN, AUTH_HTTP_NONCE_HASH_LEN);
 
$hash = md5($time . $this->server['HTTP_USER_AGENT'] . $this->options['noncekey']);
 
if ($hash_cli != $hash) {
return false;
}
return true;
}
 
// }}}
// {{{ _getNonce()
/**
* return nonce to detect timeout
*
* @access private
* @return string nonce value
*/
function _getNonce()
{
if (method_exists($this, '_importGlobalVariable')) {
$this->server = &$this->_importGlobalVariable('server');
}
 
$time = time();
$hash = md5($time . $this->server['HTTP_USER_AGENT'] . $this->options['noncekey']);
 
return base64_encode($time) . $hash;
}
 
// }}}
// {{{ authenticationInfo()
/**
* output HTTP Authentication-Info header
*
* @notes md5 hash of contents is required if 'qop' is 'auth-int'
*
* @access private
* @param string MD5 hash of content
*/
function authenticationInfo($contentMD5 = '') {
if($this->getAuth() && ($this->getAuthData('a1') != null)) {
$a1 = $this->getAuthData('a1');
 
// Work out authorisation response
$a2unhashed = ":".$this->selfURI();
if($this->auth['qop'] == 'auth-int') {
$a2unhashed .= ':'.$contentMD5;
}
$a2 = md5($a2unhashed);
$combined = $a1.':'.
$this->nonce.':'.
$this->auth['nc'].':'.
$this->auth['cnonce'].':'.
$this->auth['qop'].':'.
$a2;
// Send authentication info
$wwwauth = 'Authentication-Info: ';
if($this->nonce != $this->nextNonce) {
$wwwauth .= 'nextnonce="'.$this->nextNonce.'", ';
}
$wwwauth .= 'qop='.$this->auth['qop'].', ';
$wwwauth .= 'rspauth="'.md5($combined).'", ';
$wwwauth .= 'cnonce="'.$this->auth['cnonce'].'", ';
$wwwauth .= 'nc='.$this->auth['nc'].'';
header($wwwauth);
}
}
// }}}
// {{{ setOption()
/**
* set authentication option
*
* @access public
* @param mixed $name key of option
* @param mixed $value value of option
* @return void
*/
function setOption($name, $value = null)
{
if (is_array($name)) {
foreach($name as $key => $value) {
if (array_key_exists( $key, $this->options)) {
$this->options[$key] = $value;
}
}
} else {
if (array_key_exists( $name, $this->options)) {
$this->options[$name] = $value;
}
}
}
 
// }}}
// {{{ getOption()
/**
* get authentication option
*
* @access public
* @param string $name key of option
* @return mixed option value
*/
function getOption($name)
{
if (array_key_exists( $name, $this->options)) {
return $this->options[$name];
}
if ($name == 'CancelText') {
return $this->CancelText;
}
if ($name == 'Realm') {
return $this->realm;
}
return false;
}
 
// }}}
// {{{ selfURI()
/**
* get self URI
*
* @access public
* @return string self URI
*/
function selfURI()
{
if (method_exists($this, '_importGlobalVariable')) {
$this->server = &$this->_importGlobalVariable('server');
}
 
if (preg_match("/MSIE/",$this->server['HTTP_USER_AGENT'])) {
// query string should be removed for MSIE
$uri = preg_replace("/^(.*)\?/","\\1",$this->server['REQUEST_URI']);
} else {
$uri = $this->server['REQUEST_URI'];
}
return $uri;
}
 
// }}}
 
}
 
// }}}
 
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
*/
?>
/trunk/api/pear/Auth/Auth.php
1,5 → 1,30
<?php
 
include_once('Auth.php');
 
?>
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Provide compatibility with previous Auth include location.
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Martin Jansen <mj@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: Auth.php,v 1.2 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @deprecated File deprecated since Release 1.2.0
*/
 
/**
* Include Auth package
*/
require_once 'Auth.php';
 
?>
/trunk/api/pear/Auth/Container.php
1,177 → 1,224
<?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: Martin Jansen <mj@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Container.php,v 1.1 2005-03-30 08:50:33 jpm Exp $
//
 
define("AUTH_METHOD_NOT_SUPPORTED", -4);
 
/**
* Storage class for fetching login data
*
* @author Martin Jansen <mj@php.net>
* @package Auth
*/
class Auth_Container
{
 
/**
* User that is currently selected from the storage container.
*
* @access public
*/
var $activeUser = "";
 
// {{{ Constructor
 
/**
* Constructor
*
* Has to be overwritten by each storage class
*
* @access public
*/
function Auth_Container()
{
}
 
// }}}
// {{{ fetchData()
 
/**
* Fetch data from storage container
*
* Has to be overwritten by each storage class
*
* @access public
*/
function fetchData()
{
}
 
// }}}
// {{{ verifyPassword()
 
/**
* Crypt and verfiy the entered password
*
* @param string Entered password
* @param string Password from the data container (usually this password
* is already encrypted.
* @param string Type of algorithm with which the password from
* the container has been crypted. (md5, crypt etc.)
* Defaults to "md5".
* @return bool True, if the passwords match
*/
function verifyPassword($password1, $password2, $cryptType = "md5")
{
switch ($cryptType) {
case "crypt" :
return (($password2 == "**" . $password1) ||
(crypt($password1, $password2) == $password2)
);
break;
 
case "none" :
return ($password1 == $password2);
break;
 
case "md5" :
return (md5($password1) == $password2);
break;
 
default :
if (function_exists($cryptType)) {
return ($cryptType($password1) == $password2);
}
else if (method_exists($this,$cryptType)) {
return ($this->$cryptType($password1) == $password2);
} else {
return false;
}
break;
}
}
 
// }}}
// {{{ listUsers()
 
/**
* List all users that are available from the storage container
*/
function listUsers()
{
return AUTH_METHOD_NOT_SUPPORTED;
}
 
/**
* Returns a user assoc array
*
* Containers which want should overide this
*
* @param string The username
*/
function getUser($username)
{
$users = $this->listUsers();
if($users === AUTH_METHOD_NOT_SUPPORTED){
return(AUTH_METHOD_NOT_SUPPORTED);
}
for($i=0;$c = count($users),$i<$c;$i++){
if($users[$i]['username'] == $username){
return($users[$i]);
}
}
return(false);
}
 
// }}}
// {{{ addUser()
 
/**
* Add a new user to the storage container
*
* @param string Username
* @param string Password
* @param array Additional information
*
* @return boolean
*/
function addUser($username, $password, $additional=null)
{
return AUTH_METHOD_NOT_SUPPORTED;
}
 
// }}}
// {{{ removeUser()
 
/**
* Remove user from the storage container
*
* @param string Username
*/
function removeUser($username)
{
return AUTH_METHOD_NOT_SUPPORTED;
}
 
// }}}
 
}
?>
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Auth_Container Base Class
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Martin Jansen <mj@php.net>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: Container.php,v 1.2 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
*/
 
/**
* Storage class for fetching login data
*
* @category Authentication
* @package Auth
* @author Martin Jansen <mj@php.net>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.2 $
* @link http://pear.php.net/package/Auth
*/
class Auth_Container
{
 
// {{{ properties
 
/**
* User that is currently selected from the storage container.
*
* @access public
*/
var $activeUser = "";
 
// }}}
// {{{ Auth_Container() [constructor]
 
/**
* Constructor
*
* Has to be overwritten by each storage class
*
* @access public
*/
function Auth_Container()
{
}
 
// }}}
// {{{ fetchData()
 
/**
* Fetch data from storage container
*
* Has to be overwritten by each storage class
*
* @access public
*/
function fetchData($username, $password, $isChallengeResponse=false)
{
}
 
// }}}
// {{{ verifyPassword()
 
/**
* Crypt and verfiy the entered password
*
* @param string Entered password
* @param string Password from the data container (usually this password
* is already encrypted.
* @param string Type of algorithm with which the password from
* the container has been crypted. (md5, crypt etc.)
* Defaults to "md5".
* @return bool True, if the passwords match
*/
function verifyPassword($password1, $password2, $cryptType = "md5")
{
switch ($cryptType) {
case "crypt" :
return ((string)crypt($password1, $password2) === (string)$password2);
break;
case "none" :
case "" :
return ((string)$password1 === (string)$password2);
break;
case "md5" :
return ((string)md5($password1) === (string)$password2);
break;
default :
if (function_exists($cryptType)) {
return ((string)$cryptType($password1) === (string)$password2);
} elseif (method_exists($this,$cryptType)) {
return ((string)$this->$cryptType($password1) === (string)$password2);
} else {
return false;
}
break;
}
}
 
// }}}
// {{{ supportsChallengeResponse()
/**
* Returns true if the container supports Challenge Response
* password authentication
*/
function supportsChallengeResponse()
{
return(false);
}
 
// }}}
// {{{ getCryptType()
/**
* Returns the crypt current crypt type of the container
*
* @return string
*/
function getCryptType()
{
return('');
}
 
// }}}
// {{{ listUsers()
 
/**
* List all users that are available from the storage container
*/
function listUsers()
{
return AUTH_METHOD_NOT_SUPPORTED;
}
 
// }}}
// {{{ getUser()
 
/**
* Returns a user assoc array
*
* Containers which want should overide this
*
* @param string The username
*/
function getUser($username)
{
$users = $this->listUsers();
if ($users === AUTH_METHOD_NOT_SUPPORTED) {
return AUTH_METHOD_NOT_SUPPORTED;
}
for ($i=0; $c = count($users), $i<$c; $i++) {
if ($users[$i]['username'] == $username) {
return $users[$i];
}
}
return false;
}
 
// }}}
// {{{ addUser()
 
/**
* Add a new user to the storage container
*
* @param string Username
* @param string Password
* @param array Additional information
*
* @return boolean
*/
function addUser($username, $password, $additional=null)
{
return AUTH_METHOD_NOT_SUPPORTED;
}
 
// }}}
// {{{ removeUser()
 
/**
* Remove user from the storage container
*
* @param string Username
*/
function removeUser($username)
{
return AUTH_METHOD_NOT_SUPPORTED;
}
 
// }}}
// {{{ changePassword()
 
/**
* Change password for user in the storage container
*
* @param string Username
* @param string The new password
*/
function changePassword($username, $password)
{
return AUTH_METHOD_NOT_SUPPORTED;
}
 
// }}}
 
}
 
?>
/trunk/api/pear/Auth/SASL.php
New file
0,0 → 1,98
<?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@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id: SASL.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
 
/**
* Client implementation of various SASL mechanisms
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
 
require_once('PEAR.php');
 
class Auth_SASL
{
/**
* Factory class. Returns an object of the request
* type.
*
* @param string $type One of: Anonymous
* Plain
* CramMD5
* DigestMD5
* Types are not case sensitive
*/
function &factory($type)
{
switch (strtolower($type)) {
case 'anonymous':
$filename = 'Auth/SASL/Anonymous.php';
$classname = 'Auth_SASL_Anonymous';
break;
 
case 'login':
$filename = 'Auth/SASL/Login.php';
$classname = 'Auth_SASL_Login';
break;
 
case 'plain':
$filename = 'Auth/SASL/Plain.php';
$classname = 'Auth_SASL_Plain';
break;
 
case 'crammd5':
$filename = 'Auth/SASL/CramMD5.php';
$classname = 'Auth_SASL_CramMD5';
break;
 
case 'digestmd5':
$filename = 'Auth/SASL/DigestMD5.php';
$classname = 'Auth_SASL_DigestMD5';
break;
 
default:
return PEAR::raiseError('Invalid SASL mechanism type');
break;
}
 
require_once($filename);
return new $classname();
}
}
 
?>
/trunk/api/pear/Auth/Controller.php
New file
0,0 → 1,302
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* Auth Controller
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Yavor Shahpasov <yavo@netsmart.com.cy>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: Controller.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
* @since File available since Release 1.3.0
*/
 
/**
* Controlls access to a group of php access
* and redirects to a predefined login page as
* needed
*
* In all pages
* <code>
* include_once('Auth.php');
* include_once('Auth/Controller.php');
* $_auth = new Auth('File', 'passwd');
* $authController = new Auth_Controller($_auth, 'login.php', 'index.php');
* $authController->start();
* </code>
*
* In login.php
* <code>
* include_once('Auth.php');
* include_once('Auth/Controller.php');
* $_auth = new Auth('File', 'passwd');
* $authController = new Auth_Controller($_auth, 'login.php', 'index.php');
* $authController->start();
* if( $authController->isAuthorised() ){
* $authController->redirectBack();
* }
* </code>
*
* @category Authentication
* @author Yavor Shahpasov <yavo@netsmart.com.cy>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.1 $
* @link http://pear.php.net/package/Auth
* @since Class available since Release 1.3.0
*/
class Auth_Controller
{
 
// {{{ properties
 
/**
* The Auth instance this controller is managing
*
* @var object Auth
*/
var $auth = null;
/**
* The login URL
* @var string
* */
var $login = null;
/**
* The default index page to use when the caller page is not set
*
* @var string
*/
var $default = null;
/**
* If this is set to true after a succesfull login the
* Auth_Controller::redirectBack() is invoked automatically
*
* @var boolean
*/
var $autoRedirectBack = false;
 
// }}}
// {{{ Auth_Controller() [constructor]
/**
* Constructor
*
* @param Auth An auth instance
* @param string The login page
* @param string The default page to go to if return page is not set
* @param array Some rules about which urls need to be sent to the login page
* @return void
* @todo Add a list of urls which need redirection
*/
function Auth_Controller(&$auth_obj, $login='login.php', $default='index.php', $accessList=array())
{
$this->auth =& $auth_obj;
$this->_loginPage = $login;
$this->_defaultPage = $default;
@session_start();
if (!empty($_GET['return']) && $_GET['return'] && !strstr($_GET['return'], $this->_loginPage)) {
$this->auth->setAuthData('returnUrl', $_GET['return']);
}
 
if(!empty($_GET['authstatus']) && $this->auth->status == '') {
$this->auth->status = $_GET['authstatus'];
}
}
 
// }}}
// {{{ setAutoRedirectBack()
/**
* Enables auto redirection when login is done
*
* @param bool Sets the autoRedirectBack flag to this
* @see Auth_Controller::autoRedirectBack
* @return void
*/
function setAutoRedirectBack($flag = true)
{
$this->autoRedirectBack = $flag;
}
 
// }}}
// {{{ redirectBack()
/**
* Redirects Back to the calling page
*
* @return void
*/
function redirectBack()
{
// If redirectback go there
// else go to the default page
$returnUrl = $this->auth->getAuthData('returnUrl');
if(!$returnUrl) {
$returnUrl = $this->_defaultPage;
}
// Add some entropy to the return to make it unique
// avoind problems with cached pages and proxies
if(strpos($returnUrl, '?') === false) {
$returnUrl .= '?';
}
$returnUrl .= uniqid('');
 
// Track the auth status
if($this->auth->status != '') {
$url .= '&authstatus='.$this->auth->status;
}
header('Location:'.$returnUrl);
print("You could not be redirected to <a href=\"$returnUrl\">$returnUrl</a>");
}
 
// }}}
// {{{ redirectLogin()
/**
* Redirects to the login Page if not authorised
*
* put return page on the query or in auth
*
* @return void
*/
function redirectLogin()
{
// Go to the login Page
// For Auth, put some check to avoid infinite redirects, this should at least exclude
// the login page
$url = $this->_loginPage;
if(strpos($url, '?') === false) {
$url .= '?';
}
 
if(!strstr($_SERVER['PHP_SELF'], $this->_loginPage)) {
$url .= 'return='.urlencode($_SERVER['PHP_SELF']);
}
 
// Track the auth status
if($this->auth->status != '') {
$url .= '&authstatus='.$this->auth->status;
}
 
header('Location:'.$url);
print("You could not be redirected to <a href=\"$url\">$url</a>");
}
 
// }}}
// {{{ start()
/**
* Starts the Auth Procedure
*
* If the page requires login the user is redirected to the login page
* otherwise the Auth::start is called to initialize Auth
*
* @return void
* @todo Implement an access list which specifies which urls/pages need login and which do not
*/
function start()
{
// Check the accessList here
// ACL should be a list of urls with allow/deny
// If allow set allowLogin to false
// Some wild card matching should be implemented ?,*
if(!strstr($_SERVER['PHP_SELF'], $this->_loginPage) && !$this->auth->checkAuth()) {
$this->redirectLogin();
} else {
$this->auth->start();
// Logged on and on login page
if(strstr($_SERVER['PHP_SELF'], $this->_loginPage) && $this->auth->checkAuth()){
$this->autoRedirectBack ?
$this->redirectBack() :
null ;
}
}
}
 
// }}}
// {{{ isAuthorised()
/**
* Checks is the user is logged on
* @see Auth::checkAuth()
*/
function isAuthorised()
{
return($this->auth->checkAuth());
}
 
// }}}
// {{{ checkAuth()
 
/**
* Proxy call to auth
* @see Auth::checkAuth()
*/
function checkAuth()
{
return($this->auth->checkAuth());
}
 
// }}}
// {{{ logout()
 
/**
* Proxy call to auth
* @see Auth::logout()
*/
function logout()
{
return($this->auth->logout());
}
 
// }}}
// {{{ getUsername()
 
/**
* Proxy call to auth
* @see Auth::getUsername()
*/
function getUsername()
{
return($this->auth->getUsername());
}
 
// }}}
// {{{ getStatus()
 
/**
* Proxy call to auth
* @see Auth::getStatus()
*/
function getStatus()
{
return($this->auth->getStatus());
}
 
// }}}
 
}
 
?>
/trunk/api/pear/Auth/PrefManager.php
New file
0,0 → 1,426
<?php
require_once("DB.php");
 
/**
* A simple preference manager, takes userid, preference name pairs and returns the value
* of that preference.
*
* CREATE TABLE `preferences` (
* `user_id` varchar( 255 ) NOT NULL default '',
* `pref_id` varchar( 32 ) NOT NULL default '',
* `pref_value` longtext NOT NULL ,
* PRIMARY KEY ( `user_id` , `pref_id` )
* )
*
* @author Jon Wood <jon@jellybob.co.uk>
* @package Auth_PrefManager
* @category Authentication
*/
class Auth_PrefManager
{
/**
* The database object.
* @var object
* @access private
*/
var $_db;
 
/**
* The user name to get preferences from if the user specified doesn't
* have that preference set.
* @var string
* @access private
*/
var $_defaultUser = "__default__";
 
/**
* Should we search for default values, or just fail when we find out that
* the specified user didn't have it set.
*
* @var bool
* @access private
*/
var $_returnDefaults = true;
 
/**
* The table containing the preferences.
* @var string
* @access private
*/
var $_table = "preferences";
 
/**
* The column containing user ids.
* @var string
* @access private
*/
var $_userColumn = "user_id";
 
/**
* The column containing preference names.
* @var string
* @access private
*/
var $_nameColumn = "pref_id";
 
/**
* The column containing preference values.
* @var string
* @access private
*/
var $_valueColumn = "pref_value";
 
/**
* The quoted value column.
* @var string
* @access private
*/
var $_valueColumnQuoted = "pref_value";
/**
* The session variable that the cache array is stored in.
* @var string
* @access private
*/
var $_cacheName = "prefCache";
 
/**
* The last error given.
* @var string
* @access private
*/
var $_lastError;
 
/**
* Defines whether the cache should be used or not.
* @var bool
* @access private
*/
var $_useCache = true;
/**
* Defines whether values should be serialized before saving.
* @var bool
* @access private
*/
var $_serialize = false;
/**
* Constructor
*
* Options:
* table: The table to get prefs from. [preferences]
* userColumn: The field name to search for userid's [user_id]
* nameColumn: The field name to search for preference names [pref_name]
* valueColumn: The field name to search for preference values [pref_value]
* defaultUser: The userid assigned to default values [__default__]
* cacheName: The name of cache in the session variable ($_SESSION[cacheName]) [prefsCache]
* useCache: Whether or not values should be cached.
* serialize: Should preference values be serialzed before saving?
*
* @param string $dsn The DSN of the database connection to make, or a DB object.
* @param array $properties An array of properties to set.
* @param string $defaultUser The default user to manage for.
* @return bool Success or failure.
* @access public
*/
function Auth_PrefManager($dsn, $properties = NULL)
{
// Connect to the database.
if (isset($dsn)) {
if (is_string($dsn)) {
$this->_db = DB::Connect($dsn);
if (DB::isError($this->_db)) {
$this->_lastError = "DB Error: ".$this->_db->getMessage();
}
} else if (is_subclass_of($dsn, 'db_common')) {
$this->_db = &$dsn;
} else {
$this->_lastError = "Invalid DSN specified.";
return false;
}
} else {
$this->_lastError = "No DSN specified.";
return false;
}
 
if (is_array($properties)) {
if (isset($properties["table"])) { $this->_table = $this->_db->quoteIdentifier($properties["table"]); }
if (isset($properties["userColumn"])) { $this->_userColumn = $this->_db->quoteIdentifier($properties["userColumn"]); }
if (isset($properties["nameColumn"])) { $this->_nameColumn = $this->_db->quoteIdentifier($properties["nameColumn"]); }
if (isset($properties["valueColumn"])) { $this->_valueColumn = $properties["valueColumn"]; }
if (isset($properties["valueColumn"])) { $this->_valueColumnQuoted = $this->_db->quoteIdentifier($properties["valueColumn"]); }
if (isset($properties["defaultUser"])) { $this->_defaultUser = $properties["defaultUser"]; }
if (isset($properties["cacheName"])) { $this->_cacheName = $properties["cacheName"]; }
if (isset($properties["useCache"])) { $this->_useCache = $properties["useCache"]; }
if (isset($properties["serialize"])) { $this->_serialize = $properties["serialize"]; }
}
 
return true;
}
 
function setReturnDefaults($returnDefaults = true)
{
if (is_bool($returnDefaults)) {
$this->_returnDefaults = $returnDefaults;
}
}
 
/**
* Sets whether the cache should be used.
*
* @param bool $use Should the cache be used.
* @access public
*/
function useCache($use = true)
{
$this->_useCache = $use;
}
/**
* Cleans out the cache.
*
* @access public
*/
function clearCache()
{
unset($_SESSION[$this->_cacheName]);
}
 
/**
* Get a preference for the specified user, or, if returning default values
* is enabled, the default.
*
* @param string $user_id The user to get the preference for.
* @param string $pref_id The preference to get.
* @param bool $showDefaults Should default values be searched (overrides the global setting).
* @return mixed The value if it's found, or NULL if it isn't.
* @access public
*/
function getPref($user_id, $pref_id, $showDefaults = true)
{
if (isset($_SESSION[$this->_cacheName][$user_id][$pref_id]) && $this->_useCache) {
// Value is cached for the specified user, so give them the cached copy.
return $_SESSION[$this->_cacheName][$user_id][$pref_id];
} else {
// Not cached, search the database for this user's preference.
$query = sprintf("SELECT * FROM %s WHERE %s=%s AND %s=%s", $this->_table,
$this->_userColumn,
$this->_db->quote($user_id),
$this->_nameColumn,
$this->_db->quote($pref_id));
$result = $this->_db->query($query);
if (DB::isError($result)) {
// Ouch! The query failed!
$this->_lastError = "DB Error: ".$result->getMessage();
return NULL;
} else if ($result->numRows()) {
// The query found a value, so we can cache that, and then return it.
$row = $result->fetchRow(DB_FETCHMODE_ASSOC);
$_SESSION[$this->_cacheName][$user_id][$pref_id] = $this->_unpack($row[$this->_valueColumn]);
return $_SESSION[$this->_cacheName][$user_id][$pref_id];
} else if ($this->_returnDefaults && $showDefaults) {
// I was doing this with a call to getPref again, but it threw things into an
// infinite loop if the default value didn't exist. If you can fix that, it would
// be great ;)
if (isset($_SESSION[$this->_cacheName][$this->_defaultUser][$pref_id]) && $this->_useCache) {
$_SESSION[$this->_cacheName][$user_id][$pref_id] = $_SESSION[$this->_cacheName][$this->_defaultUser][$pref_id];
return $_SESSION[$this->_cacheName][$this->_defaultUser][$pref_id];
} else {
$query = sprintf("SELECT * FROM %s WHERE %s=%s AND %s=%s", $this->_table,
$this->_userColumn,
$this->_db->quote($this->_defaultUser),
$this->_nameColumn,
$this->_db->quote($pref_id));
$result = $this->_db->query($query);
if (DB::isError($result)) {
$this->_lastError = "DB Error: ".$result->getMessage();
return NULL;
} else {
if ($result->numRows()) {
$row = $result->fetchRow(DB_FETCHMODE_ASSOC);
$_SESSION[$this->_cacheName][$this->_defaultUser][$pref_id] = $this->_unpack($row[$this->_valueColumn]);
$_SESSION[$this->_cacheName][$user_id][$pref_id] = $_SESSION[$this->_cacheName][$this->_defaultUser][$pref_id];
return $_SESSION[$this->_cacheName][$user_id][$pref_id];
} else {
return NULL;
}
}
}
} else {
// We've used up all the resources we're allowed to search, so return a NULL.
return NULL;
}
}
}
 
/**
* A shortcut function for getPref($this->_defaultUser, $pref_id, $value),
* useful if you have a logged in user, but want to get defaults anyway.
*
* @param string $pref_id The name of the preference to get.
* @return mixed The value if it's found, or NULL if it isn't.
* @access public
*/
function getDefaultPref($pref_id)
{
return $this->getPref($this->_defaultUser, $pref_id);
}
 
/**
* Set a preference for the specified user.
*
* @param string $user_id The user to set for.
* @param string $pref_id The preference to set.
* @param mixed $value The value it should be set to.
* @return bool Sucess or failure.
* @access public
*/
function setPref($user_id, $pref_id, $value)
{
// Start off by checking if the preference is already set (if it is we need to do
// an UPDATE, if not, it's an INSERT.
if ($this->_exists($user_id, $pref_id, false)) {
$query = sprintf("UPDATE %s SET %s=%s WHERE %s=%s AND %s=%s", $this->_table,
$this->_valueColumnQuoted,
$this->_db->quote($this->_pack($value)),
$this->_userColumn,
$this->_db->quote($user_id),
$this->_nameColumn,
$this->_db->quote($pref_id));
} else {
$query = sprintf("INSERT INTO %s (%s, %s, %s) VALUES(%s, %s, %s)", $this->_table,
$this->_userColumn,
$this->_nameColumn,
$this->_valueColumnQuoted,
$this->_db->quote($user_id),
$this->_db->quote($pref_id),
$this->_db->quote($this->_pack($value)));
}
$result = $this->_db->query($query);
if (DB::isError($result)) {
$this->_lastError = "DB Error: ".$result->getMessage();
return false;
} else {
if ($this->_useCache) {
$_SESSION[$this->_cacheName][$user_id][$pref_id] = $value;
}
return true;
}
}
 
/**
* A shortcut function for setPref($this->_defaultUser, $pref_id, $value)
*
* @param string $pref_id The name of the preference to set.
* @param mixed $value The value to set it to.
* @return bool Sucess or failure.
* @access public
*/
function setDefaultPref($pref_id, $value)
{
return $this->setPref($this->_defaultUser, $pref_id, $value);
}
 
/**
* Deletes a preference for the specified user.
*
* @param string $user_id The userid of the user to delete from.
* @param string $pref_id The preference to delete.
* @return bool Success/Failure
* @access public
*/
function deletePref($user_id, $pref_id)
{
if ($this->getPref($user_id, $pref_id) == NULL) {
// The user doesn't have this variable anyway ;)
return true;
} else {
$query = sprintf("DELETE FROM %s WHERE %s=%s AND %s=%s", $this->_table,
$this->_userColumn,
$this->_db->quote($user_id),
$this->_nameColumn,
$this->_db->quote($pref_id));
$result = $this->_db->query($query);
if (DB::isError($result)) {
$this->_lastError = "DB Error: ".$result->getMessage();
return false;
} else {
if ($this->_useCache) {
unset($_SESSION[$this->_cacheName][$user_id][$pref_id]);
}
return true;
}
}
}
 
/**
* Deletes a preference for the default user.
*
* @param string $pref_id The preference to delete.
* @return bool Success/Failure
* @access public
*/
function deleteDefaultPref($pref_id)
{
return $this->deletePref($this->_defaultUser, $pref_id);
}
/**
* Checks if a preference exists in the database.
*
* @param string $user_id The userid of the preference owner.
* @param string $pref_id The preference to check for.
* @return bool True if the preference exists.
* @access private
*/
function _exists($user_id, $pref_id)
{
$query = sprintf("SELECT COUNT(%s) FROM %s WHERE %s=%s AND %s=%s", $this->_nameColumn,
$this->_table,
$this->_userColumn,
$this->_db->quoteSmart($user_id),
$this->_nameColumn,
$this->_db->quote($pref_id));
$result = $this->_db->getOne($query);
if (DB::isError($result)) {
$this->_lastError = "DB Error: ".$result->getMessage();
return false;
} else {
return (bool)$result;
}
}
 
/**
* Does anything needed to prepare a value for saving in the database.
*
* @param mixed $value The value to be saved.
* @return string The value in a format valid for saving to the database.
* @access private
*/
function _pack($value)
{
if ($this->_serialize) {
return serialize($value);
} else {
return $value;
}
}
/**
* Does anything needed to create a value of the preference, such as unserializing.
*
* @param string $value The value of the preference.
* @return mixed The unpacked version of the preference.
* @access private
*/
function _unpack($value)
{
if ($this->_serialize) {
return unserialize($value);
} else {
return $value;
}
}
}
?>
/trunk/api/pear/Auth/RADIUS.php
New file
0,0 → 1,964
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/*
Copyright (c) 2003, Michael Bretterklieber <michael@bretterklieber.com>
All rights reserved.
 
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
 
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. 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.
3. 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.
 
This code cannot simply be copied and put under the GNU Public License or
any other GPL-like (LGPL, GPL2) License.
 
$Id: RADIUS.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
*/
 
require_once 'PEAR.php';
 
/**
* Client implementation of RADIUS. This are wrapper classes for
* the RADIUS PECL.
* Provides RADIUS Authentication (RFC2865) and RADIUS Accounting (RFC2866).
*
* @package Auth_RADIUS
* @author Michael Bretterklieber <michael@bretterklieber.com>
* @access public
* @version $Revision: 1.1 $
*/
 
/**
* class Auth_RADIUS
*
* Abstract base class for RADIUS
*
* @package Auth_RADIUS
*/
class Auth_RADIUS extends PEAR {
 
/**
* List of RADIUS servers.
* @var array
* @see addServer(), putServer()
*/
var $_servers = array();
/**
* Path to the configuration-file.
* @var string
* @see setConfigFile()
*/
var $_configfile = null;
/**
* Resource.
* @var resource
* @see open(), close()
*/
var $res = null;
/**
* Username for authentication and accounting requests.
* @var string
*/
var $username = null;
 
/**
* Password for plaintext-authentication (PAP).
* @var string
*/
var $password = null;
/**
* List of known attributes.
* @var array
* @see dumpAttributes(), getAttributes()
*/
var $attributes = array();
/**
* List of raw attributes.
* @var array
* @see dumpAttributes(), getAttributes()
*/
var $rawAttributes = array();
 
/**
* List of raw vendor specific attributes.
* @var array
* @see dumpAttributes(), getAttributes()
*/
var $rawVendorAttributes = array();
/**
* Constructor
*
* Loads the RADIUS PECL/extension
*
* @return void
*/
function Auth_RADIUS()
{
$this->PEAR();
$this->loadExtension('radius');
}
/**
* Adds a RADIUS server to the list of servers for requests.
*
* At most 10 servers may be specified. When multiple servers
* are given, they are tried in round-robin fashion until a
* valid response is received
*
* @access public
* @param string $servername Servername or IP-Address
* @param integer $port Portnumber
* @param string $sharedSecret Shared secret
* @param integer $timeout Timeout for each request
* @param integer $maxtries Max. retries for each request
* @return void
*/
function addServer($servername = 'localhost', $port = 0, $sharedSecret = 'testing123', $timeout = 3, $maxtries = 3)
{
$this->_servers[] = array($servername, $port, $sharedSecret, $timeout, $maxtries);
}
/**
* Returns an error message, if an error occurred.
*
* @access public
* @return string
*/
function getError()
{
return radius_strerror($this->res);
}
/**
* Sets the configuration-file.
*
* @access public
* @param string $file Path to the configuration file
* @return void
*/
function setConfigfile($file)
{
$this->_configfile = $file;
}
 
/**
* Puts an attribute.
*
* @access public
* @param integer $attrib Attribute-number
* @param mixed $port Attribute-value
* @param type $type Attribute-type
* @return bool true on success, false on error
*/
function putAttribute($attrib, $value, $type = null)
{
if ($type == null) {
$type = gettype($value);
}
switch ($type) {
case 'integer':
return radius_put_int($this->res, $attrib, $value);
case 'addr':
return radius_put_addr($this->res, $attrib, $value);
case 'string':
default:
return radius_put_attr($this->res, $attrib, $value);
}
}
/**
* Puts a vendor-specific attribute.
*
* @access public
* @param integer $vendor Vendor (MSoft, Cisco, ...)
* @param integer $attrib Attribute-number
* @param mixed $port Attribute-value
* @param type $type Attribute-type
* @return bool true on success, false on error
*/
function putVendorAttribute($vendor, $attrib, $value, $type = null)
{
if ($type == null) {
$type = gettype($value);
}
switch ($type) {
case 'integer':
return radius_put_vendor_int($this->res, $vendor, $attrib, $value);
case 'addr':
return radius_put_vendor_addr($this->res, $vendor,$attrib, $value);
case 'string':
default:
return radius_put_vendor_attr($this->res, $vendor, $attrib, $value);
}
}
 
/**
* Prints known attributes received from the server.
*
* @access public
*/
function dumpAttributes()
{
foreach ($this->attributes as $name => $data) {
echo "$name:$data<br>\n";
}
}
/**
* Overwrite this.
*
* @access public
*/
function open()
{
}
 
/**
* Overwrite this.
*
* @access public
*/
function createRequest()
{
}
/**
* Puts standard attributes.
*
* @access public
*/
function putStandardAttributes()
{
if (isset($_SERVER)) {
$var = &$_SERVER;
} else {
$var = &$GLOBALS['HTTP_SERVER_VARS'];
}
$this->putAttribute(RADIUS_NAS_IDENTIFIER, isset($var['HTTP_HOST']) ? $var['HTTP_HOST'] : 'localhost');
$this->putAttribute(RADIUS_NAS_PORT_TYPE, RADIUS_VIRTUAL);
$this->putAttribute(RADIUS_SERVICE_TYPE, RADIUS_FRAMED);
$this->putAttribute(RADIUS_FRAMED_PROTOCOL, RADIUS_PPP);
$this->putAttribute(RADIUS_CALLING_STATION_ID, isset($var['REMOTE_HOST']) ? $var['REMOTE_HOST'] : '127.0.0.1');
}
/**
* Puts custom attributes.
*
* @access public
*/
function putAuthAttributes()
{
if (isset($this->username)) {
$this->putAttribute(RADIUS_USER_NAME, $this->username);
}
}
/**
* Configures the radius library.
*
* @access public
* @param string $servername Servername or IP-Address
* @param integer $port Portnumber
* @param string $sharedSecret Shared secret
* @param integer $timeout Timeout for each request
* @param integer $maxtries Max. retries for each request
* @return bool true on success, false on error
* @see addServer()
*/
function putServer($servername, $port = 0, $sharedsecret = 'testing123', $timeout = 3, $maxtries = 3)
{
if (!radius_add_server($this->res, $servername, $port, $sharedsecret, $timeout, $maxtries)) {
return false;
}
return true;
}
/**
* Configures the radius library via external configurationfile
*
* @access public
* @param string $servername Servername or IP-Address
* @return bool true on success, false on error
*/
function putConfigfile($file)
{
if (!radius_config($this->res, $file)) {
return false;
}
return true;
}
/**
* Initiates a RADIUS request.
*
* @access public
* @return bool true on success, false on errors
*/
function start()
{
if (!$this->open()) {
return false;
}
foreach ($this->_servers as $s) {
// Servername, port, sharedsecret, timeout, retries
if (!$this->putServer($s[0], $s[1], $s[2], $s[3], $s[4])) {
return false;
}
}
if (!empty($this->_configfile)) {
if (!$this->putConfigfile($this->_configfile)) {
return false;
}
}
$this->createRequest();
$this->putStandardAttributes();
$this->putAuthAttributes();
return true;
}
/**
* Sends a prepared RADIUS request and waits for a response
*
* @access public
* @return mixed true on success, false on reject, PEAR_Error on error
*/
function send()
{
$req = radius_send_request($this->res);
if (!$req) {
return $this->raiseError('Error sending request: ' . $this->getError());
}
 
switch($req) {
case RADIUS_ACCESS_ACCEPT:
if (is_subclass_of($this, 'auth_radius_acct')) {
return $this->raiseError('RADIUS_ACCESS_ACCEPT is unexpected for accounting');
}
return true;
 
case RADIUS_ACCESS_REJECT:
return false;
case RADIUS_ACCOUNTING_RESPONSE:
if (is_subclass_of($this, 'auth_radius_pap')) {
return $this->raiseError('RADIUS_ACCOUNTING_RESPONSE is unexpected for authentication');
}
return true;
 
default:
return $this->raiseError("Unexpected return value: $req");
}
}
 
/**
* Reads all received attributes after sending the request.
*
* This methos stores know attributes in the property attributes,
* all attributes (including known attibutes) are stored in rawAttributes
* or rawVendorAttributes.
* NOTE: call this functio also even if the request was rejected, because the
* Server returns usualy an errormessage
*
* @access public
* @return bool true on success, false on error
*/
function getAttributes()
{
 
while ($attrib = radius_get_attr($this->res)) {
 
if (!is_array($attrib)) {
return false;
}
 
$attr = $attrib['attr'];
$data = $attrib['data'];
 
$this->rawAttributes[$attr] = $data;
 
switch ($attr) {
case RADIUS_FRAMED_IP_ADDRESS:
$this->attributes['framed_ip'] = radius_cvt_addr($data);
break;
 
case RADIUS_FRAMED_IP_NETMASK:
$this->attributes['framed_mask'] = radius_cvt_addr($data);
break;
 
case RADIUS_FRAMED_MTU:
$this->attributes['framed_mtu'] = radius_cvt_int($data);
break;
 
case RADIUS_FRAMED_COMPRESSION:
$this->attributes['framed_compression'] = radius_cvt_int($data);
break;
 
case RADIUS_SESSION_TIMEOUT:
$this->attributes['session_timeout'] = radius_cvt_int($data);
break;
 
case RADIUS_IDLE_TIMEOUT:
$this->attributes['idle_timeout'] = radius_cvt_int($data);
break;
 
case RADIUS_SERVICE_TYPE:
$this->attributes['service_type'] = radius_cvt_int($data);
break;
 
case RADIUS_CLASS:
$this->attributes['class'] = radius_cvt_int($data);
break;
 
case RADIUS_FRAMED_PROTOCOL:
$this->attributes['framed_protocol'] = radius_cvt_int($data);
break;
 
case RADIUS_FRAMED_ROUTING:
$this->attributes['framed_routing'] = radius_cvt_int($data);
break;
 
case RADIUS_FILTER_ID:
$this->attributes['filter_id'] = radius_cvt_string($data);
break;
 
case RADIUS_VENDOR_SPECIFIC:
$attribv = radius_get_vendor_attr($data);
if (!is_array($attribv)) {
return false;
}
$vendor = $attribv['vendor'];
$attrv = $attribv['attr'];
$datav = $attribv['data'];
$this->rawVendorAttributes[$vendor][$attrv] = $datav;
 
if ($vendor == RADIUS_VENDOR_MICROSOFT) {
 
switch ($attrv) {
case RADIUS_MICROSOFT_MS_CHAP2_SUCCESS:
$this->attributes['ms_chap2_success'] = radius_cvt_string($datav);
break;
 
case RADIUS_MICROSOFT_MS_CHAP_ERROR:
$this->attributes['ms_chap_error'] = radius_cvt_string(substr($datav,1));
break;
 
case RADIUS_MICROSOFT_MS_CHAP_DOMAIN:
$this->attributes['ms_chap_domain'] = radius_cvt_string($datav);
break;
 
case RADIUS_MICROSOFT_MS_MPPE_ENCRYPTION_POLICY:
$this->attributes['ms_mppe_encryption_policy'] = radius_cvt_int($datav);
break;
 
case RADIUS_MICROSOFT_MS_MPPE_ENCRYPTION_TYPES:
$this->attributes['ms_mppe_encryption_types'] = radius_cvt_int($datav);
break;
 
case RADIUS_MICROSOFT_MS_CHAP_MPPE_KEYS:
$demangled = radius_demangle($this->res, $datav);
$this->attributes['ms_chap_mppe_lm_key'] = substr($demangled, 0, 8);
$this->attributes['ms_chap_mppe_nt_key'] = substr($demangled, 8, RADIUS_MPPE_KEY_LEN);
break;
 
case RADIUS_MICROSOFT_MS_MPPE_SEND_KEY:
$this->attributes['ms_chap_mppe_send_key'] = radius_demangle_mppe_key($this->res, $datav);
break;
 
case RADIUS_MICROSOFT_MS_MPPE_RECV_KEY:
$this->attributes['ms_chap_mppe_recv_key'] = radius_demangle_mppe_key($this->res, $datav);
break;
 
case RADIUS_MICROSOFT_MS_PRIMARY_DNS_SERVER:
$this->attributes['ms_primary_dns_server'] = radius_cvt_string($datav);
break;
}
}
break;
}
}
 
return true;
}
/**
* Frees resources.
*
* Calling this method is always a good idea, because all security relevant
* attributes are filled with Nullbytes to leave nothing in the mem.
*
* @access public
*/
function close()
{
if ($this->res != null) {
radius_close($this->res);
$this->res = null;
}
$this->username = str_repeat("\0", strlen($this->username));
$this->password = str_repeat("\0", strlen($this->password));
}
}
 
/**
* class Auth_RADIUS_PAP
*
* Class for authenticating using PAP (Plaintext)
*
* @package Auth_RADIUS
*/
class Auth_RADIUS_PAP extends Auth_RADIUS
{
 
/**
* Constructor
*
* @param string $username Username
* @param string $password Password
* @return void
*/
function Auth_RADIUS_PAP($username = null, $password = null)
{
$this->Auth_RADIUS();
$this->username = $username;
$this->password = $password;
}
/**
* Creates a RADIUS resource
*
* Creates a RADIUS resource for authentication. This should be the first
* call before you make any other things with the library.
*
* @return bool true on success, false on error
*/
function open()
{
$this->res = radius_auth_open();
if (!$this->res) {
return false;
}
return true;
}
/**
* Creates an authentication request
*
* Creates an authentication request.
* You MUST call this method before you can put any attribute
*
* @return bool true on success, false on error
*/
function createRequest()
{
if (!radius_create_request($this->res, RADIUS_ACCESS_REQUEST)) {
return false;
}
return true;
}
 
/**
* Put authentication specific attributes
*
* @return void
*/
function putAuthAttributes()
{
if (isset($this->username)) {
$this->putAttribute(RADIUS_USER_NAME, $this->username);
}
if (isset($this->password)) {
$this->putAttribute(RADIUS_USER_PASSWORD, $this->password);
}
}
 
}
 
/**
* class Auth_RADIUS_CHAP_MD5
*
* Class for authenticating using CHAP-MD5 see RFC1994.
* Instead og the plaintext password the challenge and
* the response are needed.
*
* @package Auth_RADIUS
*/
class Auth_RADIUS_CHAP_MD5 extends Auth_RADIUS_PAP
{
/**
* 8 Bytes binary challenge
* @var string
*/
var $challenge = null;
 
/**
* 16 Bytes MD5 response binary
* @var string
*/
var $response = null;
/**
* Id of the authentication request. Should incremented after every request.
* @var integer
*/
var $chapid = 1;
/**
* Constructor
*
* @param string $username Username
* @param string $challenge 8 Bytes Challenge (binary)
* @param integer $chapid Requestnumber
* @return void
*/
function Auth_RADIUS_CHAP_MD5($username = null, $challenge = null, $chapid = 1)
{
$this->Auth_RADIUS_PAP();
$this->username = $username;
$this->challenge = $challenge;
$this->chapid = $chapid;
}
/**
* Put CHAP-MD5 specific attributes
*
* For authenticating using CHAP-MD5 via RADIUS you have to put the challenge
* and the response. The chapid is inserted in the first byte of the response.
*
* @return void
*/
function putAuthAttributes()
{
if (isset($this->username)) {
$this->putAttribute(RADIUS_USER_NAME, $this->username);
}
if (isset($this->response)) {
$response = pack('C', $this->chapid) . $this->response;
$this->putAttribute(RADIUS_CHAP_PASSWORD, $response);
}
if (isset($this->challenge)) {
$this->putAttribute(RADIUS_CHAP_CHALLENGE, $this->challenge);
}
}
/**
* Frees resources.
*
* Calling this method is always a good idea, because all security relevant
* attributes are filled with Nullbytes to leave nothing in the mem.
*
* @access public
*/
function close()
{
Auth_RADIUS_PAP::close();
$this->challenge = str_repeat("\0", strlen($this->challenge));
$this->response = str_repeat("\0", strlen($this->response));
}
}
 
/**
* class Auth_RADIUS_MSCHAPv1
*
* Class for authenticating using MS-CHAPv1 see RFC2433
*
* @package Auth_RADIUS
*/
class Auth_RADIUS_MSCHAPv1 extends Auth_RADIUS_CHAP_MD5
{
/**
* LAN-Manager-Response
* @var string
*/
var $lmResponse = null;
 
/**
* Wether using deprecated LM-Responses or not.
* 0 = use LM-Response, 1 = use NT-Response
* @var bool
*/
var $flags = 1;
/**
* Put MS-CHAPv1 specific attributes
*
* For authenticating using MS-CHAPv1 via RADIUS you have to put the challenge
* and the response. The response has this structure:
* struct rad_mschapvalue {
* u_char ident;
* u_char flags;
* u_char lm_response[24];
* u_char response[24];
* };
*
* @return void
*/
function putAuthAttributes()
{
if (isset($this->username)) {
$this->putAttribute(RADIUS_USER_NAME, $this->username);
}
if (isset($this->response) || isset($this->lmResponse)) {
$lmResp = isset($this->lmResponse) ? $this->lmResponse : str_repeat ("\0", 24);
$ntResp = isset($this->response) ? $this->response : str_repeat ("\0", 24);
$resp = pack('CC', $this->chapid, $this->flags) . $lmResp . $ntResp;
$this->putVendorAttribute(RADIUS_VENDOR_MICROSOFT, RADIUS_MICROSOFT_MS_CHAP_RESPONSE, $resp);
}
if (isset($this->challenge)) {
$this->putVendorAttribute(RADIUS_VENDOR_MICROSOFT, RADIUS_MICROSOFT_MS_CHAP_CHALLENGE, $this->challenge);
}
}
}
 
/**
* class Auth_RADIUS_MSCHAPv2
*
* Class for authenticating using MS-CHAPv2 see RFC2759
*
* @package Auth_RADIUS
*/
class Auth_RADIUS_MSCHAPv2 extends Auth_RADIUS_MSCHAPv1
{
/**
* 16 Bytes binary challenge
* @var string
*/
var $challenge = null;
/**
* 16 Bytes binary Peer Challenge
* @var string
*/
var $peerChallenge = null;
 
/**
* Put MS-CHAPv2 specific attributes
*
* For authenticating using MS-CHAPv1 via RADIUS you have to put the challenge
* and the response. The response has this structure:
* struct rad_mschapv2value {
* u_char ident;
* u_char flags;
* u_char pchallenge[16];
* u_char reserved[8];
* u_char response[24];
* };
* where pchallenge is the peer challenge. Like for MS-CHAPv1 we set the flags field to 1.
* @return void
*/
function putAuthAttributes()
{
if (isset($this->username)) {
$this->putAttribute(RADIUS_USER_NAME, $this->username);
}
if (isset($this->response) && isset($this->peerChallenge)) {
// Response: chapid, flags (1 = use NT Response), Peer challenge, reserved, Response
$resp = pack('CCa16a8a24',$this->chapid , 1, $this->peerChallenge, str_repeat("\0", 8), $this->response);
$this->putVendorAttribute(RADIUS_VENDOR_MICROSOFT, RADIUS_MICROSOFT_MS_CHAP2_RESPONSE, $resp);
}
if (isset($this->challenge)) {
$this->putVendorAttribute(RADIUS_VENDOR_MICROSOFT, RADIUS_MICROSOFT_MS_CHAP_CHALLENGE, $this->challenge);
}
}
/**
* Frees resources.
*
* Calling this method is always a good idea, because all security relevant
* attributes are filled with Nullbytes to leave nothing in the mem.
*
* @access public
*/
function close()
{
Auth_RADIUS_MSCHAPv1::close();
$this->peerChallenge = str_repeat("\0", strlen($this->peerChallenge));
}
}
 
/**
* class Auth_RADIUS_Acct
*
* Class for RADIUS accounting
*
* @package Auth_RADIUS
*/
class Auth_RADIUS_Acct extends Auth_RADIUS
{
/**
* Defines where the Authentication was made, possible values are:
* RADIUS_AUTH_RADIUS, RADIUS_AUTH_LOCAL, RADIUS_AUTH_REMOTE
* @var integer
*/
var $authentic = null;
 
/**
* Defines the type of the accounting request, on of:
* RADIUS_START, RADIUS_STOP, RADIUS_ACCOUNTING_ON, RADIUS_ACCOUNTING_OFF
* @var integer
*/
var $status_type = null;
 
/**
* The time the user was logged in in seconds
* @var integer
*/
var $session_time = null;
 
/**
* A uniq identifier for the session of the user, maybe the PHP-Session-Id
* @var string
*/
var $session_id = null;
/**
* Constructor
*
* Generates a predefined session_id. We use the Remote-Address, the PID, and the Current user.
* @return void
*/
function Auth_RADIUS_Acct()
{
$this->Auth_RADIUS();
if (isset($_SERVER)) {
$var = &$_SERVER;
} else {
$var = &$GLOBALS['HTTP_SERVER_VARS'];
}
 
$this->session_id = sprintf("%s:%d-%s", isset($var['REMOTE_ADDR']) ? $var['REMOTE_ADDR'] : '127.0.0.1' , getmypid(), get_current_user());
}
 
/**
* Creates a RADIUS resource
*
* Creates a RADIUS resource for accounting. This should be the first
* call before you make any other things with the library.
*
* @return bool true on success, false on error
*/
function open()
{
$this->res = radius_acct_open();
if (!$this->res) {
return false;
}
return true;
}
 
/**
* Creates an accounting request
*
* Creates an accounting request.
* You MUST call this method before you can put any attribute.
*
* @return bool true on success, false on error
*/
function createRequest()
{
if (!radius_create_request($this->res, RADIUS_ACCOUNTING_REQUEST)) {
return false;
}
return true;
}
/**
* Put attributes for accounting.
*
* Here we put some accounting values. There many more attributes for accounting,
* but for web-applications only certain attributes make sense.
* @return void
*/
function putAuthAttributes()
{
$this->putAttribute(RADIUS_ACCT_SESSION_ID, $this->session_id);
$this->putAttribute(RADIUS_ACCT_STATUS_TYPE, $this->status_type);
if (isset($this->session_time) && $this->status_type == RADIUS_STOP) {
$this->putAttribute(RADIUS_ACCT_SESSION_TIME, $this->session_time);
}
if (isset($this->authentic)) {
$this->putAttribute(RADIUS_ACCT_AUTHENTIC, $this->authentic);
}
}
}
 
/**
* class Auth_RADIUS_Acct_Start
*
* Class for RADIUS accounting. Its usualy used, after the user has logged in.
*
* @package Auth_RADIUS
*/
class Auth_RADIUS_Acct_Start extends Auth_RADIUS_Acct
{
/**
* Defines the type of the accounting request.
* It is set to RADIUS_START by default in this class.
* @var integer
*/
var $status_type = Auth_RADIUS_Acct_Stop;
}
 
/**
* class Auth_RADIUS_Acct_Start
*
* Class for RADIUS accounting. Its usualy used, after the user has logged out.
*
* @package Auth_RADIUS
*/
class Auth_RADIUS_Acct_Stop extends Auth_RADIUS_Acct
{
/**
* Defines the type of the accounting request.
* It is set to RADIUS_STOP by default in this class.
* @var integer
*/
var $status_type = RADIUS_STOP;
}
 
?>
/trunk/api/pear/DB/pgsql.php
21,7 → 21,7
* @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: pgsql.php,v 1.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: pgsql.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
43,7 → 43,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
*/
class DB_pgsql extends DB_common
/trunk/api/pear/DB/ifx.php
20,7 → 20,7
* @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.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: ifx.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
48,7 → 48,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
*/
class DB_ifx extends DB_common
/trunk/api/pear/DB/sybase.php
21,7 → 21,7
* @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.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: sybase.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
46,7 → 46,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
*/
class DB_sybase extends DB_common
/trunk/api/pear/DB/common.php
20,7 → 20,7
* @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: common.php,v 1.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: common.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
42,7 → 42,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
*/
class DB_common extends PEAR
/trunk/api/pear/DB/Pager.php
New file
0,0 → 1,250
<?php
//
// Pear DB Pager - Retrieve and return information of databases
// result sets
//
// Copyright (C) 2001 Tomas Von Veschler Cox <cox@idecnet.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
//
// $Id: Pager.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
 
require_once 'PEAR.php';
require_once 'DB.php';
 
/**
* This class handles all the stuff needed for displaying paginated results
* from a database query of Pear DB, in a very easy way.
* Documentation and examples of use, can be found in:
* http://vulcanonet.com/soft/pager/ (could be outdated)
*
* IMPORTANT!
* Since PEAR DB already support native row limit (more fast and avaible in
* all the drivers), there is no more need to use $pager->build() or
* the $pager->fetch*() methods.
*
* Usage example:
*
*< ?php
* require_once 'DB/Pager.php';
* $db = DB::connect('your DSN string');
* $from = 0; // The row to start to fetch from (you might want to get this
* // param from the $_GET array
* $limit = 10; // The number of results per page
* $maxpages = 10; // The number of pages for displaying in the pager (optional)
* $res = $db->limitQuery($sql, $from, $limit);
* $nrows = 0; // Alternative you could use $res->numRows()
* while ($row = $res->fetchrow()) {
* // XXX code for building the page here
* $nrows++;
* }
* $data = DB_Pager::getData($from, $limit, $nrows, $maxpages);
* // XXX code for building the pager here
* ? >
*
* @version 0.7
* @author Tomas V.V.Cox <cox@idecnet.com>
* @see http://vulcanonet.com/soft/pager/
*/
 
class DB_Pager extends PEAR
{
 
/**
* Constructor
*
* @param object $res A DB_result object from Pear_DB
* @param int $from The row to start fetching
* @param int $limit How many results per page
* @param int $numrows Pager will automatically
* find this param if is not given. If your Pear_DB backend extension
* doesn't support numrows(), you can manually calculate it
* and supply later to the constructor
* @deprecated
*/
function DB_Pager (&$res, $from, $limit, $numrows = null)
{
$this->res = $res;
$this->from = $from;
$this->limit = $limit;
$this->numrows = $numrows;
}
 
/**
* Calculates all the data needed by Pager to work
*
* @return mixed An assoc array with all the data (see getData)
* or DB_Error on error
* @see DB_Pager::getData
* @deprecated
*/
function build()
{
// if there is no numrows given, calculate it
if ($this->numrows === null) {
$this->numrows = $this->res->numrows();
if (DB::isError($this->numrows)) {
return $this->numrows;
}
}
$data = $this->getData($this->from, $this->limit, $this->numrows);
if (DB::isError($data)) {
return $data;
}
$this->current = $this->from - 1;
$this->top = $data['to'];
return $data;
}
 
/**
* @deprecated
*/
function fetchRow($mode=DB_FETCHMODE_DEFAULT)
{
$this->current++;
if ($this->current >= $this->top) {
return null;
}
return $this->res->fetchRow($mode, $this->current);
}
 
/**
* @deprecated
*/
function fetchInto(&$arr, $mode=DB_FETCHMODE_DEFAULT)
{
$this->current++;
if ($this->current >= $this->top) {
return null;
}
return $this->res->fetchInto($arr, $mode, $this->current);
}
 
/*
* Gets all the data needed to paginate results
* This is an associative array with the following
* values filled in:
*
* array(
* 'current' => X, // current page you are
* 'numrows' => X, // total number of results
* 'next' => X, // row number where next page starts
* 'prev' => X, // row number where prev page starts
* 'remain' => X, // number of results remaning *in next page*
* 'numpages'=> X, // total number of pages
* 'from' => X, // the row to start fetching
* 'to' => X, // the row to stop fetching
* 'limit' => X, // how many results per page
* 'maxpages' => X, // how many pages to show (google style)
* 'firstpage' => X, // the row number of the first page
* 'lastpage' => X, // the row number where the last page starts
* 'pages' => array( // assoc with page "number => start row"
* 1 => X,
* 2 => X,
* 3 => X
* )
* );
* @param int $from The row to start fetching
* @param int $limit How many results per page
* @param int $numrows Number of results from query
*
* @return array associative array with data or DB_error on error
*
*/
function &getData($from, $limit, $numrows, $maxpages = false)
{
if (empty($numrows) || ($numrows < 0)) {
return null;
}
$from = (empty($from)) ? 0 : $from;
 
if ($limit <= 0) {
return PEAR::raiseError (null, 'wrong "limit" param', null,
null, null, 'DB_Error', true);
}
 
// Total number of pages
$pages = ceil($numrows/$limit);
$data['numpages'] = $pages;
 
// first & last page
$data['firstpage'] = 1;
$data['lastpage'] = $pages;
 
// Build pages array
$data['pages'] = array();
for ($i=1; $i <= $pages; $i++) {
$offset = $limit * ($i-1);
$data['pages'][$i] = $offset;
// $from must point to one page
if ($from == $offset) {
// The current page we are
$data['current'] = $i;
}
}
if (!isset($data['current'])) {
return PEAR::raiseError (null, 'wrong "from" param', null,
null, null, 'DB_Error', true);
}
 
// Limit number of pages (goole algoritm)
if ($maxpages) {
$radio = floor($maxpages/2);
$minpage = $data['current'] - $radio;
if ($minpage < 1) {
$minpage = 1;
}
$maxpage = $data['current'] + $radio - 1;
if ($maxpage > $data['numpages']) {
$maxpage = $data['numpages'];
}
foreach (range($minpage, $maxpage) as $page) {
$tmp[$page] = $data['pages'][$page];
}
$data['pages'] = $tmp;
$data['maxpages'] = $maxpages;
} else {
$data['maxpages'] = null;
}
 
// Prev link
$prev = $from - $limit;
$data['prev'] = ($prev >= 0) ? $prev : null;
 
// Next link
$next = $from + $limit;
$data['next'] = ($next < $numrows) ? $next : null;
 
// Results remaining in next page & Last row to fetch
if ($data['current'] == $pages) {
$data['remain'] = 0;
$data['to'] = $numrows;
} else {
if ($data['current'] == ($pages - 1)) {
$data['remain'] = $numrows - ($limit*($pages-1));
} else {
$data['remain'] = $limit;
}
$data['to'] = $data['current'] * $limit;
}
$data['numrows'] = $numrows;
$data['from'] = $from + 1;
$data['limit'] = $limit;
 
return $data;
}
}
?>
/trunk/api/pear/DB/NestedSet.php
New file
0,0 → 1,2747
<?php
//
// +----------------------------------------------------------------------+
// | PEAR :: DB_NestedSet |
// +----------------------------------------------------------------------+
// | 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 |f
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Daniel Khan <dk@webcluster.at> |
// | Jason Rust <jason@rustyparts.com> |
// +----------------------------------------------------------------------+
// $Id: NestedSet.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
//
 
// CREDITS:
// --------
// - Thanks to Kristian Koehntopp for publishing an explanation of the Nested Set
// technique and for the great work he did and does for the php community
// - Thanks to Daniel T. Gorski for his great tutorial on www.develnet.org
//
// - Thanks to my parents for ... just kidding :]
 
require_once 'PEAR.php';
 
// {{{ constants
 
// Error and message codes
define('NESE_ERROR_RECURSION', 'E100');
define('NESE_ERROR_NODRIVER', 'E200');
define('NESE_ERROR_NOHANDLER', 'E300');
define('NESE_ERROR_TBLOCKED', 'E010');
define('NESE_MESSAGE_UNKNOWN', 'E0');
define('NESE_ERROR_NOTSUPPORTED', 'E1');
define('NESE_ERROR_PARAM_MISSING','E400');
define('NESE_ERROR_NOT_FOUND', 'E500');
define('NESE_ERROR_WRONG_MPARAM', 'E2');
 
// for moving a node before another
define('NESE_MOVE_BEFORE', 'BE');
// for moving a node after another
define('NESE_MOVE_AFTER', 'AF');
// for moving a node below another
define('NESE_MOVE_BELOW', 'SUB');
 
 
// Sortorders
define('NESE_SORT_LEVEL', 'SLV');
define('NESE_SORT_PREORDER', 'SPO');
 
// }}}
// {{{ DB_NestedSet:: class
/**
* DB_NestedSet is a class for handling nested sets
*
* @author Daniel Khan <dk@webcluster.at>
* @package DB_NestedSet
* @version $Revision: 1.1 $
* @access public
*/
 
// }}}
class DB_NestedSet {
// {{{ properties
 
/**
* @var array The field parameters of the table with the nested set. Format: 'realFieldName' => 'fieldId'
* @access public
*/
var $params = array(
'STRID' => 'id',
'ROOTID'=> 'rootid',
'l' => 'l',
'r' => 'r',
'STREH' => 'norder',
'LEVEL' => 'level',
// 'parent'=>'parent', // Optional but very useful
'STRNA' => 'name'
);
 
// To be used with 2.0 - would be an api break atm
// var $quotedParams = array('name');
 
/**
* @var string The table with the actual tree data
* @access public
*/
var $node_table = 'tb_nodes';
 
/**
* @var string The table to handle locking
* @access public
*/
var $lock_table = 'tb_locks';
 
/**
* @var string The table used for sequences
* @access public
*/
var $sequence_table;
 
/**
* Secondary order field. Normally this is the order field, but can be changed to
* something else (i.e. the name field so that the tree can be shown alphabetically)
*
* @var string
* @access public
*/
var $secondarySort;
 
/**
* Used to store the secondary sort method set by the user while doing manipulative queries
*
* @var string
* @access private
*/
var $_userSecondarySort = false;
 
/**
* The default sorting field - will be set to the table column inside the constructor
*
* @var string
* @access private
*/
var $_defaultSecondarySort = 'norder';
 
/**
* @var int The time to live of the lock
* @access public
*/
var $lockTTL = 1;
 
/**
* @var bool Enable debugging statements?
* @access public
*/
var $debug = 0;
 
/**
* @var bool Lock the structure of the table?
* @access private
*/
var $_structureTableLock = false;
 
 
/**
* @var bool Don't allow unlocking (used inside of moves)
* @access private
*/
var $_lockExclusive = false;
 
/**
* @var object cache Optional PEAR::Cache object
* @access public
*/
var $cache = false;
 
/**
* Specify the sortMode of the query methods
* NESE_SORT_LEVEL is the 'old' sorting method and sorts a tree by level
* all nodes of level 1, all nodes of level 2,...
* NESE_SORT_PREORDER will sort doing a preorder walk.
* So all children of node x will come right after it
* Note that moving a node within it's siblings will obviously not change the output
* in this mode
*
* @var constant Order method (NESE_SORT_LEVEL|NESE_SORT_PREORDER)
* @access private
*/
var $_sortMode = NESE_SORT_LEVEL;
 
/**
* @var array Available sortModes
* @access private
*/
var $_sortModes = array(NESE_SORT_LEVEL, NESE_SORT_PREORDER);
 
/**
* @var array An array of field ids that must exist in the table
* @access private
*/
var $_requiredParams = array('id', 'rootid', 'l', 'r', 'norder', 'level');
 
/**
* @var bool Skip the callback events?
* @access private
*/
var $_skipCallbacks = false;
 
/**
* @var bool Do we want to use caching
* @access private
*/
var $_caching = false;
 
/**
* @var array The above parameters flipped for easy access
* @access private
*/
var $_flparams = array();
 
/**
*
* @var bool Temporary switch for cache
* @access private
*/
var $_restcache = false;
 
/**
* Used to determine the presence of listeners for an event in triggerEvent()
*
* If any event listeners are registered for an event, the event name will
* have a key set in this array, otherwise, it will not be set.
* @see triggerEvent()
* @var arrayg
* @access private
*/
var $_hasListeners = array();
 
 
/**
* @var string packagename
* @access private
*/
var $_packagename = 'DB_NestedSet';
 
/**
* @var int Majorversion
* @access private
*/
var $_majorversion = 1;
 
/**
* @var string Minorversion
* @access private
*/
var $_minorversion = '3';
 
/**
* @var array Used for mapping a cloned tree to the real tree for move_* operations
* @access private
*/
var $_relations = array();
 
/**
* Used for _internal_ tree conversion
* @var bool Turn off user param verification and id generation
* @access private
*/
var $_dumbmode = false;
 
/**
* @var array Map of error messages to their descriptions
*/
var $messages = array(
NESE_ERROR_RECURSION => '%s: This operation would lead to a recursion',
NESE_ERROR_TBLOCKED => 'The structure Table is locked for another database operation, please retry.',
NESE_ERROR_NODRIVER => 'The selected database driver %s wasn\'t found',
NESE_ERROR_NOTSUPPORTED => 'Method not supported yet',
NESE_ERROR_NOHANDLER => 'Event handler not found',
NESE_ERROR_PARAM_MISSING=> 'Parameter missing',
NESE_MESSAGE_UNKNOWN => 'Unknown error or message',
NESE_ERROR_NOT_FOUND => '%s: Node %s not found',
NESE_ERROR_WRONG_MPARAM => '%s: %s'
);
 
/**
* @var array The array of event listeners
* @access private
*/
var $eventListeners = array();
 
 
// }}}
// +---------------------------------------+
// | Base methods |
// +---------------------------------------+
// {{{ constructor
 
/**
* Constructor
*
* @param array $params Database column fields which should be returned
*
* @access private
* @return void
*/
function DB_NestedSet($params) {
 
if ($this->debug) {
$this->_debugMessage('DB_NestedSet()');
}
if (is_array($params) && count($params) > 0) {
$this->params = $params;
}
 
$this->_flparams = array_flip($this->params);
$this->sequence_table = $this->node_table . '_' . $this->_flparams['id'];
$this->secondarySort = $this->_flparams[$this->_defaultSecondarySort];
register_shutdown_function(array(&$this,'_DB_NestedSet'));
}
 
// }}}
// {{{ destructor
 
/**
* PEAR Destructor
* Releases all locks
* Closes open database connections
*
* @access private
* @return void
*/
function _DB_NestedSet() {
if ($this->debug) {
$this->_debugMessage('_DB_NestedSet()');
}
$this->_releaseLock(true);
}
 
// }}}
// {{{ factory
 
/**
* Handles the returning of a concrete instance of DB_NestedSet based on the driver.
* If the class given by $driver allready exists it will be used.
* If not the driver will be searched inside the default path ./NestedSet/
*
* @param string $driver The driver, such as DB or MDB
* @param string $dsn The dsn for connecting to the database
* @param array $params The field name params for the node table
*
* @static
* @access public
* @return object The DB_NestedSet object
*/
function & factory($driver, $dsn, $params = array()) {
 
$classname = 'DB_NestedSet_' . $driver;
if (!class_exists($classname)) {
$driverpath = dirname(__FILE__).'/NestedSet/'.$driver.'.php';
if(!file_exists($driverpath) || !$driver) {
return PEAR::raiseError("factory(): The database driver '$driver' wasn't found", NESE_ERROR_NODRIVER, PEAR_ERROR_TRIGGER, E_USER_ERROR);
}
include_once($driverpath);
}
return new $classname($dsn, $params);
}
 
// }}}
 
 
// }}}
// +----------------------------------------------+
// | NestedSet manipulation and query methods |
// |----------------------------------------------+
// | Querying the tree |
// +----------------------------------------------+
 
// {{{ getAllNodes()
 
/**
* Fetch the whole NestedSet
*
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param array $addSQL (optional) Array of additional params to pass to the query.
*
* @access public
* @return mixed False on error, or an array of nodes
*/
function getAllNodes($keepAsArray = false, $aliasFields = true, $addSQL = array()) {
if ($this->debug) {
$this->_debugMessage('getAllNodes()');
}
 
if($this->_sortMode == NESE_SORT_LEVEL) {
$sql = sprintf('SELECT %s %s FROM %s %s %s ORDER BY %s.%s, %s.%s ASC',
$this->_getSelectFields($aliasFields),
$this->_addSQL($addSQL, 'cols'),
$this->node_table,
$this->_addSQL($addSQL, 'join'),
$this->_addSQL($addSQL, 'append'),
$this->node_table,
$this->_flparams['level'],
$this->node_table,
$this->secondarySort);
} elseif ($this->_sortMode == NESE_SORT_PREORDER) {
$nodeSet = array();
$rootnodes = $this->getRootNodes(true);
foreach($rootnodes AS $rid=>$rootnode) {
$nodeSet = $nodeSet+$this->getBranch($rootnode, true);
}
return $nodeSet;
}
 
if (!$this->_caching) {
$nodeSet = $this->_processResultSet($sql, $keepAsArray, $aliasFields);
} else {
$nodeSet = $this->cache->call('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
}
 
if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeLoad'])) {
// EVENT (nodeLoad)
foreach (array_keys($nodeSet) as $key) {
$this->triggerEvent('nodeLoad', $nodeSet[$key]);
}
}
return $nodeSet;
}
 
// }}}
// {{{ getRootNodes()
 
/**
* Fetches the first level (the rootnodes) of the NestedSet
*
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param array $addSQL (optional) Array of additional params to pass to the query.
*
* @see _addSQL()
* @access public
* @return mixed False on error, or an array of nodes
*/
function getRootNodes($keepAsArray = false, $aliasFields = true, $addSQL = array()) {
if ($this->debug) {
$this->_debugMessage('getRootNodes()');
}
$sql = sprintf('SELECT %s %s FROM %s %s WHERE %s.%s=%s.%s %s ORDER BY %s.%s ASC',
$this->_getSelectFields($aliasFields),
$this->_addSQL($addSQL, 'cols'),
$this->node_table,
$this->_addSQL($addSQL, 'join'),
$this->node_table,
$this->_flparams['id'],
$this->node_table,
$this->_flparams['rootid'],
$this->_addSQL($addSQL, 'append'),
$this->node_table,
$this->secondarySort);
 
if (!$this->_caching) {
$nodeSet = $this->_processResultSet($sql, $keepAsArray, $aliasFields);
} else {
$nodeSet = $this->cache->call('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
}
 
if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeLoad'])) {
// EVENT (nodeLoad)
foreach (array_keys($nodeSet) as $key) {
$this->triggerEvent('nodeLoad', $nodeSet[$key]);
}
}
return $nodeSet;
}
 
// }}}
 
// {{{ getBranch()
 
/**
* Fetch the whole branch where a given node id is in
*
* @param int $id The node ID
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param array $addSQL (optional) Array of additional params to pass to the query.
*
* @see _addSQL()
* @access public
* @return mixed False on error, or an array of nodes
*/
function getBranch($id, $keepAsArray = false, $aliasFields = true, $addSQL = array()) {
if ($this->debug) {
$this->_debugMessage('getBranch($id)');
}
if (!($thisnode = $this->pickNode($id, true))) {
$epr = array('getBranch()', $id);
return $this->_raiseError(NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_NOTICE, $epr);
}
if($this->_sortMode == NESE_SORT_LEVEL) {
$firstsort = $this->_flparams['level'];
$sql = sprintf('SELECT %s %s FROM %s %s WHERE %s.%s=%s %s ORDER BY %s.%s, %s.%s ASC',
$this->_getSelectFields($aliasFields),
$this->_addSQL($addSQL, 'cols'),
$this->node_table,
$this->_addSQL($addSQL, 'join'),
$this->node_table,
$this->_flparams['rootid'],
$thisnode['rootid'],
$this->_addSQL($addSQL, 'append'),
$this->node_table,
$firstsort,
$this->node_table,
$this->secondarySort);
} elseif($this->_sortMode == NESE_SORT_PREORDER) {
$firstsort = $this->_flparams['l'];
$sql = sprintf('SELECT %s %s FROM %s %s WHERE %s.%s=%s %s ORDER BY %s.%s ASC',
$this->_getSelectFields($aliasFields),
$this->_addSQL($addSQL, 'cols'),
$this->node_table,
$this->_addSQL($addSQL, 'join'),
$this->node_table,
$this->_flparams['rootid'],
$thisnode['rootid'],
$this->_addSQL($addSQL, 'append'),
$this->node_table,
$firstsort);
}
 
 
if (!$this->_caching) {
$nodeSet = $this->_processResultSet($sql, $keepAsArray, $aliasFields);
} else {
$nodeSet = $this->cache->call('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
}
 
if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeLoad'])) {
// EVENT (nodeLoad)
foreach (array_keys($nodeSet) as $key) {
$this->triggerEvent('nodeLoad', $nodeSet[$key]);
}
}
if($this->_sortMode == NESE_SORT_PREORDER && ($this->params[$this->secondarySort] != $this->_defaultSecondarySort)) {
uasort($nodeSet, array($this, '_secSort'));
}
return $nodeSet;
}
 
// }}}
// {{{ getParents()
 
/**
* Fetch the parents of a node given by id
*
* @param int $id The node ID
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param array $addSQL (optional) Array of additional params to pass to the query.
*
* @see _addSQL()
* @access public
* @return mixed False on error, or an array of nodes
*/
function getParents($id, $keepAsArray = false, $aliasFields = true, $addSQL = array()) {
if ($this->debug) {
$this->_debugMessage('getParents($id)');
}
if (!($child = $this->pickNode($id, true))) {
$epr = array('getParents()', $id);
return $this->_raiseError(NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_NOTICE, $epr);
}
 
$sql = sprintf('SELECT %s %s FROM %s %s
WHERE %s.%s=%s AND %s.%s<%s AND %s.%s<%s AND %s.%s>%s %s
ORDER BY %s.%s ASC',
$this->_getSelectFields($aliasFields),
$this->_addSQL($addSQL, 'cols'),
$this->node_table,
$this->_addSQL($addSQL, 'join'),
$this->node_table,
$this->_flparams['rootid'],
$child['rootid'],
$this->node_table,
$this->_flparams['level'],
$child['level'],
$this->node_table,
$this->_flparams['l'],
$child['l'],
$this->node_table,
$this->_flparams['r'],
$child['r'],
$this->_addSQL($addSQL, 'append'),
$this->node_table,
$this->_flparams['level']);
 
if (!$this->_caching) {
$nodeSet = $this->_processResultSet($sql, $keepAsArray, $aliasFields);
} else {
$nodeSet = $this->cache->call('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
}
 
if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeLoad'])) {
// EVENT (nodeLoad)
foreach (array_keys($nodeSet) as $key) {
$this->triggerEvent('nodeLoad', $nodeSet[$key]);
}
}
return $nodeSet;
}
 
// }}}
// {{{ getParent()
 
/**
* Fetch the immediate parent of a node given by id
*
* @param int $id The node ID
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param array $addSQL (optional) Array of additional params to pass to the query.
*
* @see _addSQL()
* @access public
* @return mixed False on error, or the parent node
*/
function getParent($id, $keepAsArray = false, $aliasFields = true, $addSQL = array()) {
if ($this->debug) {
$this->_debugMessage('getParent($id)');
}
if (!($child = $this->pickNode($id, true))) {
$epr = array('getParent()', $id);
return $this->_raiseError(NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_NOTICE, $epr);
}
 
if($child['id'] == $child['rootid']) {
return false;
}
 
// If parent node is set inside the db simply return it
if(isset($child['parent']) && !empty($child['parent'])) {
return $this->pickNode($child['parent'], $keepAsArray, $aliasFields, 'id', $addSQL);
}
 
$addSQL['append'] = sprintf('AND %s.%s = %s',
$this->node_table,
$this->_flparams['level'],
$child['level']-1);
 
$nodeSet = $this->getParents($id, $keepAsArray, $aliasFields, $addSQL);
 
if(!empty($nodeSet)) {
$keys = array_keys($nodeSet);
return $nodeSet[$keys[0]];
} else {
return false;
}
}
 
// }}}
// {{{ getSiblings)
 
/**
* Fetch all siblings of the node given by id
* Important: The node given by ID will also be returned
* Do a unset($array[$id]) on the result if you don't want that
*
* @param int $id The node ID
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param array $addSQL (optional) Array of additional params to pass to the query.
*
* @see _addSQL()
* @access public
* @return mixed False on error, or the parent node
*/
function getSiblings($id, $keepAsArray = false, $aliasFields = true, $addSQL = array()) {
if ($this->debug) {
$this->_debugMessage('getParents($id)');
}
 
if (!($sibling = $this->pickNode($id, true))) {
$epr = array('getSibling()', $id);
return $this->_raiseError(NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_NOTICE, $epr);
}
 
$parent = $this->getParent($sibling, true);
 
return $this->getChildren($parent, $keepAsArray, $aliasFields, $addSQL);
}
 
// }}}
// {{{ getChildren()
 
/**
* Fetch the children _one level_ after of a node given by id
*
* @param int $id The node ID
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param bool $forceNorder (optional) Force the result to be ordered by the norder
* param (as opposed to the value of secondary sort). Used by the move and
* add methods.
* @param array $addSQL (optional) Array of additional params to pass to the query.
*
* @see _addSQL()
* @access public
* @return mixed False on error, or an array of nodes
*/
function getChildren($id, $keepAsArray = false, $aliasFields = true, $forceNorder = false, $addSQL = array()) {
if ($this->debug) {
$this->_debugMessage('getChildren($id)');
}
 
if (!($parent = $this->pickNode($id, true))) {
$epr = array('getChildren()', $id);
return $this->_raiseError(NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_NOTICE, $epr);
}
if (!$parent || $parent['l'] == ($parent['r'] - 1)) {
return false;
}
 
$sql = sprintf('SELECT %s %s FROM %s %s
WHERE %s.%s=%s AND %s.%s=%s+1 AND %s.%s BETWEEN %s AND %s %s
ORDER BY %s.%s ASC',
$this->_getSelectFields($aliasFields),
$this->_addSQL($addSQL, 'cols'),
$this->node_table,
$this->_addSQL($addSQL, 'join'),
$this->node_table,
$this->_flparams['rootid'],
$parent['rootid'],
$this->node_table,
$this->_flparams['level'],
$parent['level'],
$this->node_table,
$this->_flparams['l'],
$parent['l'],
$parent['r'],
$this->_addSQL($addSQL, 'append'),
$this->node_table,
$this->secondarySort);
 
if (!$this->_caching) {
$nodeSet = $this->_processResultSet($sql, $keepAsArray, $aliasFields);
} else {
$nodeSet = $this->cache->call('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
}
 
if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeLoad'])) {
// EVENT (nodeLoad)
foreach (array_keys($nodeSet) as $key) {
$this->triggerEvent('nodeLoad', $nodeSet[$key]);
}
}
return $nodeSet;
}
 
// }}}
// {{{ getSubBranch()
 
/**
* Fetch all the children of a node given by id
*
* getChildren only queries the immediate children
* getSubBranch returns all nodes below the given node
*
* @param string $id The node ID
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param array $addSQL (optional) Array of additional params to pass to the query.
*
* @see _addSQL()
* @access public
* @return mixed False on error, or an array of nodes
*/
function getSubBranch($id, $keepAsArray = false, $aliasFields = true, $addSQL = array()) {
 
if ($this->debug) {
$this->_debugMessage('getSubBranch($id)');
}
if (!($parent = $this->pickNode($id, true))) {
$epr = array('getSubBranch()', $id);
return $this->_raiseError(NESE_ERROR_NOT_FOUND, E_USER_NOTICE, $epr);
}
if($this->_sortMode == NESE_SORT_LEVEL) {
$firstsort = $this->_flparams['level'];
$sql = sprintf('SELECT %s %s FROM %s %s WHERE %s.%s BETWEEN %s AND %s AND %s.%s=%s AND %s.%s!=%s %s ORDER BY %s.%s, %s.%s ASC',
$this->_getSelectFields($aliasFields),
$this->_addSQL($addSQL, 'cols'),
$this->node_table,
$this->_addSQL($addSQL, 'join'),
$this->node_table,
$this->_flparams['l'],
$parent['l'],
$parent['r'],
$this->node_table,
$this->_flparams['rootid'],
$parent['rootid'],
$this->node_table,
$this->_flparams['id'],
$this->_addSQL($addSQL, 'append'),
$id,
$this->node_table,
$firstsort,
$this->node_table,
$this->secondarySort
);
} elseif($this->_sortMode == NESE_SORT_PREORDER) {
$firstsort = $this->_flparams['l'];
$firstsort = $this->_flparams['level'];
$sql = sprintf('SELECT %s %s FROM %s %s WHERE %s.%s BETWEEN %s AND %s AND %s.%s=%s AND %s.%s!=%s %s ORDER BY %s.%s ASC',
$this->_getSelectFields($aliasFields),
$this->_addSQL($addSQL, 'cols'),
$this->node_table,
$this->_addSQL($addSQL, 'join'),
$this->node_table,
$this->_flparams['l'],
$parent['l'],
$parent['r'],
$this->node_table,
$this->_flparams['rootid'],
$parent['rootid'],
$this->node_table,
$this->_flparams['id'],
$this->_addSQL($addSQL, 'append'),
$id,
$this->node_table,
$firstsort
);
}
 
if (!$this->_caching) {
$nodeSet = $this->_processResultSet($sql, $keepAsArray, $aliasFields);
} else {
$nodeSet = $this->cache->call('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
}
 
if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeLoad'])) {
// EVENT (nodeLoad)
foreach (array_keys($nodeSet) as $key) {
$this->triggerEvent('nodeLoad', $nodeSet[$key]);
}
}
if($this->params[$this->secondarySort] != $this->_defaultSecondarySort) {
uasort($nodeSet, array($this, '_secSort'));
}
return $nodeSet;
}
 
// }}}
// {{{ pickNode()
 
/**
* Fetch the data of a node with the given id
*
* @param int $id The node id of the node to fetch
* @param bool $keepAsArray (optional) Keep the result as an array or transform it into
* a set of DB_NestedSet_Node objects?
* @param bool $aliasFields (optional) Should we alias the fields so they are the names
* of the parameter keys, or leave them as is?
* @param string $idfield (optional) Which field has to be compared with $id?
* This is can be used to pick a node by other values (e.g. it's name).
* @param array $addSQL (optional) Array of additional params to pass to the query.
*
* @see _addSQL()
* @access public
* @return mixed False on error, or an array of nodes
*/
function pickNode($id, $keepAsArray = false, $aliasFields = true, $idfield = 'id', $addSQL = array()) {
if ($this->debug) {
$this->_debugMessage('pickNode($id)');
}
 
if (is_object($id) && $id->id) {
return $id;
} elseif (is_array($id) && isset($id['id'])) {
return $id;
}
 
if(!$id) {
return false;
}
 
$sql = sprintf('SELECT %s %s FROM %s %s WHERE %s.%s=%s %s',
$this->_getSelectFields($aliasFields),
$this->_addSQL($addSQL, 'cols'),
$this->node_table,
$this->_addSQL($addSQL, 'join'),
$this->node_table,
$this->_flparams[$idfield],
$id,
$this->_addSQL($addSQL, 'append'));
 
if (!$this->_caching) {
$nodeSet = $this->_processResultSet($sql, $keepAsArray, $aliasFields);
} else {
$nodeSet = $this->cache->call('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
}
 
$nsKey = false;
 
if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeLoad'])) {
// EVENT (nodeLoad)
foreach (array_keys($nodeSet) as $key) {
$this->triggerEvent('nodeLoad', $nodeSet[$key]);
$nsKey = $key;
}
} else {
foreach (array_keys($nodeSet) as $key) {
$nsKey = $key;
}
}
 
if (is_array($nodeSet) && $idfield != 'id') {
$id = $nsKey;
}
 
return isset($nodeSet[$id]) ? $nodeSet[$id] : false;
}
 
// }}}
// {{{ isParent()
 
/**
* See if a given node is a parent of another given node
*
* A node is considered to be a parent if it resides above the child
* So it doesn't mean that the node has to be an immediate parent.
* To get this information simply compare the levels of the two nodes
* after you know that you have a parent relation.
*
* @param mixed $parent The parent node as array or object
* @param mixed $child The child node as array or object
*
* @access public
* @return bool True if it's a parent
*/
function isParent($parent, $child) {
 
if ($this->debug) {
$this->_debugMessage('isParent($parent, $child)');
}
 
if (!isset($parent)|| !isset($child)) {
return false;
}
 
if (is_array($parent)) {
$p_rootid = $parent['rootid'];
$p_l = $parent['l'];
$p_r = $parent['r'];
 
} elseif (is_object($parent)) {
$p_rootid = $parent->rootid;
$p_l = $parent->l;
$p_r = $parent->r;
}
 
if (is_array($child)) {
$c_rootid = $child['rootid'];
$c_l = $child['l'];
$c_r = $child['r'];
} elseif (is_object($child)) {
$c_rootid = $child->rootid;
$c_l = $child->l;
$c_r = $child->r;
}
 
if (($p_rootid == $c_rootid) && ($p_l < $c_l && $p_r > $c_r)) {
return true;
}
 
return false;
}
 
 
// }}}
// +----------------------------------------------+
// | NestedSet manipulation and query methods |
// |----------------------------------------------+
// | insert / delete / update of nodes |
// +----------------------------------------------+
// | [PUBLIC] |
// +----------------------------------------------+
// {{{ createRootNode()
 
/**
* Creates a new root node
* Optionally it deletes the whole tree and creates one initial rootnode
*
* <pre>
* +-- root1 [target]
* |
* +-- root2 [new]
* |
* +-- root3
* </pre>
*
* @param array $values Hash with param => value pairs of the node (see $this->params)
* @param integer $id ID of target node (the rootnode after which the node should be inserted)
* @param bool $first Danger: Deletes and (re)init's the hole tree - sequences are reset
*
* @access public
* @return mixed The node id or false on error
*/
function createRootNode($values, $id = false, $first = false, $_pos = 'AF') {
 
if ($this->debug) {
$this->_debugMessage('createRootNode($values, $id = false, $first = false, $_pos = \'AF\')');
}
 
$this->_verifyUserValues('createRootNode()', $values);
 
if(!$first && (!$id || !$parent = $this->pickNode($id, true))) {
$epr = array('createRootNode()', $id);
return $this->_raiseError(NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_ERROR, $epr);
} elseif($first && $id) {
 
// No notice for now.
// But tehese 2 params don't make sense together
$epr = array(
'createRootNode()',
'[id] AND [first] were passed - that doesn\'t make sense');
//$this->_raiseError(NESE_ERROR_WRONG_MPARAM, E_USER_WARNING, $epr);
}
 
// Try to aquire a table lock
if(PEAR::isError($lock=$this->_setLock())) {
return $lock;
}
 
$sql = array();
$addval = array();
$addval[$this->_flparams['level']] = 1;
 
// Shall we delete the existing tree (reinit)
if ($first) {
$dsql = sprintf('DELETE FROM %s',
$this->node_table);
$this->db->query($dsql);
$this->db->dropSequence($this->sequence_table);
// New order of the new node will be 1
$addval[$this->_flparams['norder']] = 1;
} else {
// Let's open a gap for the new node
if($_pos == NESE_MOVE_AFTER) {
$addval[$this->_flparams['norder']] = $parent['norder'] + 1;
$sql[] = sprintf('UPDATE %s SET %s=%s+1 WHERE %s=%s AND %s > %s',
$this->node_table,
$this->_flparams['norder'],
$this->_flparams['norder'],
$this->_flparams['id'],
$this->_flparams['rootid'],
$this->_flparams['norder'],
$parent['norder']);
} elseif($_pos == NESE_MOVE_BEFORE) {
$addval[$this->_flparams['norder']] = $parent['norder'];
$sql[] = sprintf('UPDATE %s SET %s=%s+1 WHERE %s=%s AND %s >= %s',
$this->node_table,
$this->_flparams['norder'],
$this->_flparams['norder'],
$this->_flparams['id'],
$this->_flparams['rootid'],
$this->_flparams['norder'],
$parent['norder']);
}
}
 
if(isset($this->_flparams['parent'])) {
$addval[$this->_flparams['parent']] = 0;
}
// Sequence of node id (equals to root id in this case
 
if(!$this->_dumbmode || !$node_id=isset($values[$this->_flparams['id']]) || !isset($values[$this->_flparams['rootid']])) {
$addval[$this->_flparams['rootid']] = $node_id = $addval[$this->_flparams['id']] = $this->db->nextId($this->sequence_table);
} else {
$node_id = $values[$this->_flparams['id']];
}
// Left/Right values for rootnodes
$addval[$this->_flparams['l']] = 1;
$addval[$this->_flparams['r']] = 2;
// Transform the node data hash to a query
if (!$qr = $this->_values2Query($values, $addval)) {
$this->_releaseLock();
return false;
}
 
// Insert the new node
$sql[] = sprintf('INSERT INTO %s SET %s',
$this->node_table,
$qr);
 
for($i=0;$i<count($sql);$i++) {
$res = $this->db->query($sql[$i]);
$this->_testFatalAbort($res, __FILE__, __LINE__);
}
 
// EVENT (nodeCreate)
 
if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeCreate'])) {
$this->triggerEvent('nodeCreate', $this->pickNode($node_id));
}
$this->_releaseLock();
return $node_id;
}
 
// }}}
// {{{ createSubNode()
 
/**
* Creates a subnode
*
* <pre>
* +-- root1
* |
* +-\ root2 [target]
* | |
* | |-- subnode1 [new]
* |
* +-- root3
* </pre>
*
* @param integer $id Parent node ID
* @param array $values Hash with param => value pairs of the node (see $this->params)
*
* @access public
* @return mixed The node id or false on error
*/
function createSubNode($id, $values) {
if ($this->debug) {
$this->_debugMessage('createSubNode($id, $values)');
}
 
// invalid parent id, bail out
if (!($thisnode = $this->pickNode($id, true))) {
$epr = array('createSubNode()', $id);
return $this->_raiseError(NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_ERROR, $epr);
}
 
// Try to aquire a table lock
if(PEAR::isError($lock = $this->_setLock())) {
return $lock;
}
 
$this->_verifyUserValues('createRootNode()', $values);
 
// Get the children of the target node
$children = $this->getChildren($id, true);
 
// We have children here
if ($thisnode['r']-1 != $thisnode['l']) {
// Get the last child
$last = array_pop($children);
// What we have to do is virtually an insert of a node after the last child
// So we don't have to proceed creating a subnode
$newNode = $this->createRightNode($last['id'], $values);
$this->_releaseLock();
return $newNode;
}
 
$sql = array();
$sql[] = sprintf('
UPDATE %s SET
%s=IF(%s>=%s, %s+2, %s),
%s=IF(%s>=%s, %s+2, %s)
WHERE %s=%s',
$this->node_table,
$this->_flparams['l'],
$this->_flparams['l'],
$thisnode['r'],
$this->_flparams['l'],
$this->_flparams['l'],
$this->_flparams['r'],
$this->_flparams['r'],
$thisnode['r'],
$this->_flparams['r'],
$this->_flparams['r'],
$this->_flparams['rootid'],
$thisnode['rootid']
);
 
$addval = array();
if(isset($this->_flparams['parent'])) {
$addval[$this->_flparams['parent']] = $thisnode['id'];
}
 
$addval[$this->_flparams['l']] = $thisnode['r'];
$addval[$this->_flparams['r']] = $thisnode['r'] + 1;
$addval[$this->_flparams['rootid']] = $thisnode['rootid'];
$addval[$this->_flparams['norder']] = 1;
$addval[$this->_flparams['level']] = $thisnode['level'] + 1;
 
if(!$this->_dumbmode || !$node_id=isset($values[$this->_flparams['id']])) {
$node_id = $addval[$this->_flparams['id']] = $this->db->nextId($this->sequence_table);
} else {
$node_id = $values[$this->_flparams['id']];
}
if (!$qr = $this->_values2Query($values, $addval)) {
$this->_releaseLock();
return false;
}
 
$sql[] = sprintf('INSERT INTO %s SET %s',
$this->node_table,
$qr);
for($i=0;$i<count($sql);$i++) {
$res = $this->db->query($sql[$i]);
$this->_testFatalAbort($res, __FILE__, __LINE__);
}
 
// EVENT (NodeCreate)
if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeCreate'])) {
$thisnode = $this->pickNode($node_id);
$this->triggerEvent('nodeCreate', $this->pickNode($id));
}
$this->_releaseLock();
return $node_id;
}
 
// }}}
// {{{ createLeftNode()
/**
* Creates a node before a given node
* <pre>
* +-- root1
* |
* +-\ root2
* | |
* | |-- subnode2 [new]
* | |-- subnode1 [target]
* | |-- subnode3
* |
* +-- root3
* </pre>
*
* @param int $id Target node ID
* @param array $values Hash with param => value pairs of the node (see $this->params)
* @param bool $returnID Tell the method to return a node id instead of an object.
* ATTENTION: That the method defaults to return an object instead of the node id
* has been overseen and is basically a bug. We have to keep this to maintain BC.
* You will have to set $returnID to true to make it behave like the other creation methods.
* This flaw will get fixed with the next major version.
*
* @access public
* @return mixed The node id or false on error
*/
function createLeftNode($id, $values) {
 
if ($this->debug) {
$this->_debugMessage('createLeftNode($target, $values)');
}
 
$this->_verifyUserValues('createLeftode()', $values);
 
// invalid target node, bail out
if (!($thisnode = $this->pickNode($id, true))) {
$epr = array('createLeftNode()', $id);
return $this->_raiseError(NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_ERROR, $epr);
}
 
if(PEAR::isError($lock=$this->_setLock())) {
return $lock;
}
 
 
// If the target node is a rootnode we virtually want to create a new root node
if ($thisnode['rootid'] == $thisnode['id']) {
return $this->createRootNode($values, $id, false, NESE_MOVE_BEFORE);
}
 
 
$addval = array();
$parent = $this->getParent($id, true);
if(isset($this->_flparams['parent'])) {
$addval[$this->_flparams['parent']] = $parent['id'];
}
 
$sql = array();
 
 
$sql[] = sprintf('UPDATE %s SET %s=%s+1
WHERE
%s=%s AND %s>=%s AND %s=%s AND %s BETWEEN %s AND %s',
$this->node_table,
$this->_flparams['norder'],
$this->_flparams['norder'],
$this->_flparams['rootid'],
$thisnode['rootid'],
$this->_flparams['norder'],
$thisnode['norder'],
$this->_flparams['level'],
$thisnode['level'],
$this->_flparams['l'],
$parent['l'],
$parent['r']);
 
 
// Update all nodes which have dependent left and right values
$sql[] = sprintf('
UPDATE %s SET
%s=IF(%s>=%s, %s+2, %s),
%s=IF(%s>=%s, %s+2, %s)
WHERE %s=%s',
$this->node_table,
$this->_flparams['l'],
$this->_flparams['l'],
$thisnode['l'],
$this->_flparams['l'],
$this->_flparams['l'],
$this->_flparams['r'],
$this->_flparams['r'],
$thisnode['r'],
$this->_flparams['r'],
$this->_flparams['r'],
$this->_flparams['rootid'],
$thisnode['rootid']
);
 
 
 
$addval[$this->_flparams['norder']] = $thisnode['norder'];
$addval[$this->_flparams['l']] = $thisnode['l'];
$addval[$this->_flparams['r']] = $thisnode['l']+1;
$addval[$this->_flparams['rootid']] = $thisnode['rootid'];
$addval[$this->_flparams['level']] = $thisnode['level'];
 
if(!$this->_dumbmode || !$node_id=isset($values[$this->_flparams['id']])) {
$node_id = $addval[$this->_flparams['id']] = $this->db->nextId($this->sequence_table);
} else {
$node_id = $values[$this->_flparams['id']];
}
if (!$qr = $this->_values2Query($values, $addval)) {
$this->_releaseLock();
return false;
}
 
// Insert the new node
$sql[] = sprintf('INSERT INTO %s SET %s',
$this->node_table,
$qr);
 
for($i=0;$i<count($sql);$i++) {
$res = $this->db->query($sql[$i]);
$this->_testFatalAbort($res, __FILE__, __LINE__);
}
 
// EVENT (NodeCreate)
if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeCreate'])) {
$this->triggerEvent('nodeCreate', $this->pickNode($id));
}
$this->_releaseLock();
return $node_id;
}
 
/**
* Creates a node after a given node
* <pre>
* +-- root1
* |
* +-\ root2
* | |
* | |-- subnode1 [target]
* | |-- subnode2 [new]
* | |-- subnode3
* |
* +-- root3
* </pre>
*
* @param int $id Target node ID
* @param array $values Hash with param => value pairs of the node (see $this->params)
* @param bool $returnID Tell the method to return a node id instead of an object.
* ATTENTION: That the method defaults to return an object instead of the node id
* has been overseen and is basically a bug. We have to keep this to maintain BC.
* You will have to set $returnID to true to make it behave like the other creation methods.
* This flaw will get fixed with the next major version.
*
* @access public
* @return mixed The node id or false on error
*/
function createRightNode($id, $values) {
 
if ($this->debug) {
$this->_debugMessage('createRightNode($target, $values)');
}
 
$this->_verifyUserValues('createRootNode()', $values);
 
// invalid target node, bail out
if (!($thisnode = $this->pickNode($id, true))) {
$epr = array('createRightNode()', $id);
return $this->_raiseError(NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_ERROR, $epr);
}
 
if(PEAR::isError($lock=$this->_setLock())) {
return $lock;
}
 
 
// If the target node is a rootnode we virtually want to create a new root node
if ($thisnode['rootid'] == $thisnode['id']) {
 
$nid = $this->createRootNode($values, $id);
$this->_releaseLock();
return $nid;
}
 
 
$addval = array();
$parent = $this->getParent($id, true);
if(isset($this->_flparams['parent'])) {
$addval[$this->_flparams['parent']] = $parent['id'];
}
 
$sql = array();
 
$sql[] = sprintf('UPDATE %s SET %s=%s+1
WHERE
%s=%s AND %s>%s AND %s=%s AND %s BETWEEN %s AND %s',
$this->node_table,
$this->_flparams['norder'],
$this->_flparams['norder'],
$this->_flparams['rootid'],
$thisnode['rootid'],
$this->_flparams['norder'],
$thisnode['norder'],
$this->_flparams['level'],
$thisnode['level'],
$this->_flparams['l'],
$parent['l'],
$parent['r']);
 
 
// Update all nodes which have dependent left and right values
 
 
$sql[] = sprintf('
UPDATE %s SET
%s=IF(%s>%s, %s+2, %s),
%s=IF(%s>%s, %s+2, %s)
WHERE %s=%s',
$this->node_table,
$this->_flparams['l'],
$this->_flparams['l'],
$thisnode['r'],
$this->_flparams['l'],
$this->_flparams['l'],
$this->_flparams['r'],
$this->_flparams['r'],
$thisnode['r'],
$this->_flparams['r'],
$this->_flparams['r'],
$this->_flparams['rootid'],
$thisnode['rootid']
);
 
$addval[$this->_flparams['norder']] = $thisnode['norder'] + 1;
$addval[$this->_flparams['l']] = $thisnode['r'] + 1;
$addval[$this->_flparams['r']] = $thisnode['r'] + 2;
$addval[$this->_flparams['rootid']] = $thisnode['rootid'];
$addval[$this->_flparams['level']] = $thisnode['level'];
 
if(!$this->_dumbmode || !isset($values[$this->_flparams['id']])) {
$node_id = $addval[$this->_flparams['id']] = $this->db->nextId($this->sequence_table);
} else {
$node_id = $values[$this->_flparams['id']];
}
if (!$qr = $this->_values2Query($values, $addval)) {
$this->_releaseLock();
return false;
}
 
// Insert the new node
$sql[] = sprintf('INSERT INTO %s SET %s', $this->node_table, $qr);
 
for($i=0;$i<count($sql);$i++) {
$res = $this->db->query($sql[$i]);
$this->_testFatalAbort($res, __FILE__, __LINE__);
}
 
// EVENT (NodeCreate)
if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeCreate'])) {
$this->triggerEvent('nodeCreate', $this->pickNode($id));
}
$this->_releaseLock();
return $node_id;
}
 
// }}}
// {{{ deleteNode()
 
/**
* Deletes a node
*
* @param int $id ID of the node to be deleted
*
* @access public
* @return bool True if the delete succeeds
*/
function deleteNode($id) {
 
if ($this->debug) {
$this->_debugMessage("deleteNode($id)");
}
 
// invalid target node, bail out
if (!($thisnode = $this->pickNode($id, true))) {
$epr = array('deleteNode()', $id);
return $this->_raiseError(NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_ERROR, $epr);
}
 
if (PEAR::isError($lock = $this->_setLock())) {
return $lock;
}
 
if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeDelete'])) {
// EVENT (NodeDelete)
$this->triggerEvent('nodeDelete', $this->pickNode($id));
}
 
$parent = $this->getParent($id, true);
$len = $thisnode['r'] - $thisnode['l'] + 1;
 
 
$sql = array();
 
// Delete the node
$sql[] = sprintf('DELETE FROM %s WHERE %s BETWEEN %s AND %s AND %s=%s',
$this->node_table,
$this->_flparams['l'],
$thisnode['l'],
$thisnode['r'],
$this->_flparams['rootid'],
$thisnode['rootid']
);
 
if ($thisnode['id'] != $thisnode['rootid']) {
 
// The node isn't a rootnode so close the gap
$sql[] = sprintf('UPDATE %s SET
%s=IF(%s>%s, %s-%s, %s),
%s=IF(%s>%s, %s-%s, %s)
WHERE %s=%s AND
(%s>%s OR %s>%s)',
$this->node_table,
$this->_flparams['l'],
$this->_flparams['l'],
$thisnode['l'],
$this->_flparams['l'],
$len,
$this->_flparams['l'],
$this->_flparams['r'],
$this->_flparams['r'],
$thisnode['l'],
$this->_flparams['r'],
$len,
$this->_flparams['r'],
$this->_flparams['rootid'],
$thisnode['rootid'],
$this->_flparams['l'],
$thisnode['l'],
$this->_flparams['r'],
$thisnode['r']
);
 
// Re-order
 
$sql[] = sprintf('UPDATE %s SET %s=%s-1 WHERE %s=%s AND %s=%s AND %s>%s AND %s BETWEEN %s AND %s',
$this->node_table,
$this->_flparams['norder'],
$this->_flparams['norder'],
$this->_flparams['rootid'],
$thisnode['rootid'],
$this->_flparams['level'],
$thisnode['level'],
$this->_flparams['norder'],
$thisnode['norder'],
$this->_flparams['l'],
$parent['l'],
$parent['r']);
 
} else {
// A rootnode was deleted and we only have to close the gap inside the order
$sql[] = sprintf('UPDATE %s SET %s=%s+1 WHERE %s=%s AND %s > %s',
$this->node_table,
$this->_flparams['norder'],
$this->_flparams['norder'],
$this->_flparams['rootid'],
$this->_flparams['id'],
$this->_flparams['norder'],
$thisnode['norder']);
}
for($i=0;$i<count($sql);$i++) {
$res = $this->db->query($sql[$i]);
$this->_testFatalAbort($res, __FILE__, __LINE__);
}
$this->_releaseLock();
return true;
}
 
// }}}
// {{{ updateNode()
 
/**
* Changes the payload of a node
*
* @param int $id Node ID
* @param array $values Hash with param => value pairs of the node (see $this->params)
* @param bool $_intermal Internal use only. Used to skip value validation. Leave this as it is.
*
* @access public
* @return bool True if the update is successful
*/
function updateNode($id, $values, $_internal=false) {
if ($this->debug) {
$this->_debugMessage('updateNode($id, $values)');
}
 
if (PEAR::isError($lock = $this->_setLock())) {
return $lock;
}
 
if(!$_internal) {
$this->_verifyUserValues('createRootNode()', $values);
}
 
$eparams = array('values' => $values);
if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeUpdate'])) {
// EVENT (NodeUpdate)
$this->triggerEvent('nodeUpdate', $this->pickNode($id), $eparams);
}
 
$addvalues = array();
if (!$qr = $this->_values2Query($values, $addvalues)) {
$this->_releaseLock();
return false;
}
 
$sql = sprintf('UPDATE %s SET %s WHERE %s = %s',
$this->node_table,
$qr,
$this->_flparams['id'],
$id);
$res = $this->db->query($sql);
$this->_testFatalAbort($res, __FILE__, __LINE__);
$this->_releaseLock();
return true;
}
 
 
 
// }}}
// +----------------------------------------------+
// | Moving and copying |
// |----------------------------------------------+
// | [PUBLIC] |
// +----------------------------------------------+
// {{{ moveTree()
 
/**
* Wrapper for node moving and copying
*
* @param int $id Source ID
* @param int $target Target ID
* @param constant $pos Position (use one of the NESE_MOVE_* constants)
* @param bool $copy Shall we create a copy
*
* @see _moveInsideLevel
* @see _moveAcross
* @see _moveRoot2Root
* @access public
* @return int ID of the moved node or false on error
*/
function moveTree($id, $targetid, $pos, $copy = false) {
 
if ($this->debug) {
$this->_debugMessage('moveTree($id, $target, $pos, $copy = false)');
}
if($id == $targetid && !$copy) {
// TRIGGER BOGUS MESSAGE
return false;
}
 
// Get information about source and target
if (!($source = $this->pickNode($id, true))) {
$epr = array('moveTree()', $id);
return $this->_raiseError(NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_ERROR, $epr);
}
 
if (!($target = $this->pickNode($targetid, true))) {
$epr = array('moveTree()', $targetid);
return $this->_raiseError(NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_ERROR, $epr);
}
 
if (PEAR::isError($lock = $this->_setLock(true))) {
return $lock;
}
 
$this->_relations = array();
// This operations don't need callbacks except the copy handler
// which ignores this setting
$this->_skipCallbacks = true;
 
if(!$copy) {
// We have a recursion - let's stop
if (($target['rootid'] == $source['rootid']) &&
(($source['l'] <= $target['l']) &&
($source['r'] >= $target['r']))) {
$this->_releaseLock(true);
$epr = array('moveTree()');
return $this->_raiseError(NESE_ERROR_RECURSION, PEAR_ERROR_RETURN, E_USER_NOTICE, $epr);
}
 
// Insert/move before or after
 
if (($source['rootid'] == $source['id']) &&
($target['rootid'] == $target['id'])) {
// We have to move a rootnode which is different from moving inside a tree
$nid = $this->_moveRoot2Root($source, $target, $pos, $copy);
$this->_releaseLock(true);
return $nid;
}
} elseif(($target['rootid'] == $source['rootid']) &&
(($source['l'] < $target['l']) &&
($source['r'] > $target['r']))) {
$this->_releaseLock(true);
$epr = array('moveTree()');
return $this->_raiseError(NESE_ERROR_RECURSION, PEAR_ERROR_RETURN, E_USER_NOTICE, $epr);
}
 
 
// We have to move between different levels and maybe subtrees - let's rock ;)
$this->_moveAcross($source, $target, $pos);
$this->_moveCleanup($copy);
$this->_releaseLock(true);
}
 
// }}}
// {{{ _moveAcross()
 
/**
* Moves nodes and trees to other subtrees or levels
*
* <pre>
* [+] <--------------------------------+
* +-[\] root1 [target] |
* <-------------------------+ |p
* +-\ root2 | |
* | | | |
* | |-- subnode1 [target] | |B
* | |-- subnode2 [new] |S |E
* | |-- subnode3 |U |F
* | |B |O
* +-\ root3 | |R
* |-- subnode 3.1 | |E
* |-\ subnode 3.2 [source] >--+------+
* |-- subnode 3.2.1
*</pre>
*
* @param object NodeCT $source Source node
* @param object NodeCT $target Target node
* @param string $pos Position [SUBnode/BEfore]
* @param bool $copy Shall we create a copy
*
* @access private
* @see moveTree
* @see _r_moveAcross
* @see _moveCleanup
*/
function _moveAcross($source, $target, $pos) {
if ($this->debug) {
$this->_debugMessage('_moveAcross($source, $target, $pos, $copy = false)');
}
 
// Get the current data from a node and exclude the id params which will be changed
// because of the node move
$values = array();
foreach($this->params as $key => $val) {
if ($source[$val] && !in_array($val, $this->_requiredParams)) {
$values[$key] = trim($source[$val]);
}
}
 
switch($pos) {
 
case NESE_MOVE_BEFORE:
$clone_id = $this->createLeftNode($target['id'], $values);
break;
 
case NESE_MOVE_AFTER:
$clone_id = $this->createRightNode($target['id'], $values);
break;
 
case NESE_MOVE_BELOW:
$clone_id = $this->createSubNode($target['id'], $values);
break;
}
 
 
$children = $this->getChildren($source['id'], true, true, true);
 
 
if ($children) {
$pos = NESE_MOVE_BELOW;
$sclone_id = $clone_id;
// Recurse through the child nodes
foreach($children AS $cid => $child) {
$sclone = $this->pickNode($sclone_id, true);
$sclone_id = $this->_moveAcross($child, $sclone, $pos);
 
$pos = NESE_MOVE_AFTER;
}
}
 
$this->_relations[$source['id']] = $clone_id;
return $clone_id;
}
 
// }}}
// {{{ _moveCleanup()
 
/**
* Deletes the old subtree (node) and writes the node id's into the cloned tree
*
*
* @param array $relations Hash in der Form $h[alteid]=neueid
* @param array $copy Are we in copy mode?
* @access private
*/
function _moveCleanup($copy = false) {
 
$relations = $this->_relations;
if ($this->debug) {
$this->_debugMessage('_moveCleanup($relations, $copy = false)');
}
 
$deletes = array();
$updates = array();
$tb = $this->node_table;
$fid = $this->_flparams['id'];
$froot = $this->_flparams['rootid'];
foreach($relations AS $key => $val) {
$clone = $this->pickNode($val);
if ($copy) {
// EVENT (NodeCopy)
 
$eparams = array('clone' => $clone);
 
if (!$this->_skipCallbacks && isset($this->_hasListeners['nodeCopy'])) {
$this->triggerEvent('nodeCopy', $this->pickNode($key), $eparams);
}
continue;
}
 
// No callbacks here because the node itself doesn't get changed
// Only it's position
// If one needs a callback here please let me know
 
$deletes[] = $key;
// It's isn't a rootnode
if ($clone->id != $clone->rootid) {
 
 
$sql = sprintf('UPDATE %s SET %s=%s WHERE %s = %s',
$this->node_table,
$fid,
$key,
$fid,
$val);
$updates[] = $sql;
} else {
$sql = sprintf('UPDATE %s SET %s=%s, %s=%s WHERE %s=%s',
$tb,
$fid,
$key,
$froot,
$val,
$fid,
$val);
 
$updates[] = $sql;
$orootid = $clone->rootid;
 
$sql = sprintf('UPDATE %s SET %s=%s WHERE %s=%s',
$tb,
$froot,
$key,
$froot,
$orootid);
$updates[] = $sql;
}
$this->_skipCallbacks = false;
}
 
if(!empty($deletes)) {
for($i=0;$i<count($deletes);$i++) {
$this->deleteNode($deletes[$i]);
}
}
 
if(!empty($updates)) {
for($i=0;$i<count($updates);$i++) {
$res = $this->db->query($updates[$i]);
$this->_testFatalAbort($res, __FILE__, __LINE__);
}
}
 
return true;
}
 
// }}}
// {{{ _moveRoot2Root()
 
/**
* Moves rootnodes
*
* <pre>
* +-- root1
* |
* +-\ root2
* | |
* | |-- subnode1 [target]
* | |-- subnode2 [new]
* | |-- subnode3
* |
* +-\ root3
* [|] <-----------------------+
* |-- subnode 3.1 [target] |
* |-\ subnode 3.2 [source] >--+
* |-- subnode 3.2.1
* </pre>
*
* @param object NodeCT $source Source
* @param object NodeCT $target Target
* @param string $pos BEfore | AFter
* @access private
* @see moveTree
*/
function _moveRoot2Root($source, $target, $pos) {
 
if ($this->debug) {
$this->_debugMessage('_moveRoot2Root($source, $target, $pos, $copy)');
}
if(PEAR::isError($lock=$this->_setLock())) {
return $lock;
}
 
$tb = $this->node_table;
$fid = $this->_flparams['id'];
$froot = $this->_flparams['rootid'];
$freh = $this->_flparams['norder'];
$s_order = $source['norder'];
$t_order = $target['norder'];
$s_id = $source['id'];
$t_id = $target['id'];
 
 
if ($s_order < $t_order) {
if ($pos == NESE_MOVE_BEFORE) {
$sql = "UPDATE $tb SET $freh=$freh-1
WHERE $freh BETWEEN $s_order AND $t_order AND
$fid!=$t_id AND
$fid!=$s_id AND
$froot=$fid";
$res = $this->db->query($sql);
$this->_testFatalAbort($res, __FILE__, __LINE__);
$sql = "UPDATE $tb SET $freh=$t_order -1 WHERE $fid=$s_id";
$res = $this->db->query($sql);
$this->_testFatalAbort($res, __FILE__, __LINE__);
}
elseif($pos == NESE_MOVE_AFTER) {
 
$sql = "UPDATE $tb SET $freh=$freh-1
WHERE $freh BETWEEN $s_order AND $t_order AND
$fid!=$s_id AND
$froot=$fid";
$res = $this->db->query($sql);
$this->_testFatalAbort($res, __FILE__, __LINE__);
 
$sql = "UPDATE $tb SET $freh=$t_order WHERE $fid=$s_id";
$res = $this->db->query($sql);
$this->_testFatalAbort($res, __FILE__, __LINE__);
}
}
 
if ($s_order > $t_order) {
if ($pos == NESE_MOVE_BEFORE) {
$sql = "UPDATE $tb SET $freh=$freh+1
WHERE $freh BETWEEN $t_order AND $s_order AND
$fid != $s_id AND
$froot=$fid";
$res = $this->db->query($sql);
$this->_testFatalAbort($res, __FILE__, __LINE__);
 
$sql = "UPDATE $tb SET $freh=$t_order WHERE $fid=$s_id";
$res = $this->db->query($sql);
$this->_testFatalAbort($res, __FILE__, __LINE__);
}
elseif ($pos == NESE_MOVE_AFTER) {
$sql = "UPDATE $tb SET $freh=$freh+1
WHERE $freh BETWEEN $t_order AND $s_order AND
$fid!=$t_id AND
$fid!=$s_id AND
$froot=$fid";
$res = $this->db->query($sql);
$this->_testFatalAbort($res, __FILE__, __LINE__);
 
$sql = "UPDATE $tb SET $freh=$t_order+1 WHERE $fid = $s_id";
$res = $this->db->query($sql);
$this->_testFatalAbort($res, __FILE__, __LINE__);
}
}
$this->_releaseLock();
return $source->id;
}
 
// }}}
// +-----------------------+
// | Helper methods |
// +-----------------------+
 
// }}}
// {{{ _secSort()
 
/**
* Callback for uasort used to sort siblings
*
* @access private
*/
function _secSort($node1, $node2) {
// Within the same level?
if($node1['level'] != $node2['level']) {
return strnatcmp($node1['l'], $node2['l']);
}
 
// Are they siblings?
$p1 = $this->getParent($node1);
$p2 = $this->getParent($node2);
if($p1['id'] != $p2['id']) {
return strnatcmp($node1['l'], $node2['l']);
}
 
// Same field value? Use the lft value then
$field = $this->params[$this->secondarySort];
if($node1[$field] == $node2[$field]) {
return strnatcmp($node1['l'], $node2[l]);
}
 
// Compare between siblings with different field value
return strnatcmp($node1[$field], $node2[$field]);
}
 
// }}}
// {{{ _addSQL()
 
/**
* Adds a specific type of SQL to a query string
*
* @param array $addSQL The array of SQL strings to add. Example value:
* $addSQL = array(
* 'cols' => 'tb2.col2, tb2.col3', // Additional tables/columns
* 'join' => 'LEFT JOIN tb1 USING(STRID)', // Join statement
* 'append' => 'GROUP by tb1.STRID'); // Group condition
* @param string $type The type of SQL. Can be 'cols', 'join', or 'append'.
*
* @access private
* @return string The SQL, properly formatted
*/
function _addSQL($addSQL, $type) {
if (!isset($addSQL[$type])) {
return '';
}
 
switch($type) {
case 'cols':
return ', ' . $addSQL[$type];
default:
return $addSQL[$type];
}
}
 
// }}}
// {{{ _getSelectFields()
 
/**
* Gets the select fields based on the params
*
* @param bool $aliasFields Should we alias the fields so they are the names of the
* parameter keys, or leave them as is?
*
* @access private
* @return string A string of query fields to select
*/
function _getSelectFields($aliasFields) {
$queryFields = array();
foreach ($this->params as $key => $val) {
$tmp_field = $this->node_table . '.' . $key;
if ($aliasFields) {
$tmp_field .= ' AS ' . $val;
}
$queryFields[] = $tmp_field;
}
 
$fields = implode(', ', $queryFields);
return $fields;
}
 
// }}}
// {{{ _processResultSet()
 
/**
* Processes a DB result set by checking for a DB error and then transforming the result
* into a set of DB_NestedSet_Node objects or leaving it as an array.
*
* @param string $sql The sql query to be done
* @param bool $keepAsArray Keep the result as an array or transform it into a set of
* DB_NestedSet_Node objects?
* @param bool $fieldsAreAliased Are the fields aliased?
*
* @access private
* @return mixed False on error or the transformed node set.
*/
function _processResultSet($sql, $keepAsArray, $fieldsAreAliased) {
$result = $this->db->getAll($sql);
if ($this->_testFatalAbort($result, __FILE__, __LINE__)) {
return false;
}
 
$nodes = array();
$idKey = $fieldsAreAliased ? 'id' : $this->_flparams['id'];
foreach ($result as $row) {
$node_id = $row[$idKey];
if ($keepAsArray) {
$nodes[$node_id] = $row;
} else {
// Create an instance of the node container
$nodes[$node_id] =& new DB_NestedSet_Node($row);
}
 
}
return $nodes;
}
 
// }}}
// {{{ _testFatalAbort()
 
/**
* Error Handler
*
* Tests if a given ressource is a PEAR error object
* ans raises a fatal error in case of an error object
*
* @param object PEAR::Error $errobj The object to test
* @param string $file The filename wher the error occured
* @param int $line The line number of the error
* @return void
* @access private
*/
function _testFatalAbort($errobj, $file, $line) {
if (!$this->_isDBError($errobj)) {
return false;
}
 
if ($this->debug) {
$this->_debugMessage('_testFatalAbort($errobj, $file, $line)');
}
if ($this->debug) {
$message = $errobj->getUserInfo();
$code = $errobj->getCode();
$msg = "$message ($code) in file $file at line $line";
} else {
$msg = $errobj->getMessage();
$code = $errobj->getCode(); }
 
PEAR::raiseError($msg, $code, PEAR_ERROR_TRIGGER, E_USER_ERROR);
}
 
// {{{ __raiseError()
 
/**
* @access private
*/
function _raiseError($code, $mode, $option, $epr=array()) {
$message = vsprintf($this->_getMessage($code), $epr);
return PEAR::raiseError($message, $code, $mode, $option);
}
 
// }}}
 
// {{{ addListener()
 
/**
* Add an event listener
*
* Adds an event listener and returns an ID for it
*
* @param string $event The ivent name
* @param string $listener The listener object
* @return string
* @access public
*/
function addListener($event, &$listener) {
$listenerID = uniqid('el');
$this->eventListeners[$event][$listenerID] =& $listener;
$this->_hasListeners[$event] = true;
return $listenerID;
}
 
// }}}
// {{{ removeListener()
 
/**
* Removes an event listener
*
* Removes the event listener with the given ID
*
* @param string $event The ivent name
* @param string $listenerID The listener's ID
* @return bool
* @access public
*/
function removeListener($event, $listenerID) {
unset($this->eventListeners[$event][$listenerID]);
if (!isset($this->eventListeners[$event]) ||
!is_array($this->eventListeners[$event]) ||
count($this->eventListeners[$event]) == 0) {
unset($this->_hasListeners[$event]);
}
return true;
}
 
// }}}
// {{{ triggerEvent()
 
/**
* Triggers and event an calls the event listeners
*
* @param string $event The Event that occured
* @param object node $node A Reference to the node object which was subject to changes
* @param array $eparams A associative array of params which may be needed by the handler
* @return bool
* @access public
*/
function triggerEvent($event, &$node, $eparams = false) {
if ($this->_skipCallbacks || !isset($this->_hasListeners[$event])) {
return false;
}
 
foreach($this->eventListeners[$event] as $key => $val) {
if (!method_exists($val, 'callEvent')) {
return new PEAR_Error($this->_getMessage(NESE_ERROR_NOHANDLER), NESE_ERROR_NOHANDLER);
}
 
$val->callEvent($event, $node, $eparams);
}
 
return true;
}
 
// }}}
// {{{ apiVersion()
 
function apiVersion() {
return array(
'package:'=>$this->_packagename,
'majorversion'=>$this->_majorversion,
'minorversion'=>$this->_minorversion,
'version'=>sprintf('%s.%s',$this->_majorversion, $this->_minorversion),
'revision'=>str_replace('$', '',"$Revision: 1.1 $")
);
}
 
// }}}
// {{{ setAttr()
 
/**
* Sets an object attribute
*
* @param array $attr An associative array with attributes
*
* @return bool
* @access public
*/
function setAttr($attr) {
static $hasSetSequence;
if (!isset($hasSetSequence)) {
$hasSetSequence = false;
}
 
if (!is_array($attr) || count($attr) == 0) {
return false;
}
 
foreach ($attr as $key => $val) {
$this->$key = $val;
if ($key == 'sequence_table') {
$hasSetSequence = true;
}
 
// only update sequence to reflect new table if they haven't set it manually
if (!$hasSetSequence && $key == 'node_table') {
$this->sequence_table = $this->node_table . '_' . $this->_flparams['id'];
}
if($key == 'cache' && is_object($val)) {
$this->_caching = true;
$GLOBALS['DB_NestedSet'] = & $this;
}
}
 
return true;
}
 
// }}}
// {{{ setsortMode()
/**
* This enables you to set specific options for each output method
*
* @param constant $sortMode
*
* @access public
* @return Current sortMode
*/
function setsortMode($sortMode=false) {
if($sortMode && in_array($sortMode, $this->_sortModes)) {
$this->_sortMode = $sortMode;
} else {
return $this->_sortMode;
}
return $this->_sortMode;
}
// }}}
// {{{ setDbOption()
 
/**
* Sets a db option. Example, setting the sequence table format
*
* @var string $option The option to set
* @var string $val The value of the option
*
* @access public
* @return void
*/
function setDbOption($option, $val) {
$this->db->setOption($option, $val);
}
 
// }}}
// {{{ testLock()
 
/**
* Tests if a database lock is set
*
* @access public
*/
function testLock() {
if ($this->debug) {
$this->_debugMessage('testLock()');
}
 
if($lockID = $this->_structureTableLock) {
return $lockID;
}
$this->_lockGC();
$sql = sprintf('SELECT lockID FROM %s WHERE lockTable=%s',
$this->lock_table,
$this->_quote($this->node_table)) ;
 
$res = $this->db->query($sql);
$this->_testFatalAbort($res, __FILE__, __LINE__);
 
if ($this->_numRows($res)) {
return new PEAR_Error($this->_getMessage(NESE_ERROR_TBLOCKED),NESE_ERROR_TBLOCKED);
}
 
return false;
}
 
// }}}
// {{{ _setLock()
 
/**
* @access private
*/
function _setLock($exclusive=false) {
$lock = $this->testLock();
if(PEAR::isError($lock)) {
return $lock;
}
 
if ($this->debug) {
$this->_debugMessage('_setLock()');
}
if($this->_caching) {
@$this->cache->flush('function_cache');
$this->_caching = false;
$this->_restcache = true;
}
 
if (!$lockID = $this->_structureTableLock) {
$lockID = $this->_structureTableLock = uniqid('lck-');
 
$sql = sprintf('INSERT INTO %s SET lockID=%s, lockTable=%s, lockStamp=%s',
$this->lock_table,
$this->_quote($lockID),
$this->_quote($this->node_table),
time());
 
} else {
$sql = sprintf('UPDATE %s set lockStamp=%s WHERE lockID=%s AND lockTable=%s',
$this->lock_table,
time(),
$this->_quote($lockID),
$this->_quote($this->node_table));
}
if($exclusive) {
$this->_lockExclusive = true;
}
 
$res = $this->db->query($sql);
$this->_testFatalAbort($res, __FILE__, __LINE__);
return $lockID;
}
 
// }}}
// {{{ _releaseLock()
 
/**
* @access private
*/
function _releaseLock($exclusive=false) {
if ($this->debug) {
$this->_debugMessage('_releaseLock()');
}
 
if($exclusive) {
$this->_lockExclusive = false;
}
 
if ((!$lockID = $this->_structureTableLock) || $this->_lockExclusive) {
return false;
}
 
$tb = $this->lock_table;
$stb = $this->node_table;
$sql = "DELETE FROM $tb
WHERE lockTable=" . $this->_quote($stb) . " AND
lockID=" . $this->_quote($lockID);
 
$res = $this->db->query($sql);
$this->_testFatalAbort($res, __FILE__, __LINE__);
$this->_structureTableLock = false;
if($this->_restcache) {
$this->_caching = true;
$this->_restcache = false;
}
return true;
}
 
// }}}
// {{{ _lockGC()
 
/**
* @access private
*/
function _lockGC() {
if ($this->debug) {
$this->_debugMessage('_lockGC()');
}
$tb = $this->lock_table;
$stb = $this->node_table;
$lockTTL = time() - $this->lockTTL;
$sql = "DELETE FROM $tb
WHERE lockTable=" . $this->_quote($stb) . " AND
lockStamp < $lockTTL";
 
$res = $this->db->query($sql);
$this->_testFatalAbort($res, __FILE__, __LINE__);
}
 
// }}}
// {{{ _values2Query()
 
/**
* @access private
*/
function _values2Query($values, $addval = false) {
 
if ($this->debug) {
$this->_debugMessage('_values2Query($values, $addval = false)');
}
if (is_array($addval)) {
$values = $values + $addval;
}
 
$arq = array();
foreach($values AS $key => $val) {
$k = trim($key);
$v = trim($val);
if ($k) {
// To be used with the next mahor version
// $iv = in_array($this->params[$k], $this->_quotedParams) ? $this->_quote($v) : $v;
$iv = $this->_quote($v);
$arq[] = "$k=$iv";
}
}
 
if (!is_array($arq) || count($arq) == 0) {
return false;
}
 
$query = implode(', ', $arq);
return $query;
}
 
// }}}
// {{{ _verifyUserValues()
 
/**
* Clean values from protected or unknown columns
*
* @var string $caller The calling method
* @var string $values The values array
*
* @access private
* @return void
*/
function _verifyUserValues($caller, &$values) {
 
if($this->_dumbmode) {
return true;
}
foreach($values AS $field=>$value) {
if(!isset($this->params[$field])) {
$epr = array(
$caller,
sprintf('Unknown column/param \'%s\'', $field));
$this->_raiseError(NESE_ERROR_WRONG_MPARAM, PEAR_ERROR_RETURN, E_USER_NOTICE, $epr);
unset($values[$field]);
} else {
$flip = $this->params[$field];
if(in_array($flip, $this->_requiredParams)) {
$epr = array(
$caller,
sprintf('\'%s\' is autogenerated and can\'t be passed - it will be ignored', $field));
$this->_raiseError(NESE_ERROR_WRONG_MPARAM, PEAR_ERROR_RETURN, E_USER_NOTICE, $epr);
unset($values[$field]);
}
}
}
}
 
// }}}
// {{{ _debugMessage()
 
/**
* @access private
*/
function _debugMessage($msg) {
if ($this->debug) {
$time = $this->_getmicrotime();
echo "$time::Debug:: $msg<br />\n";
}
}
 
// }}}
// {{{ _getMessage()
 
/**
* @access private
*/
function _getMessage($code) {
if ($this->debug) {
$this->_debugMessage('_getMessage($code)');
}
return isset($this->messages[$code]) ? $this->messages[$code] : $this->messages[NESE_MESSAGE_UNKNOWN];
 
}
 
// }}}
// {{{ _getmicrotime()
 
/**
* @access private
*/
function _getmicrotime() {
list($usec, $sec) = explode(' ', microtime());
return ((float)$usec + (float)$sec);
}
 
// }}}
// {{{ convertTreeModel()
 
/**
* Convert a <1.3 tree into a 1.3 tree format
*
* This will convert the tree into a format needed for some new features in
* 1.3. Your <1.3 tree will still work without converting but some new features
* like preorder sorting won't work as expected.
*
* <pre>
* Usage:
* - Create a new node table (tb_nodes2) from the current node table (tb_nodes1) (only copy the structure).
* - Create a nested set instance of the 'old' set (NeSe1) and one of the new set (NeSe2)
* - Now you have 2 identical objects where only node_table differs
* - Call DB_NestedSet::convertTreeModel(&$orig, &$copy);
* - After that you have a cleaned up copy of tb_nodes1 inside tb_nodes2
* </pre>
*
* @param object DB_NestedSet $orig Nested set we want to copy
* @param object DB_NestedSet $copy Object where the new tree is copied to
* @param integer $_parent ID of the parent node (private)
*
* @static
* @access public
* @return bool True uns success
*/
function convertTreeModel(&$orig, &$copy, $_parent=false) {
 
static $firstSet;
 
$isRoot = false;
if(!$_parent) {
if(!is_object($orig) || !is_object($copy)) {
return false;
}
if($orig->node_table == $copy->node_table) {
return false;
}
$copy->_dumbmode = true;
$orig->sortMode = NESE_SORT_LEVEL;
$copy->sortMode = NESE_SORT_LEVEL;
$sibl = $orig->getRootNodes(true);
$isRoot = true;
} else {
$sibl = $orig->getChildren($_parent, true);
}
 
if(empty($sibl)) {
return false;
}
 
foreach($sibl AS $sid=>$sibling) {
unset($sibling['l']);
unset($sibling['r']);
unset($sibling['norder']);
 
$values = array();
foreach($sibling AS $key=>$val) {
if(!isset($copy->_flparams[$key])) {
continue;
}
$values[$copy->_flparams[$key]] = $val;
}
 
if(!$firstSet) {
$psid = $copy->createRootNode($values, false, true);
$firstSet = true;
} elseif($isRoot) {
$psid = $copy->createRightNode($psid, $values);
} else {
$copy->createSubNode($_parent, $values);
}
 
DB_NestedSet::convertTreeModel($orig, $copy, $sid);
}
return true;
}
// }}}
// {{{ _numRows()
/**
* Fetches the number of rows the last query returned
* @access private
* @abstract
*/
function _numRows($res) {
}
// }}}
// {{{ _isDBError()
/**
* Returns true if a db return value is an error object
* @access private
* @abstract
*/
function _isDBError($err) {
}
// }}}
// {{{ quote()
/**
* Quotes a string to use it inside queries
* @access private
* @abstract
*/
function _quote($str) {
}
 
}
 
// {{{ DB_NestedSet_Node:: class
 
/**
* Generic class for node objects
*
* @autor Daniel Khan <dk@webcluster.at>;
* @version $Revision: 1.1 $
* @package DB_NestedSet
*
* @access private
*/
 
class DB_NestedSet_Node {
// {{{ constructor
 
/**
* Constructor
*/
function DB_NestedSet_Node($data) {
if (!is_array($data) || count($data) == 0) {
return new PEAR_ERROR($data, NESE_ERROR_PARAM_MISSING);
}
 
$this->setAttr($data);
return true;
}
 
// }}}
// {{{ setAttr()
 
function setAttr($data) {
if(!is_array($data) || count($data) == 0) {
return false;
}
 
foreach ($data as $key => $val) {
$this->$key = $val;
}
}
 
// }}}
 
}
// }}}
 
 
?>
/trunk/api/pear/DB/mssql.php
20,7 → 20,7
* @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.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: mssql.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
41,7 → 41,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
*/
class DB_mssql extends DB_common
/trunk/api/pear/DB/DataObject.php
New file
0,0 → 1,3827
<?php
/**
* Object Based Database Query Builder and data store
*
* 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_DataObject
* @author Alan Knowles <alan@akbkhome.com>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: DataObject.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB_DataObject
*/
 
/* ===========================================================================
*
* !!!!!!!!!!!!! W A R N I N G !!!!!!!!!!!
*
* THIS MAY SEGFAULT PHP IF YOU ARE USING THE ZEND OPTIMIZER (to fix it,
* just add "define('DB_DATAOBJECT_NO_OVERLOAD',true);" before you include
* this file. reducing the optimization level may also solve the segfault.
* ===========================================================================
*/
 
/**
* The main "DB_DataObject" class is really a base class for your own tables classes
*
* // Set up the class by creating an ini file (refer to the manual for more details
* [DB_DataObject]
* database = mysql:/username:password@host/database
* schema_location = /home/myapplication/database
* class_location = /home/myapplication/DBTables/
* clase_prefix = DBTables_
*
*
* //Start and initialize...................... - dont forget the &
* $config = parse_ini_file('example.ini',true);
* $options = &PEAR::getStaticProperty('DB_DataObject','options');
* $options = $config['DB_DataObject'];
*
* // example of a class (that does not use the 'auto generated tables data')
* class mytable extends DB_DataObject {
* // mandatory - set the table
* var $_database_dsn = "mysql://username:password@localhost/database";
* var $__table = "mytable";
* function table() {
* return array(
* 'id' => 1, // integer or number
* 'name' => 2, // string
* );
* }
* function keys() {
* return array('id');
* }
* }
*
* // use in the application
*
*
* Simple get one row
*
* $instance = new mytable;
* $instance->get("id",12);
* echo $instance->somedata;
*
*
* Get multiple rows
*
* $instance = new mytable;
* $instance->whereAdd("ID > 12");
* $instance->whereAdd("ID < 14");
* $instance->find();
* while ($instance->fetch()) {
* echo $instance->somedata;
* }
 
 
/**
* Needed classes
* - we use getStaticProperty from PEAR pretty extensively (cant remove it ATM)
*/
 
require_once 'PEAR.php';
 
/**
* We are setting a global fetchmode assoc constant of 2 to be compatible with
* both DB and MDB2
*/
define('DB_DATAOBJECT_FETCHMODE_ASSOC',2);
 
 
 
 
 
/**
* these are constants for the get_table array
* user to determine what type of escaping is required around the object vars.
*/
define('DB_DATAOBJECT_INT', 1); // does not require ''
define('DB_DATAOBJECT_STR', 2); // requires ''
 
define('DB_DATAOBJECT_DATE', 4); // is date #TODO
define('DB_DATAOBJECT_TIME', 8); // is time #TODO
define('DB_DATAOBJECT_BOOL', 16); // is boolean #TODO
define('DB_DATAOBJECT_TXT', 32); // is long text #TODO
define('DB_DATAOBJECT_BLOB', 64); // is blob type
 
 
define('DB_DATAOBJECT_NOTNULL', 128); // not null col.
define('DB_DATAOBJECT_MYSQLTIMESTAMP' , 256); // mysql timestamps (ignored by update/insert)
/*
* Define this before you include DataObjects.php to disable overload - if it segfaults due to Zend optimizer..
*/
//define('DB_DATAOBJECT_NO_OVERLOAD',true)
 
 
/**
* Theses are the standard error codes, most methods will fail silently - and return false
* to access the error message either use $table->_lastError
* or $last_error = PEAR::getStaticProperty('DB_DataObject','lastError');
* the code is $last_error->code, and the message is $last_error->message (a standard PEAR error)
*/
 
define('DB_DATAOBJECT_ERROR_INVALIDARGS', -1); // wrong args to function
define('DB_DATAOBJECT_ERROR_NODATA', -2); // no data available
define('DB_DATAOBJECT_ERROR_INVALIDCONFIG', -3); // something wrong with the config
define('DB_DATAOBJECT_ERROR_NOCLASS', -4); // no class exists
define('DB_DATAOBJECT_ERROR_INVALID_CALL' ,-7); // overlad getter/setter failure
 
/**
* Used in methods like delete() and count() to specify that the method should
* build the condition only out of the whereAdd's and not the object parameters.
*/
define('DB_DATAOBJECT_WHEREADD_ONLY', true);
 
/**
*
* storage for connection and result objects,
* it is done this way so that print_r()'ing the is smaller, and
* it reduces the memory size of the object.
* -- future versions may use $this->_connection = & PEAR object..
* although will need speed tests to see how this affects it.
* - includes sub arrays
* - connections = md5 sum mapp to pear db object
* - results = [id] => map to pear db object
* - resultseq = sequence id for results & results field
* - resultfields = [id] => list of fields return from query (for use with toArray())
* - ini = mapping of database to ini file results
* - links = mapping of database to links file
* - lasterror = pear error objects for last error event.
* - config = aliased view of PEAR::getStaticPropery('DB_DataObject','options') * done for performance.
* - array of loaded classes by autoload method - to stop it doing file access request over and over again!
*/
$GLOBALS['_DB_DATAOBJECT']['RESULTS'] = array();
$GLOBALS['_DB_DATAOBJECT']['RESULTSEQ'] = 1;
$GLOBALS['_DB_DATAOBJECT']['RESULTFIELDS'] = array();
$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'] = array();
$GLOBALS['_DB_DATAOBJECT']['INI'] = array();
$GLOBALS['_DB_DATAOBJECT']['LINKS'] = array();
$GLOBALS['_DB_DATAOBJECT']['SEQUENCE'] = array();
$GLOBALS['_DB_DATAOBJECT']['LASTERROR'] = null;
$GLOBALS['_DB_DATAOBJECT']['CONFIG'] = array();
$GLOBALS['_DB_DATAOBJECT']['CACHE'] = array();
$GLOBALS['_DB_DATAOBJECT']['OVERLOADED'] = false;
$GLOBALS['_DB_DATAOBJECT']['QUERYENDTIME'] = 0;
 
 
// this will be horrifically slow!!!!
// NOTE: Overload SEGFAULTS ON PHP4 + Zend Optimizer (see define before..)
// these two are BC/FC handlers for call in PHP4/5
 
if ( substr(phpversion(),0,1) == 5) {
class DB_DataObject_Overload
{
function __call($method,$args)
{
$return = null;
$this->_call($method,$args,$return);
return $return;
}
function __sleep()
{
return array_keys(get_object_vars($this)) ;
}
}
} else {
if (version_compare(phpversion(),'4.3.10','eq') && !defined('DB_DATAOBJECT_NO_OVERLOAD')) {
trigger_error(
"overload does not work with PHP4.3.10, either upgrade
(snaps.php.net) or more recent version
or define DB_DATAOBJECT_NO_OVERLOAD as per the manual.
",E_USER_ERROR);
}
 
if (!function_exists('clone')) {
// emulate clone - as per php_compact, slow but really the correct behaviour..
eval('function clone($t) { $r = $t; if (method_exists($r,"__clone")) { $r->__clone(); } return $r; }');
}
eval('
class DB_DataObject_Overload {
function __call($method,$args,&$return) {
return $this->_call($method,$args,$return);
}
}
');
}
 
 
 
 
/*
*
* @package DB_DataObject
* @author Alan Knowles <alan@akbkhome.com>
* @since PHP 4.0
*/
class DB_DataObject extends DB_DataObject_Overload
{
/**
* The Version - use this to check feature changes
*
* @access private
* @var string
*/
var $_DB_DataObject_version = "1.7.15";
 
/**
* The Database table (used by table extends)
*
* @access private
* @var string
*/
var $__table = ''; // database table
 
/**
* The Number of rows returned from a query
*
* @access public
* @var int
*/
var $N = 0; // Number of rows returned from a query
 
 
/* ============================================================= */
/* Major Public Methods */
/* (designed to be optionally then called with parent::method()) */
/* ============================================================= */
 
 
/**
* Get a result using key, value.
*
* for example
* $object->get("ID",1234);
* Returns Number of rows located (usually 1) for success,
* and puts all the table columns into this classes variables
*
* see the fetch example on how to extend this.
*
* if no value is entered, it is assumed that $key is a value
* and get will then use the first key in keys()
* to obtain the key.
*
* @param string $k column
* @param string $v value
* @access public
* @return int No. of rows
*/
function get($k = null, $v = null)
{
global $_DB_DATAOBJECT;
if (empty($_DB_DATAOBJECT['CONFIG'])) {
DB_DataObject::_loadConfig();
}
$keys = array();
if ($v === null) {
$v = $k;
$keys = $this->keys();
if (!$keys) {
$this->raiseError("No Keys available for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
return false;
}
$k = $keys[0];
}
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("$k $v " .print_r($keys,true), "GET");
}
if ($v === null) {
$this->raiseError("No Value specified for get", DB_DATAOBJECT_ERROR_INVALIDARGS);
return false;
}
$this->$k = $v;
return $this->find(1);
}
 
/**
* An autoloading, caching static get method using key, value (based on get)
*
* Usage:
* $object = DB_DataObject::staticGet("DbTable_mytable",12);
* or
* $object = DB_DataObject::staticGet("DbTable_mytable","name","fred");
*
* or write it into your extended class:
* function &staticGet($k,$v=NULL) { return DB_DataObject::staticGet("This_Class",$k,$v); }
*
* @param string $class class name
* @param string $k column (or value if using keys)
* @param string $v value (optional)
* @access public
* @return object
*/
function &staticGet($class, $k, $v = null)
{
$lclass = strtolower($class);
global $_DB_DATAOBJECT;
if (empty($_DB_DATAOBJECT['CONFIG'])) {
DB_DataObject::_loadConfig();
}
 
 
$key = "$k:$v";
if ($v === null) {
$key = $k;
}
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
DB_DataObject::debug("$class $key","STATIC GET - TRY CACHE");
}
if (!empty($_DB_DATAOBJECT['CACHE'][$lclass][$key])) {
return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
}
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
DB_DataObject::debug("$class $key","STATIC GET - NOT IN CACHE");
}
 
$obj = DB_DataObject::factory(substr($class,strlen($_DB_DATAOBJECT['CONFIG']['class_prefix'])));
if (PEAR::isError($obj)) {
DB_DataObject::raiseError("could not autoload $class", DB_DATAOBJECT_ERROR_NOCLASS);
return false;
}
if (!isset($_DB_DATAOBJECT['CACHE'][$lclass])) {
$_DB_DATAOBJECT['CACHE'][$lclass] = array();
}
if (!$obj->get($k,$v)) {
DB_DataObject::raiseError("No Data return from get $k $v", DB_DATAOBJECT_ERROR_NODATA);
return false;
}
$_DB_DATAOBJECT['CACHE'][$lclass][$key] = $obj;
return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
}
 
/**
* find results, either normal or crosstable
*
* for example
*
* $object = new mytable();
* $object->ID = 1;
* $object->find();
*
*
* will set $object->N to number of rows, and expects next command to fetch rows
* will return $object->N
*
* @param boolean $n Fetch first result
* @access public
* @return mixed (number of rows returned, or true if numRows fetching is not supported)
*/
function find($n = false)
{
global $_DB_DATAOBJECT;
if (!isset($this->_query)) {
$this->raiseError(
"You cannot do two queries on the same object (copy it before finding)",
DB_DATAOBJECT_ERROR_INVALIDARGS);
return false;
}
if (empty($_DB_DATAOBJECT['CONFIG'])) {
DB_DataObject::_loadConfig();
}
 
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug($n, "__find",1);
}
if (!$this->__table) {
// xdebug can backtrace this!
php_error("NO \$__table SPECIFIED in class definition",E_USER_ERROR);
}
$this->N = 0;
$query_before = $this->_query;
$this->_build_condition($this->table()) ;
$quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
$this->_connect();
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
/* We are checking for method modifyLimitQuery as it is PEAR DB specific */
$sql = 'SELECT ' .
$this->_query['data_select'] .
' FROM ' . ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table) . " " .
$this->_join .
$this->_query['condition'] . ' '.
$this->_query['group_by'] . ' '.
$this->_query['having'] . ' '.
$this->_query['order_by'] . ' ';
if ((!isset($_DB_DATAOBJECT['CONFIG']['db_driver'])) ||
($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) {
/* PEAR DB specific */
if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
$sql = $DB->modifyLimitQuery($sql,$this->_query['limit_start'], $this->_query['limit_count']);
}
} else {
/* theoretically MDB! */
if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
$DB->setLimit($this->_query['limit_count'],$this->_query['limit_start']);
}
}
$this->_query($sql);
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("CHECK autofetchd $n", "__find", 1);
}
// unset the
if ($n && $this->N > 0 ) {
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("ABOUT TO AUTOFETCH", "__find", 1);
}
$this->fetch() ;
}
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("DONE", "__find", 1);
}
$this->_query = $query_before;
return $this->N;
}
 
/**
* fetches next row into this objects var's
*
* returns 1 on success 0 on failure
*
*
*
* Example
* $object = new mytable();
* $object->name = "fred";
* $object->find();
* $store = array();
* while ($object->fetch()) {
* echo $this->ID;
* $store[] = $object; // builds an array of object lines.
* }
*
* to add features to a fetch
* function fetch () {
* $ret = parent::fetch();
* $this->date_formated = date('dmY',$this->date);
* return $ret;
* }
*
* @access public
* @return boolean on success
*/
function fetch()
{
 
global $_DB_DATAOBJECT;
if (empty($_DB_DATAOBJECT['CONFIG'])) {
DB_DataObject::_loadConfig();
}
if (empty($this->N)) {
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("No data returned from FIND (eg. N is 0)","FETCH", 3);
}
return false;
}
if (empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]) ||
!is_object($result = &$_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]))
{
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug('fetched on object after fetch completed (no results found)');
}
return false;
}
$array = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ASSOC);
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug(serialize($array),"FETCH");
}
 
if ($array === null) {
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$t= explode(' ',microtime());
$this->debug("Last Data Fetch'ed after " .
($t[0]+$t[1]- $_DB_DATAOBJECT['QUERYENDTIME'] ) .
" seconds",
"FETCH", 1);
}
// reduce the memory usage a bit... (but leave the id in, so count() works ok on it)
unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
// this is probably end of data!!
//DB_DataObject::raiseError("fetch: no data returned", DB_DATAOBJECT_ERROR_NODATA);
return false;
}
if (!isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
// note: we dont declare this to keep the print_r size down.
$_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]= array_flip(array_keys($array));
}
foreach($array as $k=>$v) {
$kk = str_replace(".", "_", $k);
$kk = str_replace(" ", "_", $kk);
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("$kk = ". $array[$k], "fetchrow LINE", 3);
}
$this->$kk = $array[$k];
}
// set link flag
$this->_link_loaded=false;
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("{$this->__table} DONE", "fetchrow",2);
}
if (isset($this->_query) && empty($_DB_DATAOBJECT['CONFIG']['keep_query_after_fetch'])) {
unset($this->_query);
}
return true;
}
 
/**
* Adds a condition to the WHERE statement, defaults to AND
*
* $object->whereAdd(); //reset or cleaer ewhwer
* $object->whereAdd("ID > 20");
* $object->whereAdd("age > 20","OR");
*
* @param string $cond condition
* @param string $logic optional logic "OR" (defaults to "AND")
* @access public
* @return string|PEAR::Error - previous condition or Error when invalid args found
*/
function whereAdd($cond = false, $logic = 'AND')
{
if (!isset($this->_query)) {
return $this->raiseError(
"You cannot do two queries on the same object (clone it before finding)",
DB_DATAOBJECT_ERROR_INVALIDARGS);
}
if ($cond === false) {
$r = $this->_query['condition'];
$this->_query['condition'] = '';
return $r;
}
// check input...= 0 or ' ' == error!
if (!trim($cond)) {
return $this->raiseError("WhereAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
}
$r = $this->_query['condition'];
if ($this->_query['condition']) {
$this->_query['condition'] .= " {$logic} {$cond}";
return $r;
}
$this->_query['condition'] = " WHERE {$cond}";
return $r;
}
 
/**
* Adds a order by condition
*
* $object->orderBy(); //clears order by
* $object->orderBy("ID");
* $object->orderBy("ID,age");
*
* @param string $order Order
* @access public
* @return none|PEAR::Error - invalid args only
*/
function orderBy($order = false)
{
if (!isset($this->_query)) {
$this->raiseError(
"You cannot do two queries on the same object (copy it before finding)",
DB_DATAOBJECT_ERROR_INVALIDARGS);
return false;
}
if ($order === false) {
$this->_query['order_by'] = '';
return;
}
// check input...= 0 or ' ' == error!
if (!trim($order)) {
return $this->raiseError("orderBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
}
if (!$this->_query['order_by']) {
$this->_query['order_by'] = " ORDER BY {$order} ";
return;
}
$this->_query['order_by'] .= " , {$order}";
}
 
/**
* Adds a group by condition
*
* $object->groupBy(); //reset the grouping
* $object->groupBy("ID DESC");
* $object->groupBy("ID,age");
*
* @param string $group Grouping
* @access public
* @return none|PEAR::Error - invalid args only
*/
function groupBy($group = false)
{
if (!isset($this->_query)) {
$this->raiseError(
"You cannot do two queries on the same object (copy it before finding)",
DB_DATAOBJECT_ERROR_INVALIDARGS);
return false;
}
if ($group === false) {
$this->_query['group_by'] = '';
return;
}
// check input...= 0 or ' ' == error!
if (!trim($group)) {
return $this->raiseError("groupBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
}
if (!$this->_query['group_by']) {
$this->_query['group_by'] = " GROUP BY {$group} ";
return;
}
$this->_query['group_by'] .= " , {$group}";
}
 
/**
* Adds a having clause
*
* $object->having(); //reset the grouping
* $object->having("sum(value) > 0 ");
*
* @param string $having condition
* @access public
* @return none|PEAR::Error - invalid args only
*/
function having($having = false)
{
if (!isset($this->_query)) {
$this->raiseError(
"You cannot do two queries on the same object (copy it before finding)",
DB_DATAOBJECT_ERROR_INVALIDARGS);
return false;
}
if ($having === false) {
$this->_query['having'] = '';
return;
}
// check input...= 0 or ' ' == error!
if (!trim($having)) {
return $this->raiseError("Having: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
}
if (!$this->_query['having']) {
$this->_query['having'] = " HAVING {$having} ";
return;
}
$this->_query['having'] .= " AND {$having}";
}
 
/**
* Sets the Limit
*
* $boject->limit(); // clear limit
* $object->limit(12);
* $object->limit(12,10);
*
* Note this will emit an error on databases other than mysql/postgress
* as there is no 'clean way' to implement it. - you should consider refering to
* your database manual to decide how you want to implement it.
*
* @param string $a limit start (or number), or blank to reset
* @param string $b number
* @access public
* @return none|PEAR::Error - invalid args only
*/
function limit($a = null, $b = null)
{
if (!isset($this->_query)) {
$this->raiseError(
"You cannot do two queries on the same object (copy it before finding)",
DB_DATAOBJECT_ERROR_INVALIDARGS);
return false;
}
if ($a === null) {
$this->_query['limit_start'] = '';
$this->_query['limit_count'] = '';
return;
}
// check input...= 0 or ' ' == error!
if ((!is_int($a) && ((string)((int)$a) !== (string)$a))
|| (($b !== null) && (!is_int($b) && ((string)((int)$b) !== (string)$b)))) {
return $this->raiseError("limit: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
}
global $_DB_DATAOBJECT;
$this->_connect();
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
$this->_query['limit_start'] = ($b == null) ? 0 : (int)$a;
$this->_query['limit_count'] = ($b == null) ? (int)$a : (int)$b;
}
 
/**
* Adds a select columns
*
* $object->selectAdd(); // resets select to nothing!
* $object->selectAdd("*"); // default select
* $object->selectAdd("unixtime(DATE) as udate");
* $object->selectAdd("DATE");
*
* to prepend distict:
* $object->selectAdd('distinct ' . $object->selectAdd());
*
* @param string $k
* @access public
* @return mixed null or old string if you reset it.
*/
function selectAdd($k = null)
{
if (!isset($this->_query)) {
$this->raiseError(
"You cannot do two queries on the same object (copy it before finding)",
DB_DATAOBJECT_ERROR_INVALIDARGS);
return false;
}
if ($k === null) {
$old = $this->_query['data_select'];
$this->_query['data_select'] = '';
return $old;
}
// check input...= 0 or ' ' == error!
if (!trim($k)) {
return $this->raiseError("selectAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
}
if ($this->_query['data_select']) {
$this->_query['data_select'] .= ', ';
}
$this->_query['data_select'] .= " $k ";
}
/**
* Adds multiple Columns or objects to select with formating.
*
* $object->selectAs(null); // adds "table.colnameA as colnameA,table.colnameB as colnameB,......"
* // note with null it will also clear the '*' default select
* $object->selectAs(array('a','b'),'%s_x'); // adds "a as a_x, b as b_x"
* $object->selectAs(array('a','b'),'ddd_%s','ccc'); // adds "ccc.a as ddd_a, ccc.b as ddd_b"
* $object->selectAdd($object,'prefix_%s'); // calls $object->get_table and adds it all as
* objectTableName.colnameA as prefix_colnameA
*
* @param array|object|null the array or object to take column names from.
* @param string format in sprintf format (use %s for the colname)
* @param string table name eg. if you have joinAdd'd or send $from as an array.
* @access public
* @return void
*/
function selectAs($from = null,$format = '%s',$tableName=false)
{
global $_DB_DATAOBJECT;
if (!isset($this->_query)) {
$this->raiseError(
"You cannot do two queries on the same object (copy it before finding)",
DB_DATAOBJECT_ERROR_INVALIDARGS);
return false;
}
if ($from === null) {
// blank the '*'
$this->selectAdd();
$from = $this;
}
$table = $this->__table;
if (is_object($from)) {
$table = $from->__table;
$from = array_keys($from->table());
}
if ($tableName !== false) {
$table = $tableName;
}
$s = '%s';
if (!empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers'])) {
$this->_connect();
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
$s = $DB->quoteIdentifier($s);
}
foreach ($from as $k) {
$this->selectAdd(sprintf("{$s}.{$s} as {$format}",$table,$k,$k));
}
$this->_query['data_select'] .= "\n";
}
/**
* Insert the current objects variables into the database
*
* Returns the ID of the inserted element (if auto increment or sequences are used.)
*
* for example
*
* Designed to be extended
*
* $object = new mytable();
* $object->name = "fred";
* echo $object->insert();
*
* @access public
* @return mixed false on failure, int when auto increment or sequence used, otherwise true on success
*/
function insert()
{
global $_DB_DATAOBJECT;
// we need to write to the connection (For nextid) - so us the real
// one not, a copyied on (as ret-by-ref fails with overload!)
if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
$this->_connect();
}
$quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
$items = isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?
$_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
if (!$items) {
$this->raiseError("insert:No table definition for {$this->__table}",
DB_DATAOBJECT_ERROR_INVALIDCONFIG);
return false;
}
$options = &$_DB_DATAOBJECT['CONFIG'];
 
 
$datasaved = 1;
$leftq = '';
$rightq = '';
$seqKeys = isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table]) ?
$_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] :
$this->sequenceKey();
$key = isset($seqKeys[0]) ? $seqKeys[0] : false;
$useNative = isset($seqKeys[1]) ? $seqKeys[1] : false;
$seq = isset($seqKeys[2]) ? $seqKeys[2] : false;
$dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn["phptype"];
// nativeSequences or Sequences..
 
// big check for using sequences
if (($key !== false) && !$useNative) {
if (!$seq) {
$this->$key = $DB->nextId($this->__table);
} else {
$f = $DB->getOption('seqname_format');
$DB->setOption('seqname_format','%s');
$this->$key = $DB->nextId($seq);
$DB->setOption('seqname_format',$f);
}
}
 
 
 
foreach($items as $k => $v) {
// if we are using autoincrement - skip the column...
if ($key && ($k == $key) && $useNative) {
continue;
}
if (!isset($this->$k)) {
continue;
}
// dont insert data into mysql timestamps
// use query() if you really want to do this!!!!
if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) {
continue;
}
if ($leftq) {
$leftq .= ', ';
$rightq .= ', ';
}
$leftq .= ($quoteIdentifiers ? ($DB->quoteIdentifier($k) . ' ') : "$k ");
if (is_a($this->$k,'db_dataobject_cast')) {
$value = $this->$k->toString($v,$DB);
if (PEAR::isError($value)) {
$this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
return false;
}
$rightq .= $value;
continue;
}
 
if ((strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
$rightq .= " NULL ";
continue;
}
// DATE is empty... on a col. that can be null..
// note: this may be usefull for time as well..
if (!$this->$k &&
(($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) &&
!($v & DB_DATAOBJECT_NOTNULL)) {
$rightq .= " NULL ";
continue;
}
if ($v & DB_DATAOBJECT_STR) {
$rightq .= $this->_quote((string) (
($v & DB_DATAOBJECT_BOOL) ?
// this is thanks to the braindead idea of postgres to
// use t/f for boolean.
(($this->$k == 'f') ? 0 : (int)(bool) $this->$k) :
$this->$k
)) . " ";
continue;
}
if (is_numeric($this->$k)) {
$rightq .=" {$this->$k} ";
continue;
}
// at present we only cast to integers
// - V2 may store additional data about float/int
$rightq .= ' ' . intval($this->$k) . ' ';
 
}
// not sure why we let empty insert here.. - I guess to generate a blank row..
if ($leftq || $useNative) {
$table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
$r = $this->_query("INSERT INTO {$table} ($leftq) VALUES ($rightq) ");
if (PEAR::isError($r)) {
$this->raiseError($r);
return false;
}
if ($r < 1) {
return 0;
}
// now do we have an integer key!
if ($key && $useNative) {
switch ($dbtype) {
case 'mysql':
case 'mysqli':
$method = "{$dbtype}_insert_id";
$this->$key = $method(
$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection
);
break;
case 'mssql':
// note this is not really thread safe - you should wrapp it with
// transactions = eg.
// $db->query('BEGIN');
// $db->insert();
// $db->query('COMMIT');
$mssql_key = $DB->getOne("SELECT @@IDENTITY");
if (PEAR::isError($mssql_key)) {
$this->raiseError($r);
return false;
}
$this->$key = $mssql_key;
break;
case 'pgsql':
if (!$seq) {
$seq = $DB->getSequenceName($this->__table );
}
$pgsql_key = $DB->getOne("SELECT last_value FROM ".$seq);
if (PEAR::isError($pgsql_key)) {
$this->raiseError($r);
return false;
}
$this->$key = $pgsql_key;
break;
case 'ifx':
$this->$key = array_shift (
ifx_fetch_row (
ifx_query(
"select DBINFO('sqlca.sqlerrd1') FROM systables where tabid=1",
$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection,
IFX_SCROLL
),
"FIRST"
)
);
break;
}
}
 
if (isset($_DB_DATAOBJECT['CACHE'][strtolower(get_class($this))])) {
$this->_clear_cache();
}
if ($key) {
return $this->$key;
}
return true;
}
$this->raiseError("insert: No Data specifed for query", DB_DATAOBJECT_ERROR_NODATA);
return false;
}
 
/**
* Updates current objects variables into the database
* uses the keys() to decide how to update
* Returns the true on success
*
* for example
*
* $object = DB_DataObject::factory('mytable');
* $object->get("ID",234);
* $object->email="testing@test.com";
* if(!$object->update())
* echo "UPDATE FAILED";
*
* to only update changed items :
* $dataobject->get(132);
* $original = $dataobject; // clone/copy it..
* $dataobject->setFrom($_POST);
* if ($dataobject->validate()) {
* $dataobject->update($original);
* } // otherwise an error...
*
* performing global updates:
* $object = DB_DataObject::factory('mytable');
* $object->status = "dead";
* $object->whereAdd('age > 150');
* $object->update(DB_DATAOBJECT_WHEREADD_ONLY);
*
* @param object dataobject (optional) | DB_DATAOBJECT_WHEREADD_ONLY - used to only update changed items.
* @access public
* @return int rows affected or false on failure
*/
function update($dataObject = false)
{
global $_DB_DATAOBJECT;
// connect will load the config!
$this->_connect();
$original_query = isset($this->_query) ? $this->_query : null;
$items = isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?
$_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
// only apply update against sequence key if it is set?????
$seq = $this->sequenceKey();
if ($seq[0] !== false) {
$keys = array($seq[0]);
if (empty($this->{$keys[0]}) && $dataObject !== true) {
$this->raiseError("update: trying to perform an update without
the key set, and argument to update is not
DB_DATAOBJECT_WHEREADD_ONLY
", DB_DATAOBJECT_ERROR_INVALIDARGS);
return false;
}
} else {
$keys = $this->keys();
}
if (!$items) {
$this->raiseError("update:No table definition for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
return false;
}
$datasaved = 1;
$settings = '';
$this->_connect();
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
$dbtype = $DB->dsn["phptype"];
$quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
foreach($items as $k => $v) {
if (!isset($this->$k)) {
continue;
}
// ignore stuff thats
// dont write things that havent changed..
if (($dataObject !== false) && isset($dataObject->$k) && ($dataObject->$k == $this->$k)) {
continue;
}
// - dont write keys to left.!!!
if (in_array($k,$keys)) {
continue;
}
// dont insert data into mysql timestamps
// use query() if you really want to do this!!!!
if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) {
continue;
}
if ($settings) {
$settings .= ', ';
}
$kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);
if (is_a($this->$k,'db_dataobject_cast')) {
$value = $this->$k->toString($v,$DB);
if (PEAR::isError($value)) {
$this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
return false;
}
$settings .= "$kSql = $value ";
continue;
}
// special values ... at least null is handled...
if ((strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
$settings .= "$kSql = NULL ";
continue;
}
// DATE is empty... on a col. that can be null..
// note: this may be usefull for time as well..
if (!$this->$k &&
(($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) &&
!($v & DB_DATAOBJECT_NOTNULL)) {
$settings .= "$kSql = NULL ";
continue;
}
 
if ($v & DB_DATAOBJECT_STR) {
$settings .= "$kSql = ". $this->_quote((string) (
($v & DB_DATAOBJECT_BOOL) ?
// this is thanks to the braindead idea of postgres to
// use t/f for boolean.
(($this->$k == 'f') ? 0 : (int)(bool) $this->$k) :
$this->$k
)) . ' ';
continue;
}
if (is_numeric($this->$k)) {
$settings .= "$kSql = {$this->$k} ";
continue;
}
// at present we only cast to integers
// - V2 may store additional data about float/int
$settings .= "$kSql = " . intval($this->$k) . ' ';
}
 
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("got keys as ".serialize($keys),3);
}
if ($dataObject !== true) {
$this->_build_condition($items,$keys);
} else {
// prevent wiping out of data!
if (empty($this->_query['condition'])) {
$this->raiseError("update: global table update not available
do \$do->whereAdd('1=1'); if you really want to do that.
", DB_DATAOBJECT_ERROR_INVALIDARGS);
return false;
}
}
// echo " $settings, $this->condition ";
if ($settings && isset($this->_query) && $this->_query['condition']) {
$table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
$r = $this->_query("UPDATE {$table} SET {$settings} {$this->_query['condition']} ");
// restore original query conditions.
$this->_query = $original_query;
if (PEAR::isError($r)) {
$this->raiseError($r);
return false;
}
if ($r < 1) {
return 0;
}
 
$this->_clear_cache();
return $r;
}
// restore original query conditions.
$this->_query = $original_query;
// if you manually specified a dataobject, and there where no changes - then it's ok..
if ($dataObject !== false) {
return true;
}
$this->raiseError(
"update: No Data specifed for query $settings , {$this->_query['condition']}",
DB_DATAOBJECT_ERROR_NODATA);
return false;
}
 
/**
* Deletes items from table which match current objects variables
*
* Returns the true on success
*
* for example
*
* Designed to be extended
*
* $object = new mytable();
* $object->ID=123;
* echo $object->delete(); // builds a conditon
*
* $object = new mytable();
* $object->whereAdd('age > 12');
* $object->limit(1);
* $object->orderBy('age DESC');
* $object->delete(true); // dont use object vars, use the conditions, limit and order.
*
* @param bool $useWhere (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
* we will build the condition only using the whereAdd's. Default is to
* build the condition only using the object parameters.
*
* @access public
* @return mixed True on success, false on failure, 0 on no data affected
*/
function delete($useWhere = false)
{
global $_DB_DATAOBJECT;
// connect will load the config!
$this->_connect();
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
$quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
$extra_cond = ' ' . (isset($this->_query['order_by']) ? $this->_query['order_by'] : '');
if (!$useWhere) {
 
$keys = $this->keys();
$this->_query = array(); // as it's probably unset!
$this->_query['condition'] = ''; // default behaviour not to use where condition
$this->_build_condition($this->table(),$keys);
// if primary keys are not set then use data from rest of object.
if (!$this->_query['condition']) {
$this->_build_condition($this->table(),array(),$keys);
}
$extra_cond = '';
}
 
// don't delete without a condition
if (isset($this->_query) && $this->_query['condition']) {
$table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
$sql = "DELETE FROM {$table} {$this->_query['condition']}{$extra_cond}";
// add limit..
if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
if (!isset($_DB_DATAOBJECT['CONFIG']['db_driver']) ||
($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) {
// pear DB
$sql = $DB->modifyLimitQuery($sql,$this->_query['limit_start'], $this->_query['limit_count']);
} else {
// MDB
$DB->setLimit( $this->_query['limit_count'],$this->_query['limit_start']);
}
}
$r = $this->_query($sql);
if (PEAR::isError($r)) {
$this->raiseError($r);
return false;
}
if ($r < 1) {
return 0;
}
$this->_clear_cache();
return $r;
} else {
$this->raiseError("delete: No condition specifed for query", DB_DATAOBJECT_ERROR_NODATA);
return false;
}
}
 
/**
* fetches a specific row into this object variables
*
* Not recommended - better to use fetch()
*
* Returens true on success
*
* @param int $row row
* @access public
* @return boolean true on success
*/
function fetchRow($row = null)
{
global $_DB_DATAOBJECT;
if (empty($_DB_DATAOBJECT['CONFIG'])) {
$this->_loadConfig();
}
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("{$this->__table} $row of {$this->N}", "fetchrow",3);
}
if (!$this->__table) {
$this->raiseError("fetchrow: No table", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
return false;
}
if ($row === null) {
$this->raiseError("fetchrow: No row specified", DB_DATAOBJECT_ERROR_INVALIDARGS);
return false;
}
if (!$this->N) {
$this->raiseError("fetchrow: No results avaiable", DB_DATAOBJECT_ERROR_NODATA);
return false;
}
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("{$this->__table} $row of {$this->N}", "fetchrow",3);
}
 
 
$result = &$_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
$array = $result->fetchrow(DB_DATAOBJECT_FETCHMODE_ASSOC,$row);
if (!is_array($array)) {
$this->raiseError("fetchrow: No results available", DB_DATAOBJECT_ERROR_NODATA);
return false;
}
 
foreach($array as $k => $v) {
$kk = str_replace(".", "_", $k);
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("$kk = ". $array[$k], "fetchrow LINE", 3);
}
$this->$kk = $array[$k];
}
 
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("{$this->__table} DONE", "fetchrow", 3);
}
return true;
}
 
/**
* Find the number of results from a simple query
*
* for example
*
* $object = new mytable();
* $object->name = "fred";
* echo $object->count();
* echo $object->count(true); // dont use object vars.
* echo $object->count('distinct mycol'); count distinct mycol.
* echo $object->count('distinct mycol',true); // dont use object vars.
* echo $object->count('distinct'); // count distinct id (eg. the primary key)
*
*
* @param bool|string (optional)
* (true|false => see below not on whereAddonly)
* (string)
* "DISTINCT" => does a distinct count on the tables 'key' column
* otherwise => normally it counts primary keys - you can use
* this to do things like $do->count('distinct mycol');
*
* @param bool $whereAddOnly (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
* we will build the condition only using the whereAdd's. Default is to
* build the condition using the object parameters as well.
*
* @access public
* @return int
*/
function count($countWhat = false,$whereAddOnly = false)
{
global $_DB_DATAOBJECT;
if (is_bool($countWhat)) {
$whereAddOnly = $countWhat;
}
$t = clone($this);
$quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
$items = $t->table();
if (!isset($t->_query)) {
$this->raiseError(
"You cannot do run count after you have run fetch()",
DB_DATAOBJECT_ERROR_INVALIDARGS);
return false;
}
$this->_connect();
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
 
if (!$whereAddOnly && $items) {
$t->_build_condition($items);
}
$keys = $this->keys();
 
if (!$keys[0] && !is_string($countWhat)) {
$this->raiseError(
"You cannot do run count without keys - use \$do->keys('id');",
DB_DATAOBJECT_ERROR_INVALIDARGS,PEAR_ERROR_DIE);
return false;
}
$table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
$key_col = ($quoteIdentifiers ? $DB->quoteIdentifier($keys[0]) : $keys[0]);
$as = ($quoteIdentifiers ? $DB->quoteIdentifier('DATAOBJECT_NUM') : 'DATAOBJECT_NUM');
// support distinct on default keys.
$countWhat = (strtoupper($countWhat) == 'DISTINCT') ?
"DISTINCT {$table}.{$key_col}" : $countWhat;
$countWhat = is_string($countWhat) ? $countWhat : "{$table}.{$key_col}";
$r = $t->_query(
"SELECT count({$countWhat}) as $as
FROM $table {$t->_join} {$t->_query['condition']}");
if (PEAR::isError($r)) {
return false;
}
$result = &$_DB_DATAOBJECT['RESULTS'][$t->_DB_resultid];
$l = $result->fetchRow();
return $l[0];
}
 
/**
* sends raw query to database
*
* Since _query has to be a private 'non overwriteable method', this is a relay
*
* @param string $string SQL Query
* @access public
* @return void or DB_Error
*/
function query($string)
{
return $this->_query($string);
}
 
 
/**
* an escape wrapper around DB->escapeSimple()
* can be used when adding manual queries or clauses
* eg.
* $object->query("select * from xyz where abc like '". $object->escape($_GET['name']) . "'");
*
* @param string $string value to be escaped
* @access public
* @return string
*/
function escape($string)
{
global $_DB_DATAOBJECT;
$this->_connect();
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
// mdb uses escape...
$dd = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 'DB' : $_DB_DATAOBJECT['CONFIG']['db_driver'];
return ($dd == 'DB') ? $DB->escapeSimple($string) : $DB->escape($string);
}
 
/* ==================================================== */
/* Major Private Vars */
/* ==================================================== */
 
/**
* The Database connection dsn (as described in the PEAR DB)
* only used really if you are writing a very simple application/test..
* try not to use this - it is better stored in configuration files..
*
* @access private
* @var string
*/
var $_database_dsn = '';
 
/**
* The Database connection id (md5 sum of databasedsn)
*
* @access private
* @var string
*/
var $_database_dsn_md5 = '';
 
/**
* The Database name
* created in __connection
*
* @access private
* @var string
*/
var $_database = '';
 
/**
* The QUERY rules
* This replaces alot of the private variables
* used to build a query, it is unset after find() is run.
*
*
*
* @access private
* @var array
*/
var $_query = array(
'condition' => '', // the WHERE condition
'group_by' => '', // the GROUP BY condition
'order_by' => '', // the ORDER BY condition
'having' => '', // the HAVING condition
'limit_start' => '', // the LIMIT condition
'limit_count' => '', // the LIMIT condition
'data_select' => '*', // the columns to be SELECTed
);
 
/**
* Database result id (references global $_DB_DataObject[results]
*
* @access private
* @var integer
*/
var $_DB_resultid; // database result object
 
 
/* ============================================================== */
/* Table definition layer (started of very private but 'came out'*/
/* ============================================================== */
 
/**
* Autoload or manually load the table definitions
*
*
* usage :
* DB_DataObject::databaseStructure( 'databasename',
* parse_ini_file('mydb.ini',true),
* parse_ini_file('mydb.link.ini',true));
*
* obviously you dont have to use ini files.. (just return array similar to ini files..)
*
* It should append to the table structure array
*
*
* @param optional string name of database to assign / read
* @param optional array structure of database, and keys
* @param optional array table links
*
* @access public
* @return true or PEAR:error on wrong paramenters.. or false if no file exists..
* or the array(tablename => array(column_name=>type)) if called with 1 argument.. (databasename)
*/
function databaseStructure()
{
 
global $_DB_DATAOBJECT;
// Assignment code
if ($args = func_get_args()) {
if (count($args) == 1) {
// this returns all the tables and their structure..
$x = new DB_DataObject;
$x->_database = $args[0];
$this->_connect();
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
$tables = $DB->getListOf('tables');
require_once 'DB/DataObject/Generator.php';
foreach($tables as $table) {
$y = new DB_DataObject_Generator;
$y->fillTableSchema($x->_database,$table);
}
return $_DB_DATAOBJECT['INI'][$x->_database];
} else {
$_DB_DATAOBJECT['INI'][$args[0]] = isset($_DB_DATAOBJECT['INI'][$args[0]]) ?
$_DB_DATAOBJECT['INI'][$args[0]] + $args[1] : $args[1];
if (isset($args[1])) {
$_DB_DATAOBJECT['LINKS'][$args[0]] = isset($_DB_DATAOBJECT['LINKS'][$args[0]]) ?
$_DB_DATAOBJECT['LINKS'][$args[0]] + $args[2] : $args[2];
}
return true;
}
}
if (!$this->_database) {
$this->_connect();
}
// loaded already?
if (!empty($_DB_DATAOBJECT['INI'][$this->_database])) {
// database loaded - but this is table is not available..
if (empty($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
require_once 'DB/DataObject/Generator.php';
$x = new DB_DataObject_Generator;
$x->fillTableSchema($this->_database,$this->__table);
}
return true;
}
if (empty($_DB_DATAOBJECT['CONFIG'])) {
DB_DataObject::_loadConfig();
}
// if you supply this with arguments, then it will take those
// as the database and links array...
$schemas = isset($_DB_DATAOBJECT['CONFIG']['schema_location']) ?
array("{$_DB_DATAOBJECT['CONFIG']['schema_location']}/{$this->_database}.ini") :
array() ;
if (isset($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"])) {
$schemas = is_array($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]) ?
$_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"] :
explode(PATH_SEPARATOR,$_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]);
}
foreach ($schemas as $ini) {
$links =
isset($_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"]) ?
$_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"] :
str_replace('.ini','.links.ini',$ini);
 
if (file_exists($ini) && is_file($ini)) {
$_DB_DATAOBJECT['INI'][$this->_database] = parse_ini_file($ini, true);
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("Loaded ini file: $ini","databaseStructure",1);
}
} else {
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("Missing ini file: $ini","databaseStructure",1);
}
}
if (empty($_DB_DATAOBJECT['LINKS'][$this->_database]) && file_exists($links) && is_file($links)) {
/* not sure why $links = ... here - TODO check if that works */
$_DB_DATAOBJECT['LINKS'][$this->_database] = parse_ini_file($links, true);
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("Loaded links.ini file: $links","databaseStructure",1);
}
} else {
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("Missing links.ini file: $links","databaseStructure",1);
}
}
}
// now have we loaded the structure.. - if not try building it..
if (empty($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
require_once 'DB/DataObject/Generator.php';
$x = new DB_DataObject_Generator;
$x->fillTableSchema($this->_database,$this->__table);
}
return true;
}
 
 
 
 
/**
* Return or assign the name of the current table
*
*
* @param string optinal table name to set
* @access public
* @return string The name of the current table
*/
function tableName()
{
$args = func_get_args();
if (count($args)) {
$this->__table = $args[0];
}
return $this->__table;
}
/**
* Return or assign the name of the current database
*
* @param string optional database name to set
* @access public
* @return string The name of the current database
*/
function database()
{
$args = func_get_args();
if (count($args)) {
$this->_database = $args[0];
}
return $this->_database;
}
/**
* get/set an associative array of table columns
*
* @access public
* @param array key=>type array
* @return array (associative)
*/
function table()
{
// for temporary storage of database fields..
// note this is not declared as we dont want to bloat the print_r output
$args = func_get_args();
if (count($args)) {
$this->_database_fields = $args[0];
}
if (isset($this->_database_fields)) {
return $this->_database_fields;
}
global $_DB_DATAOBJECT;
if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
$this->_connect();
}
if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
return $_DB_DATAOBJECT['INI'][$this->_database][$this->__table];
}
$this->databaseStructure();
$ret = array();
if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
$ret = $_DB_DATAOBJECT['INI'][$this->_database][$this->__table];
}
return $ret;
}
 
/**
* get/set an array of table primary keys
*
* set usage: $do->keys('id','code');
*
* This is defined in the table definition if it gets it wrong,
* or you do not want to use ini tables, you can override this.
* @param string optional set the key
* @param * optional set more keys
* @access private
* @return array
*/
function keys()
{
// for temporary storage of database fields..
// note this is not declared as we dont want to bloat the print_r output
$args = func_get_args();
if (count($args)) {
$this->_database_keys = $args;
}
if (isset($this->_database_keys)) {
return $this->_database_keys;
}
global $_DB_DATAOBJECT;
if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
$this->_connect();
}
if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]);
}
$this->databaseStructure();
if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]);
}
return array();
}
/**
* get/set an sequence key
*
* by default it returns the first key from keys()
* set usage: $do->sequenceKey('id',true);
*
* override this to return array(false,false) if table has no real sequence key.
*
* @param string optional the key sequence/autoinc. key
* @param boolean optional use native increment. default false
* @param false|string optional native sequence name
* @access private
* @return array (column,use_native,sequence_name)
*/
function sequenceKey()
{
global $_DB_DATAOBJECT;
// call setting
if (!$this->_database) {
$this->_connect();
}
if (!isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database])) {
$_DB_DATAOBJECT['SEQUENCE'][$this->_database] = array();
}
 
$args = func_get_args();
if (count($args)) {
$args[1] = isset($args[1]) ? $args[1] : false;
$args[2] = isset($args[2]) ? $args[2] : false;
$_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = $args;
}
if (isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table])) {
return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table];
}
// end call setting (eg. $do->sequenceKeys(a,b,c); )
$keys = $this->keys();
if (!$keys) {
return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table]
= array(false,false,false);;
}
 
$table = isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?
$_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
$dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'];
$usekey = $keys[0];
$seqname = false;
if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) {
$usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table];
if (strpos($usekey,':') !== false) {
list($usekey,$seqname) = explode(':',$usekey);
}
}
// if the key is not an integer - then it's not a sequence or native
if (!($table[$usekey] & DB_DATAOBJECT_INT)) {
return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,false);
}
if (!empty($_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'])) {
$ignore = $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'];
if (is_string($ignore) && (strtoupper($ignore) == 'ALL')) {
return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
}
if (is_string($ignore)) {
$ignore = $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'] = explode(',',$ignore);
}
if (in_array($this->__table,$ignore)) {
return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
}
}
$realkeys = $_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"];
// if you are using an old ini file - go back to old behaviour...
if (is_numeric($realkeys[$usekey])) {
$realkeys[$usekey] = 'N';
}
// multiple unique primary keys without a native sequence...
if (($realkeys[$usekey] == 'K') && (count($keys) > 1)) {
return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
}
// use native sequence keys...
// technically postgres native here...
// we need to get the new improved tabledata sorted out first.
if ( in_array($dbtype , array( 'mysql', 'mysqli', 'mssql', 'ifx')) &&
($table[$usekey] & DB_DATAOBJECT_INT) &&
isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N')
) {
return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array($usekey,true,$seqname);
}
// if not a native autoinc, and we have not assumed all primary keys are sequence
if (($realkeys[$usekey] != 'N') &&
!empty($_DB_DATAOBJECT['CONFIG']['dont_use_pear_sequences'])) {
return array(false,false,false);
}
// I assume it's going to try and be a nextval DB sequence.. (not native)
return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array($usekey,false,$seqname);
}
/* =========================================================== */
/* Major Private Methods - the core part! */
/* =========================================================== */
 
/**
* clear the cache values for this class - normally done on insert/update etc.
*
* @access private
* @return void
*/
function _clear_cache()
{
global $_DB_DATAOBJECT;
$class = get_class($this);
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("Clearing Cache for ".$class,1);
}
if (!empty($_DB_DATAOBJECT['CACHE'][$class])) {
unset($_DB_DATAOBJECT['CACHE'][$class]);
}
}
 
/**
* backend wrapper for quoting, as MDB and DB do it differently...
*
* @access private
* @return string quoted
*/
function _quote($str)
{
global $_DB_DATAOBJECT;
return (empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ||
($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB'))
? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quoteSmart($str)
: $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quote($str);
}
/**
* connects to the database
*
*
* TODO: tidy this up - This has grown to support a number of connection options like
* a) dynamic changing of ini file to change which database to connect to
* b) multi data via the table_{$table} = dsn ini option
* c) session based storage.
*
* @access private
* @return true | PEAR::error
*/
function _connect()
{
global $_DB_DATAOBJECT;
if (empty($_DB_DATAOBJECT['CONFIG'])) {
$this->_loadConfig();
}
 
// is it already connected ?
 
if ($this->_database_dsn_md5 && !empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
return $this->raiseError(
$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->message,
$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, PEAR_ERROR_DIE
);
}
 
if (!$this->_database) {
$this->_database = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite')
&& is_file($this->_database))
{
$this->_database = basename($this->_database);
}
}
// theoretically we have a md5, it's listed in connections and it's not an error.
// so everything is ok!
return true;
}
 
// it's not currently connected!
// try and work out what to use for the dsn !
 
$options= &$_DB_DATAOBJECT['CONFIG'];
$dsn = isset($this->_database_dsn) ? $this->_database_dsn : null;
 
if (!$dsn) {
if (!$this->_database) {
$this->_database = isset($options["table_{$this->__table}"]) ? $options["table_{$this->__table}"] : null;
}
if ($this->_database && !empty($options["database_{$this->_database}"])) {
$dsn = $options["database_{$this->_database}"];
} else if (!empty($options['database'])) {
$dsn = $options['database'];
}
}
// if still no database...
if (!$dsn) {
return $this->raiseError(
"No database name / dsn found anywhere",
DB_DATAOBJECT_ERROR_INVALIDCONFIG, PEAR_ERROR_DIE
);
}
 
$this->_database_dsn_md5 = md5($dsn);
 
if (!empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("USING CACHED CONNECTION", "CONNECT",3);
}
if (!$this->_database) {
$this->_database = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn["database"];
if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite')
&& is_file($this->_database))
{
$this->_database = basename($this->_database);
}
}
return true;
}
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug("NEW CONNECTION", "CONNECT",3);
/* actualy make a connection */
$this->debug("{$dsn} {$this->_database_dsn_md5}", "CONNECT",3);
}
// Note this is verbose deliberatly!
if (!isset($_DB_DATAOBJECT['CONFIG']['db_driver']) ||
($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) {
/* PEAR DB connect */
// this allows the setings of compatibility on DB
$db_options = PEAR::getStaticProperty('DB','options');
require_once 'DB.php';
if ($db_options) {
$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn,$db_options);
} else {
$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn);
}
} else {
/* assumption is MDB */
require_once 'MDB2.php';
// this allows the setings of compatibility on MDB2
$db_options = PEAR::getStaticProperty('MDB2','options');
if ($db_options) {
$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = MDB2::connect($dsn,$db_options);
} else {
$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = MDB2::connect($dsn);
}
}
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug(serialize($_DB_DATAOBJECT['CONNECTIONS']), "CONNECT",5);
}
if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
$this->debug($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->toString(), "CONNECT FAILED",5);
return $this->raiseError(
"Connect failed, turn on debugging to 5 see why",
$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, PEAR_ERROR_DIE
);
 
}
 
if (!$this->_database) {
$this->_database = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn["database"];
if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite')
&& is_file($this->_database))
{
$this->_database = basename($this->_database);
}
}
// Oracle need to optimize for portibility - not sure exactly what this does though :)
$c = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
return true;
}
 
/**
* sends query to database - this is the private one that must work
* - internal functions use this rather than $this->query()
*
* @param string $string
* @access private
* @return mixed none or PEAR_Error
*/
function _query($string)
{
global $_DB_DATAOBJECT;
$this->_connect();
 
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
 
$options = &$_DB_DATAOBJECT['CONFIG'];
$_DB_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ?
'DB': $_DB_DATAOBJECT['CONFIG']['db_driver'];
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug($string,$log="QUERY");
}
if (strtoupper($string) == 'BEGIN') {
if ($_DB_driver == 'DB') {
$DB->autoCommit(false);
} else {
$DB->beginTransaction();
}
// db backend adds begin anyway from now on..
return true;
}
if (strtoupper($string) == 'COMMIT') {
$res = $DB->commit();
if ($_DB_driver == 'DB') {
$DB->autoCommit(true);
}
return $res;
}
if (strtoupper($string) == 'ROLLBACK') {
$DB->rollback();
if ($_DB_driver == 'DB') {
$DB->autoCommit(true);
}
return true;
}
 
if (!empty($options['debug_ignore_updates']) &&
(strtolower(substr(trim($string), 0, 6)) != 'select') &&
(strtolower(substr(trim($string), 0, 4)) != 'show') &&
(strtolower(substr(trim($string), 0, 8)) != 'describe')) {
 
$this->debug('Disabling Update as you are in debug mode');
return $this->raiseError("Disabling Update as you are in debug mode", null) ;
 
}
//if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 1) {
// this will only work when PEAR:DB supports it.
//$this->debug($DB->getAll('explain ' .$string,DB_DATAOBJECT_FETCHMODE_ASSOC), $log="sql",2);
//}
// some sim
$t= explode(' ',microtime());
$_DB_DATAOBJECT['QUERYENDTIME'] = $time = $t[0]+$t[1];
$result = $DB->query($string);
 
if (is_a($result,'DB_Error')) {
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug($result->toString(), "Query Error",1 );
}
return $this->raiseError($result);
}
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$t= explode(' ',microtime());
$_DB_DATAOBJECT['QUERYENDTIME'] = $t[0]+$t[1];
$this->debug('QUERY DONE IN '.($t[0]+$t[1]-$time)." seconds", 'query',1);
}
switch (strtolower(substr(trim($string),0,6))) {
case 'insert':
case 'update':
case 'delete':
if ($_DB_driver == 'DB') {
// pear DB specific
return $DB->affectedRows();
}
return $result;
}
if (is_object($result)) {
// lets hope that copying the result object is OK!
$_DB_resultid = $GLOBALS['_DB_DATAOBJECT']['RESULTSEQ']++;
$_DB_DATAOBJECT['RESULTS'][$_DB_resultid] = $result;
$this->_DB_resultid = $_DB_resultid;
}
$this->N = 0;
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug(serialize($result), 'RESULT',5);
}
if (method_exists($result, 'numrows')) {
$DB->expectError(DB_ERROR_UNSUPPORTED);
$this->N = $result->numrows();
if (is_a($this->N,'DB_Error')) {
$this->N = true;
}
$DB->popExpect();
}
}
 
/**
* Builds the WHERE based on the values of of this object
*
* @param mixed $keys
* @param array $filter (used by update to only uses keys in this filter list).
* @param array $negative_filter (used by delete to prevent deleting using the keys mentioned..)
* @access private
* @return string
*/
function _build_condition($keys, $filter = array(),$negative_filter=array())
{
global $_DB_DATAOBJECT;
$this->_connect();
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
$quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
// if we dont have query vars.. - reset them.
if (!isset($this->_query)) {
$x = new DB_DataObject;
$this->_query= $x->_query;
}
 
foreach($keys as $k => $v) {
// index keys is an indexed array
/* these filter checks are a bit suspicious..
- need to check that update really wants to work this way */
 
if ($filter) {
if (!in_array($k, $filter)) {
continue;
}
}
if ($negative_filter) {
if (in_array($k, $negative_filter)) {
continue;
}
}
if (!isset($this->$k)) {
continue;
}
$kSql = $quoteIdentifiers
? ( $DB->quoteIdentifier($this->__table) . '.' . $DB->quoteIdentifier($k) )
: "{$this->__table}.{$k}";
if (is_a($this->$k,'db_dataobject_cast')) {
$dbtype = $DB->dsn["phptype"];
$value = $this->$k->toString($v,$DB);
if (PEAR::isError($value)) {
$this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
return false;
}
if ((strtolower($value) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
$this->whereAdd(" $kSql IS NULL");
continue;
}
$this->whereAdd(" $kSql = $value");
continue;
}
if ((strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
$this->whereAdd(" $kSql IS NULL");
continue;
}
 
if ($v & DB_DATAOBJECT_STR) {
$this->whereAdd(" $kSql = " . $this->_quote((string) (
($v & DB_DATAOBJECT_BOOL) ?
// this is thanks to the braindead idea of postgres to
// use t/f for boolean.
(($this->$k == 'f') ? 0 : (int)(bool) $this->$k) :
$this->$k
)) );
continue;
}
if (is_numeric($this->$k)) {
$this->whereAdd(" $kSql = {$this->$k}");
continue;
}
/* this is probably an error condition! */
$this->whereAdd(" $kSql = ".intval($this->$k));
}
}
 
/**
* autoload Class relating to a table
* (depreciated - use ::factory)
*
* @param string $table table
* @access private
* @return string classname on Success
*/
function staticAutoloadTable($table)
{
global $_DB_DATAOBJECT;
if (empty($_DB_DATAOBJECT['CONFIG'])) {
DB_DataObject::_loadConfig();
}
$p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
$_DB_DATAOBJECT['CONFIG']['class_prefix'] : '';
$class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
$class = (class_exists($class)) ? $class : DB_DataObject::_autoloadClass($class);
return $class;
}
/**
* classic factory method for loading a table class
* usage: $do = DB_DataObject::factory('person')
* WARNING - this may emit a include error if the file does not exist..
* use @ to silence it (if you are sure it is acceptable)
* eg. $do = @DB_DataObject::factory('person')
*
* table name will eventually be databasename/table
* - and allow modular dataobjects to be written..
* (this also helps proxy creation)
*
*
* @param string $table tablename (use blank to create a new instance of the same class.)
* @access private
* @return DataObject|PEAR_Error
*/
 
function factory($table = '') {
global $_DB_DATAOBJECT;
if (empty($_DB_DATAOBJECT['CONFIG'])) {
DB_DataObject::_loadConfig();
}
if ($table === '') {
if (is_a($this,'DB_DataObject') && strlen($this->__table)) {
$table = $this->__table;
} else {
return DB_DataObject::raiseError(
"factory did not recieve a table name",
DB_DATAOBJECT_ERROR_INVALIDARGS);
}
}
$p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
$_DB_DATAOBJECT['CONFIG']['class_prefix'] : '';
$class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
$class = (class_exists($class)) ? $class : DB_DataObject::_autoloadClass($class);
// proxy = full|light
if (!$class && isset($_DB_DATAOBJECT['CONFIG']['proxy'])) {
$proxyMethod = 'getProxy'.$_DB_DATAOBJECT['CONFIG']['proxy'];
require_once 'DB/DataObject/Generator.php';
$d = new DB_DataObject;
$d->__table = $table;
$d->_connect();
$x = new DB_DataObject_Generator;
return $x->$proxyMethod( $d->_database, $table);
}
if (!$class) {
return DB_DataObject::raiseError(
"factory could not find class $class from $table",
DB_DATAOBJECT_ERROR_INVALIDCONFIG);
}
 
return new $class;
}
/**
* autoload Class
*
* @param string $class Class
* @access private
* @return string classname on Success
*/
function _autoloadClass($class)
{
global $_DB_DATAOBJECT;
if (empty($_DB_DATAOBJECT['CONFIG'])) {
DB_DataObject::_loadConfig();
}
$table = substr($class,strlen($_DB_DATAOBJECT['CONFIG']['class_prefix']));
 
// only include the file if it exists - and barf badly if it has parse errors :)
if (!empty($_DB_DATAOBJECT['CONFIG']['proxy']) && empty($_DB_DATAOBJECT['CONFIG']['class_location'])) {
return false;
}
if (strpos($_DB_DATAOBJECT['CONFIG']['class_location'],'%s') !== false) {
$file = sprintf($_DB_DATAOBJECT['CONFIG']['class_location'], preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)));
} else {
$file = $_DB_DATAOBJECT['CONFIG']['class_location'].'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php";
}
if (!file_exists($file)) {
$found = false;
foreach(explode(PATH_SEPARATOR, ini_get('include_path')) as $p) {
if (file_exists("$p/$file")) {
$file = "$p/$file";
$found = true;
break;
}
}
if (!$found) {
DB_DataObject::raiseError(
"autoload:Could not find class {$class} using class_location value",
DB_DATAOBJECT_ERROR_INVALIDCONFIG);
return false;
}
}
include_once $file;
if (!class_exists($class)) {
DB_DataObject::raiseError(
"autoload:Could not autoload {$class}",
DB_DATAOBJECT_ERROR_INVALIDCONFIG);
return false;
}
return $class;
}
/**
* Have the links been loaded?
* if they have it contains a array of those variables.
*
* @access private
* @var boolean | array
*/
var $_link_loaded = false;
/**
* Get the links associate array as defined by the links.ini file.
*
*
* Experimental... -
* Should look a bit like
* [local_col_name] => "related_tablename:related_col_name"
*
*
* @return array|null
* array = if there are links defined for this table.
* empty array - if there is a links.ini file, but no links on this table
* null - if no links.ini exists for this database (hence try auto_links).
* @access public
* @see DB_DataObject::getLinks(), DB_DataObject::getLink()
*/
function links()
{
global $_DB_DATAOBJECT;
if (empty($_DB_DATAOBJECT['CONFIG'])) {
$this->_loadConfig();
}
if (isset($_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table])) {
return $_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table];
}
$this->databaseStructure();
// if there is no link data at all on the file!
// we return null.
if (!isset($_DB_DATAOBJECT['LINKS'][$this->_database])) {
return null;
}
if (isset($_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table])) {
return $_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table];
}
return array();
}
/**
* load related objects
*
* There are two ways to use this, one is to set up a <dbname>.links.ini file
* into a static property named <dbname>.links and specifies the table joins,
* the other highly dependent on naming columns 'correctly' :)
* using colname = xxxxx_yyyyyy
* xxxxxx = related table; (yyyyy = user defined..)
* looks up table xxxxx, for value id=$this->xxxxx
* stores it in $this->_xxxxx_yyyyy
* you can change what object vars the links are stored in by
* changeing the format parameter
*
*
* @param string format (default _%s) where %s is the table name.
* @author Tim White <tim@cyface.com>
* @access public
* @return boolean , true on success
*/
function getLinks($format = '_%s')
{
// get table will load the options.
if ($this->_link_loaded) {
return true;
}
$this->_link_loaded = false;
$cols = $this->table();
$links = $this->links();
$loaded = array();
if ($links) {
foreach($links as $key => $match) {
list($table,$link) = explode(':', $match);
$k = sprintf($format, str_replace('.', '_', $key));
// makes sure that '.' is the end of the key;
if ($p = strpos($key,'.')) {
$key = substr($key, 0, $p);
}
$this->$k = $this->getLink($key, $table, $link);
if (is_object($this->$k)) {
$loaded[] = $k;
}
}
$this->_link_loaded = $loaded;
return true;
}
// this is the autonaming stuff..
// it sends the column name down to getLink and lets that sort it out..
// if there is a links file then it is not used!
// IT IS DEPRECIATED!!!! - USE
if (!is_null($links)) {
return false;
}
foreach (array_keys($cols) as $key) {
if (!($p = strpos($key, '_'))) {
continue;
}
// does the table exist.
$k =sprintf($format, $key);
$this->$k = $this->getLink($key);
if (is_object($this->$k)) {
$loaded[] = $k;
}
}
$this->_link_loaded = $loaded;
return true;
}
 
/**
* return name from related object
*
* There are two ways to use this, one is to set up a <dbname>.links.ini file
* into a static property named <dbname>.links and specifies the table joins,
* the other is highly dependant on naming columns 'correctly' :)
*
* NOTE: the naming convention is depreciated!!! - use links.ini
*
* using colname = xxxxx_yyyyyy
* xxxxxx = related table; (yyyyy = user defined..)
* looks up table xxxxx, for value id=$this->xxxxx
* stores it in $this->_xxxxx_yyyyy
*
* you can also use $this->getLink('thisColumnName','otherTable','otherTableColumnName')
*
*
* @param string $row either row or row.xxxxx
* @param string $table name of table to look up value in
* @param string $link name of column in other table to match
* @author Tim White <tim@cyface.com>
* @access public
* @return mixed object on success
*/
function &getLink($row, $table = null, $link = false)
{
// GUESS THE LINKED TABLE.. (if found - recursevly call self)
if ($table === null) {
$links = $this->links();
if (is_array($links)) {
if ($links[$row]) {
list($table,$link) = explode(':', $links[$row]);
if ($p = strpos($row,".")) {
$row = substr($row,0,$p);
}
return $r = &$this->getLink($row,$table,$link);
}
$this->raiseError(
"getLink: $row is not defined as a link (normally this is ok)",
DB_DATAOBJECT_ERROR_NODATA);
return false; // technically a possible error condition?
 
}
// use the old _ method - this shouldnt happen if called via getLinks()
if (!($p = strpos($row, '_'))) {
return null;
}
$table = substr($row, 0, $p);
return $r = &$this->getLink($row, $table);
 
}
if (!isset($this->$row)) {
$this->raiseError("getLink: row not set $row", DB_DATAOBJECT_ERROR_NODATA);
return false;
}
// check to see if we know anything about this table..
$obj = $this->factory($table);
if (!is_a($obj,'DB_DataObject')) {
$this->raiseError(
"getLink:Could not find class for row $row, table $table",
DB_DATAOBJECT_ERROR_INVALIDCONFIG);
return false;
}
if ($link) {
if ($obj->get($link, $this->$row)) {
return $obj;
}
return false;
}
if ($obj->get($this->$row)) {
return $obj;
}
return false;
}
 
/**
* IS THIS SUPPORTED/USED ANYMORE????
*return a list of options for a linked table
*
* This is highly dependant on naming columns 'correctly' :)
* using colname = xxxxx_yyyyyy
* xxxxxx = related table; (yyyyy = user defined..)
* looks up table xxxxx, for value id=$this->xxxxx
* stores it in $this->_xxxxx_yyyyy
*
* @access public
* @return array of results (empty array on failure)
*/
function &getLinkArray($row, $table = null)
{
$ret = array();
if (!$table) {
$links = $this->links();
if (is_array($links)) {
if (!isset($links[$row])) {
// failed..
return $ret;
}
list($table,$link) = explode(':',$links[$row]);
} else {
if (!($p = strpos($row,'_'))) {
return $ret;
}
$table = substr($row,0,$p);
}
}
$c = $this->factory($table);
if (!is_a($c,'DB_DataObject')) {
$this->raiseError(
"getLinkArray:Could not find class for row $row, table $table",
DB_DATAOBJECT_ERROR_INVALIDCONFIG
);
return $ret;
}
 
// if the user defined method list exists - use it...
if (method_exists($c, 'listFind')) {
$c->listFind($this->id);
} else {
$c->find();
}
while ($c->fetch()) {
$ret[] = $c;
}
return $ret;
}
 
/**
* The JOIN condition
*
* @access private
* @var string
*/
var $_join = '';
 
/**
* joinAdd - adds another dataobject to this, building a joined query.
*
* example (requires links.ini to be set up correctly)
* // get all the images for product 24
* $i = new DataObject_Image();
* $pi = new DataObjects_Product_image();
* $pi->product_id = 24; // set the product id to 24
* $i->joinAdd($pi); // add the product_image connectoin
* $i->find();
* while ($i->fetch()) {
* // do stuff
* }
* // an example with 2 joins
* // get all the images linked with products or productgroups
* $i = new DataObject_Image();
* $pi = new DataObject_Product_image();
* $pgi = new DataObject_Productgroup_image();
* $i->joinAdd($pi);
* $i->joinAdd($pgi);
* $i->find();
* while ($i->fetch()) {
* // do stuff
* }
*
*
* @param optional $obj object |array the joining object (no value resets the join)
* If you use an array here it should be in the format:
* array('local_column','remotetable:remote_column');
* if remotetable does not have a definition, you should
* use @ to hide the include error message..
*
*
* @param optional $joinType string 'LEFT'|'INNER'|'RIGHT'|'' Inner is default, '' indicates
* just select ... from a,b,c with no join and
* links are added as where items.
*
* @param optional $joinAs string if you want to select the table as anther name
* useful when you want to select multiple columsn
* from a secondary table.
* @param optional $joinCol string The column on This objects table to match (needed
* if this table links to the child object in
* multiple places eg.
* user->friend (is a link to another user)
* user->mother (is a link to another user..)
*
* @return none
* @access public
* @author Stijn de Reede <sjr@gmx.co.uk>
*/
function joinAdd($obj = false, $joinType='INNER', $joinAs=false, $joinCol=false)
{
global $_DB_DATAOBJECT;
if ($obj === false) {
$this->_join = '';
return;
}
// support for array as first argument
// this assumes that you dont have a links.ini for the specified table.
// and it doesnt exist as am extended dataobject!! - experimental.
$ofield = false; // object field
$tfield = false; // this field
$toTable = false;
if (is_array($obj)) {
$tfield = $obj[0];
list($toTable,$ofield) = explode(':',$obj[1]);
$obj = DB_DataObject::factory($toTable);
if (!$obj || is_a($obj,'PEAR_Error')) {
$obj = new DB_DataObject;
$obj->__table = $toTable;
}
$obj->_connect();
// set the table items to nothing.. - eg. do not try and match
// things in the child table...???
$items = array();
}
if (!is_object($obj)) {
$this->raiseError("joinAdd: called without an object", DB_DATAOBJECT_ERROR_NODATA,PEAR_ERROR_DIE);
}
/* make sure $this->_database is set. */
$this->_connect();
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
 
/* look up the links for obj table */
//print_r($obj->links());
if (!$ofield && ($olinks = $obj->links())) {
foreach ($olinks as $k => $v) {
/* link contains {this column} = {linked table}:{linked column} */
$ar = explode(':', $v);
if ($ar[0] == $this->__table) {
// you have explictly specified the column
// and the col is listed here..
// not sure if 1:1 table could cause probs here..
if ($joinCol !== false) {
$this->raiseError(
"joinAdd: You cannot target a join column in the " .
"'link from' table ({$obj->__table}). " .
"Either remove the fourth argument to joinAdd() ".
"({$joinCol}), or alter your links.ini file.",
DB_DATAOBJECT_ERROR_NODATA);
return false;
}
$ofield = $k;
$tfield = $ar[1];
break;
}
}
}
 
/* otherwise see if there are any links from this table to the obj. */
//print_r($this->links());
if (($ofield === false) && ($links = $this->links())) {
foreach ($links as $k => $v) {
/* link contains {this column} = {linked table}:{linked column} */
$ar = explode(':', $v);
if ($ar[0] == $obj->__table) {
if ($joinCol !== false) {
if ($k == $joinCol) {
$tfield = $k;
$ofield = $ar[1];
break;
} else {
continue;
}
} else {
$tfield = $k;
$ofield = $ar[1];
break;
}
}
}
}
/* did I find a conneciton between them? */
 
if ($ofield === false) {
$this->raiseError(
"joinAdd: {$obj->__table} has no link with {$this->__table}",
DB_DATAOBJECT_ERROR_NODATA);
return false;
}
$joinType = strtoupper($joinType);
// we default to joining as the same name (this is remvoed later..)
if ($joinAs === false) {
$joinAs = $obj->__table;
}
$quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
// not sure how portable adding database prefixes is..
$objTable = $quoteIdentifiers ?
$DB->quoteIdentifier($obj->__table) :
$obj->__table ;
// as far as we know only mysql supports database prefixes..
if (
in_array($DB->dsn['phptype'],array('mysql','mysqli')) &&
($obj->_database != $this->_database) &&
strlen($obj->_database)
)
{
// prefix database (quoted if neccessary..)
$objTable = ($quoteIdentifiers
? $DB->quoteIdentifier($obj->_database)
: $obj->_database)
. '.' . $objTable;
}
// nested (join of joined objects..)
$appendJoin = '';
if ($obj->_join) {
// postgres allows nested queries, with ()'s
// not sure what the results are with other databases..
// may be unpredictable..
if (in_array($DB->dsn["phptype"],array('pgsql'))) {
$objTable = "($objTable {$obj->_join})";
} else {
$appendJoin = $obj->_join;
}
}
$table = $this->__table;
if ($quoteIdentifiers) {
$joinAs = $DB->quoteIdentifier($joinAs);
$table = $DB->quoteIdentifier($table);
$ofield = $DB->quoteIdentifier($ofield);
$tfield = $DB->quoteIdentifier($tfield);
}
// add database prefix if they are different databases
$fullJoinAs = '';
$addJoinAs = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->__table) : $obj->__table) != $joinAs;
if ($addJoinAs) {
$fullJoinAs = "AS {$joinAs}";
} else {
// if
if (
in_array($DB->dsn['phptype'],array('mysql','mysqli')) &&
($obj->_database != $this->_database) &&
strlen($this->_database)
)
{
$joinAs = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->_database) : $obj->_database) . '.' . $joinAs;
}
}
switch ($joinType) {
case 'INNER':
case 'LEFT':
case 'RIGHT': // others??? .. cross, left outer, right outer, natural..?
$this->_join .= "\n {$joinType} JOIN {$objTable} {$fullJoinAs}".
" ON {$joinAs}.{$ofield}={$table}.{$tfield} {$appendJoin} ";
break;
case '': // this is just a standard multitable select..
$this->_join .= "\n , {$objTable} {$fullJoinAs} {$appendJoin}";
$this->whereAdd("{$joinAs}.{$ofield}={$table}.{$tfield}");
}
// if obj only a dataobject - eg. no extended class has been defined..
// it obvioulsy cant work out what child elements might exist...
// untill we get on the fly querying of tables..
if ( strtolower(get_class($obj)) == 'db_dataobject') {
return true;
}
/* now add where conditions for anything that is set in the object */
$items = $obj->table();
// will return an array if no items..
// only fail if we where expecting it to work (eg. not joined on a array)
if (!$items) {
$this->raiseError(
"joinAdd: No table definition for {$obj->__table}",
DB_DATAOBJECT_ERROR_INVALIDCONFIG);
return false;
}
 
foreach($items as $k => $v) {
if (!isset($obj->$k)) {
continue;
}
$kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);
if ($v & DB_DATAOBJECT_STR) {
$this->whereAdd("{$joinAs}.{$kSql} = " . $this->_quote((string) (
($v & DB_DATAOBJECT_BOOL) ?
// this is thanks to the braindead idea of postgres to
// use t/f for boolean.
(($obj->$k == 'f') ? 0 : (int)(bool) $obj->$k) :
$obj->$k
)));
continue;
}
if (is_numeric($obj->$k)) {
$this->whereAdd("{$joinAs}.{$kSql} = {$obj->$k}");
continue;
}
/* this is probably an error condition! */
$this->whereAdd("{$joinAs}.{$kSql} = 0");
}
if (!isset($this->_query)) {
$this->raiseError(
"joinAdd can not be run from a object that has had a query run on it,
clone the object or create a new one and use setFrom()",
DB_DATAOBJECT_ERROR_INVALIDARGS);
return false;
}
// and finally merge the whereAdd from the child..
if (!$obj->_query['condition']) {
return true;
}
$cond = preg_replace('/^\sWHERE/i','',$obj->_query['condition']);
$this->whereAdd("($cond)");
return true;
 
}
 
/**
* Copies items that are in the table definitions from an
* array or object into the current object
* will not override key values.
*
*
* @param array | object $from
* @param string $format eg. map xxxx_name to $object->name using 'xxxx_%s' (defaults to %s - eg. name -> $object->name
* @access public
* @return true on success or array of key=>setValue error message
*/
function setFrom(&$from, $format = '%s', $checkEmpty=false)
{
global $_DB_DATAOBJECT;
$keys = $this->keys();
$items = $this->table();
if (!$items) {
$this->raiseError(
"setFrom:Could not find table definition for {$this->__table}",
DB_DATAOBJECT_ERROR_INVALIDCONFIG);
return;
}
$overload_return = array();
foreach (array_keys($items) as $k) {
if (in_array($k,$keys)) {
continue; // dont overwrite keys
}
if (!$k) {
continue; // ignore empty keys!!! what
}
if (is_object($from) && isset($from->{sprintf($format,$k)})) {
$kk = (strtolower($k) == 'from') ? '_from' : $k;
if (method_exists($this,'set'.$kk)) {
$ret = $this->{'set'.$kk}($from->{sprintf($format,$k)});
if (is_string($ret)) {
$overload_return[$k] = $ret;
}
continue;
}
$this->$k = $from->{sprintf($format,$k)};
continue;
}
if (is_object($from)) {
continue;
}
if (!isset($from[sprintf($format,$k)])) {
continue;
}
$kk = (strtolower($k) == 'from') ? '_from' : $k;
if (method_exists($this,'set'. $kk)) {
$ret = $this->{'set'.$kk}($from[sprintf($format,$k)]);
if (is_string($ret)) {
$overload_return[$k] = $ret;
}
continue;
}
if (is_object($from[sprintf($format,$k)])) {
continue;
}
if (is_array($from[sprintf($format,$k)])) {
continue;
}
$ret = $this->fromValue($k,$from[sprintf($format,$k)]);
if ($ret !== true) {
$overload_return[$k] = 'Not A Valid Value';
}
//$this->$k = $from[sprintf($format,$k)];
}
if ($overload_return) {
return $overload_return;
}
return true;
}
 
/**
* Returns an associative array from the current data
* (kind of oblivates the idea behind DataObjects, but
* is usefull if you use it with things like QuickForms.
*
* you can use the format to return things like user[key]
* by sending it $object->toArray('user[%s]')
*
* will also return links converted to arrays.
*
* @param string sprintf format for array
* @param bool empty only return elemnts that have a value set.
*
* @access public
* @return array of key => value for row
*/
 
function toArray($format = '%s', $hideEmpty = false)
{
global $_DB_DATAOBJECT;
$ret = array();
$ar = isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]) ?
array_merge($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid],$this->table()) :
$this->table();
 
foreach($ar as $k=>$v) {
if (!isset($this->$k)) {
if (!$hideEmpty) {
$ret[sprintf($format,$k)] = '';
}
continue;
}
// call the overloaded getXXXX() method. - except getLink and getLinks
if (method_exists($this,'get'.$k) && !in_array(strtolower($k),array('links','link'))) {
$ret[sprintf($format,$k)] = $this->{'get'.$k}();
continue;
}
// should this call toValue() ???
$ret[sprintf($format,$k)] = $this->$k;
}
if (!$this->_link_loaded) {
return $ret;
}
foreach($this->_link_loaded as $k) {
$ret[sprintf($format,$k)] = $this->$k->toArray();
}
return $ret;
}
 
/**
* validate - override this to set up your validation rules
*
* validate the current objects values either just testing strings/numbers or
* using the user defined validate{Row name}() methods.
* will attempt to call $this->validate{column_name}() - expects true = ok false = ERROR
* you can the use the validate Class from your own methods.
*
* This should really be in a extenal class - eg. DB_DataObject_Validate.
*
* @access public
* @return array of validation results or true
*/
function validate()
{
require_once 'Validate.php';
$table = $this->table();
$ret = array();
$seq = $this->sequenceKey();
foreach($table as $key => $val) {
// call user defined validation always...
$method = "Validate" . ucfirst($key);
if (method_exists($this, $method)) {
$ret[$key] = $this->$method();
continue;
}
// if not null - and it's not set.......
if (!isset($this->$key) && ($val & DB_DATAOBJECT_NOTNULL)) {
// dont check empty sequence key values..
if (($key == $seq[0]) && ($seq[1] == true)) {
continue;
}
$ret[$key] = false;
continue;
}
if (is_string($this->$key) && (strtolower($this->$key) == 'null') && ($val & DB_DATAOBJECT_NOTNULL)) {
$ret[$key] = false;
continue;
}
// ignore things that are not set. ?
if (!isset($this->$key)) {
continue;
}
// if the string is empty.. assume it is ok..
if (!is_object($this->$key) && !is_array($this->$key) && !strlen((string) $this->$key)) {
continue;
}
switch (true) {
// todo: date time.....
case ($val & DB_DATAOBJECT_STR):
$ret[$key] = Validate::string($this->$key, VALIDATE_PUNCTUATION . VALIDATE_NAME);
continue;
case ($val & DB_DATAOBJECT_INT):
$ret[$key] = Validate::number($this->$key, array('decimal'=>'.'));
continue;
}
}
 
foreach ($ret as $key => $val) {
if ($val === false) {
return $ret;
}
}
return true; // everything is OK.
}
 
/**
* Gets the DB object related to an object - so you can use funky peardb stuf with it :)
*
* @access public
* @return object The DB connection
*/
function &getDatabaseConnection()
{
global $_DB_DATAOBJECT;
 
if (($e = $this->_connect()) !== true) {
return $e;
}
if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
return false;
}
return $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
}
/**
* Gets the DB result object related to the objects active query
* - so you can use funky pear stuff with it - like pager for example.. :)
*
* @access public
* @return object The DB result object
*/
function &getDatabaseResult()
{
global $_DB_DATAOBJECT;
$this->_connect();
if (!isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {
return false;
}
return $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
}
 
/**
* Overload Extension support
* - enables setCOLNAME/getCOLNAME
* if you define a set/get method for the item it will be called.
* otherwise it will just return/set the value.
* NOTE this currently means that a few Names are NO-NO's
* eg. links,link,linksarray, from, Databaseconnection,databaseresult
*
* note
* - set is automatically called by setFrom.
* - get is automatically called by toArray()
*
* setters return true on success. = strings on failure
* getters return the value!
*
* this fires off trigger_error - if any problems.. pear_error,
* has problems with 4.3.2RC2 here
*
* @access public
* @return true?
* @see overload
*/
 
function _call($method,$params,&$return) {
//$this->debug("ATTEMPTING OVERLOAD? $method");
// ignore constructors : - mm
if (strtolower($method) == strtolower(get_class($this))) {
return true;
}
$type = strtolower(substr($method,0,3));
$class = get_class($this);
if (($type != 'set') && ($type != 'get')) {
return false;
}
// deal with naming conflick of setFrom = this is messy ATM!
if (strtolower($method) == 'set_from') {
$return = $this->toValue('from',isset($params[0]) ? $params[0] : null);
return true;
}
$element = substr($method,3);
// dont you just love php's case insensitivity!!!!
$array = array_keys(get_class_vars($class));
/* php5 version which segfaults on 5.0.3 */
if (class_exists('ReflectionClass')) {
$reflection = new ReflectionClass($class);
$array = array_keys($reflection->getdefaultProperties());
}
if (!in_array($element,$array)) {
// munge case
foreach($array as $k) {
$case[strtolower($k)] = $k;
}
if ((substr(phpversion(),0,1) == 5) && isset($case[strtolower($element)])) {
trigger_error("PHP5 set/get calls should match the case of the variable",E_USER_WARNING);
$element = strtolower($element);
}
// does it really exist?
if (!isset($case[$element])) {
return false;
}
// use the mundged case
$element = $case[$element]; // real case !
}
if ($type == 'get') {
$return = $this->toValue($element,isset($params[0]) ? $params[0] : null);
return true;
}
$return = $this->fromValue($element, $params[0]);
return true;
}
/**
* standard set* implementation.
*
* takes data and uses it to set dates/strings etc.
* normally called from __call..
*
* Current supports
* date = using (standard time format, or unixtimestamp).... so you could create a method :
* function setLastread($string) { $this->fromValue('lastread',strtotime($string)); }
*
* time = using strtotime
* datetime = using same as date - accepts iso standard or unixtimestamp.
* string = typecast only..
*
* TODO: add formater:: eg. d/m/Y for date! ???
*
* @param string column of database
* @param mixed value to assign
*
* @return true| false (False on error)
* @access public
* @see DB_DataObject::_call
*/
function fromValue($col,$value)
{
$cols = $this->table();
// dont know anything about this col..
if (!isset($cols[$col])) {
$this->$col = $value;
return true;
}
//echo "FROM VALUE $col, {$cols[$col]}, $value\n";
switch (true) {
// set to null and column is can be null...
case ((strtolower($value) == 'null') && (!($cols[$col] & DB_DATAOBJECT_NOTNULL))):
case (is_object($value) && is_a($value,'DB_DataObject_Cast')):
$this->$col = $value;
return true;
// fail on setting null on a not null field..
case ((strtolower($value) == 'null') && ($cols[$col] & DB_DATAOBJECT_NOTNULL)):
return false;
case (($cols[$col] & DB_DATAOBJECT_DATE) && ($cols[$col] & DB_DATAOBJECT_TIME)):
// empty values get set to '' (which is inserted/updated as NULl
if (!$value) {
$this->$col = '';
}
if (is_numeric($value)) {
$this->$col = date('Y-m-d H:i:s', $value);
return true;
}
// eak... - no way to validate date time otherwise...
$this->$col = (string) $value;
return true;
case ($cols[$col] & DB_DATAOBJECT_DATE):
// empty values get set to '' (which is inserted/updated as NULl
if (!$value) {
$this->$col = '';
return true;
}
if (is_numeric($value)) {
$this->$col = date('Y-m-d',$value);
return true;
}
// try date!!!!
require_once 'Date.php';
$x = new Date($value);
$this->$col = $x->format("%Y-%m-%d");
return true;
case ($cols[$col] & DB_DATAOBJECT_TIME):
// empty values get set to '' (which is inserted/updated as NULl
if (!$value) {
$this->$col = '';
}
$guess = strtotime($value);
if ($guess != -1) {
$this->$col = date('H:i:s', $guess);
return $return = true;
}
// otherwise an error in type...
return false;
case ($cols[$col] & DB_DATAOBJECT_STR):
$this->$col = (string) $value;
return true;
// todo : floats numerics and ints...
default:
$this->$col = $value;
return true;
}
}
/**
* standard get* implementation.
*
* with formaters..
* supported formaters:
* date/time : %d/%m/%Y (eg. php strftime) or pear::Date
* numbers : %02d (eg. sprintf)
* NOTE you will get unexpected results with times like 0000-00-00 !!!
*
*
*
* @param string column of database
* @param format foramt
*
* @return true Description
* @access public
* @see DB_DataObject::_call(),strftime(),Date::format()
*/
function toValue($col,$format = null)
{
if (is_null($format)) {
return $this->$col;
}
$cols = $this->table();
switch (true) {
case (($cols[$col] & DB_DATAOBJECT_DATE) && ($cols[$col] & DB_DATAOBJECT_TIME)):
if (!$this->$col) {
return '';
}
$guess = strtotime($this->$col);
if ($guess != -1) {
return strftime($format, $guess);
}
// eak... - no way to validate date time otherwise...
return $this->$col;
case ($cols[$col] & DB_DATAOBJECT_DATE):
if (!$this->$col) {
return '';
}
$guess = strtotime($this->$col);
if ($guess != -1) {
return strftime($format,$guess);
}
// try date!!!!
require_once 'Date.php';
$x = new Date($this->$col);
return $x->format($format);
case ($cols[$col] & DB_DATAOBJECT_TIME):
if (!$this->$col) {
return '';
}
$guess = strtotime($this->$col);
if ($guess > -1) {
return strftime($format, $guess);
}
// otherwise an error in type...
return $this->$col;
case ($cols[$col] & DB_DATAOBJECT_MYSQLTIMESTAMP):
if (!$this->$col) {
return '';
}
require_once 'Date.php';
$x = new Date($this->$col);
return $x->format($format);
case ($cols[$col] & DB_DATAOBJECT_BOOLEAN):
if ($cols[$col] & DB_DATAOBJECT_STR) {
// it's a 't'/'f' !
return ($cols[$col] == 't');
}
return (bool) $cols[$col];
default:
return sprintf($format,$this->col);
}
 
}
/* ----------------------- Debugger ------------------ */
 
/**
* Debugger. - use this in your extended classes to output debugging information.
*
* Uses DB_DataObject::DebugLevel(x) to turn it on
*
* @param string $message - message to output
* @param string $logtype - bold at start
* @param string $level - output level
* @access public
* @return none
*/
function debug($message, $logtype = 0, $level = 1)
{
global $_DB_DATAOBJECT;
 
if (empty($_DB_DATAOBJECT['CONFIG']['debug']) ||
(is_numeric($_DB_DATAOBJECT['CONFIG']['debug']) && $_DB_DATAOBJECT['CONFIG']['debug'] < $level)) {
return;
}
// this is a bit flaky due to php's wonderfull class passing around crap..
// but it's about as good as it gets..
$class = (isset($this) && is_a($this,'DB_DataObject')) ? get_class($this) : 'DB_DataObject';
if (!is_string($message)) {
$message = print_r($message,true);
}
if (!is_numeric( $_DB_DATAOBJECT['CONFIG']['debug']) && is_callable( $_DB_DATAOBJECT['CONFIG']['debug'])) {
return call_user_func($_DB_DATAOBJECT['CONFIG']['debug'], $class, $message, $logtype, $level);
}
if (!ini_get('html_errors')) {
echo "$class : $logtype : $message\n";
flush();
return;
}
if (!is_string($message)) {
$message = print_r($message,true);
}
echo "<code><B>$class: $logtype:</B> $message</code><BR>\n";
flush();
}
 
/**
* sets and returns debug level
* eg. DB_DataObject::debugLevel(4);
*
* @param int $v level
* @access public
* @return none
*/
function debugLevel($v = null)
{
global $_DB_DATAOBJECT;
if (empty($_DB_DATAOBJECT['CONFIG'])) {
DB_DataObject::_loadConfig();
}
if ($v !== null) {
$r = isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0;
$_DB_DATAOBJECT['CONFIG']['debug'] = $v;
return $r;
}
return isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0;
}
 
/**
* Last Error that has occured
* - use $this->_lastError or
* $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
*
* @access public
* @var object PEAR_Error (or false)
*/
var $_lastError = false;
 
/**
* Default error handling is to create a pear error, but never return it.
* if you need to handle errors you should look at setting the PEAR_Error callback
* this is due to the fact it would wreck havoc on the internal methods!
*
* @param int $message message
* @param int $type type
* @param int $behaviour behaviour (die or continue!);
* @access public
* @return error object
*/
function raiseError($message, $type = null, $behaviour = null)
{
global $_DB_DATAOBJECT;
if ($behaviour == PEAR_ERROR_DIE && !empty($_DB_DATAOBJECT['CONFIG']['dont_die'])) {
$behaviour = null;
}
$error = &PEAR::getStaticProperty('DB_DataObject','lastError');
if (PEAR::isError($message)) {
$error = $message;
} else {
require_once 'DB/DataObject/Error.php';
$error = PEAR::raiseError($message, $type, $behaviour,
$opts=null, $userinfo=null, 'DB_DataObject_Error'
);
}
// this will never work totally with PHP's object model.
// as this is passed on static calls (like staticGet in our case)
 
if (isset($this) && is_object($this) && is_subclass_of($this,'db_dataobject')) {
$this->_lastError = $error;
}
 
$_DB_DATAOBJECT['LASTERROR'] = $error;
 
// no checks for production here?.......
DB_DataObject::debug($message,"ERROR",1);
return $error;
}
 
/**
* Define the global $_DB_DATAOBJECT['CONFIG'] as an alias to PEAR::getStaticProperty('DB_DataObject','options');
*
* After Profiling DB_DataObject, I discoved that the debug calls where taking
* considerable time (well 0.1 ms), so this should stop those calls happening. as
* all calls to debug are wrapped with direct variable queries rather than actually calling the funciton
* THIS STILL NEEDS FURTHER INVESTIGATION
*
* @access public
* @return object an error object
*/
function _loadConfig()
{
global $_DB_DATAOBJECT;
 
$_DB_DATAOBJECT['CONFIG'] = &PEAR::getStaticProperty('DB_DataObject','options');
 
 
}
/**
* Free global arrays associated with this object.
*
* Note: as we now store resultfields in a global, it is never freed, if you do alot of calls to find(),
* memory will grow gradually.
*
*
* @access public
* @return none
*/
function free()
{
global $_DB_DATAOBJECT;
if (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]);
}
if (isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {
unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
}
// this is a huge bug in DB!
if (isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->num_rows = array();
}
}
/* ---- LEGACY BC METHODS - NOT DOCUMENTED - See Documentation on New Methods. ---*/
function _get_table() { return $this->table(); }
function _get_keys() { return $this->keys(); }
}
// technially 4.3.2RC1 was broken!!
// looks like 4.3.3 may have problems too....
if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) {
 
if ((phpversion() != '4.3.2-RC1') && (version_compare( phpversion(), "4.3.1") > 0)) {
if (version_compare( phpversion(), "5") < 0) {
overload('DB_DataObject');
}
$GLOBALS['_DB_DATAOBJECT']['OVERLOADED'] = true;
}
}
 
/trunk/api/pear/DB/sqlite.php
21,7 → 21,7
* @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.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: sqlite.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
47,7 → 47,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
*/
class DB_sqlite extends DB_common
/trunk/api/pear/DB/oci8.php
20,7 → 20,7
* @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: oci8.php,v 1.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: oci8.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
47,7 → 47,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
*/
class DB_oci8 extends DB_common
/trunk/api/pear/DB/QueryTool.php
New file
0,0 → 1,63
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
 
/**
* Contains the DB_QueryTool class
*
* 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_QueryTool
* @author Wolfram Kriesing <wk@visionp.de>
* @author Paolo Panto <wk@visionp.de>
* @copyright 2003-2005 Wolfram Kriesing, Paolo Panto
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: QueryTool.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB_QueryTool
*/
 
/**
* require the DB_QueryTool_EasyJoin class
*/
require_once 'DB/QueryTool/EasyJoin.php';
 
/**
* MDB_QueryTool class
*
* This class should be extended; it's here to make it easy using the base
* class of the package by its package name.
* Since I tried to seperate the functionality a bit inside the
* really working classes i decided to have this class here just to
* provide the name, since the functionality inside the other
* classes might be restructured a bit but this name always stays.
*
* @category Database
* @package DB_QueryTool
* @author Wolfram Kriesing <wk@visionp.de>
* @copyright 2003-2005 Wolfram Kriesing
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @link http://pear.php.net/package/DB_QueryTool
*/
class DB_QueryTool extends DB_QueryTool_EasyJoin
{
// {{{ DB_QueryTool()
 
/**
* call parent constructor
* @param mixed $dsn DSN string, DSN array or DB object
* @param array $options
*/
function DB_QueryTool($dsn=false, $options=array())
{
parent::__construct($dsn, $options);
}
 
// }}}
}
?>
/trunk/api/pear/DB/storage.php
18,7 → 18,7
* @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.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: storage.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
38,7 → 38,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
*/
class DB_storage extends PEAR
/trunk/api/pear/DB/mysql.php
20,7 → 20,7
* @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: mysql.php,v 1.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: mysql.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
41,7 → 41,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
*/
class DB_mysql extends DB_common
/trunk/api/pear/DB/odbc.php
20,7 → 20,7
* @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.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: odbc.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
44,7 → 44,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
*/
class DB_odbc extends DB_common
/trunk/api/pear/DB/fbsql.php
20,7 → 20,7
* @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.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: fbsql.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
41,7 → 41,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
* @since Class functional since Release 1.7.0
*/
/trunk/api/pear/DB/NestedSet/TigraMenu.php
New file
0,0 → 1,402
<?php
//
// +----------------------------------------------------------------------+
// | PEAR :: DB_NestedSet_TigraMenu |
// +----------------------------------------------------------------------+
// | 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: Daniel Khan <dk@webcluster.at> |
// +----------------------------------------------------------------------+
//
// $Id: TigraMenu.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
//
 
// {{{ DB_NestedSet_TigraMenu:: class
 
/**
* This class can be used to generate the data to build javascript popup menu
* from a DB_NestedSet node array.
* The Javascript part is done using the free available TigraMenu
* available at http://www.softcomplex.com/products/tigra_menu/.
* Currently version 1.0 is supported.
* Parts of this class where taken ftom the TreemMenu driver by Jason Rust
*
* @author Daniel Khan <dk@webcluster.at>
* @package DB_NestedSet
* @version $Revision: 1.1 $
* @access public
*/
// }}}
class DB_NestedSet_TigraMenu extends DB_NestedSet_Output {
// {{{{ properties
 
/**
* @var integer The depth of the current menu.
* @access private
*/
var $_levels = 1;
 
/**
* @var integer The level we started at
* @access private
*/
var $_levelOffset = false;
 
 
/**
* @var array The current menu structure
* @access private
*/
var $_structTigraMenu = false;
 
/**
* @var array The longest text for each level
* @access private
*/
var $_strlenByLevel = array();
 
// }}}
// {{{ DB_NestedSet_TigraMenu
 
/**
* Constructor
*
* @param array $params A hash with parameters needed by the class
* @see _createFromStructure()
* @return bool
**/
function &DB_NestedSet_TigraMenu($params) {
$this->_menu_id = $params['menu_id'];
$this->_structTigraMenu = $this->_createFromStructure($params);
return true;
}
 
// }}}
// {{{ _createFromStructure()
 
/**
* Creates the JavaScript array for TigraMenu
* Initially this method was introduced for the TreeMenu driver by Jason Rust
*
* o 'structure' => the result from $nestedSet->getAllNodes(true)
* o 'textField' => the field in the table that has the text for node
* o 'linkField' => the field in the table that has the link for the node
*
* @access private
* @return string The TigraMenu JavaScript array
*/
function &_createFromStructure($params)
{
// Basically we go through the array of nodes checking to see
// if each node has children and if so recursing. The reason this
// works is because the data from getAllNodes() is ordered by level
// so a root node will always be first, and sub children will always
// be after them.
 
static $rootlevel;
 
// always start at level 1
if (!isset($params['currentLevel'])) {
$params['currentLevel'] = 1;
}
 
if (!isset($rootlevel)) {
$rootlevel = $params['currentLevel'];
}
 
if (isset($params['tigraMenu'])) {
$tigraMenu = $tigraMenu.$params['tigraMenu'];
}
 
if(!$this->_levelOffset) {
$this->_levelOffset = $params['currentLevel'];
}
 
if($this->_levels < ($params['currentLevel']- $this->_levelOffset)) {
$this->_levels = $params['currentLevel'] - $this->_levelOffset;
}
 
 
// have to use a while loop here because foreach works on a copy of the array and
// the child nodes are passed by reference during the recursion so that the parent
// will know when they have been hit.
reset($params['structure']);
while(list($key, $node) = each($params['structure'])) {
// see if we've already been here before
if (isset($node['hit']) || $node['level'] < $params['currentLevel']) {
continue;
}
 
// mark that we've hit this node
$params['structure'][$key]['hit'] = $node['hit'] = true;
 
$tag = array(
isset($node[$params['textField']]) ? "'".$node[$params['textField']]."'" : 'null',
isset($node[$params['linkField']]) ? "'".$node[$params['linkField']]."'" : 'null'
);
 
if (!$this->_strlenByLevel[$params['currentLevel'] - $this->_levelOffset] ||
strlen($node[$params['textField']]) > $this->_strlenByLevel[$params['currentLevel'] - $this->_levelOffset]) {
$this->_strlenByLevel[$params['currentLevel'] - $this->_levelOffset] = strlen($node[$params['textField']]);
};
 
$tigraMenu = $tigraMenu.$this->_openSubMenu($tag);
 
// see if it has children
if (($node['r'] - 1) != $node['l']) {
$children = array();
// harvest all the children
$tempStructure = $params['structure'];
foreach ($tempStructure as $childKey => $childNode) {
if (!isset($childNode['hit']) &&
$node['rootid'] == $childNode['rootid'] &&
$node['l'] < $childNode['l'] &&
$node['r'] > $childNode['r'] &&
$childNode['level'] > $params['currentLevel']) {
// important that we assign it by reference here, so that when the child
// marks itself 'hit' the parent loops will know
$children[] =& $params['structure'][$childKey];
}
}
 
$recurseParams = $params;
$recurseParams['structure'] = $children;
$recurseParams['currentLevel']++;
$tigraMenu = $tigraMenu.$this->_createFromStructure($recurseParams);
}
 
$tigraMenu = $tigraMenu.$this->_closeSubMenu();
}
return $tigraMenu;
}
 
// }}}
// {{{ _openMenu()
 
/**
* Returns the string which opens the JavaScript menu
*
* @access private
* @param int $menu_id ID of the menu needed to use more than one menu on a page
* @return string The JavaScript piece
*/
function _openMenu($menu_id=1)
{
$str = false;
$str = $str."var MENU_ITEMS".$menu_id." = new Array();\n";
$str = $str."MENU_ITEMS".$menu_id." = [\n";
return $str;
}
 
// }}}
// {{{ _openSubMenu()
 
/**
* Returns the string which opens a submenu within the JavaScript menu
*
* @access private
* @param array $tag Contains the content of the current item (name, link)
* @return string The JavaScript piece
*/
function _openSubMenu($tag)
{
$rtag = implode(', ', $tag);
return "\n[".$rtag.',';
}
 
// }}}
// {{{ _closeMenu()
 
/**
* Closes the JavaScript array
*
* @access private
* @return string The JavaScript piece
*/
function _closeMenu()
{
 
return '];';
}
 
// }}}
// {{{ _closeSubMenu()
 
/**
* Closes the JavaScript array of a submenu
*
* @access private
* @return string The JavaScript piece
*/
function _closeSubMenu()
{
return "\n],";
}
 
// }}}
// {{{ _addStyles()
 
/**
* Creates the JavaScript code which sets the styles for each level
*
* @access private
* @param int $menu_id ID of the menu needed to use more than one menu on a page
* @param array $rootStyles Array of style attributes for the top items
* @param array $childStyles Array of style attributes for the sub items
* @return string The JavaScript piece
*/
function _addStyles($menu_id, $rootStyles, $childStyles = false)
{
if (!$childStyles) {
$childStyles = $rootStyles;
}
 
$styles = array();
foreach ($rootStyles as $key => $val) {
foreach ($val as $skey => $sval) {
$styles["'$key'"][$skey][] = "'$sval'";
}
}
 
foreach ($childStyles as $key => $val) {
foreach ($val as $skey => $sval) {
for ($i = 1; $i <= $this->_levels; $i++) {
$styles["'$key'"][$skey][] = "'$sval'";
}
}
}
 
$menustyles = false;
$menustyles = $menustyles . 'var MENU_STYLES'.$menu_id." = new Array();\n";
foreach ($styles as $key => $val) {
$menustyles = $menustyles.'MENU_STYLES'.$menu_id."[$key] = [\n";
foreach ($val as $skey => $sval) {
$menustyles = $menustyles . "'$skey', [".implode(', ', $sval)."],\n";
}
$menustyles = $menustyles."];\n";
}
 
return $menustyles;
}
 
// }}}
// {{{ _addGeometry()
 
/**
* Creates the JavaScript code which sets the position and geometry of the menu
*
* @access private
* @param int $menu_id ID of the menu needed to use more than one menu on a page
* @param array $rootGeometry Array of geometry attributes for the top items
* @param array $childGeometry Array of geometry attributes for the sub items
* @return string The JavaScript piece
*/
function _addGeometry($menu_id, $rootGeometry, $childGeometry = false)
{
if (!$childGeometry) {
$childGeometry = $rootGeometry;
}
 
$params = array();
$geometry = array();
foreach ($rootGeometry as $key => $val) {
$geometry["'$key'"][] = $val;
$incr = false;
if (strpos($val, ',') !== false) {
list($start, $interval) = explode(',',$val);
$incr = true;
}
 
$ratio = false;
if ($key == 'width' && strpos($val, '*') !== false) {
$ratio = trim(str_replace('*','', $val));
}
if ($incr) {
$val = trim($interval);
if ($key == 'left' && preg_match('/[+-]/', $interval)) {
$val = $params[0]['width'] + trim($val);
}
} elseif ($incr) {
$val = trim($start);
} elseif ($ratio) {
$val = $ratio * $this->_strlenByLevel[0];
}
$geometry["'$key'"][0] = $val;
$params[0][$key] = $val;
}
 
foreach($childGeometry as $key => $val) {
$incr = false;
if (strpos($val, ',') !== false) {
list($start, $interval) = explode(',', $val);
$incr = true;
}
 
$ratio = false;
if ($key == 'width' && strpos($val, '*') !== false) {
$ratio = trim(str_replace('*', '', $val));
}
 
for ($i = 1; $i <= $this->_levels; $i++) {
if ($incr && isset($lastval[$key])) {
$val = trim($interval);
if($key == 'block_left' && preg_match('/[+-]/', $interval)) {
$val = $params[$i - 1]['width'] + trim($val);
}
} elseif($incr) {
$val = trim($start);
} elseif ($ratio) {
$val = $ratio * $this->_strlenByLevel[$i];
if($val < $params[0]['width']) {
$val = $params[0]['width'];
}
}
 
$lastval[$key] = $val;
$geometry["'$key'"][] = $val;
$params[$i][$key] = $val;
}
 
}
 
$pos = false;
$pos = $pos . 'var MENU_POS'.$menu_id." = new Array();\n";
foreach ($geometry as $key => $val) {
$pos = $pos . 'MENU_POS' . $menu_id . "[$key] = [" . implode(', ', $val) . "];\n";
}
 
return $pos;
}
 
// }}}
// {{{ printTree()
 
/**
* Print's the current tree using the output driver
*
* @access public
*/
function printTree()
{
if (!$options = $this->_getOptions('printTree')) {
return PEAR::raiseError("TigraMenu::printTree() needs options. See TigraMenu::setOptions()", NESEO_ERROR_NO_OPTIONS, PEAR_ERROR_TRIGGER, E_USER_ERROR);
}
 
echo $this->_openMenu($options['menu_id']) . $this->_structTigraMenu .$this->_closeMenu();
echo "\n\n";
echo $this->_addStyles($options['menu_id'], $options['rootStyles'], $options['childStyles']);
echo "\n\n";
echo $this->_addGeometry($options['menu_id'], $options['rootGeometry'], $options['childGeometry']);
}
 
// }}}
}
?>
/trunk/api/pear/DB/NestedSet/Output.php
New file
0,0 → 1,211
<?php
//
// +----------------------------------------------------------------------+
// | PEAR :: DB_NestedSet_Output |
// +----------------------------------------------------------------------+
// | 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: Daniel Khan <dk@webcluster.at> |
// | Jason Rust <jason@rustyparts.com> |
// +----------------------------------------------------------------------+
// $Id: Output.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
//
 
require_once 'PEAR.php';
 
// {{{ constants
 
define('NESEO_ERROR_NO_METHOD', 'E1000');
define('NESEO_DRIVER_NOT_FOUND', 'E1100');
define('NESEO_ERROR_NO_OPTIONS', 'E2100');
 
// }}}
// {{{ DB_NestedSet_Output:: class
 
/**
* DB_NestedSet_Output is a unified API for other output drivers
* Status is beta
*
* At the moment PEAR::HTML_TreeMenu written by Jason Rust is supported
* A driver for treemenu.org will follow soon.
*
* Usage example:
*
* require_once('DB_NestedSet/NestedSet/Output.php');
* $icon = 'folder.gif';
* $expandedIcon = 'folder-expanded.gif';
* // get data (important to fetch it as an array, using the true flag)
* $data = $NeSe->getAllNodes(true);
* // change the events for one of the elements
* $data[35]['events'] = array('onexpand' => 'alert("we expanded!");');
* // add links to each item
* foreach ($data as $a_data) {
* $a_data['link'] = 'http://foo.com/foo.php?' . $a_data['id'];
* }
* $params = array(
* 'structure' => $data,
* 'options' => array(
* 'icon' => $icon,
* 'expandedIcon' => $expandedIcon,
* ),
* 'textField' => 'name',
* 'linkField' => 'link',
* );
* $menu =& DB_NestedSet_Output::factory('TreeMenu', $params);
* $menu->printListbox();
*
* @author Daniel Khan <dk@webcluster.at>
* @package DB_NestedSet
* @version $Revision: 1.1 $
* @access public
*
*/
 
// }}}
class DB_NestedSet_Output {
// {{{ properties
 
/**
* @var object The tree menu structure
* @access private
*/
var $_structTreeMenu = false;
 
/**
* @var array Array of options to be passed to the ouput methods
* @access public
*/
var $options = array();
 
// }}}
// {{{ factory()
 
/**
* Returns a output driver object
*
* @param array $params A DB_NestedSet nodeset
* @param string $driver (optional) The driver, such as TreeMenu (default)
*
* @access public
* @return object The DB_NestedSet_Ouput object
*/
function &factory ($params, $driver = 'TreeMenu') {
 
$path = dirname(__FILE__).'/'.$driver.'.php';
 
if(is_dir($path) || !file_exists($path)) {
PEAR::raiseError("The output driver '$driver' wasn't found", NESEO_DRIVER_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_ERROR);
}
 
require_once($path);
$driverClass = 'DB_NestedSet_'.$driver;
return new $driverClass($params);
}
 
// }}}
// {{{ setOptions()
 
/**
* Set's options for a specific output group (printTree, printListbox)
* This enables you to set specific options for each output method
*
* @param string $group Output group ATM 'printTree' or 'printListbox'
* @param array $options Hash with options
*
* @access public
* @return bool
*/
function setOptions($group, $options) {
$this->options[$group] = $options;
return true;
}
 
// }}}
// {{{ _getOptions()
 
/**
* Get's all option for a specific output group (printTree, printListbox)
*
* @param string $group Output group ATM 'printTree' or 'printListbox'
*
* @access private
* @return array Options
*/
function _getOptions($group) {
 
if (!isset($this->options[$group])) {
return array();
}
return $this->options[$group];
}
 
// }}}
// {{{ printTree()
 
/**
* Print's the current tree using the output driver
* Overriden by the driver class
*
* @access public
*/
function printTree() {
PEAR::raiseError("Method not available for this driver", NESEO_ERROR_NO_METHOD, PEAR_ERROR_TRIGGER, E_USER_ERROR);
}
 
// }}}
// {{{ printListbox()
 
/**
* Print's a listbox representing the current tree
* Overriden by the driver class
*
* @access public
*/
function printListbox() {
PEAR::raiseError("Method not available for this driver", NESEO_ERROR_NO_METHOD, PEAR_ERROR_TRIGGER, E_USER_ERROR);
}
 
// }}}
 
// {{{ toHTML()
 
/**
* Returns the HTML for the DHTML-menu. This method can be
* used instead of printMenu() to use the menu system
* with a template system.
*
* @access public
* @return string The HTML for the menu
* @author Emanuel Zueger
*/
function tree_toHTML() {
PEAR::raiseError("Method not available for this driver", NESEO_ERROR_NO_METHOD, PEAR_ERROR_TRIGGER, E_USER_ERROR);
}
 
// }}}
// {{{ listbox_toHTML()
 
/**
* Returns the HTML for the listbox. This method can be
* used instead of printListbox() to use the menu system
* with a template system.
*
* @access public
* @return string The HTML for the listbox
* @author Emanuel Zueger
*/
function listbox_toHTML() {
PEAR::raiseError("Method not available for this driver", NESEO_ERROR_NO_METHOD, PEAR_ERROR_TRIGGER, E_USER_ERROR);
}
 
// }}}
}
?>
/trunk/api/pear/DB/NestedSet/Event.php
New file
0,0 → 1,79
<?php
/**
// +----------------------------------------------------------------------+
// | PEAR :: DB_NestedSet_DB |
// +----------------------------------------------------------------------+
// | 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: Daniel Khan <dk@webcluster.at> |
// +----------------------------------------------------------------------+
//
// $Id: Event.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
//
//
*/
 
/**
* Poor mans event handler for DB_NestedSet
*
* Mostly for demo purposes or for extending it if
* someone has ideas...
*
* @author Daniel Khan <dk@webcluster.at>
* @package DB_NestedSet
* @version $Revision: 1.1 $
* @access public
*/
Class DB_NestedSetEvent extends PEAR {
 
/**
* Constructor
*
* @return void
*/
function DB_NestedSetEvent() {
 
$this->PEAR();
}
 
/**
* Destructor
*
* @return void
*/
function _DB_NestedSetEvent() {
 
$this->_PEAR();
}
 
 
/**
* Calls the event handler
*
* You may want to do a switch() here and call you methods
* depending on the event
*
* @param string $event The Event that occured
* @param object node $node A Reference to the node object which was subject to changes
* @param array $eparams A associative array of params which may be needed by the handler
* @return void
* @access private
*/
function callEvent($event, &$node, $eparams = array()) {
 
echo "<br>Override callEvent() if you want to have custom event handlers<br>\n";
echo "Event $event was called with the following params:<br><br>\n";
echo "<PRE>";
print_r($eparams);
echo "</PRE><br>\n";
}
}
?>
/trunk/api/pear/DB/NestedSet/MDB.php
New file
0,0 → 1,136
<?php
//
// +----------------------------------------------------------------------+
// | PEAR :: DB_NestedSet_MDB |
// +----------------------------------------------------------------------+
// | 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: Daniel Khan <dk@webcluster.at> |
// +----------------------------------------------------------------------+
// Thanks to Hans Lellelid for suggesting support for PEAR::MDB
// and for his help in implementing this.
//
// $Id: MDB.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
//
 
require_once 'MDB.php';
 
// {{{ DB_NestedSet_MDB:: class
 
/**
* Wrapper class for PEAR::MDB
*
* @author Daniel Khan <dk@webcluster.at>
* @package DB_NestedSet
* @version $Revision: 1.1 $
* @access public
*/
// }}}
class DB_NestedSet_MDB extends DB_NestedSet {
// {{{ properties
 
/**
* @var object The MDB object
*/
var $db;
 
// }}}
// {{{ constructor
 
/**
* Constructor
*
* @param mixed $dsn DSN as PEAR dsn URI or dsn Array
* @param array $params Database column fields which should be returned
*
*/
function DB_NestedSet_MDB($dsn, $params = array())
{
$this->_debugMessage('DB_NestedSet_MDB($dsn, $params = array())');
$this->DB_NestedSet($params);
$this->db =& $this->_db_Connect($dsn);
$this->db->setFetchMode(MDB_FETCHMODE_ASSOC);
}
 
// }}}
// {{{ destructor
 
/**
* Destructor
*/
function _DB_NestedSet_MDB()
{
$this->_debugMessage('_DB_NestedSet_MDB()');
$this->_DB_NestedSet();
$this->_db_Disconnect();
}
 
// }}}
// {{{ _db_Connect()
 
/**
* Connects to the db
*
* @return object DB The database object
* @access private
*/
function &_db_Connect($dsn)
{
$this->_debugMessage('_db_Connect($dsn)');
if (is_object($this->db)) {
return $this->db;
}
 
$db =& MDB::connect($dsn);
$this->_testFatalAbort($db, __FILE__, __LINE__);
 
return $db;
}
 
// }}}
 
function _isDBError($err) {
if(!MDB::isError($err)) {
return false;
}
return true;
}
 
function _numRows($res) {
return $this->db->numRows($res);
}
 
function _quote($str) {
return $this->db->getTextValue($str);
}
 
// {{{ _db_Disconnect()
 
/**
* Disconnects from db
*
* @return void
* @access private
*/
function _db_Disconnect()
{
$this->_debugMessage('_db_Disconnect()');
if (is_object($this->db)) {
@$this->db->disconnect();
}
 
return true;
}
 
// }}}
}
 
?>
/trunk/api/pear/DB/NestedSet/TreeMenu.php
New file
0,0 → 1,205
<?php
//
// +----------------------------------------------------------------------+
// | PEAR :: DB_NestedSet_TreeMenu |
// +----------------------------------------------------------------------+
// | 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: Jason Rust <jrust@rustyparts.com> |
// +----------------------------------------------------------------------+
//
// $Id: TreeMenu.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
//
 
require_once 'HTML/TreeMenu.php';
 
// {{{ DB_NestedSet_TreeMenu:: class
 
/**
* A helper class to translate the data from a nested set table into a HTML_TreeMenu object
* so that it can be used to create a dynamic tree menu using the PEAR HTML_TreeMenu class.
*
* @see docs/TreeMenu_example.php
* @author Jason Rust <jrust@rustyparts.com>
* @package DB_NestedSet
* @version $Revision: 1.1 $
* @access public
*/
// }}}
class DB_NestedSet_TreeMenu extends DB_NestedSet_Output {
// {{{ properties
 
/**
* @var array The current menu structure
* @access private
*/
var $_structTreeMenu = false;
 
// }}}
// {{{ DB_NestedSet_TreeMenu
 
function &DB_NestedSet_TreeMenu($params) {
$this->_structTreeMenu =& $this->_createFromStructure($params);
}
 
// }}}
// {{{ _createFromStructure()
 
/**
* <pre>Creates a HTML_TreeMenu structure based off of the results from getAllNodes() method
* of the DB_NestedSet class. The needed parameters are:
* o 'structure' => the result from $nestedSet->getAllNodes(true)
* o 'textField' => the field in the table that has the text for node
* o 'linkField' => the field in the table that has the link for the node
* o 'options' => (optional) an array of any additional options to pass to the node when
* Additionally these parameters may be added to the individual nodes to control their
* behavior:
* o 'ensureVisible' => (optional) whether or not the field should be forced as visible
* creating it such as 'icon' or 'expandedIcon'
* o 'events' => (optional) an array of any events to pass to the node when creating it
* such as 'onclick' or 'onexpand'</pre>
* </pre>
* @access public
* @return object A HTML_TreeMenu object
*/
function &_createFromStructure($params)
{
// Basically we go through the array of nodes checking to see
// if each node has children and if so recursing. The reason this
// works is because the data from getAllNodes() is ordered by level
// so a root node will always be first, and sub children will always
// be after them.
if (!isset($params['treeMenu'])) {
$treeMenu =& new HTML_TreeMenu();
} else {
$treeMenu =& $params['treeMenu'];
}
 
// always start at level 1
if (!isset($params['currentLevel'])) {
$params['currentLevel'] = 1;
}
 
// have to use a while loop here because foreach works on a copy of the array and
// the child nodes are passed by reference during the recursion so that the parent
// will know when they have been hit.
reset($params['structure']);
while(list($key, $node) = each($params['structure'])) {
// see if we've already been here before
if (isset($node['hit'])) {
continue;
}
 
// mark that we've hit this node
$params['structure'][$key]['hit'] = $node['hit'] = true;
$tag = array(
'text' => $node[$params['textField']],
'link' => $node[$params['linkField']],
'ensureVisible' => isset($node['ensureVisible']) ? $node['ensureVisible'] : false,
);
$options = isset($params['options']) ? array_merge($params['options'], $tag) : $tag;
$events = isset($node['events']) ? $node['events'] : array();
$parentNode =& $treeMenu->addItem(new HTML_TreeNode($options, $events));
// see if it has children
if (($node['r'] - 1) != $node['l']) {
$children = array();
// harvest all the children
$tempStructure = $params['structure'];
foreach ($tempStructure as $childKey => $childNode) {
if (!isset($childNode['hit']) &&
$childNode['l'] > $node['l'] &&
$childNode['r'] < $node['r'] &&
$childNode['rootid'] == $node['rootid']) {
// important that we assign it by reference here, so that when the child
// marks itself 'hit' the parent loops will know
$children[] =& $params['structure'][$childKey];
}
}
 
$recurseParams = $params;
$recurseParams['structure'] = $children;
$recurseParams['treeMenu'] =& $parentNode;
$recurseParams['currentLevel']++;
$this->_createFromStructure($recurseParams);
}
}
 
return $treeMenu;
}
 
// }}}
// {{{ printTree()
 
/**
* Print's the current tree using the output driver
*
* @access public
*/
function printTree() {
$options = $this->_getOptions('printTree');
$tree =& new HTML_TreeMenu_DHTML($this->_structTreeMenu, $options);
$tree->printMenu();
}
 
// }}}
// {{{ printListbox()
 
/**
* Print's a listbox representing the current tree
*
* @access public
*/
function printListbox() {
$options = $this->_getOptions('printListbox');
$listBox =& new HTML_TreeMenu_Listbox($this->_structTreeMenu, $options);
$listBox->printMenu();
}
// }}}
 
// }}}
// {{{ tree_toHTML()
 
/**
* Returns the HTML for the DHTML-menu. This method can be
* used instead of printMenu() to use the menu system
* with a template system.
*
* @access public
* @return string The HTML for the menu
* @Author Emanuel Zueger
*/
function tree_toHTML() {
$options = $this->_getOptions('toHTML');
$tree =& new HTML_TreeMenu_DHTML($this->_structTreeMenu, $options);
return $tree->toHTML();
}
 
// }}}
// {{{ listbox_toHTML()
 
/**
* Returns the HTML for the listbox. This method can be
* used instead of printListbox() to use the menu system
* with a template system.
*
* @access public
* @return string The HTML for the listbox
* @author Emanuel Zueger
*/
function listbox_toHTML() {
$options = $this->_getOptions('toHTML');
$listBox =& new HTML_TreeMenu_Listbox($this->_structTreeMenu, $options);
return $listBox->toHTML();
}
 
// }}}
}
?>
/trunk/api/pear/DB/NestedSet/DB.php
New file
0,0 → 1,135
<?php
//
// +----------------------------------------------------------------------+
// | PEAR :: DB_NestedSet_DB |
// +----------------------------------------------------------------------+
// | 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: Daniel Khan <dk@webcluster.at> |
// +----------------------------------------------------------------------+
//
// $Id: DB.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
//
 
require_once 'DB.php';
 
// {{{ DB_NestedSet_DB:: class
 
/**
* Wrapper class for PEAR::DB
*
* @author Daniel Khan <dk@webcluster.at>
* @package DB_NestedSet
* @version $Revision: 1.1 $
* @access public
*/
 
// }}}
class DB_NestedSet_DB extends DB_NestedSet {
// {{{ properties
 
/**
* @var object Db object
*/
var $db;
 
// }}}
// {{{ constructor
 
/**
* Constructor
*
* @param mixed $dsn DSN as PEAR dsn URI or dsn Array
* @param array $params Database column fields which should be returned
*
*/
function DB_NestedSet_DB($dsn, $params = array())
{
$this->_debugMessage('DB_NestedSet_DB($dsn, $params = array())');
$this->DB_NestedSet($params);
$this->db =& $this->_db_Connect($dsn);
$this->db->setFetchMode(DB_FETCHMODE_ASSOC);
}
 
// }}}
// {{{ destructor
 
/**
* Destructor
*/
function _DB_NestedSet_DB()
{
$this->_debugMessage('_DB_NestedSet_DB()');
$this->_DB_NestedSet();
$this->_db_Disconnect();
}
 
// }}}
// {{{ _db_Connect()
 
/**
* Connects to the db
*
* @return object DB The database object
* @access private
*/
function &_db_Connect($dsn)
{
$this->_debugMessage('_db_Connect($dsn)');
if (is_object($this->db)) {
return $this->db;
}
 
$db =& DB::connect($dsn);
$this->_testFatalAbort($db, __FILE__, __LINE__);
return $db;
}
 
// }}}
 
 
function _numRows($res) {
return $res->numRows();
}
 
function _isDBError($err) {
if(!DB::isError($err)) {
return false;
}
return true;
}
 
function _quote($str) {
return $this->db->quote($str);
}
 
// {{{ _db_Disconnect()
 
/**
* Disconnects from db
*
* @return void
* @access private
*/
function _db_Disconnect()
{
$this->_debugMessage('_db_Disconnect()');
if (is_object($this->db)) {
@$this->db->disconnect();
}
 
return true;
}
 
// }}}
}
 
?>
/trunk/api/pear/DB/DataObject/Generator.php
New file
0,0 → 1,929
<?php
/**
* Generation tools for DB_DataObject
*
* 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_DataObject
* @author Alan Knowles <alan@akbkhome.com>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: Generator.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB_DataObject
*/
/**
*
* Config _$ptions
* [DB_DataObject_Generator]
* ; optional default = DB/DataObject.php
* extends_location =
* ; optional default = DB_DataObject
* extends =
* ; alter the extends field when updating a class (defaults to only replacing DB_DataObject)
* generator_class_rewrite = ANY|specific_name // default is DB_DataObject
*
*/
 
/**
* Needed classes
*/
require_once 'DB/DataObject.php';
//require_once('Config.php');
 
/**
* Generator class
*
* @package DB_DataObject
*/
class DB_DataObject_Generator extends DB_DataObject
{
/* =========================================================== */
/* Utility functions - for building db config files */
/* =========================================================== */
 
/**
* Array of table names
*
* @var array
* @access private
*/
var $tables;
 
/**
* associative array table -> array of table row objects
*
* @var array
* @access private
*/
var $_definitions;
 
/**
* active table being output
*
* @var string
* @access private
*/
var $table; // active tablename
 
 
/**
* The 'starter' = call this to start the process
*
* @access public
* @return none
*/
function start()
{
$options = &PEAR::getStaticProperty('DB_DataObject','options');
$databases = array();
foreach($options as $k=>$v) {
if (substr($k,0,9) == 'database_') {
$databases[substr($k,9)] = $v;
}
}
 
if (@$options['database']) {
require_once 'DB.php';
$dsn = DB::parseDSN($options['database']);
if (!isset($database[$dsn['database']])) {
$databases[$dsn['database']] = $options['database'];
}
}
 
foreach($databases as $databasename => $database) {
if (!$database) {
continue;
}
$this->debug("CREATING FOR $databasename\n");
$class = get_class($this);
$t = new $class;
$t->_database_dsn = $database;
$t->_database = $databasename;
$dsn = DB::parseDSN($database);
if (($dsn['phptype'] == 'sqlite') && is_file($databasename)) {
$t->_database = basename($t->_database);
}
$t->_createTableList();
 
foreach(get_class_methods($class) as $method) {
if (substr($method,0,8 ) != 'generate') {
continue;
}
$this->debug("calling $method");
$t->$method();
}
}
$this->debug("DONE\n\n");
}
 
/**
* Output File was config object, now just string
* Used to generate the Tables
*
* @var string outputbuffer for table definitions
* @access private
*/
var $_newConfig;
 
/**
* Build a list of tables;
* Currently this is very Mysql Specific - ideas for more generic stiff welcome
*
* @access private
* @return none
*/
function _createTableList()
{
$this->_connect();
$options = &PEAR::getStaticProperty('DB_DataObject','options');
 
$__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
// try getting a list of schema tables first. (postgres)
$__DB->expectError(DB_ERROR_UNSUPPORTED);
$this->tables = $__DB->getListOf('schema.tables');
$__DB->popExpect();
if (empty($this->tables) || is_a($this->tables , 'PEAR_Error')) {
//if that fails fall back to clasic tables list.
$this->tables = $__DB->getListOf('tables');
}
if (is_a($this->tables , 'PEAR_Error')) {
return PEAR::raiseError($this->tables->toString(), null, PEAR_ERROR_DIE);
}
// build views as well if asked to.
if (!empty($options['build_views'])) {
$views = $__DB->getListOf('views');
if (is_a($views,'PEAR_Error')) {
return PEAR::raiseError(
'Error getting Views (check the PEAR bug database for the fix to DB), ' .
$views->toString(),
null,
PEAR_ERROR_DIE
);
}
$this->tables = array_merge ($this->tables, $views);
}
// declare a temporary table to be filled with matching tables names
$tmp_table = array();
 
 
foreach($this->tables as $table) {
if (isset($options['generator_include_regex']) &&
!preg_match($options['generator_include_regex'],$table)) {
continue;
} else if (isset($options['generator_exclude_regex']) &&
preg_match($options['generator_exclude_regex'],$table)) {
continue;
}
// postgres strip the schema bit from the
if (!empty($options['generator_strip_schema'])) {
$bits = explode('.', $table,2);
$table = $bits[0];
if (count($bits) > 1) {
$table = $bits[1];
}
}
$defs = $__DB->tableInfo($table);
if (is_a($defs,'PEAR_Error')) {
echo $defs->toString();
exit;
}
// cast all definitions to objects - as we deal with that better.
foreach($defs as $def) {
if (!is_array($def)) {
continue;
}
$this->_definitions[$table][] = (object) $def;
}
// we find a matching table, just store it into a temporary array
$tmp_table[] = $table;
}
// the temporary table array is now the right one (tables names matching
// with regex expressions have been removed)
$this->tables = $tmp_table;
//print_r($this->_definitions);
}
 
/**
* Auto generation of table data.
*
* it will output to db_oo_{database} the table definitions
*
* @access private
* @return none
*/
function generateDefinitions()
{
$this->debug("Generating Definitions file: ");
if (!$this->tables) {
$this->debug("-- NO TABLES -- \n");
return;
}
 
$options = &PEAR::getStaticProperty('DB_DataObject','options');
 
 
//$this->_newConfig = new Config('IniFile');
$this->_newConfig = '';
foreach($this->tables as $this->table) {
$this->_generateDefinitionsTable();
}
$this->_connect();
// dont generate a schema if location is not set
// it's created on the fly!
if (!@$options['schema_location'] && @!$options["ini_{$this->_database}"] ) {
return;
}
$base = @$options['schema_location'];
if (isset($options["ini_{$this->_database}"])) {
$file = $options["ini_{$this->_database}"];
} else {
$file = "{$base}/{$this->_database}.ini";
}
if (!file_exists(dirname($file))) {
require_once 'System.php';
System::mkdir(array('-p','-m',0755,dirname($file)));
}
$this->debug("Writing ini as {$file}\n");
touch($file);
//print_r($this->_newConfig);
$fh = fopen($file,'w');
fwrite($fh,$this->_newConfig);
fclose($fh);
//$ret = $this->_newConfig->writeInput($file,false);
 
//if (PEAR::isError($ret) ) {
// return PEAR::raiseError($ret->message,null,PEAR_ERROR_DIE);
// }
}
 
/**
* The table geneation part
*
* @access private
* @return tabledef and keys array.
*/
function _generateDefinitionsTable()
{
global $_DB_DATAOBJECT;
$defs = $this->_definitions[$this->table];
$this->_newConfig .= "\n[{$this->table}]\n";
$keys_out = "\n[{$this->table}__keys]\n";
$keys_out_primary = '';
$keys_out_secondary = '';
if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
echo "TABLE STRUCTURE FOR {$this->table}\n";
print_r($defs);
}
$DB = $this->getDatabaseConnection();
$dbtype = $DB->phptype;
$ret = array(
'table' => array(),
'keys' => array(),
);
$ret_keys_primary = array();
$ret_keys_secondary = array();
foreach($defs as $t) {
$n=0;
 
switch (strtoupper($t->type)) {
 
case 'INT':
case 'INT2': // postgres
case 'INT4': // postgres
case 'INT8': // postgres
case 'SERIAL4': // postgres
case 'SERIAL8': // postgres
case 'INTEGER':
case 'TINYINT':
case 'SMALLINT':
case 'MEDIUMINT':
case 'BIGINT':
$type = DB_DATAOBJECT_INT;
if ($t->len == 1) {
$type += DB_DATAOBJECT_BOOL;
}
break;
case 'REAL':
case 'DOUBLE':
case 'FLOAT':
case 'FLOAT8': // double precision (postgres)
case 'DECIMAL':
case 'NUMERIC':
case 'NUMBER': // oci8
$type = DB_DATAOBJECT_INT; // should really by FLOAT!!! / MONEY...
break;
case 'YEAR':
$type = DB_DATAOBJECT_INT;
break;
case 'BIT':
case 'BOOL':
case 'BOOLEAN':
$type = DB_DATAOBJECT_BOOL;
// postgres needs to quote '0'
if ($dbtype == 'pgsql') {
$type += DB_DATAOBJECT_STR;
}
break;
case 'STRING':
case 'CHAR':
case 'VARCHAR':
case 'VARCHAR2':
case 'TINYTEXT':
case 'ENUM':
case 'SET': // not really but oh well
case 'TIMESTAMPTZ': // postgres
case 'BPCHAR': // postgres
case 'INTERVAL': // postgres (eg. '12 days')
case 'CIDR': // postgres IP net spec
case 'INET': // postgres IP
case 'MACADDR': // postgress network Mac address.
$type = DB_DATAOBJECT_STR;
break;
case 'TEXT':
case 'MEDIUMTEXT':
case 'LONGTEXT':
$type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT;
break;
case 'DATE':
$type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE;
break;
case 'TIME':
$type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TIME;
break;
case 'DATETIME':
$type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
break;
case 'TIMESTAMP': // do other databases use this???
$type = ($dbtype == 'mysql') ?
DB_DATAOBJECT_MYSQLTIMESTAMP :
DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
break;
case 'TINYBLOB':
case 'BLOB': /// these should really be ignored!!!???
case 'MEDIUMBLOB':
case 'LONGBLOB':
case 'BYTEA': // postgres blob support..
$type = DB_DATAOBJECT_STR + DB_DATAOBJECT_BLOB;
break;
}
if (!strlen(trim($t->name))) {
continue;
}
if (preg_match('/not_null/i',$t->flags)) {
$type += DB_DATAOBJECT_NOTNULL;
}
$write_ini = true;
if (in_array($t->name,array('null','yes','no','true','false'))) {
echo "*****************************************************************\n".
"** WARNING **\n".
"** Found column '{$t->name}', which is invalid in an .ini file **\n".
"** This line will not be writen to the file - you will have **\n".
"** define the keys()/method manually. **\n".
"*****************************************************************\n";
$write_ini = false;
} else {
$this->_newConfig .= "{$t->name} = $type\n";
}
$ret['table'][$t->name] = $type;
// i've no idea if this will work well on other databases?
// only use primary key or nextval(), cause the setFrom blocks you setting all key items...
// if no keys exist fall back to using unique
//echo "\n{$t->name} => {$t->flags}\n";
if (preg_match("/(auto_increment|nextval\()/i",rawurldecode($t->flags))) {
// native sequences = 2
if ($write_ini) {
$keys_out_primary .= "{$t->name} = N\n";
}
$ret_keys_primary[$t->name] = 'N';
} else if (preg_match("/(primary|unique)/i",$t->flags)) {
// keys.. = 1
if ($write_ini) {
$keys_out_secondary .= "{$t->name} = K\n";
}
$ret_keys_secondary[$t->name] = 'K';
}
}
$this->_newConfig .= $keys_out . (empty($keys_out_primary) ? $keys_out_secondary : $keys_out_primary);
$ret['keys'] = empty($keys_out_primary) ? $ret_keys_secondary : $ret_keys_primary;
if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
print_r(array("dump for {$this->table}", $ret));
}
return $ret;
}
 
/*
* building the class files
* for each of the tables output a file!
*/
function generateClasses()
{
//echo "Generating Class files: \n";
$options = &PEAR::getStaticProperty('DB_DataObject','options');
$base = $options['class_location'];
if (strpos($base,'%s') !== false) {
$base = dirname($base);
}
if (!file_exists($base)) {
require_once 'System.php';
System::mkdir(array('-p',$base));
}
$class_prefix = $options['class_prefix'];
if ($extends = @$options['extends']) {
$this->_extends = $extends;
$this->_extendsFile = $options['extends_location'];
}
 
foreach($this->tables as $this->table) {
$this->table = trim($this->table);
$this->classname = $class_prefix.preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table));
$i = '';
if (strpos($options['class_location'],'%s') !== false) {
$outfilename = sprintf($options['class_location'], preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)));
} else {
$outfilename = "{$base}/".preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)).".php";
}
$oldcontents = '';
if (file_exists($outfilename)) {
// file_get_contents???
$oldcontents = implode('',file($outfilename));
}
$out = $this->_generateClassTable($oldcontents);
$this->debug( "writing $this->classname\n");
$fh = fopen($outfilename, "w");
fputs($fh,$out);
fclose($fh);
}
//echo $out;
}
 
/**
* class being extended (can be overridden by [DB_DataObject_Generator] extends=xxxx
*
* @var string
* @access private
*/
var $_extends = 'DB_DataObject';
 
/**
* line to use for require('DB/DataObject.php');
*
* @var string
* @access private
*/
var $_extendsFile = "DB/DataObject.php";
 
/**
* class being generated
*
* @var string
* @access private
*/
var $_className;
 
/**
* The table class geneation part - single file.
*
* @access private
* @return none
*/
function _generateClassTable($input = '')
{
// title = expand me!
$foot = "";
$head = "<?php\n/**\n * Table Definition for {$this->table}\n */\n";
// requires
$head .= "require_once '{$this->_extendsFile}';\n\n";
// add dummy class header in...
// class
$head .= "class {$this->classname} extends {$this->_extends} \n{";
 
$body = "\n ###START_AUTOCODE\n";
$body .= " /* the code below is auto generated do not remove the above tag */\n\n";
// table
$padding = (30 - strlen($this->table));
if ($padding < 2) $padding =2;
$p = str_repeat(' ',$padding) ;
$options = &PEAR::getStaticProperty('DB_DataObject','options');
$var = (substr(phpversion(),0,1) > 4) ? 'public' : 'var';
$body .= " {$var} \$__table = '{$this->table}'; {$p}// table name\n";
// if we are using the option database_{databasename} = dsn
// then we should add var $_database = here
// as database names may not always match..
if (isset($options["database_{$this->_database}"])) {
$body .= " {$var} \$_database = '{$this->_database}'; {$p}// database name (used with database_{*} config)\n";
}
$var = (substr(phpversion(),0,1) > 4) ? 'public' : 'var';
if (!empty($options['generator_novars'])) {
$var = '//'.$var;
}
$defs = $this->_definitions[$this->table];
 
// show nice information!
$connections = array();
$sets = array();
foreach($defs as $t) {
if (!strlen(trim($t->name))) {
continue;
}
$padding = (30 - strlen($t->name));
if ($padding < 2) $padding =2;
$p = str_repeat(' ',$padding) ;
$body .=" {$var} \${$t->name}; {$p}// {$t->type}({$t->len}) {$t->flags}\n";
// can not do set as PEAR::DB table info doesnt support it.
//if (substr($t->Type,0,3) == "set")
// $sets[$t->Field] = "array".substr($t->Type,3);
$body .= $this->derivedHookVar($t,$padding);
}
 
// THIS IS TOTALLY BORKED old FC creation
// IT WILL BE REMOVED!!!!! in DataObjects 1.6
// grep -r __clone * to find all it's uses
// and replace them with $x = clone($y);
// due to the change in the PHP5 clone design.
if ( substr(phpversion(),0,1) < 5) {
$body .= "\n";
$body .= " /* ZE2 compatibility trick*/\n";
$body .= " function __clone() { return \$this;}\n";
}
 
// simple creation tools ! (static stuff!)
$body .= "\n";
$body .= " /* Static get */\n";
$body .= " function staticGet(\$k,\$v=NULL) { return DB_DataObject::staticGet('{$this->classname}',\$k,\$v); }\n";
// generate getter and setter methods
$body .= $this->_generateGetters($input);
$body .= $this->_generateSetters($input);
/*
theoretically there is scope here to introduce 'list' methods
based up 'xxxx_up' column!!! for heiracitcal trees..
*/
 
// set methods
//foreach ($sets as $k=>$v) {
// $kk = strtoupper($k);
// $body .=" function getSets{$k}() { return {$v}; }\n";
//}
$body .= $this->derivedHookFunctions();
 
$body .= "\n /* the code above is auto generated do not remove the tag below */";
$body .= "\n ###END_AUTOCODE\n";
 
 
// stubs..
if (!empty($options['generator_add_validate_stubs'])) {
foreach($defs as $t) {
if (!strlen(trim($t->name))) {
continue;
}
$validate_fname = 'validate' . ucfirst(strtolower($t->name));
// dont re-add it..
if (preg_match('/\s+function\s+' . $validate_fname . '\s*\(/i', $input)) {
continue;
}
$body .= "\n function {$validate_fname}()\n {\n return false;\n }\n";
}
}
 
 
 
 
$foot .= "}\n";
$full = $head . $body . $foot;
 
if (!$input) {
return $full;
}
if (!preg_match('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n)/s',$input)) {
return $full;
}
if (!preg_match('/(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',$input)) {
return $full;
}
 
 
/* this will only replace extends DB_DataObject by default,
unless use set generator_class_rewrite to ANY or a name*/
 
$class_rewrite = 'DB_DataObject';
$options = &PEAR::getStaticProperty('DB_DataObject','options');
if (!($class_rewrite = @$options['generator_class_rewrite'])) {
$class_rewrite = 'DB_DataObject';
}
if ($class_rewrite == 'ANY') {
$class_rewrite = '[a-z_]+';
}
 
$input = preg_replace(
'/(\n|\r\n)class\s*[a-z0-9_]+\s*extends\s*' .$class_rewrite . '\s*\{(\n|\r\n)/si',
"\nclass {$this->classname} extends {$this->_extends} \n{\n",
$input);
 
return preg_replace(
'/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',
$body,$input);
}
 
/**
* hook to add extra methods to all classes
*
* called once for each class, use with $this->table and
* $this->_definitions[$this->table], to get data out of the current table,
* use it to add extra methods to the default classes.
*
* @access public
* @return string added to class eg. functions.
*/
function derivedHookFunctions()
{
// This is so derived generator classes can generate functions
// It MUST NOT be changed here!!!
return "";
}
 
/**
* hook for var lines
* called each time a var line is generated, override to add extra var
* lines
*
* @param object t containing type,len,flags etc. from tableInfo call
* @param int padding number of spaces
* @access public
* @return string added to class eg. functions.
*/
function derivedHookVar(&$t,$padding)
{
// This is so derived generator classes can generate variabels
// It MUST NOT be changed here!!!
return "";
}
 
 
/**
* getProxyFull - create a class definition on the fly and instantate it..
*
* similar to generated files - but also evals the class definitoin code.
*
*
* @param string database name
* @param string table name of table to create proxy for.
*
*
* @return object Instance of class. or PEAR Error
* @access public
*/
function getProxyFull($database,$table) {
if ($err = $this->fillTableSchema($database,$table)) {
return $err;
}
$options = &PEAR::getStaticProperty('DB_DataObject','options');
$class_prefix = $options['class_prefix'];
if ($extends = @$options['extends']) {
$this->_extends = $extends;
$this->_extendsFile = $options['extends_location'];
}
 
$classname = $this->classname = $class_prefix.preg_replace('/[^A-Z0-9]/i','_',ucfirst(trim($this->table)));
 
$out = $this->_generateClassTable();
//echo $out;
eval('?>'.$out);
return new $classname;
}
/**
* fillTableSchema - set the database schema on the fly
*
*
*
* @param string database name
* @param string table name of table to create schema info for
*
* @return none | PEAR::error()
* @access public
*/
function fillTableSchema($database,$table) {
global $_DB_DATAOBJECT;
$this->_database = $database;
$this->_connect();
$table = trim($table);
$__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
$defs = $__DB->tableInfo($table);
if (PEAR::isError($defs)) {
return $defs;
}
if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
$this->debug("getting def for $database/$table",'fillTable');
$this->debug(print_r($defs,true),'defs');
}
// cast all definitions to objects - as we deal with that better.
foreach($defs as $def) {
if (is_array($def)) {
$this->_definitions[$table][] = (object) $def;
}
}
 
$this->table = trim($table);
$ret = $this->_generateDefinitionsTable();
$_DB_DATAOBJECT['INI'][$database][$table] = $ret['table'];
$_DB_DATAOBJECT['INI'][$database][$table.'__keys'] = $ret['keys'];
return false;
}
/**
* Generate getter methods for class definition
*
* @param string $input Existing class contents
* @return string
* @access public
*/
function _generateGetters($input) {
 
$options = &PEAR::getStaticProperty('DB_DataObject','options');
$getters = '';
 
// only generate if option is set to true
if (empty($options['generate_getters'])) {
return '';
}
 
// remove auto-generated code from input to be able to check if the method exists outside of the auto-code
$input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
 
$getters .= "\n\n";
$defs = $this->_definitions[$this->table];
 
// loop through properties and create getter methods
foreach ($defs = $defs as $t) {
 
// build mehtod name
$methodName = 'get' . ucfirst($t->name);
 
if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
continue;
}
 
$getters .= " /**\n";
$getters .= " * Getter for \${$t->name}\n";
$getters .= " *\n";
$getters .= (stristr($t->flags, 'multiple_key')) ? " * @return object\n"
: " * @return {$t->type}\n";
$getters .= " * @access public\n";
$getters .= " */\n";
$getters .= (substr(phpversion(),0,1) > 4) ? ' public '
: ' ';
$getters .= "function $methodName() {\n";
$getters .= " return \$this->{$t->name};\n";
$getters .= " }\n\n";
}
 
return $getters;
}
 
 
/**
* Generate setter methods for class definition
*
* @param string Existing class contents
* @return string
* @access public
*/
function _generateSetters($input) {
 
$options = &PEAR::getStaticProperty('DB_DataObject','options');
$setters = '';
 
// only generate if option is set to true
if (empty($options['generate_setters'])) {
return '';
}
 
// remove auto-generated code from input to be able to check if the method exists outside of the auto-code
$input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
 
$setters .= "\n";
$defs = $this->_definitions[$this->table];
 
// loop through properties and create setter methods
foreach ($defs = $defs as $t) {
 
// build mehtod name
$methodName = 'set' . ucfirst($t->name);
 
if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
continue;
}
 
$setters .= " /**\n";
$setters .= " * Setter for \${$t->name}\n";
$setters .= " *\n";
$setters .= " * @param mixed input value\n";
$setters .= " * @access public\n";
$setters .= " */\n";
$setters .= (substr(phpversion(),0,1) > 4) ? ' public '
: ' ';
$setters .= "function $methodName(\$value) {\n";
$setters .= " \$this->{$t->name} = \$value;\n";
$setters .= " }\n\n";
}
 
return $setters;
}
 
}
/trunk/api/pear/DB/DataObject/Error.php
New file
0,0 → 1,53
<?php
/**
* DataObjects error handler, loaded on demand...
*
* DB_DataObject_Error is a quick wrapper around pear error, so you can distinguish the
* error code source.
*
* 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_DataObject
* @author Alan Knowles <alan@akbkhome.com>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: Error.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB_DataObject
*/
class DB_DataObject_Error extends PEAR_Error
{
/**
* DB_DataObject_Error constructor.
*
* @param mixed $code DB error code, or string with error message.
* @param integer $mode what "error mode" to operate in
* @param integer $level what error level to use for $mode & PEAR_ERROR_TRIGGER
* @param mixed $debuginfo additional debug info, such as the last query
*
* @access public
*
* @see PEAR_Error
*/
function DB_DataObject_Error($message = '', $code = DB_ERROR, $mode = PEAR_ERROR_RETURN,
$level = E_USER_NOTICE)
{
$this->PEAR_Error('DB_DataObject Error: ' . $message, $code, $mode, $level);
}
// todo : - support code -> message handling, and translated error messages...
}
/trunk/api/pear/DB/DataObject/Cast.php
New file
0,0 → 1,546
<?php
/**
* Prototype Castable Object.. for DataObject queries
*
* Storage for Data that may be cast into a variety of formats.
*
* 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_DataObject
* @author Alan Knowles <alan@akbkhome.com>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: Cast.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB_DataObject
*/
/**
*
* Common usages:
* // blobs
* $data = DB_DataObject_Cast::blob($somefile);
* $data = DB_DataObject_Cast::string($somefile);
* $dataObject->someblobfield = $data
*
* // dates?
* $d1 = new DB_DataObject_Cast::date('12/12/2000');
* $d2 = new DB_DataObject_Cast::date(2000,12,30);
* $d3 = new DB_DataObject_Cast::date($d1->year, $d1->month+30, $d1->day+30);
*
* // time, datetime.. ?????????
*
* // raw sql????
* $data = DB_DataObject_Cast::sql('cast("123123",datetime)');
* $data = DB_DataObject_Cast::sql('NULL');
*
* // int's/string etc. are proably pretty pointless..!!!!
*
*
* inside DB_DataObject,
* if (is_a($v,'db_dataobject_class')) {
* $value .= $v->toString(DB_DATAOBJECT_INT,'mysql');
* }
*
*
*
*
 
*/
class DB_DataObject_Cast {
/**
* Type of data Stored in the object..
*
* @var string (date|blob|.....?)
* @access public
*/
var $type;
/**
* Data For date representation
*
* @var int day/month/year
* @access public
*/
var $day;
var $month;
var $year;
 
/**
* Generic Data..
*
* @var string
* @access public
*/
 
var $value;
 
 
 
/**
* Blob consructor
*
* create a Cast object from some raw data.. (binary)
*
*
* @param string (with binary data!)
*
* @return object DB_DataObject_Cast
* @access public
*/
function blob($value) {
$r = new DB_DataObject_Cast;
$r->type = 'blob';
$r->value = $value;
return $r;
}
 
 
/**
* String consructor (actually use if for ints and everything else!!!
*
* create a Cast object from some string (not binary)
*
*
* @param string (with binary data!)
*
* @return object DB_DataObject_Cast
* @access public
*/
function string($value) {
$r = new DB_DataObject_Cast;
$r->type = 'string';
$r->value = $value;
return $r;
}
/**
* SQL constructor (for raw SQL insert)
*
* create a Cast object from some sql
*
* @param string (with binary data!)
*
* @return object DB_DataObject_Cast
* @access public
*/
function sql($value)
{
$r = new DB_DataObject_Cast;
$r->type = 'sql';
$r->value = $value;
return $r;
}
 
 
/**
* Date Constructor
*
* create a Cast object from some string (not binary)
* NO VALIDATION DONE, although some crappy re-calcing done!
*
* @param vargs... accepts
* dd/mm
* dd/mm/yyyy
* yyyy-mm
* yyyy-mm-dd
* array(yyyy,dd)
* array(yyyy,dd,mm)
*
*
*
* @return object DB_DataObject_Cast
* @access public
*/
function date()
{
$args = func_get_args();
switch(count($args)) {
case 0: // no args = today!
$bits = explode('-',date('Y-m-d'));
break;
case 1: // one arg = a string
if (strpos($args[0],'/') !== false) {
$bits = array_reverse(explode('/',$args[0]));
} else {
$bits = explode('-',$args[0]);
}
break;
default: // 2 or more..
$bits = $args;
}
if (count($bits) == 1) { // if YYYY set day = 1st..
$bits[] = 1;
}
if (count($bits) == 2) { // if YYYY-DD set day = 1st..
$bits[] = 1;
}
// if year < 1970 we cant use system tools to check it...
// so we make a few best gueses....
// basically do date calculations for the year 2000!!!
// fix me if anyone has more time...
if (($bits[0] < 1975) || ($bits[0] > 2030)) {
$oldyear = $bits[0];
$bits = explode('-',date('Y-m-d',mktime(1,1,1,$bits[1],$bits[2],2000)));
$bits[0] = ($bits[0] - 2000) + $oldyear;
} else {
// now mktime
$bits = explode('-',date('Y-m-d',mktime(1,1,1,$bits[1],$bits[2],$bits[0])));
}
$r = new DB_DataObject_Cast;
$r->type = 'date';
list($r->year,$r->month,$r->day) = $bits;
return $r;
}
 
/**
* Data For time representation ** does not handle timezones!!
*
* @var int hour/minute/second
* @access public
*/
var $hour;
var $minute;
var $second;
 
/**
* DateTime Constructor
*
* create a Cast object from a Date/Time
* Maybe should accept a Date object.!
* NO VALIDATION DONE, although some crappy re-calcing done!
*
* @param vargs... accepts
* noargs (now)
* yyyy-mm-dd HH:MM:SS (Iso)
* array(yyyy,mm,dd,HH,MM,SS)
*
*
* @return object DB_DataObject_Cast
* @access public
* @author therion 5 at hotmail
*/
function dateTime()
{
$args = func_get_args();
switch(count($args)) {
case 0: // no args = now!
$datetime = date('Y-m-d G:i:s', mktime());
case 1:
// continue on from 0 args.
if (!isset($datetime)) {
$datetime = $args[0];
}
$parts = explode(' ', $datetime);
$bits = explode('-', $parts[0]);
$bits = array_merge($bits, explode(':', $parts[1]));
break;
default: // 2 or more..
$bits = $args;
}
 
if (count($bits) != 6) {
// PEAR ERROR?
return false;
}
$r = DB_DataObject_Cast::date($bits[0], $bits[1], $bits[2]);
if (!$r) {
return $r; // pass thru error (False) - doesnt happen at present!
}
// change the type!
$r->type = 'datetime';
// should we mathematically sort this out..
// (or just assume that no-one's dumb enough to enter 26:90:90 as a time!
$r->hour = $bits[3];
$r->minute = $bits[4];
$r->second = $bits[5];
return $r;
 
}
 
 
 
/**
* time Constructor
*
* create a Cast object from a Date/Time
* Maybe should accept a Date object.!
* NO VALIDATION DONE, and no-recalcing done!
*
* @param vargs... accepts
* noargs (now)
* HH:MM:SS (Iso)
* array(HH,MM,SS)
*
*
* @return object DB_DataObject_Cast
* @access public
* @author therion 5 at hotmail
*/
function time()
{
$args = func_get_args();
switch (count($args)) {
case 0: // no args = now!
$time = date('G:i:s', mktime());
case 1:
// continue on from 0 args.
if (!isset($time)) {
$time = $args[0];
}
$bits = explode(':', $time);
break;
default: // 2 or more..
$bits = $args;
}
if (count($bits) != 3) {
return false;
}
// now take data from bits into object fields
$r = new DB_DataObject_Cast;
$r->type = 'time';
$r->hour = $bits[0];
$r->minute = $bits[1];
$r->second = $bits[2];
return $r;
 
}
 
/**
* get the string to use in the SQL statement for this...
*
*
* @param int $to Type (DB_DATAOBJECT_*
* @param object $db DB Connection Object
*
*
* @return string
* @access public
*/
function toString($to=false,$db)
{
// if $this->type is not set, we are in serious trouble!!!!
// values for to:
$method = 'toStringFrom'.$this->type;
return $this->$method($to,$db);
}
/**
* get the string to use in the SQL statement from a blob of binary data
* ** Suppots only blob->postgres::bytea
*
* @param int $to Type (DB_DATAOBJECT_*
* @param object $db DB Connection Object
*
*
* @return string
* @access public
*/
function toStringFromBlob($to,$db)
{
// first weed out invalid casts..
// in blobs can only be cast to blobs.!
// perhaps we should support TEXT fields???
if (!($to & DB_DATAOBJECT_BLOB)) {
return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::blob to something other than a blob!');
}
switch ($db->dsn["phptype"]) {
case 'pgsql':
return "'".pg_escape_bytea($this->value)."'::bytea";
case 'mysql':
return "'".mysql_real_escape_string($this->value,$db->connection)."'";
case 'mysqli':
// this is funny - the parameter order is reversed ;)
return "'".mysqli_real_escape_string($db->connection, $this->value)."'";
default:
return PEAR::raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet");
}
}
/**
* get the string to use in the SQL statement for a blob from a string!
* ** Suppots only string->postgres::bytea
*
*
* @param int $to Type (DB_DATAOBJECT_*
* @param object $db DB Connection Object
*
*
* @return string
* @access public
*/
function toStringFromString($to,$db)
{
// first weed out invalid casts..
// in blobs can only be cast to blobs.!
// perhaps we should support TEXT fields???
//
if (!($to & DB_DATAOBJECT_BLOB)) {
return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::string to something other than a blob!'.
' (why not just use native features)');
}
switch ($db->dsn['phptype']) {
case 'pgsql':
return "'".pg_escape_string($this->value)."'::bytea";
case 'mysql':
return "'".mysql_real_escape_string($this->value,$db->connection)."'";
case 'mysqli':
return "'".mysqli_real_escape_string($db->connection, $this->value)."'";
 
default:
return PEAR::raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet");
}
}
/**
* get the string to use in the SQL statement for a date
*
*
*
* @param int $to Type (DB_DATAOBJECT_*
* @param object $db DB Connection Object
*
*
* @return string
* @access public
*/
function toStringFromDate($to,$db)
{
// first weed out invalid casts..
// in blobs can only be cast to blobs.!
// perhaps we should support TEXT fields???
//
if (($to !== false) && !($to & DB_DATAOBJECT_DATE)) {
return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::string to something other than a date!'.
' (why not just use native features)');
}
return "'{$this->year}-{$this->month}-{$this->day}'";
}
/**
* get the string to use in the SQL statement for a datetime
*
*
*
* @param int $to Type (DB_DATAOBJECT_*
* @param object $db DB Connection Object
*
*
* @return string
* @access public
* @author therion 5 at hotmail
*/
function toStringFromDateTime($to,$db)
{
// first weed out invalid casts..
// in blobs can only be cast to blobs.!
// perhaps we should support TEXT fields???
if (($to !== false) &&
!($to & (DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME))) {
return PEAR::raiseError('Invalid Cast from a ' .
' DB_DataObject_Cast::dateTime to something other than a datetime!' .
' (try using native features)');
}
return "'{$this->year}-{$this->month}-{$this->day} {$this->hour}:{$this->minute}:{$this->second}'";
}
 
/**
* get the string to use in the SQL statement for a time
*
*
*
* @param int $to Type (DB_DATAOBJECT_*
* @param object $db DB Connection Object
*
*
* @return string
* @access public
* @author therion 5 at hotmail
*/
 
function toStringFromTime($to,$db)
{
// first weed out invalid casts..
// in blobs can only be cast to blobs.!
// perhaps we should support TEXT fields???
if (($to !== false) && !($to & DB_DATAOBJECT_TIME)) {
return PEAR::raiseError('Invalid Cast from a' .
' DB_DataObject_Cast::time to something other than a time!'.
' (try using native features)');
}
return "'{$this->hour}:{$this->minute}:{$this->second}'";
}
/**
* get the string to use in the SQL statement for a raw sql statement.
*
* @param int $to Type (DB_DATAOBJECT_*
* @param object $db DB Connection Object
*
*
* @return string
* @access public
*/
function toStringFromSql($to,$db)
{
return $this->value;
}
}
 
/trunk/api/pear/DB/DataObject/createTables.php
New file
0,0 → 1,55
#!/usr/bin/php -q
<?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: Alan Knowles <alan@akbkhome.com>
// +----------------------------------------------------------------------+
//
// $Id: createTables.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
//
 
// since this version doesnt use overload,
// and I assume anyone using custom generators should add this..
 
define('DB_DATAOBJECT_NO_OVERLOAD',1);
 
require_once 'DB/DataObject/Generator.php';
 
if (!ini_get('register_argc_argv')) {
PEAR::raiseError("\nERROR: You must turn register_argc_argv On in you php.ini file for this to work\neg.\n\nregister_argc_argv = On\n\n", null, PEAR_ERROR_DIE);
exit;
}
 
if (!@$_SERVER['argv'][1]) {
PEAR::raiseError("\nERROR: createTable.php usage:\n\nC:\php\pear\DB\DataObjects\createTable.php example.ini\n\n", null, PEAR_ERROR_DIE);
exit;
}
 
$config = parse_ini_file($_SERVER['argv'][1], true);
foreach($config as $class=>$values) {
$options = &PEAR::getStaticProperty($class,'options');
$options = $values;
}
 
 
$options = &PEAR::getStaticProperty('DB_DataObject','options');
if (empty($options)) {
PEAR::raiseError("\nERROR: could not read ini file\n\n", null, PEAR_ERROR_DIE);
exit;
}
set_time_limit(0);
DB_DataObject::debugLevel(1);
$generator = new DB_DataObject_Generator;
$generator->start();
/trunk/api/pear/DB/msql.php
23,7 → 23,7
* @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.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: msql.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
47,7 → 47,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
* @since Class not functional until Release 1.7.0
*/
/trunk/api/pear/DB/QueryTool/EasyJoin.php
New file
0,0 → 1,135
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
 
/**
* Contains the DB_QueryTool_EasyJoin class
*
* 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_QueryTool
* @author Wolfram Kriesing <wk@visionp.de>
* @author Paolo Panto <wk@visionp.de>
* @copyright 2003-2005 Wolfram Kriesing, Paolo Panto
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: EasyJoin.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
* @link http://pear.php.net/package/DB_QueryTool
*/
 
/**
* require the DB_QueryTool_Query class
*/
require_once 'DB/QueryTool/Query.php';
 
/**
* DB_QueryTool_EasyJoin class
*
* @category Database
* @package DB_QueryTool
* @author Wolfram Kriesing <wk@visionp.de>
* @copyright 2003-2005 Wolfram Kriesing
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @link http://pear.php.net/package/DB_QueryTool
*/
class DB_QueryTool_EasyJoin extends DB_QueryTool_Query
{
// {{{ class vars
 
/**
* This is the regular expression that shall be used to find a table's shortName
* in a column name, the string found by using this regular expression will be removed
* from the column name and it will be checked if it is a table name
* i.e. the default '/_id$/' would find the table name 'user' from the column name 'user_id'
*
* @var string regexp
*/
var $_tableNamePreg = '/_id$/';
 
/**
* This is to find the column name that is referred by it, so the default find
* from 'user_id' the column 'id' which will be used to refer to the 'user' table
*
* @var string regexp
*/
var $_columnNamePreg = '/^.*_/';
 
// }}}
// {{{ __construct()
 
/**
* call parent constructor
* @param mixed $dsn DSN string, DSN array or DB object
* @param array $options
*/
function __construct($dsn=false, $options=array())
{
parent::DB_QueryTool_Query($dsn, $options);
}
 
// }}}
// {{{ autoJoin()
 
/**
* Join the given tables, using the column names, to find out how to join the tables;
* i.e., if table1 has a column named &quot;table2_id&quot;, this method will join
* &quot;WHERE table1.table2_id=table2.id&quot;.
* All joins made here are only concatenated via AND.
* @param array $tables
*/
function autoJoin($tables)
{
// FIXXME if $tables is empty autoJoin all available tables that have a relation
// to $this->table, starting to search in $this->table
settype($tables, 'array');
// add this->table to the tables array, so we go thru the current table first
$tables = array_merge(array($this->table), $tables);
 
$shortNameIndexed = $this->getTableSpec(true, $tables);
$nameIndexed = $this->getTableSpec(false, $tables);
 
//print_r($shortNameIndexed);
//print_r($tables); print '<br><br>';
if (sizeof($shortNameIndexed) != sizeof($tables)) {
$this->_errorLog("autoJoin-ERROR: not all the tables are in the tableSpec!<br />");
}
 
$joinTables = array();
$joinConditions = array();
foreach ($tables as $aTable) { // go through $this->table and all the given tables
if ($metadata = $this->metadata($aTable))
foreach ($metadata as $aCol => $x) { // go through each row to check which might be related to $aTable
$possibleTableShortName = preg_replace($this->_tableNamePreg, '' , $aCol);
$possibleColumnName = preg_replace($this->_columnNamePreg, '' , $aCol);
//print "$aTable.$aCol .... possibleTableShortName=$possibleTableShortName .... possibleColumnName=$possibleColumnName<br />";
if (isset($shortNameIndexed[$possibleTableShortName])) {
// are the tables given in the tableSpec?
if (!$shortNameIndexed[$possibleTableShortName]['name'] ||
!$nameIndexed[$aTable]['name']) {
// its an error of the developer, so log the error, dont show it to the end user
$this->_errorLog("autoJoin-ERROR: '$aTable' is not given in the tableSpec!<br />");
} else {
// do only join different table.col combination,
// we should not join stuff like 'question.question=question.question'
// this would be quite stupid, but it used to be :-(
if ($shortNameIndexed[$possibleTableShortName]['name'] != $aTable ||
$possibleColumnName != $aCol
) {
$where = $shortNameIndexed[$possibleTableShortName]['name'].".$possibleColumnName=$aTable.$aCol";
$this->addJoin($nameIndexed[$aTable]['name'], $where);
$this->addJoin($shortNameIndexed[$possibleTableShortName]['name'], $where);
}
}
}
}
}
}
 
// }}}
}
?>
/trunk/api/pear/DB/QueryTool/Query.php
New file
0,0 → 1,2395
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
 
/**
* Contains the DB_QueryTool_Query class
*
* 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_QueryTool
* @author Wolfram Kriesing <wk@visionp.de>
* @author Paolo Panto <wk@visionp.de>
* @author Lorenzo Alberton <l dot alberton at quipo dot it>
* @copyright 2003-2005 Wolfram Kriesing, Paolo Panto, Lorenzo Alberton
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: Query.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
* @link http://pear.php.net/package/DB_QueryTool
*/
 
/**
* require the PEAR and DB classes
*/
require_once 'PEAR.php';
require_once 'DB.php';
 
/**
* DB_QueryTool_Query class
*
* This class should be extended
*
* @category Database
* @package DB_QueryTool
* @author Wolfram Kriesing <wk@visionp.de>
* @author Paolo Panto <wk@visionp.de>
* @author Lorenzo Alberton <l dot alberton at quipo dot it>
* @copyright 2003-2005 Wolfram Kriesing, Paolo Panto, Lorenzo Alberton
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @link http://pear.php.net/package/DB_QueryTool
*/
class DB_QueryTool_Query
{
// {{{ class vars
 
/**
* @var string the name of the primary column
*/
var $primaryCol = 'id';
 
/**
* @var string the current table the class works on
*/
var $table = '';
 
/**
* @var string the name of the sequence for this table
*/
var $sequenceName = null;
 
/**
* @var object the db-object, a PEAR::DB instance
*/
var $db = null;
 
/**
* @var string the where condition
* @access private
*/
var $_where = '';
 
/**
* @var string the order condition
* @access private
*/
var $_order = '';
 
/**
* @var string the having definition
* @access private
*/
var $_having = '';
 
/**
* @var array contains the join content
* the key is the join type, for now we have 'default' and 'left'
* inside each key 'table' contains the table
* key 'where' contains the where clause for the join
* @access private
*/
var $_join = array();
 
/**
* @var string which column to index the result by
* @access private
*/
var $_index = null;
 
/**
* @var string the group-by clause
* @access private
*/
var $_group = '';
 
/**
* @var array the limit
* @access private
*/
var $_limit = array();
 
/**
* @var string type of result to return
* @access private
*/
var $_resultType = 'none';
 
/**
* @var array the metadata temporary saved
* @access private
*/
var $_metadata = array();
 
/**
* @var string
* @access private
*/
var $_lastQuery = null;
 
/**
* @var string the rows that shall be selected
* @access private
*/
var $_select = '*';
 
/**
* @var string the rows that shall not be selected
* @access private
*/
var $_dontSelect = '';
 
/**
* @var array this array saves different modes in which this class works
* i.e. 'raw' means no quoting before saving/updating data
* @access private
*/
var $options = array(
'raw' => false,
'verbose' => true, // set this to false in a productive environment
// it will produce error-logs if set to true
'useCache' => false,
'logFile' => false,
);
 
/**
* this array contains information about the tables
* those are
* - 'name' => the real table name
* - 'shortName' => the short name used, so that when moving the table i.e.
* onto a provider's db and u have to rename the tables to
* longer names this name will be relevant, i.e. when
* autoJoining, i.e. a table name on your local machine is:
* 'user' but online it has to be 'applName_user' then the
* shortName will be used to determine if a column refers to
* another table, if the colName is 'user_id', it knows the
* shortName 'user' refers to the table 'applName_user'
*/
var $tableSpec = array();
 
/**
* this is the regular expression that shall be used to find a table's shortName
* in a column name, the string found by using this regular expression will be removed
* from the column name and it will be checked if it is a table name
* i.e. the default '/_id$/' would find the table name 'user' from the column name 'user_id'
*/
var $_tableNameToShortNamePreg = '/^.*_/';
 
/**
* @var array this array caches queries that have already been built once
* to reduce the execution time
*/
var $_queryCache = array();
 
/**
* The object that contains the log-instance
*/
var $_logObject = null;
 
/**
* Some internal data the logging needs
*/
var $_logData = array();
 
// }}}
// {{{ __construct()
 
/**
* this is the constructor, as it will be implemented in ZE2 (php5)
*
* @version 2002/04/02
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param object db-object
*/
/*
function __construct($dsn=false, $options=array())
{
if (!isset($options['autoConnect'])) {
$autoConnect = true;
} else {
$autoConnect = $options['autoConnect'];
}
if (isset($options['errorCallback'])) {
$this->setErrorCallback($options['errorCallback']);
}
if (isset($options['errorSetCallback'])) {
$this->setErrorSetCallback($options['errorSetCallback']);
}
if (isset($options['errorLogCallback'])) {
$this->setErrorLogCallback($options['errorLogCallback']);
}
 
if ($autoConnect && $dsn) {
$this->connect($dsn, $options);
}
//we would need to parse the dsn first ... i dont feel like now :-)
// oracle has all column names in upper case
//FIXXXME make the class work only with upper case when we work with oracle
//if ($this->db->phptype=='oci8' && !$this->primaryCol) {
// $this->primaryCol = 'ID';
//}
 
if ($this->sequenceName == null) {
$this->sequenceName = $this->table;
}
}
*/
 
// }}}
// {{{ DB_QueryTool_Query()
 
/**
* @version 2002/04/02
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param mixed $dsn DSN string, DSN array or DB object
* @param array $options
*/
function DB_QueryTool_Query($dsn=false, $options=array())
{
//$this->__construct($dsn, $options);
if (!isset($options['autoConnect'])) {
$autoConnect = true;
} else {
$autoConnect = $options['autoConnect'];
unset($options['autoConnect']);
}
if (isset($options['errorCallback'])) {
$this->setErrorCallback($options['errorCallback']);
unset($options['errorCallback']);
}
if (isset($options['errorSetCallback'])) {
$this->setErrorSetCallback($options['errorSetCallback']);
unset($options['errorSetCallback']);
}
if (isset($options['errorLogCallback'])) {
$this->setErrorLogCallback($options['errorLogCallback']);
unset($options['errorLogCallback']);
}
if ($autoConnect && $dsn) {
$this->connect($dsn, $options);
}
if (is_null($this->sequenceName)) {
$this->sequenceName = $this->table;
}
}
 
// }}}
// {{{ connect()
 
/**
* use this method if you want to connect manually
* @param mixed $dsn DSN string, DSN array or MDB object
* @param array $options
*/
function connect($dsn, $options=array())
{
if (is_object($dsn)) {
$res = $this->db =& $dsn;
} else {
$res = $this->db = DB::connect($dsn, $options);
}
if (DB::isError($res)) {
// FIXXME what shall we do here?
$this->_errorLog($res->getUserInfo());
} else {
$this->db->setFetchMode(DB_FETCHMODE_ASSOC);
}
}
 
// }}}
// {{{ getDbInstance()
 
/**
* @return reference to current DB instance
*/
function &getDbInstance()
{
return $this->db;
}
 
// }}}
// {{{ setDbInstance()
 
/**
* Setup using an existing connection.
* this also sets the DB_FETCHMODE_ASSOC since this class
* needs this to be set!
*
* @param object a reference to an existing DB-object
* @return void
*/
function setDbInstance(&$dbh)
{
$this->db =& $dbh;
$this->db->setFetchMode(DB_FETCHMODE_ASSOC);
}
 
// }}}
// {{{ get()
 
/**
* get the data of a single entry
* if the second parameter is only one column the result will be returned
* directly not as an array!
*
* @version 2002/03/05
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param integer the id of the element to retrieve
* @param string if this is given only one row shall be returned, directly, not an array
* @return mixed (1) an array of the retrieved data
* (2) if the second parameter is given and its only one column,
* only this column's data will be returned
* (3) false in case of failure
*/
function get($id, $column='')
{
$table = $this->table;
$getMethod = 'getRow';
if ($column && !strpos($column, ',')) { // if only one column shall be selected
$getMethod = 'getOne';
}
// we dont use 'setSelect' here, since this changes the setup of the class, we
// build the query directly
// if $column is '' then _buildSelect selects '*' anyway, so that's the same behaviour as before
$query['select'] = $this->_buildSelect($column);
$query['where'] = $this->_buildWhere($this->table.'.'.$this->primaryCol.'='.$id);
$queryString = $this->_buildSelectQuery($query);
 
return $this->returnResult($this->execute($queryString,$getMethod));
}
 
// }}}
// {{{ getMultiple()
 
/**
* gets the data of the given ids
*
* @version 2002/04/23
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param array this is an array of ids to retreive
* @param string the column to search in for
* @return mixed an array of the retreived data, or false in case of failure
* when failing an error is set in $this->_error
*/
function getMultiple($ids, $column='')
{
$col = $this->primaryCol;
if ($column) {
$col = $column;
}
// FIXXME if $ids has no table.col syntax and we are using joins, the table better be put in front!!!
$ids = $this->_quoteArray($ids);
 
$query['where'] = $this->_buildWhere($col.' IN ('.implode(',', $ids).')');
$queryString = $this->_buildSelectQuery($query);
 
return $this->returnResult($this->execute($queryString));
}
 
// }}}
// {{{ getAll()
 
/**
* get all entries from the DB
* for sorting use setOrder!!!, the last 2 parameters are deprecated
*
* @version 2002/03/05
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param int to start from
* @param int the number of rows to show
* @param string the DB-method to use, i dont know if we should leave this param here ...
* @return mixed an array of the retreived data, or false in case of failure
* when failing an error is set in $this->_error
*/
function getAll($from=0,$count=0,$method='getAll')
{
$query = array();
if ($count) {
$query = array('limit' => array($from, $count));
}
return $this->returnResult($this->execute($this->_buildSelectQuery($query), $method));
}
 
// }}}
// {{{ getCol()
 
/**
* this method only returns one column, so the result will be a one dimensional array
* this does also mean that using setSelect() should be set to *one* column, the one you want to
* have returned a most common use case for this could be:
* $table->setSelect('id');
* $ids = $table->getCol();
* OR
* $ids = $table->getCol('id');
* so ids will be an array with all the id's
*
* @version 2003/02/25
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the column that shall be retreived
* @param int to start from
* @param int the number of rows to show
* @return mixed an array of the retreived data, or false in case of failure
* when failing an error is set in $this->_error
*/
function getCol($column=null, $from=0, $count=0)
{
$query = array();
if (!is_null($column)) {
// by using _buildSelect() i can be sure that the table name will not be ambigious
// i.e. in a join, where all the joined tables have a col 'id'
// _buildSelect() will put the proper table name in front in case there is none
$query['select'] = $this->_buildSelect($column);
}
if ($count) {
$query['limit'] = array($from,$count);
}
return $this->returnResult($this->execute($this->_buildSelectQuery($query), 'getCol'));
}
 
// }}}
// {{{ getCount()
 
/**
* get the number of entries
*
* @version 2002/04/02
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param
* @return mixed an array of the retreived data, or false in case of failure
* when failing an error is set in $this->_error
*/
function getCount()
{
/* the following query works on mysql
SELECT count(DISTINCT image.id) FROM image2tree
RIGHT JOIN image ON image.id = image2tree.image_id
the reason why this is needed - i just wanted to get the number of rows that do exist if the result is grouped by image.id
the following query is what i tried first, but that returns the number of rows that have been grouped together
for each image.id
SELECT count(*) FROM image2tree
RIGHT JOIN image ON image.id = image2tree.image_id GROUP BY image.id
 
so that's why we do the following, i am not sure if that is standard SQL and absolutley correct!!!
*/
 
//FIXXME see comment above if this is absolutely correct!!!
if ($group = $this->_buildGroup()) {
$query['select'] = 'COUNT(DISTINCT '.$group.')';
$query['group'] = '';
} else {
$query['select'] = 'COUNT(*)';
}
 
$query['order'] = ''; // order is not of importance and might freak up the special group-handling up there, since the order-col is not be known
/*# FIXXME use the following line, but watch out, then it has to be used in every method, or this
# value will be used always, simply try calling getCount and getAll afterwards, getAll will return the count :-)
# if getAll doesn't use setSelect!!!
*/
//$this->setSelect('count(*)');
$queryString = $this->_buildSelectQuery($query, true);
 
return ($res = $this->execute($queryString, 'getOne')) ? $res : 0;
}
 
// }}}
// {{{ getDefaultValues()
 
/**
* return an empty element where all the array elements do already exist
* corresponding to the columns in the DB
*
* @version 2002/04/05
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @return array an empty, or pre-initialized element
*/
function getDefaultValues()
{
$ret = array();
// here we read all the columns from the DB and initialize them
// with '' to prevent PHP-warnings in case we use error_reporting=E_ALL
foreach ($this->metadata() as $aCol=>$x) {
$ret[$aCol] = '';
}
return $ret;
}
 
// }}}
// {{{ getEmptyElement()
 
/**
* this is just for BC
* @deprecated
*/
function getEmptyElement()
{
$this->getDefaultValues();
}
 
// }}}
// {{{ getQueryString()
 
/**
* Render the current query and return it as a string.
*
* @return string the current query
*/
function getQueryString()
{
$ret = $this->_buildSelectQuery();
if (is_string($ret)) {
$ret = trim($ret);
}
return $ret;
}
 
// }}}
// {{{ save()
 
/**
* save data, calls either update or add
* if the primaryCol is given in the data this method knows that the
* data passed to it are meant to be updated (call 'update'), otherwise it will
* call the method 'add'.
* If you dont like this behaviour simply stick with the methods 'add'
* and 'update' and ignore this one here.
* This method is very useful when you have validation checks that have to
* be done for both adding and updating, then you can simply overwrite this
* method and do the checks in here, and both cases will be validated first.
*
* @version 2002/03/11
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param array contains the new data that shall be saved in the DB
* @return mixed the data returned by either add or update-method
*/
function save($data)
{
if (!empty($data[$this->primaryCol])) {
return $this->update($data);
}
return $this->add($data);
}
 
// }}}
// {{{ update()
 
/**
* update the member data of a data set
*
* @version 2002/03/06
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param array contains the new data that shall be saved in the DB
* the id has to be given in the field with the key 'ID'
* @return mixed true on success, or false otherwise
*/
function update($newData)
{
$query = array();
// do only set the 'where' part in $query, if a primary column is given
// if not the default 'where' clause is used
if (isset($newData[$this->primaryCol])) {
$query['where'] = $this->primaryCol.'='.$newData[$this->primaryCol];
}
$newData = $this->_checkColumns($newData, 'update');
$values = array();
$raw = $this->getOption('raw');
foreach ($newData as $key => $aData) { // quote the data
//$values[] = "{$this->table}.$key=". ($raw ? $aData : $this->db->quote($aData));
$values[] = "$key=". ($raw ? $aData : $this->db->quote($aData));
}
 
$query['set'] = implode(',', $values);
//FIXXXME _buildUpdateQuery() seems to take joins into account, whcih is bullshit here
$updateString = $this->_buildUpdateQuery($query);
#print '$updateString = '.$updateString;
return $this->execute($updateString, 'query') ? true : false;
}
 
// }}}
// {{{ add()
 
/**
* add a new member in the DB
*
* @version 2002/04/02
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param array contains the new data that shall be saved in the DB
* @return mixed the inserted id on success, or false otherwise
*/
function add($newData)
{
// if no primary col is given, get next sequence value
if (empty($newData[$this->primaryCol])) {
if ($this->primaryCol) { // do only use the sequence if a primary column is given
// otherwise the data are written as given
$id = $this->db->nextId($this->sequenceName);
$newData[$this->primaryCol] = (int)$id;
} else {
// if no primary col is given return true on success
$id = true;
}
} else {
$id = $newData[$this->primaryCol];
}
 
//unset($newData[$this->primaryCol]);
 
$newData = $this->_checkColumns($newData, 'add');
$newData = $this->_quoteArray($newData);
 
$query = sprintf( 'INSERT INTO %s (%s) VALUES (%s)',
$this->table,
implode(', ', array_keys($newData)),
implode(', ', $newData)
);
return $this->execute($query, 'query') ? $id : false;
}
 
// }}}
// {{{ addMultiple()
 
/**
* adds multiple new members in the DB
*
* @version 2002/07/17
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param array contains an array of new data that shall be saved in the DB
* the key-value pairs have to be the same for all the data!!!
* @return mixed the inserted ids on success, or false otherwise
*/
function addMultiple($data)
{
if (!sizeof($data)) {
return false;
}
// the inserted ids which will be returned or if no primaryCol is given
// we return true by default
$retIds = $this->primaryCol ? array() : true;
$allData = array(); // each row that will be inserted
foreach ($data as $key => $aData) {
$aData = $this->_checkColumns($aData, 'add');
$aData = $this->_quoteArray($aData);
 
if (empty($aData[$this->primaryCol])) {
if ($this->primaryCol) { // do only use the sequence if a primary column is given
// otherwise the data are written as given
$retIds[] = $id = (int)$this->db->nextId($this->sequenceName);
$aData[$this->primaryCol] = $id;
}
} else {
$retIds[] = $aData[$this->primaryCol];
}
$allData[] = '('.implode(', ', $aData).')';
}
 
$query = sprintf( 'INSERT INTO %s (%s) VALUES %s',
$this->table,
implode(', ', array_keys($aData)), // use the keys of the last element built
implode(', ', $allData)
);
return $this->execute($query, 'query') ? $retIds : false;
}
 
// }}}
// {{{ remove()
 
/**
* removes a member from the DB
*
* @version 2002/04/08
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param mixed integer/string - the value of the column that shall be removed
* array - multiple columns that shall be matched, the second parameter will be ignored
* @param string the column to match the data against, only if $data is not an array
* @return boolean
*/
function remove($data, $whereCol='')
{
$raw = $this->getOption('raw');
 
if (is_array($data)) {
//FIXXME check $data if it only contains columns that really exist in the table
$wheres = array();
foreach ($data as $key => $val) {
$wheres[] = $key.'='. ($raw ? $val : $this->db->quote($val));
}
$whereClause = implode(' AND ',$wheres);
} else {
if (empty($whereCol)) {
$whereCol = $this->primaryCol;
}
$whereClause = $whereCol.'='. ($raw ? $data : $this->db->quote($data));
}
 
$query = sprintf( 'DELETE FROM %s WHERE %s',
$this->table,
$whereClause
);
return $this->execute($query, 'query') ? true : false;
// i think this method should return the ID's that it removed, this way we could simply use the result
// for further actions that depend on those id ... or? make stuff easier, see ignaz::imail::remove
}
 
// }}}
// {{{ removeAll()
 
/**
* empty a table
*
* @version 2002/06/17
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @return
*/
function removeAll()
{
$query = 'DELETE FROM '.$this->table;
return $this->execute($query, 'query') ? true : false;
}
 
// }}}
// {{{ removeMultiple()
 
/**
* remove the datasets with the given ids
*
* @version 2002/04/24
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param array the ids to remove
* @return
*/
function removeMultiple($ids, $colName='')
{
if (empty($colName)) {
$colName = $this->primaryCol;
}
$ids = $this->_quoteArray($ids);
 
$query = sprintf( 'DELETE FROM %s WHERE %s IN (%s)',
$this->table,
$colName,
implode(',', $ids)
);
return $this->execute($query, 'query') ? true : false;
}
 
// }}}
// {{{ removePrimary()
 
/**
* removes a member from the DB and calls the remove methods of the given objects
* so all rows in another table that refer to this table are erased too
*
* @version 2002/04/08
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param integer the value of the primary key
* @param string the column name of the tables with the foreign keys
* @param object just for convinience, so nobody forgets to call this method
* with at least one object as a parameter
* @return boolean
*/
function removePrimary($id, $colName, $atLeastOneObject)
{
$argCounter = 2; // we have 2 parameters that need to be given at least
// func_get_arg returns false and a warning if there are no more parameters, so
// we suppress the warning and check for false
while ($object = @func_get_arg($argCounter++)) {
//FIXXXME let $object also simply be a table name
if (!$object->remove($id, $colName)) {
//FIXXXME do this better
$this->_errorSet("Error removing '$colName=$id' from table {$object->table}.");
return false;
}
}
 
return ($this->remove($id) ? true : false);
}
 
// }}}
// {{{ setLimit()
 
/**
* @param integer $from
* @param integer $count
*/
function setLimit($from=0, $count=0)
{
if ($from==0 && $count==0) {
$this->_limit = array();
} else {
$this->_limit = array($from, $count);
}
}
 
// }}}
// {{{ getLimit()
 
/**
* @return array
*/
function getLimit()
{
return $this->_limit;
}
 
// }}}
// {{{ setWhere()
 
/**
* sets the where condition which is used for the current instance
*
* @version 2002/04/16
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the where condition, this can be complete like 'X=7 AND Y=8'
*/
function setWhere($whereCondition='')
{
$this->_where = $whereCondition;
//FIXXME parse the where condition and replace ambigious column names, such as "name='Deutschland'" with "country.name='Deutschland'"
// then the users dont have to write that explicitly and can use the same name as in the setOrder i.e. setOrder('name,_net_name,_netPrefix_prefix');
}
 
// }}}
// {{{ getWhere()
 
/**
* gets the where condition which is used for the current instance
*
* @version 2002/04/22
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @return string the where condition, this can be complete like 'X=7 AND Y=8'
*/
function getWhere()
{
return $this->_where;
}
 
// }}}
// {{{ addWhere()
 
/**
* only adds a string to the where clause
*
* @version 2002/07/22
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the where clause to add to the existing one
* @param string the condition for how to concatenate the new where clause
* to the existing one
*/
function addWhere($where, $condition='AND')
{
if ($this->getWhere()) {
$where = $this->getWhere().' '.$condition.' '.$where;
}
$this->setWhere($where);
}
 
// }}}
// {{{ addWhereSearch()
 
/**
* add a where-like clause which works like a search for the given string
* i.e. calling it like this:
* $this->addWhereSearch('name', 'otto hans')
* produces a where clause like this one
* LOWER(name) LIKE "%otto%hans%"
* so the search finds the given string
*
* @version 2002/08/14
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the column to search in for
* @param string the string to search for
*/
function addWhereSearch($column, $string, $condition='AND')
{
// if the column doesn't contain a tablename use the current table name
// in case it is a defined column to prevent ambiguous rows
if (strpos($column, '.') === false) {
$meta = $this->metadata();
if (isset($meta[$column])) {
$column = $this->table.".$column";
}
}
 
$string = $this->db->quote('%'.str_replace(' ', '%', strtolower($string)).'%');
$this->addWhere("LOWER($column) LIKE $string", $condition);
}
 
// }}}
// {{{ setOrder()
 
/**
* sets the order condition which is used for the current instance
*
* @version 2002/05/16
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the where condition, this can be complete like 'X=7 AND Y=8'
* @param boolean sorting order (TRUE => ASC, FALSE => DESC)
*/
function setOrder($orderCondition='', $desc=false)
{
$this->_order = $orderCondition .($desc ? ' DESC' : '');
}
 
// }}}
// {{{ addOrder()
 
/**
* Add a order parameter to the query.
*
* @version 2003/05/28
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the where condition, this can be complete like 'X=7 AND Y=8'
* @param boolean sorting order (TRUE => ASC, FALSE => DESC)
*/
function addOrder($orderCondition='', $desc=false)
{
$order = $orderCondition .($desc ? ' DESC' : '');
if ($this->_order) {
$this->_order = $this->_order.','.$order;
} else {
$this->_order = $order;
}
}
 
// }}}
// {{{ getOrder()
 
/**
* gets the order condition which is used for the current instance
*
* @version 2002/05/16
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @return string the order condition, this can be complete like 'ID,TIMESTAMP DESC'
*/
function getOrder()
{
return $this->_order;
}
 
// }}}
// {{{ setHaving()
 
/**
* sets the having definition
*
* @version 2003/06/05
* @access public
* @author Johannes Schaefer <johnschaefer@gmx.de>
* @param string the having definition
*/
function setHaving($having='')
{
$this->_having = $having;
}
 
// }}}
// {{{ getHaving()
 
/**
* gets the having definition which is used for the current instance
*
* @version 2003/06/05
* @access public
* @author Johannes Schaefer <johnschaefer@gmx.de>
* @return string the having definition
*/
function getHaving()
{
return $this->_having;
}
 
// }}}
// {{{ addHaving()
 
/**
* Extend the current having clause. This is very useful, when you are building
* this clause from different places and don't want to overwrite the currently
* set having clause, but extend it.
*
* @param string this is a having clause, i.e. 'column' or 'table.column' or 'MAX(column)'
* @param string the connection string, which usually stays the default, which is ',' (a comma)
*/
function addHaving($what='*', $connectString=' AND ')
{
if ($this->_having) {
$this->_having = $this->_having.$connectString.$what;
} else {
$this->_having = $what;
}
}
 
// }}}
// {{{ setJoin()
 
/**
* sets a join-condition
*
* @version 2002/06/10
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param mixed either a string or an array that contains
* the table(s) to join on the current table
* @param string the where clause for the join
*/
function setJoin($table=null, $where=null, $joinType='default')
{
//FIXXME make it possible to pass a table name as a string like this too 'user u'
// where u is the string that can be used to refer to this table in a where/order
// or whatever condition
// this way it will be possible to join tables with itself, like setJoin(array('user u','user u1'))
// this wouldnt work yet, but for doing so we would need to change the _build methods too!!!
// because they use getJoin('tables') and this simply returns all the tables in use
// but don't take care of the mentioned syntax
 
if (is_null($table) || is_null($where)) { // remove the join if not sufficient parameters are given
$this->_join[$joinType] = array();
return;
}
/* this causes problems if we use the order-by, since it doenst know the name to order it by ... :-)
// replace the table names with the internal name used for the join
// this way we can also join one table multiple times if it will be implemented one day
$this->_join[$table] = preg_replace('/'.$table.'/','j1',$where);
*/
$this->_join[$joinType][$table] = $where;
}
 
// }}}
// {{{ setJoin()
 
/**
* if you do a left join on $this->table you will get all entries
* from $this->table, also if there are no entries for them in the joined table
* if both parameters are not given the left-join will be removed
* NOTE: be sure to only use either a right or a left join
*
* @version 2002/07/22
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the table(s) to be left-joined
* @param string the where clause for the join
*/
function setLeftJoin($table=null, $where=null)
{
$this->setJoin($table, $where, 'left');
}
 
// }}}
// {{{ addLeftJoin()
 
/**
* @param string the table to be left-joined
* @param string the where clause for the join
* @param string the join type
*/
function addLeftJoin($table, $where, $type='left')
{
// init value, to prevent E_ALL-warning
if (!isset($this->_join[$type]) || !$this->_join[$type]) {
$this->_join[$type] = array();
}
$this->_join[$type][$table] = $where;
}
 
// }}}
// {{{ setRightJoin()
 
/**
* see setLeftJoin for further explaination on what a left/right join is
* NOTE: be sure to only use either a right or a left join
//FIXXME check if the above sentence is necessary and if sql doesnt allow the use of both
*
* @see setLeftJoin()
* @version 2002/09/04
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the table(s) to be right-joined
* @param string the where clause for the join
*/
function setRightJoin($table=null, $where=null)
{
$this->setJoin($table, $where, 'right');
}
 
// }}}
// {{{ getJoin()
 
/**
* gets the join-condition
*
* @access public
* @param string [null|''|'table'|'tables'|'right'|'left']
* @return array gets the join parameters
*/
function getJoin($what=null)
{
// if the user requests all the join data or if the join is empty, return it
if (is_null($what) || empty($this->_join)) {
return $this->_join;
}
 
$ret = array();
switch (strtolower($what)) {
case 'table':
case 'tables':
foreach ($this->_join as $aJoin) {
if (count($aJoin)) {
$ret = array_merge($ret, array_keys($aJoin));
}
}
break;
case 'right': // return right-join data only
case 'left': // return left join data only
if (count($this->_join[$what])) {
$ret = array_merge($ret, $this->_join[$what]);
}
break;
}
return $ret;
}
 
// }}}
// {{{ addJoin()
 
/**
* adds a table and a where clause that shall be used for the join
* instead of calling
* setJoin(array(table1,table2),'<where clause1> AND <where clause2>')
* you can also call
* setJoin(table1,'<where clause1>')
* addJoin(table2,'<where clause2>')
* or where it makes more sense is to build a query which is made out of a
* left join and a standard join
* setLeftJoin(table1,'<where clause1>')
* // results in ... FROM $this->table LEFT JOIN table ON <where clause1>
* addJoin(table2,'<where clause2>')
* // results in ... FROM $this->table,table2 LEFT JOIN table ON <where clause1> WHERE <where clause2>
*
* @access public
* @param string the table to be joined
* @param string the where clause for the join
* @param string the join type
*/
function addJoin($table, $where, $type='default')
{
if ($table == $this->table) {
return; //skip. Self joins are not supported.
}
// init value, to prevent E_ALL-warning
if (!isset($this->_join[$type]) || !$this->_join[$type]) {
$this->_join[$type] = array();
}
$this->_join[$type][$table] = $where;
}
 
// }}}
// {{{ setTable()
 
/**
* sets the table this class is currently working on
*
* @version 2002/07/11
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the table name
*/
function setTable($table)
{
$this->table = $table;
}
 
// }}}
// {{{ getTable()
 
/**
* gets the table this class is currently working on
*
* @version 2002/07/11
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @return string the table name
*/
function getTable()
{
return $this->table;
}
 
// }}}
// {{{ setGroup()
 
/**
* sets the group-by condition
*
* @version 2002/07/22
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the group condition
*/
function setGroup($group='')
{
$this->_group = $group;
//FIXXME parse the condition and replace ambigious column names, such as "name='Deutschland'" with "country.name='Deutschland'"
// then the users dont have to write that explicitly and can use the same name as in the setOrder i.e. setOrder('name,_net_name,_netPrefix_prefix');
}
 
// }}}
// {{{ getGroup()
 
/**
* gets the group condition which is used for the current instance
*
* @version 2002/07/22
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @return string the group condition
*/
function getGroup()
{
return $this->_group;
}
 
// }}}
// {{{ setSelect()
 
/**
* limit the result to return only the columns given in $what
* @param string fields that shall be selected
*/
function setSelect($what='*')
{
$this->_select = $what;
}
 
// }}}
// {{{ addSelect()
 
/**
* add a string to the select part of the query
*
* add a string to the select-part of the query and connects it to an existing
* string using the $connectString, which by default is a comma.
* (SELECT xxx FROM - xxx is the select-part of a query)
*
* @version 2003/01/08
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the string that shall be added to the select-part
* @param string the string to connect the new string with the existing one
* @return void
*/
function addSelect($what='*', $connectString=',')
{
// if the select string is not empty add the string, otherwise simply set it
if ($this->_select) {
$this->_select = $this->_select.$connectString.$what;
} else {
$this->_select = $what;
}
}
 
// }}}
// {{{ getSelect()
 
/**
* @return string
*/
function getSelect()
{
return $this->_select;
}
 
// }}}
// {{{ setDontSelect()
 
/**
* @param string
*/
function setDontSelect($what='')
{
$this->_dontSelect = $what;
}
 
// }}}
// {{{ getDontSelect()
 
/**
* @return string
*/
function getDontSelect()
{
return $this->_dontSelect;
}
 
// }}}
// {{{ reset()
 
/**
* reset all the set* settings; with no parameter given, it resets them all
*
* @version 2002/09/16
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @return void
*/
function reset($what=array())
{
if (!sizeof($what)) {
$what = array(
'select',
'dontSelect',
'group',
'having',
'limit',
'where',
'index',
'order',
'join',
'leftJoin',
'rightJoin'
);
}
 
foreach ($what as $aReset) {
$this->{'set'.ucfirst($aReset)}();
}
}
 
// }}}
// {{{ setOption()
 
/**
* set mode the class shall work in
* currently we have the modes:
* 'raw' does not quote the data before building the query
*
* @version 2002/09/17
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the mode to be set
* @param mixed the value of the mode
* @return void
*/
function setOption($option, $value)
{
$this->options[strtolower($option)] = $value;
}
 
// }}}
// {{{ getOption()
 
/**
* @return string
*/
function getOption($option)
{
return $this->options[strtolower($option)];
}
 
// }}}
// {{{ _quoteArray()
 
/**
* quotes all the data in this array if we are not in raw mode!
* @param array
*/
function _quoteArray($data)
{
if (!$this->getOption('raw')) {
foreach ($data as $key => $val) {
$data[$key] = $this->db->quote($val);
}
}
return $data;
}
 
// }}}
// {{{ _checkColumns()
 
/**
* checks if the columns which are given as the array's indexes really exist
* if not it will be unset anyway
*
* @version 2002/04/16
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the actual message, first word should always be the method name,
* to build the message like this: className::methodname
* @param integer the line number
*/
function _checkColumns($newData, $method='unknown')
{
if (!$meta = $this->metadata()) { // if no metadata available, return data as given
return $newData;
}
 
foreach ($newData as $colName => $x) {
if (!isset($meta[$colName])) {
$this->_errorLog("$method, column {$this->table}.$colName doesnt exist, value was removed before '$method'",__LINE__);
unset($newData[$colName]);
} else {
// if the current column exists, check the length too, not to write content that is too long
// prevent DB-errors here
// do only check the data length if this field is given
if (isset($meta[$colName]['len']) && ($meta[$colName]['len'] != -1) &&
($oldLength=strlen($newData[$colName])) > $meta[$colName]['len']
) {
$this->_errorLog("_checkColumns, had to trim column '$colName' from $oldLength to ".
$meta[$colName]['DATA_LENGTH'].' characters.', __LINE__);
$newData[$colName] = substr($newData[$colName], 0, $meta[$colName]['len']);
}
}
}
return $newData;
}
 
// }}}
// {{{ debug()
 
/**
* overwrite this method and i.e. print the query $string
* to see the final query
*
* @param string the query mostly
*/
function debug($string){}
 
//
//
// ONLY ORACLE SPECIFIC, not very nice since it is DB dependent, but we need it!!!
//
//
 
// }}}
// {{{ metadata()
 
/**
* !!!! query COPIED FROM db_oci8.inc - from PHPLIB !!!!
*
* @access public
* @see
* @version 2001/09
* @author PHPLIB
* @param
* @return
*/
function metadata($table='')
{
// is there an alias in the table name, then we have something like this: 'user ua'
// cut of the alias and return the table name
if (strpos($table, ' ') !== false) {
$split = explode(' ', trim($table));
$table = $split[0];
}
 
$full = false;
if (empty($table)) {
$table = $this->table;
}
// to prevent multiple selects for the same metadata
if (isset($this->_metadata[$table])) {
return $this->_metadata[$table];
}
 
// FIXXXME use oci8 implementation of newer PEAR::DB-version
if ($this->db->phptype == 'oci8') {
$count = 0;
$id = 0;
$res = array();
 
//# This is a RIGHT OUTER JOIN: "(+)", if you want to see, what
//# this query results try the following:
//// $table = new Table; $this->db = new my_DB_Sql; // you have to make
//// // your own class
//// $table->show_results($this->db->query(see query vvvvvv))
////
$res = $this->db->getAll("SELECT T.column_name,T.table_name,T.data_type,".
"T.data_length,T.data_precision,T.data_scale,T.nullable,".
"T.char_col_decl_length,I.index_name".
" FROM ALL_TAB_COLUMNS T,ALL_IND_COLUMNS I".
" WHERE T.column_name=I.column_name (+)".
" AND T.table_name=I.table_name (+)".
" AND T.table_name=UPPER('$table') ORDER BY T.column_id");
 
if (DB::isError($res)) {
//$this->_errorSet($res->getMessage());
// i think we only need to log here, since this method is never used
// directly for the user's functionality, which means if it fails it
// is most probably an appl error
$this->_errorLog($res->getUserInfo());
return false;
}
foreach ($res as $key=>$val) {
$res[$key]['name'] = $val['COLUMN_NAME'];
}
} else {
if (!is_object($this->db)) {
return false;
}
$res = $this->db->tableinfo($table);
if (DB::isError($res)) {
$this->_errorSet($res->getUserInfo());
return false;
}
}
 
$ret = array();
foreach ($res as $key => $val) {
$ret[$val['name']] = $val;
}
$this->_metadata[$table] = $ret;
return $ret;
}
 
 
 
//
// methods for building the query
//
 
// }}}
// {{{ _buildFrom()
 
/**
* build the from string
*
* @access private
* @return string the string added after FROM
*/
function _buildFrom()
{
$from = $this->table;
$join = $this->getJoin();
 
if (!$join) { // no join set
return $from;
}
// handle the standard join thingy
if (isset($join['default']) && count($join['default'])) {
$from .= ','.implode(',',array_keys($join['default']));
}
 
// handle left/right joins
foreach (array('left', 'right') as $joinType) {
if (isset($join[$joinType]) && count($join[$joinType])) {
foreach($join[$joinType] as $table => $condition) {
// replace the _TABLENAME_COLUMNNAME by TABLENAME.COLUMNNAME
// since oracle doesnt work with the _TABLENAME_COLUMNNAME which i think is strange
// FIXXME i think this should become deprecated since the setWhere should not be used like this: '_table_column' but 'table.column'
$regExp = '/_('.$table.')_([^\s]+)/';
$where = preg_replace($regExp, '$1.$2', $condition);
 
// add the table name before any column that has no table prefix
// since this might cause "unambiguous column" errors
if ($meta = $this->metadata()) {
foreach ($meta as $aCol=>$x) {
// this covers the LIKE,IN stuff: 'name LIKE "%you%"' 'id IN (2,3,4,5)'
$condition = preg_replace('/\s'.$aCol.'\s/', " {$this->table}.$aCol ", $condition);
// replace also the column names which are behind a '='
// and do this also if the aCol is at the end of the where clause
// that's what the $ is for
$condition = preg_replace('/=\s*'.$aCol.'(\s|$)/', "={$this->table}.$aCol ", $condition);
// replace if colName is first and possibly also if at the beginning of the where-string
$condition = preg_replace('/(^\s*|\s+)'.$aCol.'\s*=/', "$1{$this->table}.$aCol=", $condition);
}
}
$from .= ' '.strtoupper($joinType).' JOIN '.$table.' ON '.$condition;
}
}
}
return $from;
}
 
// }}}
// {{{ getTableShortName()
 
/**
* this method gets the short name for a table
*
* get the short name for a table, this is needed to properly build the
* 'AS' parts in the select query
* @param string the real table name
* @return string the table's short name
*/
function getTableShortName($table)
{
$tableSpec = $this->getTableSpec(false);
if (isset($tableSpec[$table]['shortName']) && $tableSpec[$table]['shortName']) {
//print "$table ... ".$tableSpec[$table]['shortName'].'<br>';
return $tableSpec[$table]['shortName'];
}
 
$possibleTableShortName = preg_replace($this->_tableNameToShortNamePreg, '', $table);
//print "$table ... $possibleTableShortName<br>";
return $possibleTableShortName;
}
 
// }}}
// {{{ getTableSpec()
 
/**
* gets the tableSpec either indexed by the short name or the name
* returns the array for the tables given as parameter or if no
* parameter given for all tables that exist in the tableSpec
*
* @param array table names (not the short names!)
* @param boolean if true the table is returned indexed by the shortName
* otherwise indexed by the name
* @return array the tableSpec indexed
*/
function getTableSpec($shortNameIndexed=true, $tables=array())
{
$newSpec = array();
foreach ($this->tableSpec as $aSpec) {
if (sizeof($tables)==0 || in_array($aSpec['name'],$tables)) {
if ($shortNameIndexed) {
$newSpec[$aSpec['shortName']] = $aSpec;
} else {
$newSpec[$aSpec['name']] = $aSpec;
}
}
}
return $newSpec;
}
 
// }}}
// {{{ _buildSelect()
 
/**
* build the 'SELECT <what> FROM ... 'for a select
*
* @version 2002/07/11
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param string if given use this string
* @return string the what-clause
*/
function _buildSelect($what=null)
{
// what has preference, that means if what is set it is used
// this is only because the methods like 'get' pass an individually built value, which
// is supposed to be used, but usually it's generically build using the 'getSelect' values
if (empty($what) && $this->getSelect()) {
$what = $this->getSelect();
}
 
//
// replace all the '*' by the real column names, and take care of the dontSelect-columns!
//
$dontSelect = $this->getDontSelect();
$dontSelect = $dontSelect ? explode(',', $dontSelect) : array(); // make sure dontSelect is an array
 
// here we will replace all the '*' and 'table.*' by all the columns that this table
// contains. we do this so we can easily apply the 'dontSelect' values.
// and so we can also handle queries like: 'SELECT *,count() FROM ' and 'SELECT table.*,x FROM ' too
if (strpos($what, '*') !== false) {
// subpattern 1 get all the table names, that are written like this: 'table.*' including '*'
// for '*' the tablename will be ''
preg_match_all('/([^,]*)(\.)?\*\s*(,|$)/U', $what, $res);
//print "$what ... ";print_r($res);print "<br>";
$selectAllFromTables = array_unique($res[1]); // make the table names unique, so we do it all just once for each table
$tables = array();
if (in_array('', $selectAllFromTables)) { // was there a '*' ?
// get all the tables that we need to process, depending on if joined or not
$tables = $this->getJoin() ?
array_merge($this->getJoin('tables'), array($this->table)) : // get the joined tables and this->table
array($this->table); // create an array with only this->table
} else {
$tables = $selectAllFromTables;
}
 
$cols = array();
foreach ($tables as $aTable) { // go thru all the tables and get all columns for each, and handle 'dontSelect'
if ($meta = $this->metadata($aTable)) {
foreach ($meta as $colName => $x) {
// handle the dontSelect's
if (in_array($colName, $dontSelect) || in_array("$aTable.$colName", $dontSelect)) {
continue;
}
 
// build the AS clauses
// put " around them to enable use of reserved words, i.e. SELECT table.option as option FROM...
// and 'option' actually is a reserved word, at least in mysql
// put double quotes around them, since pgsql doesnt work with single quotes
// but don't do this for ibase because it doesn't work!
if ($aTable == $this->table) {
if ($this->db->phptype == 'ibase') {
$cols[$aTable][] = $this->table. '.' .$colName . ' AS '. $colName;
} else {
$cols[$aTable][] = $this->table. '.' .$colName . ' AS "'. $colName .'"';
}
} else {
$cols[$aTable][] = "$aTable.$colName AS \"_".$this->getTableShortName($aTable)."_$colName\"";
}
}
}
}
 
// put the extracted select back in the $what
// that means replace 'table.*' by the i.e. 'table.id AS _table_id'
// or if it is the table of this class replace 'table.id AS id'
if (in_array('', $selectAllFromTables)) {
$allCols = array();
foreach ($cols as $aTable) {
$allCols[] = implode(',', $aTable);
}
$what = preg_replace('/(^|,)\*($|,)/', '$1'.implode(',',$allCols).'$2', $what);
// remove all the 'table.*' since we have selected all anyway (because there was a '*' in the select)
$what = preg_replace('/[^,]*(\.)?\*\s*(,|$)/U', '', $what);
} else {
foreach ($cols as $tableName => $aTable) {
if (is_array($aTable) && sizeof($aTable)) {
// replace all the 'table.*' by their select of each column
$what = preg_replace('/(^|,)\s*'.$tableName.'\.\*\s*($|,)/', '$1'.implode(',',$aTable).'$2', $what);
}
}
}
}
 
if ($this->getJoin()) {
// replace all 'column' by '$this->table.column' to prevent ambigious errors
$metadata = $this->metadata();
if (is_array($metadata)) {
foreach ($metadata as $aCol => $x) {
// handle ',id as xid,MAX(id),id' etc.
// FIXXME do this better!!!
$what = preg_replace( "/(^|,|\()(\s*)$aCol(\)|\s|,|as|$)/i",
// $2 is actually just to keep the spaces, is not really
// necessary, but this way the test works independent of this functionality here
"$1$2{$this->table}.$aCol$3",
$what);
}
}
// replace all 'joinedTable.columnName' by '_joinedTable_columnName'
// this actually only has an effect if there was no 'table.*' for 'table'
// if that was there, then it has already been done before
foreach ($this->getJoin('tables') as $aTable) {
if ($meta = $this->metadata($aTable)) {
foreach ($meta as $aCol=>$x) {
// dont put the 'AS' behind it if there is already one
if (preg_match("/$aTable.$aCol\s*as/i",$what)) {
continue;
}
// this covers a ' table.colName ' surrounded by spaces, and replaces it by ' table.colName AS _table_colName'
$what = preg_replace('/\s'.$aTable.'.'.$aCol.'\s/', " $aTable.$aCol AS _".$this->getTableShortName($aTable)."_$aCol ", $what);
// replace also the column names which are behind a ','
// and do this also if the aCol is at the end that's what the $ is for
$what = preg_replace('/,\s*'.$aTable.'.'.$aCol.'(,|\s|$)/', ",$aTable.$aCol AS _".$this->getTableShortName($aTable)."_$aCol$1", $what);
// replace if colName is first and possibly also if at the beginning of the where-string
$what = preg_replace('/(^\s*|\s+)'.$aTable.'.'.$aCol.'\s*,/', "$1$aTable.$aCol AS _".$this->getTableShortName($aTable)."_$aCol,", $what);
}
}
}
}
return $what;
}
 
// }}}
// {{{ _buildWhere()
 
/**
* Build WHERE clause
*
* @param string $where WHERE clause
* @return string $where WHERE clause after processing
* @access private
*/
function _buildWhere($where='')
{
$where = trim($where);
$originalWhere = $this->getWhere();
if ($originalWhere) {
if (!empty($where)) {
$where = $originalWhere.' AND '.$where;
} else {
$where = $originalWhere;
}
}
$where = trim($where);
 
if ($join = $this->getJoin()) { // is join set?
// only those where conditions in the default-join have to be added here
// left-join conditions are added behind 'ON', the '_buildJoin()' does that
if (isset($join['default']) && count($join['default'])) {
// we have to add this join-where clause here
// since at least in mysql a query like: select * from tableX JOIN tableY ON ...
// doesnt work, may be that's even SQL-standard...
if (!empty($where)) {
$where = implode(' AND ', $join['default']).' AND '.$where;
} else {
$where = implode(' AND ', $join['default']);
}
}
// replace the _TABLENAME_COLUMNNAME by TABLENAME.COLUMNNAME
// since oracle doesnt work with the _TABLENAME_COLUMNNAME which i think is strange
// FIXXME i think this should become deprecated since the setWhere should not be used like this: '_table_column' but 'table.column'
$regExp = '/_('.implode('|', $this->getJoin('tables')).')_([^\s]+)/';
$where = preg_replace($regExp, '$1.$2', $where);
// add the table name before any column that has no table prefix
// since this might cause "unambigious column" errors
if ($meta = $this->metadata()) {
foreach ($meta as $aCol => $x) {
// this covers the LIKE,IN stuff: 'name LIKE "%you%"' 'id IN (2,3,4,5)'
$where = preg_replace('/\s'.$aCol.'\s/', " {$this->table}.$aCol ", $where);
// replace also the column names which are behind a '='
// and do this also if the aCol is at the end of the where clause
// that's what the $ is for
$where = preg_replace('/([=<>])\s*'.$aCol.'(\s|$)/', "$1{$this->table}.$aCol ", $where);
// replace if colName is first and possibly also if at the beginning of the where-string
$where = preg_replace('/(^\s*|\s+)'.$aCol.'\s*([=<>])/', "$1{$this->table}.$aCol$2", $where);
}
}
}
return $where;
}
 
// }}}
// {{{ _buildOrder()
 
/**
*
*
* @version 2002/07/11
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param
* @return
*/
function _buildOrder()
{
$order = $this->getOrder();
// replace 'column' by '$this->table.column' if the column is defined for $this->table
if ($meta = $this->metadata()) {
foreach ($meta as $aCol=>$x) {
$order = preg_replace('/(^\s*|\s+|,)'.$aCol.'\s*(,)?/U', "$1{$this->table}.$aCol$2", $order);
}
}
return $order;
}
 
// }}}
// {{{ _buildGroup()
 
/**
* Build the group-clause, replace 'column' by 'table.column'.
*
* @access public
* @param void
* @return string the rendered group clause
*/
function _buildGroup()
{
$group = $this->getGroup();
// replace 'column' by '$this->table.column' if the column is defined for $this->table
if ($meta = $this->metadata()) {
foreach ($meta as $aCol => $x) {
$group = preg_replace('/(^\s*|\s+|,)'.$aCol.'\s*(,)?/U', "$1{$this->table}.$aCol$2", $group);
}
}
return $group;
}
 
// }}}
// {{{ _buildHaving()
 
/**
*
* @version 2003/06/05
* @access public
* @author Johannes Schaefer <johnschaefer@gmx.de>
* @param
* @return string the having clause
*/
function _buildHaving()
{
$having = $this->getHaving();
// replace 'column' by '$this->table.column' if the column is defined for $this->table
if ($meta = $this->metadata()) {
foreach ($meta as $aCol => $x) {
$having = preg_replace('/(^\s*|\s+|,)'.$aCol.'\s*(,)?/U',"$1{$this->table}.$aCol$2",$having);
}
}
return $having;
}
 
// }}}
// {{{ _buildHaving()
 
/**
*
* @version 2002/07/11
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param array this array contains the elements of the query,
* indexed by their key, which are: 'select','from','where', etc.
* @param boolean whether this method is called via getCount() or not.
* @return
*/
function _buildSelectQuery($query=array(), $isCalledViaGetCount = false)
{
/*FIXXXME finish this
$cacheKey = md5(serialize(????));
if (isset($this->_queryCache[$cacheKey])) {
$this->_errorLog('using cached query',__LINE__);
return $this->_queryCache[$cacheKey];
}
*/
$where = isset($query['where']) ? $query['where'] : $this->_buildWhere();
if ($where) {
$where = 'WHERE '.$where;
}
$order = isset($query['order']) ? $query['order'] : $this->_buildOrder();
if ($order) {
$order = 'ORDER BY '.$order;
}
$group = isset($query['group']) ? $query['group'] : $this->_buildGroup();
if ($group) {
$group = 'GROUP BY '.$group;
}
$having = isset($query['having']) ? $query['having'] : $this->_buildHaving();
if ($having) {
$having = 'HAVING '.$having;
}
$queryString = sprintf( 'SELECT %s FROM %s %s %s %s %s',
isset($query['select']) ? $query['select'] : $this->_buildSelect(),
isset($query['from']) ? $query['from'] : $this->_buildFrom(),
$where,
$group,
$having,
$order
);
// $query['limit'] has preference!
$limit = isset($query['limit']) ? $query['limit'] : $this->_limit;
if (!$isCalledViaGetCount && @$limit[1]) { // is there a count set?
$queryString=$this->db->modifyLimitQuery($queryString,$limit[0],$limit[1]);
if (DB::isError($queryString)) {
$this->_errorSet('DB_QueryTool::db::modifyLimitQuery failed '.$queryString->getMessage());
$this->_errorLog($queryString->getUserInfo());
return false;
}
}
// $this->_queryCache[$cacheKey] = $queryString;
return $queryString;
}
 
// }}}
// {{{ _buildUpdateQuery()
 
/**
* this simply builds an update query.
*
* @param array the parameter array might contain the following indexes
* 'where' the where clause to be added, i.e.
* UPDATE table SET x=1 WHERE y=0
* here the 'where' part simply would be 'y=0'
* 'set' the actual data to be updated
* in the example above, that would be 'x=1'
* @return string the resulting query
*/
function _buildUpdateQuery($query=array())
{
$where = isset($query['where']) ? $query['where'] : $this->_buildWhere();
if ($where) {
$where = 'WHERE '.$where;
}
 
$updateString = sprintf('UPDATE %s SET %s %s',
$this->table,
$query['set'],
$where
);
return $updateString;
}
 
// }}}
// {{{ execute()
 
/**
*
* @version 2002/07/11
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param
* @param string query method
* @return boolean
*/
function execute($query=null, $method='getAll')
{
$this->writeLog();
if (is_null($query)) {
$query = $this->_buildSelectQuery();
}
$this->writeLog('query built: '.$query);
 
// FIXXME on ORACLE this doesnt work, since we return joined columns as _TABLE_COLNAME and the _ in front
// doesnt work on oracle, add a letter before it!!!
$this->_lastQuery = $query;
 
$this->debug($query);
$this->writeLog('start query');
if (DB::isError($res = $this->db->$method($query))) {
$this->writeLog('end query (failed)');
if ($this->getOption('verbose')) {
$this->_errorSet($res->getMessage());
} else {
$this->_errorLog($res->getMessage());
}
$this->_errorLog($res->getUserInfo(), __LINE__);
return false;
} else {
$this->writeLog('end query');
}
$res = $this->_makeIndexed($res);
return $res;
}
 
// }}}
// {{{ writeLog()
 
/**
* Write events to the logfile.
*
* It does some additional work, like time measuring etc. to
* see some additional info
*
*/
function writeLog($text='START')
{
//its still really a quicky.... 'refactor' (nice word) that
if (!isset($this->options['logfile'])) {
return;
}
 
include_once 'Log.php';
if (!class_exists('Log')) {
return;
}
if (!$this->_logObject) {
$this->_logObject =& Log::factory('file', $this->options['logfile']);
}
 
if ($text==='start query' || $text==='end query') {
$bytesSent = $this->db->getAll("SHOW STATUS like 'Bytes_sent'");
$bytesSent = $bytesSent[0]['Value'];
}
if ($text==='START') {
$startTime = split(' ', microtime());
$this->_logData['startTime'] = $startTime[1] + $startTime[0];
}
if ($text==='start query') {
$this->_logData['startBytesSent'] = $bytesSent;
$startTime = split(' ', microtime());
$this->_logData['startQueryTime'] = $startTime[1] + $startTime[0];
return;
}
if ($text==='end query') {
$text .= ' result size: '.((int)$bytesSent-(int)$this->_logData['startBytesSent']).' bytes';
$endTime = split(' ', microtime());
$endTime = $endTime[1] + $endTime[0];
$text .= ', took: '.(($endTime - $this->_logData['startQueryTime'])).' seconds';
}
if (strpos($text, 'query built')===0) {
$endTime = split(' ', microtime());
$endTime = $endTime[1] + $endTime[0];
$this->writeLog('query building took: '.(($endTime - $this->_logData['startTime'])).' seconds');
}
$this->_logObject->log($text);
 
if (strpos($text, 'end query')===0) {
$endTime = split(' ', microtime());
$endTime = $endTime[1] + $endTime[0];
$text = 'time over all: '.(($endTime - $this->_logData['startTime'])).' seconds';
$this->_logObject->log($text);
}
}
 
// }}}
// {{{ returnResult()
 
/**
* Return the chosen result type
*
* @version 2004/04/28
* @access public
* @param object reference
* @return mixed
*/
function returnResult(&$result)
{
if ($this->_resultType == 'none') {
return $result;
}
if ($result === false) {
return false;
}
//what about allowing other (custom) result types?
switch (strtolower($this->_resultType)) {
case 'object': return new DB_QueryTool_Result_Object($result);
case 'array':
default: return new DB_QueryTool_Result($result);
}
}
 
// }}}
// {{{ _makeIndexed()
 
/**
*
* @version 2002/07/11
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param mixed
* @return mixed
*/
function &_makeIndexed(&$data)
{
// we can only return an indexed result if the result has a number of columns
if (is_array($data) && sizeof($data) && $key = $this->getIndex()) {
// build the string to evaluate which might be made up out of multiple indexes of a result-row
$evalString = '$val[\''.implode('\'].\',\'.$val[\'',explode(',',$key)).'\']'; //"
 
$indexedData = array();
//FIXXME actually we also need to check ONCE if $val is an array, so to say if $data is 2-dimensional
foreach ($data as $val) {
eval("\$keyValue = $evalString;"); // get the actual real (string-)key (string if multiple cols are used as index)
$indexedData[$keyValue] = $val;
}
unset($data);
return $indexedData;
}
 
return $data;
}
 
// }}}
// {{{ setIndex()
 
/**
* format the result to be indexed by $key
* NOTE: be careful, when using this you should be aware, that if you
* use an index which's value appears multiple times you may loose data
* since a key cant exist multiple times!!
* the result for a result to be indexed by a key(=columnName)
* (i.e. 'relationtoMe') which's values are 'brother' and 'sister'
* or alike normally returns this:
* $res['brother'] = array('name'=>'xxx')
* $res['sister'] = array('name'=>'xxx')
* but if the column 'relationtoMe' contains multiple entries for 'brother'
* then the returned dataset will only contain one brother, since the
* value from the column 'relationtoMe' is used
* and which 'brother' you get depends on a lot of things, like the sortorder,
* how the db saves the data, and whatever else
*
* you can also set indexes which depend on 2 columns, simply pass the parameters like
* 'table1.id,table2.id' it will be used as a string for indexing the result
* and the index will be built using the 2 values given, so a possible
* index might be '1,2' or '2108,29389' this way you can access data which
* have 2 primary keys. Be sure to remember that the index is a string!
*
* @version 2002/07/11
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param
* @return
*/
function setIndex($key=null)
{
if ($this->getJoin()) { // is join set?
// replace TABLENAME.COLUMNNAME by _TABLENAME_COLUMNNAME
// since this is only the result-keys can be used for indexing :-)
$regExp = '/('.implode('|', $this->getJoin('tables')).')\.([^\s]+)/';
$key = preg_replace($regExp, '_$1_$2', $key);
 
// remove the table name if it is in front of '<$this->table>.columnname'
// since the key doesnt contain it neither
if ($meta = $this->metadata()) {
foreach ($meta as $aCol => $x) {
$key = preg_replace('/'.$this->table.'\.'.$aCol.'/', $aCol, $key);
}
}
}
$this->_index = $key;
}
 
// }}}
// {{{ getIndex()
 
/**
*
* @version 2002/07/11
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param
* @return
*/
function getIndex()
{
return $this->_index;
}
 
// }}}
// {{{ useResult()
 
/**
* Choose the type of the returned result
*
* @version 2004/04/28
* @access public
* @param string $type ['array' | 'object' | 'none']
* For BC reasons, $type=true is equal to 'array',
* $type=false is equal to 'none'
*/
function useResult($type='array')
{
if ($type === true) {
$type = 'array';
} elseif ($type === false) {
$type = 'none';
}
switch (strtolower($type)) {
case 'array':
$this->_resultType = 'array';
require_once 'DB/QueryTool/Result.php';
break;
case 'object':
$this->_resultType = 'object';
require_once 'DB/QueryTool/Result/Object.php';
break;
default:
$this->_resultType = 'none';
}
}
 
// }}}
// {{{ setErrorCallback()
 
/**
* set both callbacks
* @param string
*/
function setErrorCallback($param='')
{
$this->setErrorLogCallback($param);
$this->setErrorSetCallback($param);
}
 
// }}}
// {{{ setErrorLogCallback()
 
/**
* @param string
*/
function setErrorLogCallback($param='')
{
$errorLogCallback = &PEAR::getStaticProperty('DB_QueryTool','_errorLogCallback');
$errorLogCallback = $param;
}
 
// }}}
// {{{ setErrorSetCallback()
 
/**
* @param string
*/
function setErrorSetCallback($param='')
{
$errorSetCallback = &PEAR::getStaticProperty('DB_QueryTool','_errorSetCallback');
$errorSetCallback = $param;
}
 
// }}}
// {{{ _errorLog()
 
/**
* sets error log and adds additional info
*
* @version 2002/04/16
* @access public
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the actual message, first word should always be the method name,
* to build the message like this: className::methodname
* @param integer the line number
*/
function _errorLog($msg, $line='unknown')
{
$this->_errorHandler('log', $msg, $line);
/*
if ($this->getOption('verbose') == true)
{
$this->_errorLog(get_class($this)."::$msg ($line)");
return;
}
 
if ($this->_errorLogCallback)
call_user_func($this->_errorLogCallback, $msg);
*/
}
 
// }}}
// {{{ _errorSet()
 
/**
* @param string
* @param string
*/
function _errorSet($msg, $line='unknown')
{
$this->_errorHandler('set', $msg, $line);
}
 
// }}}
// {{{ _errorHandler()
 
/**
* @param
* @param string
* @param string
*/
function _errorHandler($logOrSet, $msg, $line='unknown')
{
/* what did i do this for?
if ($this->getOption('verbose') == true)
{
$this->_errorHandler($logOrSet, get_class($this)."::$msg ($line)");
return;
}
*/
 
$msg = get_class($this)."::$msg ($line)";
 
$logOrSet = ucfirst($logOrSet);
$callback = &PEAR::getStaticProperty('DB_QueryTool','_error'.$logOrSet.'Callback');
//var_dump($callback);
//if ($callback)
// call_user_func($callback, $msg);
// else
// ?????
 
}
 
// }}}
}
?>
/trunk/api/pear/DB/QueryTool/Result/Object.php
New file
0,0 → 1,89
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
 
/**
* Contains the DB_QueryTool_Result_Row and DB_QueryTool_Result_Object classes
*
* 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_QueryTool
* @author Roman Dostovalov, Com-tec-so S.A.<roman.dostovalov@ctco.lv>
* @copyright 2004-2005 Roman Dostovalov
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: Object.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
* @link http://pear.php.net/package/DB_QueryTool
*/
 
/**
* Include parent class
*/
require_once 'DB/QueryTool/Result.php';
 
/**
* DB_QueryTool_Result_Row class
*
* @category Database
* @package DB_QueryTool
* @author Roman Dostovalov, Com-tec-so S.A.<roman.dostovalov@ctco.lv>
* @copyright 2004-2005 Roman Dostovalov
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @link http://pear.php.net/package/DB_QueryTool
*/
class DB_QueryTool_Result_Row
{
/**
* create object properties from the array
* @param array
*/
function DB_QueryTool_Result_Row($arr)
{
foreach ($arr as $key => $value) {
$this->$key = $value;
}
}
}
 
// -----------------------------------------------------------------------------
 
/**
* DB_QueryTool_Result_Object class
*
* @category Database
* @package DB_QueryTool
* @author Roman Dostovalov, Com-tec-so S.A.<roman.dostovalov@ctco.lv>
* @copyright 2004-2005 Roman Dostovalov
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @link http://pear.php.net/package/DB_QueryTool
*/
class DB_QueryTool_Result_Object extends DB_QueryTool_Result
{
// {{{ fetchRow
 
/**
* This function emulates PEAR::DB fetchRow() method
* With this function DB_QueryTool can transparently replace PEAR::DB
*
* @todo implement fetchmode support?
* @access public
* @return void
*/
function fetchRow()
{
$arr = $this->getNext();
if (!PEAR::isError($arr)) {
$row = new DB_QueryTool_Result_Row($arr);
return $row;
}
return false;
}
 
// }}}
}
?>
/trunk/api/pear/DB/QueryTool/Result.php
New file
0,0 → 1,261
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
 
/**
* Contains the DB_QueryTool_Result class
*
* 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_QueryTool
* @author Wolfram Kriesing <wk@visionp.de>
* @author Paolo Panto <wk@visionp.de>
* @author Lorenzo Alberton <l dot alberton at quipo dot it>
* @copyright 2003-2005 Wolfram Kriesing, Paolo Panto, Lorenzo Alberton
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: Result.php,v 1.1 2006-12-14 15:04:29 jp_milcent Exp $
* @link http://pear.php.net/package/DB_QueryTool
*/
 
/**
* DB_QueryTool_Result class
*
* This result actually contains the 'data' itself, the number of rows
* returned and some additional info
* using ZE2 you can also get retrieve data from the result doing the following:
* <DB_QueryTool_Common-instance>->getAll()->getCount()
* or
* <DB_QueryTool_Common-instance>->getAll()->getData()
*
* @category Database
* @package DB_QueryTool
* @author Wolfram Kriesing <wk@visionp.de>
* @author Paolo Panto <wk@visionp.de>
* @author Lorenzo Alberton <l dot alberton at quipo dot it>
* @copyright 2003-2005 Wolfram Kriesing, Paolo Panto, Lorenzo Alberton
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @link http://pear.php.net/package/DB_QueryTool
*/
class DB_QueryTool_Result
{
// {{{ class vars
 
/**
* @var array
*/
var $_data = array();
 
/**
* @var array
*/
var $_dataKeys = array();
 
/**
* @var integer
*/
var $_count = 0;
 
/**
* the counter for the methods getFirst, getNext
* @var array
*/
var $_counter = null;
 
// }}}
// {{{ DB_QueryTool_Result()
 
/**
* create a new instance of result with the data returned by the query
*
* @version 2002/07/11
* @access public
* @author Wolfram Kriesing <wolfram@kriesing.de>
* @param array the data returned by the result
*/
function DB_QueryTool_Result($data)
{
if (!count($data)) {
$this->_count = 0;
} else {
list($firstElement) = $data;
if (is_array($firstElement)) { // is the array a collection of rows?
$this->_count = sizeof($data);
} else {
if (sizeof($data) > 0) {
$this->_count = 1;
} else {
$this->_count = 0;
}
}
}
$this->_data = $data;
}
 
// }}}
// {{{ numRows
 
/**
* return the number of rows returned. This is an alias for getCount().
*
* @access public
* @return integer
*/
function numRows()
{
return $this->_count;
}
 
// }}}
// {{{ getCount()
 
/**
* return the number of rows returned
*
* @version 2002/07/11
* @access public
* @author Wolfram Kriesing <wolfram@kriesing.de>
* @return integer the number of rows returned
*/
function getCount()
{
return $this->_count;
}
 
// }}}
// {{{ getData()
 
/**
* get all the data returned
*
* @version 2002/07/11
* @access public
* @author Wolfram Kriesing <wolfram@kriesing.de>
* @param string $key
* @return mixed array or PEAR_Error
*/
function getData($key=null)
{
if (is_null($key)) {
return $this->_data;
}
if ($this->_data[$key]) {
return $this->_data[$key];
}
return new PEAR_Error("there is no element with the key '$key'!");
}
 
// }}}
// {{{ getFirst()
 
/**
* get the first result set
* we are not using next, current, and reset, since those ignore keys
* which are empty or 0
*
* @version 2002/07/11
* @access public
* @author Wolfram Kriesing <wolfram@kriesing.de>
* @return mixed
*/
function getFirst()
{
if ($this->getCount() > 0) {
$this->_dataKeys = array_keys($this->_data);
$this->_counter = 0;
return $this->_data[$this->_dataKeys[$this->_counter]];
}
return new PEAR_Error('There are no elements!');
}
 
// }}}
// {{{ getNext()
 
/**
* Get next result set. If getFirst() has never been called before,
* it calls that method.
* @return mixed
* @access public
*/
function getNext()
{
if (!$this->initDone()) {
return $this->getFirst();
}
if ($this->hasMore()) {
$this->_counter++;
return $this->_data[$this->_dataKeys[$this->_counter]];
}
return new PEAR_Error('there are no more elements!');
}
 
// }}}
// {{{ hasMore()
 
/**
* check if there are other rows
*
* @return boolean
* @access public
*/
function hasMore()
{
if ($this->_counter+1 < $this->getCount()) {
return true;
}
return false;
}
 
// }}}
// {{{ fetchRow
 
/**
* This function emulates PEAR::DB fetchRow() method.
* With this method, DB_QueryTool can transparently replace PEAR_DB
*
* @todo implement fetchmode support?
* @access public
* @return void
*/
function fetchRow()
{
if ($this->hasMore()) {
$arr = $this->getNext();
if (!PEAR::isError($arr)) {
return $arr;
}
}
return false;
}
 
// }}}
// {{{ initDone
 
/**
* Helper method. Check if $this->_dataKeys has been initialized
*
* @return boolean
* @access private
*/
function initDone()
{
return (
isset($this->_dataKeys) &&
is_array($this->_dataKeys) &&
count($this->_dataKeys)
);
}
 
// }}}
 
#TODO
#function getPrevious()
#function getLast()
 
}
?>
/trunk/api/pear/DB/mysqli.php
19,7 → 19,7
* @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: mysqli.php,v 1.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: mysqli.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
43,7 → 43,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
* @since Class functional since Release 1.6.3
*/
/trunk/api/pear/DB/dbase.php
20,7 → 20,7
* @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.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: dbase.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
41,7 → 41,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
*/
class DB_dbase extends DB_common
/trunk/api/pear/DB/ldap.php
New file
0,0 → 1,994
<?php
//
// Pear DB LDAP - Database independent query interface definition
// for PHP's LDAP extension.
//
// Copyright (c) 2002-2003 Ludovico Magnocavallo <ludo@sumatrasolutions.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Contributors
// - Piotr Roszatycki <dexter@debian.org>
// DB_ldap::base() method, support for LDAP sequences, various fixes
// - Aaron Spencer Hawley <aaron dot hawley at uvm dot edu>
// fix to use port number if present in DB_ldap->connect()
//
// $Id: ldap.php,v 1.1 2006-12-14 15:04:28 jp_milcent Exp $
//
 
require_once 'DB.php';
require_once 'DB/common.php';
 
define("DB_ERROR_BIND_FAILED", -26);
define("DB_ERROR_UNKNOWN_LDAP_ACTION", -27);
 
/**
* LDAP result class
*
* LDAP_result extends DB_result to provide specific LDAP
* result methods.
*
* @version 1.0
* @author Ludovico Magnocavallo <ludo@sumatrasolutions.com>
* @package DB
*/
 
class LDAP_result extends DB_result
{
 
// {{{ properties
 
/**
* data returned from ldap_entries()
* @access private
*/
var $_entries = null;
/**
* result rows as hash of records
* @access private
*/
var $_recordset = null;
/**
* current record as hash
* @access private
*/
var $_record = null;
 
// }}}
// {{{ constructor
 
/**
* class constructor, calls DB_result constructor
* @param ref $dbh reference to the db instance
* @param resource $result ldap command result
*/
function LDAP_result(&$dbh, $result)
{
$this->DB_result($dbh, $result);
}
 
/**
* fetch rows of data into $this->_recordset
*
* called once as soon as something needs to be returned
* @access private
* @param resource $result ldap command result
* @return boolean true
*/
function getRows() {
if ($this->_recordset === null) {
// begin processing result into recordset
$this->_entries = ldap_get_entries($this->dbh->connection, $this->result);
$this->row_counter = $this->_entries['count'];
$i = 1;
$rs_template = array();
if (count($this->dbh->attributes) > 0) {
reset($this->dbh->attributes);
while (list($a_index, $a_name) = each($this->dbh->attributes)) $rs_template[$a_name] = '';
}
while (list($entry_idx, $entry) = each($this->_entries)) {
// begin first loop, iterate through entries
if (!empty($this->dbh->limit_from) && ($i < $this->dbh->limit_from)) continue;
if (!empty($this->dbh->limit_count) && ($i > $this->dbh->limit_count)) break;
$rs = $rs_template;
if (!is_array($entry)) continue;
while (list($attr, $attr_values) = each($entry)) {
// begin second loop, iterate through attributes
if (is_int($attr) || $attr == 'count') continue;
if (is_string($attr_values)) $rs[$attr] = $attr_values;
else {
$value = '';
while (list($value_idx, $attr_value) = each($attr_values)) {
// begin third loop, iterate through attribute values
if (!is_int($value_idx)) continue;
if (empty($value)) $value = $attr_value;
else {
if (is_array($value)) $value[] = $attr_value;
else $value = array($value, $attr_value);
}
// else $value .= "\n$attr_value";
// end third loop
}
$rs[$attr] = $value;
}
// end second loop
}
reset($rs);
$this->_recordset[$entry_idx] = $rs;
$i++;
// end first loop
}
$this->_entries = null;
if (!is_array($this->_recordset))
$this->_recordset = array();
if (!empty($this->dbh->sorting)) {
$sorting_method = (!empty($this->dbh->sorting_method) ? $this->dbh->sorting_method : 'cmp');
uksort($this->_recordset, array(&$this, $sorting_method));
}
reset($this->_recordset);
// end processing result into recordset
}
return DB_OK;
}
 
 
/**
* Fetch and return a row of data (it uses driver->fetchInto for that)
* @param int $fetchmode format of fetched row
* @param int $rownum the row number to fetch
*
* @return array a row of data, NULL on no more rows or PEAR_Error on error
*
* @access public
*/
function &fetchRow($fetchmode = DB_FETCHMODE_DEFAULT, $rownum=null)
{
$this->getRows();
if (count($this->_recordset) == 0) return null;
if ($this->_record !== null) $this->_record = next($this->_recordset);
else $this->_record = current($this->_recordset);
$row = $this->_record;
return $row;
}
 
 
/**
* Fetch a row of data into an existing variable.
*
* @param mixed $arr reference to data containing the row
* @param integer $fetchmode format of fetched row
* @param integer $rownum the row number to fetch
*
* @return mixed DB_OK on success, NULL on no more rows or
* a DB_Error object on error
*
* @access public
*/
 
function fetchInto(&$ar, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null)
{
$this->getRows();
if ($this->_record !== null) $this->_record = next($this->_recordset);
else $this->_record = current($this->_recordset);
$ar = $this->_record;
if (!$ar) {
return null;
}
return DB_OK;
}
 
/**
* return all records
*
* returns a hash of all records, basically returning
* a copy of $this->_recordset
* @param integer $fetchmode format of fetched row
* @param integer $rownum the row number to fetch (not used, here for interface compatibility)
*
* @return mixed DB_OK on success, NULL on no more rows or
* a DB_Error object on error
*
* @access public
*/
function fetchAll($fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null)
{
$this->getRows();
return($this->_recordset);
}
 
/**
* Get the the number of columns in a result set.
*
* @return int the number of columns, or a DB error
*
* @access public
*/
function numCols($result)
{
$this->getRows();
return(count(array_keys($this->_record)));
}
 
function cmp($a, $b)
{
return(strcmp(strtolower($this->_recordset[$a][$this->dbh->sorting]), strtolower($this->_recordset[$b][$this->dbh->sorting])));
}
 
/**
* Get the number of rows in a result set.
*
* @return int the number of rows, or a DB error
*
* @access public
*/
function numRows()
{
$this->getRows();
return $this->row_counter;
}
 
/**
* Get the next result if a batch of queries was executed.
*
* @return bool true if a new result is available or false if not.
*
* @access public
*/
function nextResult()
{
return $this->dbh->nextResult($this->result);
}
 
/**
* Frees the resources allocated for this result set.
* @return int error code
*
* @access public
*/
function free()
{
$this->_recordset = null;
$this->_record = null;
ldap_free_result($this->result);
$this->result = null;
return true;
}
 
/**
* @deprecated
*/
function tableInfo($mode = null)
{
return $this->dbh->tableInfo($this->result, $mode);
}
 
/**
* returns the actual rows number
* @return integer
*/
function getRowCounter()
{
$this->getRows();
return $this->row_counter;
}
}
 
/**
* LDAP DB interface class
*
* LDAP extends DB_common to provide DB compliant
* access to LDAP servers
*
* @version 1.0
* @author Ludovico Magnocavallo <ludo@sumatrasolutions.com>
* @package DB
*/
 
class DB_ldap extends DB_common
{
// {{{ properties
 
/**
* LDAP connection
* @access private
*/
var $connection;
/**
* base dn
* @access private
*/
var $base = '';
/**
* default base dn
* @access private
*/
var $d_base = '';
/**
* query base dn
* @access private
*/
var $q_base = '';
/**
* array of LDAP actions that only manipulate data
* returning a true/false value
* @access private
*/
var $manip = array('add', 'compare', 'delete', 'modify', 'mod_add', 'mod_del', 'mod_replace', 'rename');
/**
* store the default real LDAP action to perform
* @access private
*/
var $action = 'search';
/**
* store the real LDAP action to perform
* (ie PHP ldap function to call) for a query
* @access private
*/
var $q_action = '';
/**
* store optional parameters passed
* to the real LDAP action
* @access private
*/
var $q_params = array();
 
// }}}
 
/**
* Constructor, calls DB_common constructor
*
* @see DB_common::DB_common()
*/
function DB_ldap()
{
$this->DB_common();
$this->phptype = 'ldap';
$this->dbsyntax = 'ldap';
$this->features = array(
'prepare' => false,
'pconnect' => false,
'transactions' => false,
'limit' => false
);
$this->errorcode_map = array(
0x10 => DB_ERROR_NOSUCHFIELD, // LDAP_NO_SUCH_ATTRIBUTE
0x11 => DB_ERROR_INVALID, // LDAP_UNDEFINED_TYPE
0x12 => DB_ERROR_INVALID, // LDAP_INAPPROPRIATE_MATCHING
0x13 => DB_ERROR_INVALID, // LDAP_CONSTRAINT_VIOLATION
0x14 => DB_ERROR_ALREADY_EXISTS, // LDAP_TYPE_OR_VALUE_EXISTS
0x15 => DB_ERROR_INVALID, // LDAP_INVALID_SYNTAX
0x20 => DB_ERROR_NOT_FOUND, // LDAP_NO_SUCH_OBJECT
0x21 => DB_ERROR_NOT_FOUND, // LDAP_ALIAS_PROBLEM
0x22 => DB_ERROR_INVALID, // LDAP_INVALID_DN_SYNTAX
0x23 => DB_ERROR_INVALID, // LDAP_IS_LEAF
0x24 => DB_ERROR_INVALID, // LDAP_ALIAS_DEREF_PROBLEM
0x30 => DB_ERROR_ACCESS_VIOLATION, // LDAP_INAPPROPRIATE_AUTH
0x31 => DB_ERROR_ACCESS_VIOLATION, // LDAP_INVALID_CREDENTIALS
0x32 => DB_ERROR_ACCESS_VIOLATION, // LDAP_INSUFFICIENT_ACCESS
0x40 => DB_ERROR_MISMATCH, // LDAP_NAMING_VIOLATION
0x41 => DB_ERROR_MISMATCH, // LDAP_OBJECT_CLASS_VIOLATION
0x44 => DB_ERROR_ALREADY_EXISTS, // LDAP_ALREADY_EXISTS
0x51 => DB_ERROR_CONNECT_FAILED, // LDAP_SERVER_DOWN
0x57 => DB_ERROR_SYNTAX // LDAP_FILTER_ERROR
);
}
 
/**
* Connect and bind to LDAP server with either anonymous or authenticated bind depending on dsn info
*
* @param array $dsninfo dsn info as passed by DB::connect()
* @param boolean $persistent kept for interface compatibility
* @return DB_OK if successfully connected. A DB error code is returned on failure.
*/
function connect($dsninfo, $persistent = false)
{
if (!PEAR::loadExtension('ldap'))
return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
 
$this->dsn = $dsninfo;
$user = $dsninfo['username'];
$pw = $dsninfo['password'];
$host = $dsninfo['hostspec'];
$port = $dsninfo['port'];
$this->base = $dsninfo['database'];
$this->d_base = $this->base;
 
if (empty($host)) {
return $this->raiseError("no host specified $host");
} // else ...
 
if (isset($port)) {
$conn = ldap_connect($host, $port);
} else {
$conn = ldap_connect($host);
}
if (!$conn) {
return $this->raiseError(DB_ERROR_CONNECT_FAILED);
}
if ($user && $pw) {
$bind = @ldap_bind($conn, $user, $pw);
} else {
$bind = @ldap_bind($conn);
}
if (!$bind) {
return $this->raiseError(DB_ERROR_BIND_FAILED);
}
$this->connection = $conn;
return DB_OK;
}
 
/**
* Unbinds from LDAP server
*
* @return int ldap_unbind() return value
*/
function disconnect()
{
$ret = @ldap_unbind($this->connection);
$this->connection = null;
return $ret;
}
 
 
/**
* Performs a request against the LDAP server
*
* The type of request (and the corresponding PHP ldap function called)
* depend on two additional parameters, added in respect to the
* DB_common interface.
*
* @param string $filter text of the request to send to the LDAP server
* @param string $action type of request to perform, defaults to search (ldap_search())
* @param array $params array of additional parameters to pass to the PHP ldap function requested
* @return result from ldap function or DB Error object if no result
*/
function simpleQuery($filter, $action = null, $params = null)
{
if ($action === null) {
$action = (!empty($this->q_action) ? $this->q_action : $this->action);
}
if ($params === null) {
$params = (count($this->q_params) > 0 ? $this->q_params : array());
}
if (!$this->isManip($action)) {
$base = $this->q_base ? $this->q_base : $this->base;
$attributes = array();
$attrsonly = 0;
$sizelimit = 0;
$timelimit = 0;
$deref = LDAP_DEREF_NEVER;
$sorting = '';
$sorting_method = '';
reset($params);
while (list($k, $v) = each($params)) {
if (isset(${$k})) ${$k} = $v;
}
$this->sorting = $sorting;
$this->sorting_method = $sorting_method;
$this->attributes = $attributes;
# double escape char for filter: '(o=Przedsi\C4\99biorstwo)' => '(o=Przedsi\\C4\\99biorstwo)'
$filter = str_replace('\\', '\\\\', $filter);
$this->last_query = $filter;
if ($action == 'search')
$result = @ldap_search($this->connection, $base, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref);
else if ($action == 'list')
$result = @ldap_list($this->connection, $base, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref);
else if ($action == 'read')
$result = @ldap_read($this->connection, $base, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref);
else
return $this->ldapRaiseError(DB_ERROR_UNKNOWN_LDAP_ACTION);
if (!$result) {
return $this->ldapRaiseError();
}
} else {
# If first argument is an array, it contains the entry with DN.
if (is_array($filter)) {
$entry = $filter;
$filter = $entry["dn"];
} else {
$entry = array();
}
unset($entry["dn"]);
$attribute = '';
$value = '';
$newrdn = '';
$newparent = '';
$deleteoldrdn = false;
reset($params);
while (list($k, $v) = each($params)) {
if (isset(${$k})) ${$k} = $v;
}
$this->last_query = $filter;
if ($action == 'add')
$result = @ldap_add($this->connection, $filter, $entry);
else if ($action == 'compare')
$result = @ldap_add($this->connection, $filter, $attribute, $value);
else if ($action == 'delete')
$result = @ldap_delete($this->connection, $filter);
else if ($action == 'modify')
$result = @ldap_modify($this->connection, $filter, $entry);
else if ($action == 'mod_add')
$result = @ldap_mod_add($this->connection, $filter, $entry);
else if ($action == 'mod_del')
$result = @ldap_mod_del($this->connection, $filter, $entry);
else if ($action == 'mod_replace')
$result = @ldap_mod_replace($this->connection, $filter, $entry);
else if ($action == 'rename')
$result = @ldap_rename($this->connection, $filter, $newrdn, $newparent, $deleteoldrdn);
else
return $this->ldapRaiseError(DB_ERROR_UNKNOWN_LDAP_ACTION);
if (!$result) {
return $this->ldapRaiseError();
}
}
$this->freeQuery();
return $result;
}
 
/**
* Executes a query performing variables substitution in the query text
*
* @param string $stmt text of the request to send to the LDAP server
* @param array $data query variables values to substitute
* @param string $action type of request to perform, defaults to search (ldap_search())
* @param array $params array of additional parameters to pass to the PHP ldap function requested
* @return LDAP_result object or DB Error object if no result
* @see DB_common::executeEmulateQuery $this->simpleQuery()
*/
function execute($stmt, $data = false, $action = null, $params = array())
{
$this->q_params = $params;
$realquery = $this->executeEmulateQuery($stmt, $data);
if (DB::isError($realquery)) {
return $realquery;
}
$result = $this->simpleQuery($realquery);
if (DB::isError($result) || $result === DB_OK) {
return $result;
} else {
return new LDAP_result($this, $result);
}
}
 
/**
* Executes multiple queries performing variables substitution for each query
*
* @param string $stmt text of the request to send to the LDAP server
* @param array $data query variables values to substitute
* @param string $action type of request to perform, defaults to search (ldap_search())
* @param array $params array of additional parameters to pass to the PHP ldap function requested
* @return LDAP_result object or DB Error object if no result
* @see DB_common::executeMultiple
*/
function executeMultiple($stmt, &$data, $action = null, $params = array())
{
$this->q_action = $action ? $action : $this->action;
$this->q_params = $params;
return(parent::executeMultiple($stmt, $data));
}
 
/**
* Executes a query substituting variables if any are present
*
* @param string $query text of the request to send to the LDAP server
* @param array $data query variables values to substitute
* @param string $action type of request to perform, defaults to search (ldap_search())
* @param array $params array of additional parameters to pass to the PHP ldap function requested
* @return LDAP_result object or DB Error object if no result
* @see DB_common::prepare() $this->execute()$this->simpleQuery()
*/
function &query($query, $data = array(), $action = null, $params = array()) {
// $this->q_action = $action ? $action : $this->action;
// $this->q_params = $params;
if (sizeof($data) > 0) {
$sth = $this->prepare($query);
if (DB::isError($sth)) {
return $sth;
}
return $this->execute($sth, $data);
} else {
$result = $this->simpleQuery($query);
if (DB::isError($result) || $result === DB_OK) {
return $result;
} else {
return new LDAP_result($this, $result);
}
}
}
 
/**
* Modifies a query to return only a set of rows, stores $from and $count for LDAP_result
*
* @param string $query text of the request to send to the LDAP server
* @param int $from record position from which to start returning data
* @param int $count number of records to return
* @return modified query text (no modifications are made, see above)
*/
function modifyLimitQuery($query, $from, $count)
{
$this->limit_from = $from;
$this->limit_count = $count;
return $query;
}
 
/**
* Executes a query returning only a specified number of rows
*
* This method only saves the $from and $count parameters for LDAP_result
* where the actual records processing takes place
*
* @param string $query text of the request to send to the LDAP server
* @param int $from record position from which to start returning data
* @param int $count number of records to return
* @param string $action type of request to perform, defaults to search (ldap_search())
* @param array $params array of additional parameters to pass to the PHP ldap function requested
* @return LDAP_result object or DB Error object if no result
*/
function limitQuery($query, $from, $count, $action = null, $params = array())
{
$query = $this->modifyLimitQuery($query, $from, $count);
$this->q_action = $action ? $action : $this->action;
$this->q_params = $params;
return $this->query($query, $action, $params);
}
 
/**
* Fetch the first column of the first row of data returned from
* a query. Takes care of doing the query and freeing the results
* when finished.
*
* @param $query the SQL query
* @param $data if supplied, prepare/execute will be used
* with this array as execute parameters
* @param string $action type of request to perform, defaults to search (ldap_search())
* @param array $params array of additional parameters to pass to the PHP ldap function requested
* @return array
* @see DB_common::getOne()
* @access public
*/
function &getOne($query, $data = array(), $action = null, $params = array())
{
$this->q_action = $action ? $action : $this->action;
$this->q_params = $params;
return(parent::getOne($query, $data));
}
 
/**
* Fetch the first row of data returned from a query. Takes care
* of doing the query and freeing the results when finished.
*
* @param $query the SQL query
* @param $fetchmode the fetch mode to use
* @param $data array if supplied, prepare/execute will be used
* with this array as execute parameters
* @param string $action type of request to perform, defaults to search (ldap_search())
* @param array $params array of additional parameters to pass to the PHP ldap function requested
* @access public
* @return array the first row of results as an array indexed from
* 0, or a DB error code.
* @see DB_common::getRow()
* @access public
*/
function &getRow($query,
$data = null,
$fetchmode = DB_FETCHMODE_DEFAULT,
$action = null, $params = array())
{
$this->q_action = $action ? $action : $this->action;
$this->q_params = $params;
return(parent::getRow($query, $data, $fetchmode));
}
 
/**
* Fetch the first column of data returned from a query. Takes care
* of doing the query and freeing the results when finished.
*
* @param $query the SQL query
* @param $col which column to return (integer [column number,
* starting at 0] or string [column name])
* @param $data array if supplied, prepare/execute will be used
* with this array as execute parameters
* @param string $action type of request to perform, defaults to search (ldap_search())
* @param array $params array of additional parameters to pass to the PHP ldap function requested
* @access public
* @return array an indexed array with the data from the first
* row at index 0, or a DB error code.
* @see DB_common::getCol()
* @access public
*/
function &getCol($query, $col = 0, $data = array(), $action = null, $params = array())
{
$this->q_action = $action ? $action : $this->action;
$this->q_params = $params;
return(parent::getCol($query, $col, $data));
}
 
/**
* Calls DB_common::getAssoc()
*
* @param $query the SQL query
* @param $force_array (optional) used only when the query returns
* exactly two columns. If true, the values of the returned array
* will be one-element arrays instead of scalars.
* starting at 0] or string [column name])
* @param array $data if supplied, prepare/execute will be used
* with this array as execute parameters
* @param $fetchmode the fetch mode to use
* @param boolean $group see DB_Common::getAssoc()
* @param string $action type of request to perform, defaults to search (ldap_search())
* @param array $params array of additional parameters to pass to the PHP ldap function requested
* @access public
* @return array an indexed array with the data from the first
* row at index 0, or a DB error code.
* @see DB_common::getAssoc()
* @access public
*/
function &getAssoc($query, $force_array = false, $data = array(),
$fetchmode = DB_FETCHMODE_ORDERED, $group = false,
$action = null, $params = array())
{
$this->q_action = $action ? $action : $this->action;
$this->q_params = $params;
return(parent::getAssoc($query, $force_array, $data, $fetchmode, $group));
}
 
/**
* Fetch all the rows returned from a query.
*
* @param $query the SQL query
* @param array $data if supplied, prepare/execute will be used
* with this array as execute parameters
* @param $fetchmode the fetch mode to use
* @param string $action type of request to perform, defaults to search (ldap_search())
* @param array $params array of additional parameters to pass to the PHP ldap function requested
* @access public
* @return array an nested array, or a DB error
* @see DB_common::getAll()
*/
function &getAll($query,
$data = null,
$fetchmode = DB_FETCHMODE_DEFAULT,
$action = null, $params = array())
{
$this->q_action = $action ? $action : $this->action;
$this->q_params = $params;
return(parent::getAll($query, $data, $fetchmode));
}
 
function numRows($result)
{
return $result->numRows();
}
 
function getTables()
{
return $this->ldapRaiseError(DB_ERROR_NOT_CAPABLE);
}
 
function getListOf($type)
{
return $this->ldapRaiseError(DB_ERROR_NOT_CAPABLE);
}
 
function isManip($action)
{
return(in_array($action, $this->manip));
}
 
function freeResult()
{
return true;
}
 
function freeQuery($query = '')
{
$this->q_action = '';
$this->q_base = '';
$this->q_params = array();
$this->attributes = null;
$this->sorting = '';
return true;
}
 
// Deprecated, will be removed in future releases.
function base($base = null)
{
$this->q_base = ($base !== null) ? $base : null;
return true;
}
 
function ldapSetBase($base = null)
{
$this->base = ($base !== null) ? $base : $this->d_base;
$this->q_base = '';
return true;
}
 
function ldapSetAction($action = 'search')
{
if ($action != 'search' && $action != 'list' && $action != 'read') {
return $this->ldapRaiseError(DB_ERROR_UNKNOWN_LDAP_ACTION);
}
$this->action = $action;
$this->q_action = '';
return true;
}
 
/**
* Get the next value in a sequence.
*
* LDAP provides transactions for only one entry and we need to
* prevent race condition. If unique value before and after modify
* aren't equal then wait and try again.
*
* The name of sequence is LDAP DN of entry.
*
* @access public
* @param string $seq_name the DN of the sequence
* @param bool $ondemand whether to create the sequence on demand
* @return a sequence integer, or a DB error
*/
function nextId($seq_name, $ondemand = true)
{
$repeat = 0;
do {
// Get the sequence entry
$this->base($seq_name);
$this->pushErrorHandling(PEAR_ERROR_RETURN);
$data = $this->getRow("objectClass=*");
$this->popErrorHandling();
 
if (DB::isError($data)) {
// DB_ldap doesn't use DB_ERROR_NOT_FOUND
if ($ondemand && $repeat == 0
&& $data->getCode() == DB_ERROR) {
// Try to create sequence and repeat
$repeat = 1;
$data = $this->createSequence($seq_name);
if (DB::isError($data)) {
return $this->ldapRaiseError($data);
}
} else {
// Other error
return $this->ldapRaiseError($data);
}
} else {
// Increment sequence value
$data["cn"]++;
// Unique identificator of transaction
$seq_unique = mt_rand();
$data["uid"] = $seq_unique;
// Modify the LDAP entry
$this->pushErrorHandling(PEAR_ERROR_RETURN);
$data = $this->simpleQuery($data, 'modify');
$this->popErrorHandling();
if (DB::isError($data)) {
return $this->ldapRaiseError($data);
}
// Get the entry and check if it contains our unique value
$this->base($seq_name);
$data = $this->getRow("objectClass=*");
if (DB::isError($data)) {
return $this->ldapRaiseError($data);
}
if ($data["uid"] != $seq_unique) {
// It is not our entry. Wait a little time and repeat
sleep(1);
$repeat = 1;
} else {
$repeat = 0;
}
}
} while ($repeat);
 
if (DB::isError($data)) {
return $data;
}
return $data["cn"];
}
 
/**
* Create the sequence
*
* The sequence entry is based on core schema with extensibleObject,
* so it should work with any LDAP server which doesn't check schema
* or supports extensibleObject object class.
*
* Sequence name have to be DN started with "sn=$seq_id,", i.e.:
*
* $seq_name = "sn=uidNumber,ou=sequences,dc=php,dc=net";
*
* dn: $seq_name
* objectClass: top
* objectClass: extensibleObject
* sn: $seq_id
* cn: $seq_value
* uid: $seq_uniq
*
* @param string $seq_name the DN of the sequence
* @return mixed DB_OK on success or DB error on error
* @access public
*/
function createSequence($seq_name)
{
// Extract $seq_id from DN
ereg("^([^,]*),", $seq_name, $regs);
$seq_id = $regs[1];
 
// Create the sequence entry
$data = array(
dn => $seq_name,
objectclass => array("top", "extensibleObject"),
sn => $seq_id,
cn => 0,
uid => 0
);
 
// Add the LDAP entry
$this->pushErrorHandling(PEAR_ERROR_RETURN);
$data = $this->simpleQuery($data, 'add');
$this->popErrorHandling();
return $data;
}
 
/**
* Drop a sequence
*
* @param string $seq_name the DN of the sequence
* @return mixed DB_OK on success or DB error on error
* @access public
*/
function dropSequence($seq_name)
{
// Delete the sequence entry
$data = array(
dn => $seq_name,
);
$this->pushErrorHandling(PEAR_ERROR_RETURN);
$data = $this->simpleQuery($data, 'delete');
$this->popErrorHandling();
return $data;
}
 
// {{{ ldapRaiseError()
 
function ldapRaiseError($errno = null)
{
if ($errno === null) {
$errno = $this->errorCode(ldap_errno($this->connection));
}
if ($this->q_action !== null) {
return $this->raiseError($errno, null, null,
sprintf('%s base="%s" filter="%s"',
$this->q_action, $this->q_base, $this->last_query
),
$errno == DB_ERROR_UNKNOWN_LDAP_ACTION ? null : @ldap_error($this->connection));
} else {
return $this->raiseError($errno, null, null, "???",
@ldap_error($this->connection));
}
}
 
// }}}
 
}
 
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
*/
?>
/trunk/api/pear/DB/ibase.php
23,7 → 23,7
* @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: ibase.php,v 1.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: ibase.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
49,7 → 49,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
* @since Class became stable in Release 1.7.0
*/
/trunk/api/pear/Auth.php
1,869 → 1,1118
<?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 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: Martin Jansen <mj@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Auth.php,v 1.1 2005-03-30 08:50:19 jpm Exp $
//
 
require_once 'PEAR.php';
 
define('AUTH_IDLED', -1);
define('AUTH_EXPIRED', -2);
define('AUTH_WRONG_LOGIN', -3);
 
/**
* PEAR::Auth
*
* The PEAR::Auth class provides methods for creating an
* authentication system using PHP.
*
* @author Martin Jansen <mj@php.net>
* @package Auth
* @version $Revision: 1.1 $
*/
class Auth {
 
/**
* Auth lifetime in seconds
*
* If this variable is set to 0, auth never expires
*
* @var integer
* @see setExpire(), checkAuth()
*/
var $expire = 0;
 
/**
* Has the auth session expired?
*
* @var bool
* @see checkAuth(), drawLogin()
*/
var $expired = false;
 
/**
* Maximum time of idleness in seconds
*
* The difference to $expire is, that the idletime gets
* refreshed each time, checkAuth() is called. If this
* variable is set to 0, idle time is never checked.
*
* @var integer
* @see setIdle(), checkAuth()
*/
var $idle = 0;
 
/**
* Is the maximum idletime over?
*
* @var boolean
* @see checkAuth(), drawLogin();
*/
var $idled = false;
 
/**
* Storage object
*
* @var object
* @see Auth(), validateLogin()
*/
var $storage = '';
 
/**
* Function defined by the user, that creates the login screen
*
* @var string
*/
var $loginFunction = '';
 
/**
* Should the login form be displayed?
*
* @var bool
* @see setShowlogin()
*/
var $showLogin = true;
 
/**
* Current authentication status
*
* @var string
*/
var $status = '';
 
/**
* Username
*
* @var string
*/
var $username = '';
 
/**
* Password
*
* @var string
*/
var $password = '';
 
/**
* Login callback function name
*
* @var string
* @see setLoginCallback()
*/
var $loginCallback = '';
 
/**
* Failed Login callback function name
*
* @var string
* @see setLoginFailedCallback()
*/
var $loginFailedCallback = '';
 
/**
* Logout callback function name
*
* @var string
* @see setLogoutCallback()
*/
var $logoutCallback = '';
 
/**
* Auth session-array name
*
* @var string
*/
var $_sessionName = '_authsession';
/**
* Package Version
*
* @var string
*/
var $version = "1.2.3";
 
// {{{ Constructor
 
/**
* Constructor
*
* Set up the storage driver.
*
* @param string Type of the storage driver
* @param mixed Additional options for the storage driver
* (example: if you are using DB as the storage
* driver, you have to pass the dsn string here)
*
* @param string Name of the function that creates the login form
* @param boolean Should the login form be displayed if neccessary?
* @return void
*/
function Auth($storageDriver, $options = '', $loginFunction = '', $showLogin = true)
{
if (!empty($options['sessionName'])) {
$this->_sessionName = $options['sessionName'];
unset($options['sessionName']);
}
 
if ($loginFunction != '' && is_callable($loginFunction)) {
$this->loginFunction = $loginFunction;
}
 
if (is_bool($showLogin)) {
$this->showLogin = $showLogin;
}
 
if (is_object($storageDriver)) {
$this->storage =& $storageDriver;
} else {
$this->storage = $this->_factory($storageDriver, $options);
}
// Pass a reference to auth to the container, ugly but works
// this is used by the DB container to use method setAuthData not staticaly.
$this->storage->_auth_obj =& $this;
}
 
// }}}
// {{{ _factory()
 
/**
* Return a storage driver based on $driver and $options
*
* @access private
* @static
* @param string $driver Type of storage class to return
* @param string $options Optional parameters for the storage class
* @return object Object Storage object
*/
function _factory($driver, $options = '')
{
$storage_path = 'Auth/Container/' . $driver . '.php';
$storage_class = 'Auth_Container_' . $driver;
 
require_once $storage_path;
 
return new $storage_class($options);
}
 
// }}}
// {{{ assignData()
 
/**
* Assign data from login form to internal values
*
* This function takes the values for username and password
* from $HTTP_POST_VARS and assigns them to internal variables.
* If you wish to use another source apart from $HTTP_POST_VARS,
* you have to derive this function.
*
* @access private
* @global $HTTP_POST_VARS
* @see Auth
* @return void
*/
function assignData()
{
$post = &$this->_importGlobalVariable('post');
 
if (isset($post['username']) && $post['username'] != '') {
$this->username = (get_magic_quotes_gpc() == 1 ? stripslashes($post['username']) : $post['username']);
}
 
if (isset($post['password']) && $post['password'] != '') {
$this->password = (get_magic_quotes_gpc() == 1 ? stripslashes($post['password']) : $post['password'] );
}
 
}
 
// }}}
// {{{ start()
 
/**
* Start new auth session
*
* @access public
* @return void
*/
function start()
{
$this->assignData();
 
@session_start();
 
if (!$this->checkAuth()) {
$this->login();
}
}
 
// }}}
// {{{ login()
 
/**
* Login function
*
* @access private
* @return void
*/
function login()
{
$login_ok = false;
 
/**
* When the user has already entered a username,
* we have to validate it.
*/
if (!empty($this->username)) {
if (true === $this->storage->fetchData($this->username, $this->password)) {
$login_ok = true;
} else {
if (is_callable($this->loginFailedCallback)) {
call_user_func($this->loginFailedCallback,$this->username, $this);
}
}
}
 
if (!empty($this->username) && $login_ok) {
$this->setAuth($this->username);
if (is_callable($this->loginCallback)) {
call_user_func($this->loginCallback,$this->username, $this);
}
}
 
/**
* If the login failed or the user entered no username,
* output the login screen again.
*/
if (!empty($this->username) && !$login_ok) {
$this->status = AUTH_WRONG_LOGIN;
}
 
if ((empty($this->username) || !$login_ok) && $this->showLogin) {
$this->drawLogin($this->storage->activeUser);
return;
}
}
 
// }}}
// {{{ setExpire()
 
/**
* Set the maximum expire time
*
* @access public
* @param integer time in seconds
* @param bool add time to current expire time or not
* @return void
*/
function setExpire($time, $add = false)
{
if ($add) {
$this->expire += $time;
} else {
$this->expire = $time;
}
}
 
// }}}
// {{{ setIdle()
 
/**
* Set the maximum idle time
*
* @access public
* @param integer time in seconds
* @param bool add time to current maximum idle time or not
* @return void
*/
function setIdle($time, $add = false)
{
if ($add) {
$this->idle += $time;
} else {
$this->idle = $time;
}
}
 
// }}}
// {{{ setSessionname()
 
/**
* Set name of the session to a customized value.
*
* If you are using multiple instances of PEAR::Auth
* on the same domain, you can change the name of
* session per application via this function.
*
* @access public
* @param string New name for the session
* @return void
*/
function setSessionname($name = 'PHPSESSID')
{
@session_name($name);
}
 
// }}}
// {{{ setShowLogin()
 
/**
* Should the login form be displayed if neccessary?
*
* @access public
* @param bool show login form or not
* @return void
*/
function setShowLogin($showLogin = true)
{
$this->showLogin = $showLogin;
}
 
/**
* Register a callback function to be called on user login.
* The function will receive two parameters, the username and a reference to the auth object.
*
* @access public
* @param string callback function name
* @return void
* @see setLogoutCallback()
*/
function setLoginCallback($loginCallback)
{
$this->loginCallback = $loginCallback;
}
 
/**
* Register a callback function to be called on failed user login.
* The function will receive a single parameter, the username and a reference to the auth object.
*
* @access public
* @param string callback function name
* @return void
*/
function setFailedLoginCallback($loginFailedCallback)
{
$this->loginFailedCallback = $loginFailedCallback;
}
 
/**
* Register a callback function to be called on user logout.
* The function will receive three parameters, the username and a reference to the auth object.
*
* @access public
* @param string callback function name
* @return void
* @see setLoginCallback()
*/
function setLogoutCallback($logoutCallback)
{
$this->logoutCallback = $logoutCallback;
}
 
// }}}
// {{{ setAuthData()
 
/**
* Register additional information that is to be stored
* in the session.
*
* @access public
* @param string Name of the data field
* @param mixed Value of the data field
* @param boolean Should existing data be overwritten? (default
* is true)
* @return void
*/
function setAuthData($name, $value, $overwrite = true)
{
$session = &Auth::_importGlobalVariable('session');
 
if (!empty($session[$this->_sessionName]['data'][$name]) && $overwrite == false) {
return;
}
$session[$this->_sessionName]['data'][$name] = $value;
}
 
// }}}
// {{{ getAuthData()
 
/**
* Get additional information that is stored in the session.
*
* If no value for the first parameter is passed, the method will
* return all data that is currently stored.
*
* @access public
* @param string Name of the data field
* @return mixed Value of the data field.
*/
function getAuthData($name = null)
{
$session = &Auth::_importGlobalVariable('session');
if(!isset($session[$this->_sessionName]['data'])){
return(null);
}
 
if (is_null($name)) {
if(isset($session[$this->_sessionName]['data'])) {
return $session[$this->_sessionName]['data'];
} else {
return null;
}
}
if (isset($session[$this->_sessionName]['data'][$name])) {
return $session[$this->_sessionName]['data'][$name];
} else {
return null;
}
}
 
// }}}
// {{{ setAuth()
 
/**
* Register variable in a session telling that the user
* has logged in successfully
*
* @access public
* @param string Username
* @return void
*/
function setAuth($username)
{
$session = &Auth::_importGlobalVariable('session');
 
if (!isset($session[$this->_sessionName]) && !isset($_SESSION)) {
session_register($this->_sessionName);
}
 
if (!isset($session[$this->_sessionName]) || !is_array($session[$this->_sessionName])) {
$session[$this->_sessionName] = array();
}
 
if(!isset($session[$this->_sessionName]['data'])){
$session[$this->_sessionName]['data'] = array();
}
$session[$this->_sessionName]['registered'] = true;
$session[$this->_sessionName]['username'] = $username;
$session[$this->_sessionName]['timestamp'] = time();
$session[$this->_sessionName]['idle'] = time();
}
 
// }}}
// {{{ checkAuth()
 
/**
* Checks if there is a session with valid auth information.
*
* @access private
* @return boolean Whether or not the user is authenticated.
*/
function checkAuth()
{
$session = &$this->_importGlobalVariable('session');
 
if (isset($session[$this->_sessionName])) {
// Check if authentication session is expired
if ($this->expire > 0 &&
isset($session[$this->_sessionName]['timestamp']) &&
($session[$this->_sessionName]['timestamp'] + $this->expire) < time()) {
 
$this->logout();
$this->expired = true;
$this->status = AUTH_EXPIRED;
 
return false;
}
 
// Check if maximum idle time is reached
if ($this->idle > 0 &&
isset($session[$this->_sessionName]['idle']) &&
($session[$this->_sessionName]['idle'] + $this->idle) < time()) {
 
$this->logout();
$this->idled = true;
$this->status = AUTH_IDLED;
 
return false;
}
 
if (isset($session[$this->_sessionName]['registered']) &&
isset($session[$this->_sessionName]['username']) &&
$session[$this->_sessionName]['registered'] == true &&
$session[$this->_sessionName]['username'] != '') {
 
Auth::updateIdle();
 
return true;
}
}
 
return false;
}
 
// }}}
// {{{ getAuth()
 
/**
* Has the user been authenticated?
*
* @access public
* @return bool True if the user is logged in, otherwise false.
*/
function getAuth()
{
$session = &$this->_importGlobalVariable('session');
 
if (!empty($session) &&
(isset($session[$this->_sessionName]['registered']) &&
$session[$this->_sessionName]['registered'] === true))
{
return true;
} else {
return false;
}
}
 
// }}}
// {{{ drawLogin()
 
/**
* Draw the login form
*
* Normally you will not use this output in your application,
* because you can pass a different function name to the
* constructor. For more information on this, please
* consult the documentation.
*
* @access private
* @param string Username if already entered
* @return void
*/
function drawLogin($username = '')
{
if (is_callable($this->loginFunction)) {
call_user_func($this->loginFunction, $username, $this->status, $this);
} else {
$server = &$this->_importGlobalVariable('server');
 
echo '<center>'."\n";
 
if (!empty($this->status) && $this->status == AUTH_EXPIRED) {
echo '<i>Your session expired. Please login again!</i>'."\n";
} else if (!empty($this->status) && $this->status == AUTH_IDLED) {
echo '<i>You have been idle for too long. Please login again!</i>'."\n";
} else if (!empty ($this->status) && $this->status == AUTH_WRONG_LOGIN) {
echo '<i>Wrong login data!</i>'."\n";
}
 
PEAR::raiseError('You are using the built-in login screen of PEAR::Auth.<br />See the <a href="http://pear.php.net/manual/">manual</a> for details on how to create your own login function.', null);
 
echo '<form method="post" action="' . $server['PHP_SELF'] . '">'."\n";
echo '<table border="0" cellpadding="2" cellspacing="0" summary="login form">'."\n";
echo '<tr>'."\n";
echo ' <td colspan="2" bgcolor="#eeeeee"><b>Login:</b></td>'."\n";
echo '</tr>'."\n";
echo '<tr>'."\n";
echo ' <td>Username:</td>'."\n";
echo ' <td><input type="text" name="username" value="' . $username . '" /></td>'."\n";
echo '</tr>'."\n";
echo '<tr>'."\n";
echo ' <td>Password:</td>'."\n";
echo ' <td><input type="password" name="password" /></td>'."\n";
echo '</tr>'."\n";
echo '<tr>'."\n";
echo ' <td colspan="2" bgcolor="#eeeeee"><input type="submit" /></td>'."\n";
echo '</tr>'."\n";
echo '</table>'."\n";
echo '</form>'."\n";
echo '</center>'."\n\n";
}
}
 
// }}}
// {{{ logout()
 
/**
* Logout function
*
* This function clears any auth tokens in the currently
* active session and executes the logout callback function,
* if any
*
* @access public
* @return void
*/
function logout()
{
$session = &$this->_importGlobalVariable('session');
 
if (is_callable($this->logoutCallback)) {
call_user_func($this->logoutCallback, $session[$this->_sessionName]['username'], $this);
}
 
$this->username = '';
$this->password = '';
 
$session[$this->_sessionName] = array();
if (isset($_SESSION)) {
unset($session[$this->_sessionName]);
} else {
session_unregister($this->_sessionName);
}
}
 
// }}}
// {{{ updateIdle()
 
/**
* Update the idletime
*
* @access private
* @return void
*/
function updateIdle()
{
$session = &$this->_importGlobalVariable('session');
$session[$this->_sessionName]['idle'] = time();
}
 
// }}}
// {{{ getUsername()
 
/**
* Get the username
*
* @access public
* @return string
*/
function getUsername()
{
$session = &$this->_importGlobalVariable('session');
if (!isset($session[$this->_sessionName]['username'])) {
return '';
}
return $session[$this->_sessionName]['username'];
}
 
// }}}
// {{{ getStatus()
 
/**
* Get the current status
*
* @access public
* @return string
*/
function getStatus()
{
return $this->status;
}
 
// }}}
// {{{ sessionValidThru()
 
/**
* Returns the time up to the session is valid
*
* @access public
* @return integer
*/
function sessionValidThru()
{
$session = &$this->_importGlobalVariable('session');
if (!isset($session[$this->_sessionName]['idle'])) {
return 0;
}
return ($session[$this->_sessionName]['idle'] + $this->idle);
}
 
// }}}
// {{{ listUsers()
 
/**
* List all users that are currently available in the storage
* container
*
* @access public
* @return array
*/
function listUsers()
{
return $this->storage->listUsers();
}
 
// }}}
// {{{ addUser()
 
/**
* Add user to the storage container
*
* @access public
* @param string Username
* @param string Password
* @param mixed Additional parameters
* @return mixed True on success, PEAR error object on error
* and AUTH_METHOD_NOT_SUPPORTED otherwise.
*/
function addUser($username, $password, $additional = '')
{
return $this->storage->addUser($username, $password, $additional);
}
 
// }}}
// {{{ removeUser()
 
/**
* Remove user from the storage container
*
* @access public
* @param string Username
* @return mixed True on success, PEAR error object on error
* and AUTH_METHOD_NOT_SUPPORTED otherwise.
*/
function removeUser($username)
{
return $this->storage->removeUser($username);
}
 
// }}}
// {{{ _importGlobalVariable()
 
/**
* Import variables from special namespaces.
*
* @access private
* @param string Type of variable (server, session, post)
* @return array
*/
function &_importGlobalVariable($variable)
{
$var = null;
 
switch (strtolower($variable)) {
 
case 'server' :
if (isset($_SERVER)) {
$var = &$_SERVER;
} else {
$var = &$GLOBALS['HTTP_SERVER_VARS'];
}
break;
 
case 'session' :
if (isset($_SESSION)) {
$var = &$_SESSION;
} else {
$var = &$GLOBALS['HTTP_SESSION_VARS'];
}
break;
 
case 'post' :
if (isset($_POST)) {
$var = &$_POST;
} else {
$var = &$GLOBALS['HTTP_POST_VARS'];
}
break;
 
case 'cookie' :
if (isset($_COOKIE)) {
$var = &$_COOKIE;
} else {
$var = &$GLOBALS['HTTP_COOKIE_VARS'];
}
break;
 
case 'get' :
if (isset($_GET)) {
$var = &$_GET;
} else {
$var = &$GLOBALS['HTTP_GET_VARS'];
}
break;
 
default:
break;
 
}
 
return $var;
}
 
// }}}
}
?>
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
 
/**
* The main include file for Auth package
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.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 Authentication
* @package Auth
* @author Martin Jansen <mj@php.net>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: Auth.php,v 1.2 2006-12-14 15:04:29 jp_milcent Exp $
* @link http://pear.php.net/package/Auth
*/
 
/**
* Returned if session exceeds idle time
*/
define('AUTH_IDLED', -1);
/**
* Returned if session has expired
*/
define('AUTH_EXPIRED', -2);
/**
* Returned if container is unable to authenticate user/password pair
*/
define('AUTH_WRONG_LOGIN', -3);
/**
* Returned if a container method is not supported.
*/
define('AUTH_METHOD_NOT_SUPPORTED', -4);
/**
* Returned if new Advanced security system detects a breach
*/
define('AUTH_SECURITY_BREACH', -5);
/**
* Returned if checkAuthCallback says session should not continue.
*/
define('AUTH_CALLBACK_ABORT', -6);
 
/**
* PEAR::Auth
*
* The PEAR::Auth class provides methods for creating an
* authentication system using PHP.
*
* @category Authentication
* @package Auth
* @author Martin Jansen <mj@php.net>
* @author Adam Ashley <aashley@php.net>
* @copyright 2001-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version Release: 1.4.3 File: $Revision: 1.2 $
* @link http://pear.php.net/package/Auth
*/
class Auth {
 
// {{{ properties
 
/**
* Auth lifetime in seconds
*
* If this variable is set to 0, auth never expires
*
* @var integer
* @see setExpire(), checkAuth()
*/
var $expire = 0;
 
/**
* Has the auth session expired?
*
* @var bool
* @see checkAuth()
*/
var $expired = false;
 
/**
* Maximum idletime in seconds
*
* The difference to $expire is, that the idletime gets
* refreshed each time checkAuth() is called. If this
* variable is set to 0, idletime is never checked.
*
* @var integer
* @see setIdle(), checkAuth()
*/
var $idle = 0;
 
/**
* Is the maximum idletime over?
*
* @var boolean
* @see checkAuth()
*/
var $idled = false;
 
/**
* Storage object
*
* @var object
* @see Auth(), validateLogin()
*/
var $storage = '';
 
/**
* User-defined function that creates the login screen
*
* @var string
*/
var $loginFunction = '';
 
/**
* Should the login form be displayed
*
* @var bool
* @see setShowlogin()
*/
var $showLogin = true;
/**
* Is Login Allowed from this page
*
* @var bool
* @see setAllowLogin
*/
var $allowLogin = true;
 
/**
* Current authentication status
*
* @var string
*/
var $status = '';
 
/**
* Username
*
* @var string
*/
var $username = '';
 
/**
* Password
*
* @var string
*/
var $password = '';
 
/**
* checkAuth callback function name
*
* @var string
* @see setCheckAuthCallback()
*/
var $checkAuthCallback = '';
 
/**
* Login callback function name
*
* @var string
* @see setLoginCallback()
*/
var $loginCallback = '';
 
/**
* Failed Login callback function name
*
* @var string
* @see setFailedLoginCallback()
*/
var $loginFailedCallback = '';
 
/**
* Logout callback function name
*
* @var string
* @see setLogoutCallback()
*/
var $logoutCallback = '';
 
/**
* Auth session-array name
*
* @var string
*/
var $_sessionName = '_authsession';
 
/**
* Package Version
*
* @var string
*/
var $version = "@version@";
 
/**
* Flag to use advanced security
* When set extra checks will be made to see if the
* user's IP or useragent have changed across requests.
* Turned off by default to preserve BC.
*
* @var boolean
*/
var $advancedsecurity = false;
 
/**
* Username key in POST array
*
* @var string
*/
var $_postUsername = 'username';
 
/**
* Password key in POST array
*
* @var string
*/
var $_postPassword = 'password';
 
/**
* Holds a reference to the session auth variable
* @var array
*/
var $session;
 
/**
* Holds a reference to the global server variable
* @var array
*/
var $server;
 
/**
* Holds a reference to the global post variable
* @var array
*/
var $post;
 
/**
* Holds a reference to the global cookie variable
* @var array
*/
var $cookie;
 
/**
* A hash to hold various superglobals as reference
* @var array
*/
var $authdata;
/**
* How many times has checkAuth been called
* var int
*/
var $authChecks = 0;
 
// }}}
// {{{ Auth() [constructor]
 
/**
* Constructor
*
* Set up the storage driver.
*
* @param string Type of the storage driver
* @param mixed Additional options for the storage driver
* (example: if you are using DB as the storage
* driver, you have to pass the dsn string here)
*
* @param string Name of the function that creates the login form
* @param boolean Should the login form be displayed if neccessary?
* @return void
*/
function Auth($storageDriver, $options = '', $loginFunction = '', $showLogin = true)
{
$this->applyAuthOptions($options);
 
// Start the session suppress error if already started
if(!session_id()){
@session_start();
if(!session_id()) {
// Throw error
include_once 'PEAR.php';
PEAR::throwError('Session could not be started by Auth, '
.'possibly headers are already sent, try putting '
.'ob_start in the beginning of your script');
}
}
 
// Make Sure Auth session variable is there
if(!isset($_SESSION[$this->_sessionName])) {
$_SESSION[$this->_sessionName] = array();
}
 
// Assign Some globals to internal references, this will replace _importGlobalVariable
$this->session =& $_SESSION[$this->_sessionName];
$this->server =& $_SERVER;
$this->post =& $_POST;
$this->cookie =& $_COOKIE;
 
if ($loginFunction != '' && is_callable($loginFunction)) {
$this->loginFunction = $loginFunction;
}
 
if (is_bool($showLogin)) {
$this->showLogin = $showLogin;
}
 
if (is_object($storageDriver)) {
$this->storage =& $storageDriver;
// Pass a reference to auth to the container, ugly but works
// this is used by the DB container to use method setAuthData not staticaly.
$this->storage->_auth_obj =& $this;
} else {
// $this->storage = $this->_factory($storageDriver, $options);
//
$this->storage_driver = $storageDriver;
$this->storage_options =& $options;
}
}
 
// }}}
// {{{ applyAuthOptions()
 
/**
* Set the Auth options
*
* Some options which are Auth specific will be applied
* the rest will be left for usage by the container
*
* @param array An array of Auth options
* @return array The options which were not applied
* @access private
*/
function &applyAuthOptions(&$options)
{
if(is_array($options)){
if (!empty($options['sessionName'])) {
$this->_sessionName = $options['sessionName'];
unset($options['sessionName']);
}
if (isset($options['allowLogin'])) {
$this->allowLogin = $options['allowLogin'];
unset($options['allowLogin']);
}
if (!empty($options['postUsername'])) {
$this->_postUsername = $options['postUsername'];
unset($options['postUsername']);
}
if (!empty($options['postPassword'])) {
$this->_postPassword = $options['postPassword'];
unset($options['postPassword']);
}
if (isset($options['advancedsecurity'])) {
$this->advancedsecurity = $options['advancedsecurity'];
unset($options['advancedsecurity']);
}
}
return($options);
}
 
// }}}
// {{{ _loadStorage()
/**
* Load Storage Driver if not already loaded
*
* Suspend storage instantiation to make Auth lighter to use
* for calls which do not require login
*
* @return bool True if the conainer is loaded, false if the container
* is already loaded
* @access private
*/
function _loadStorage()
{
if(!is_object($this->storage)) {
$this->storage =& $this->_factory($this->storage_driver,
$this->storage_options);
$this->storage->_auth_obj =& $this;
return(true);
}
return(false);
}
 
// }}}
// {{{ _factory()
 
/**
* Return a storage driver based on $driver and $options
*
* @static
* @param string $driver Type of storage class to return
* @param string $options Optional parameters for the storage class
* @return object Object Storage object
* @access private
*/
function &_factory($driver, $options = '')
{
$storage_class = 'Auth_Container_' . $driver;
include_once 'Auth/Container/' . $driver . '.php';
$obj =& new $storage_class($options);
return $obj;
}
 
// }}}
// {{{ assignData()
 
/**
* Assign data from login form to internal values
*
* This function takes the values for username and password
* from $HTTP_POST_VARS/$_POST and assigns them to internal variables.
* If you wish to use another source apart from $HTTP_POST_VARS/$_POST,
* you have to derive this function.
*
* @global $HTTP_POST_VARS, $_POST
* @see Auth
* @return void
* @access private
*/
function assignData()
{
if ( isset($this->post[$this->_postUsername])
&& $this->post[$this->_postUsername] != '') {
$this->username = (get_magic_quotes_gpc() == 1
? stripslashes($this->post[$this->_postUsername])
: $this->post[$this->_postUsername]);
}
if ( isset($this->post[$this->_postPassword])
&& $this->post[$this->_postPassword] != '') {
$this->password = (get_magic_quotes_gpc() == 1
? stripslashes($this->post[$this->_postPassword])
: $this->post[$this->_postPassword] );
}
}
 
// }}}
// {{{ start()
 
/**
* Start new auth session
*
* @return void
* @access public
*/
function start()
{
$this->assignData();
if (!$this->checkAuth() && $this->allowLogin) {
$this->login();
}
}
 
// }}}
// {{{ login()
 
/**
* Login function
*
* @return void
* @access private
*/
function login()
{
$login_ok = false;
$this->_loadStorage();
// Check if using challenge response
(isset($this->post['authsecret']) && $this->post['authsecret'] == 1)
? $usingChap = true
: $usingChap = false;
 
// When the user has already entered a username, we have to validate it.
if (!empty($this->username)) {
if (true === $this->storage->fetchData($this->username, $this->password, $usingChap)) {
$this->session['challengekey'] = md5($this->username.$this->password);
$login_ok = true;
}
}
 
if (!empty($this->username) && $login_ok) {
$this->setAuth($this->username);
if (is_callable($this->loginCallback)) {
call_user_func_array($this->loginCallback, array($this->username, &$this));
}
}
 
// If the login failed or the user entered no username,
// output the login screen again.
if (!empty($this->username) && !$login_ok) {
$this->status = AUTH_WRONG_LOGIN;
if (is_callable($this->loginFailedCallback)) {
call_user_func_array($this->loginFailedCallback, array($this->username, &$this));
}
}
 
if ((empty($this->username) || !$login_ok) && $this->showLogin) {
if (is_callable($this->loginFunction)) {
call_user_func_array($this->loginFunction, array($this->username, $this->status, &$this));
} else {
// BC fix Auth used to use drawLogin for this
// call is sub classes implement this
if (is_callable(array($this, 'drawLogin'))) {
return $this->drawLogin($this->username, $this);
}
 
// New Login form
include_once 'Auth/Frontend/Html.php';
return Auth_Frontend_Html::render($this, $this->username);
}
} else {
return;
}
}
 
// }}}
// {{{ setExpire()
 
/**
* Set the maximum expire time
*
* @param integer time in seconds
* @param bool add time to current expire time or not
* @return void
* @access public
*/
function setExpire($time, $add = false)
{
$add ? $this->expire += $time : $this->expire = $time;
}
 
// }}}
// {{{ setIdle()
 
/**
* Set the maximum idle time
*
* @param integer time in seconds
* @param bool add time to current maximum idle time or not
* @return void
* @access public
*/
function setIdle($time, $add = false)
{
$add ? $this->idle += $time : $this->idle = $time;
}
 
// }}}
// {{{ setSessionName()
 
/**
* Set name of the session to a customized value.
*
* If you are using multiple instances of PEAR::Auth
* on the same domain, you can change the name of
* session per application via this function.
* This will chnage the name of the session variable
* auth uses to store it's data in the session
*
* @param string New name for the session
* @return void
* @access public
*/
function setSessionName($name = 'session')
{
$this->_sessionName = '_auth_'.$name;
$this->session =& $_SESSION[$this->_sessionName];
}
 
// }}}
// {{{ setShowLogin()
 
/**
* Should the login form be displayed if neccessary?
*
* @param bool show login form or not
* @return void
* @access public
*/
function setShowLogin($showLogin = true)
{
$this->showLogin = $showLogin;
}
 
// }}}
// {{{ setAllowLogin()
 
/**
* Should the login form be displayed if neccessary?
*
* @param bool show login form or not
* @return void
* @access public
*/
function setAllowLogin($allowLogin = true)
{
$this->allowLogin = $allowLogin;
}
 
// }}}
// {{{ setCheckAuthCallback()
 
/**
* Register a callback function to be called whenever the validity of the login is checked
* The function will receive two parameters, the username and a reference to the auth object.
*
* @param string callback function name
* @return void
* @access public
* @since Method available since Release 1.4.3
*/
function setCheckAuthCallback($checkAuthCallback)
{
$this->checkAuthCallback = $checkAuthCallback;
}
 
// }}}
// {{{ setLoginCallback()
/**
* Register a callback function to be called on user login.
* The function will receive two parameters, the username and a reference to the auth object.
*
* @param string callback function name
* @return void
* @see setLogoutCallback()
* @access public
*/
function setLoginCallback($loginCallback)
{
$this->loginCallback = $loginCallback;
}
 
// }}}
// {{{ setFailedLoginCallback()
 
/**
* Register a callback function to be called on failed user login.
* The function will receive two parameters, the username and a reference to the auth object.
*
* @param string callback function name
* @return void
* @access public
*/
function setFailedLoginCallback($loginFailedCallback)
{
$this->loginFailedCallback = $loginFailedCallback;
}
 
// }}}
// {{{ setLogoutCallback()
 
/**
* Register a callback function to be called on user logout.
* The function will receive three parameters, the username and a reference to the auth object.
*
* @param string callback function name
* @return void
* @see setLoginCallback()
* @access public
*/
function setLogoutCallback($logoutCallback)
{
$this->logoutCallback = $logoutCallback;
}
 
// }}}
// {{{ setAuthData()
 
/**
* Register additional information that is to be stored
* in the session.
*
* @param string Name of the data field
* @param mixed Value of the data field
* @param boolean Should existing data be overwritten? (default
* is true)
* @return void
* @access public
*/
function setAuthData($name, $value, $overwrite = true)
{
if (!empty($this->session['data'][$name]) && $overwrite == false) {
return;
}
$this->session['data'][$name] = $value;
}
 
// }}}
// {{{ getAuthData()
 
/**
* Get additional information that is stored in the session.
*
* If no value for the first parameter is passed, the method will
* return all data that is currently stored.
*
* @param string Name of the data field
* @return mixed Value of the data field.
* @access public
*/
function getAuthData($name = null)
{
if (!isset($this->session['data'])) {
return null;
}
if(!isset($name)) {
return $this->session['data'];
}
if (isset($name) && isset($this->session['data'][$name])) {
return $this->session['data'][$name];
}
return null;
}
 
// }}}
// {{{ setAuth()
 
/**
* Register variable in a session telling that the user
* has logged in successfully
*
* @param string Username
* @return void
* @access public
*/
function setAuth($username)
{
// #2021 - Change the session id to avoid session fixation attacks php 4.3.3 >
session_regenerate_id(true);
 
if (!isset($this->session) || !is_array($this->session)) {
$this->session = array();
}
 
if (!isset($this->session['data'])) {
$this->session['data'] = array();
}
 
$this->session['sessionip'] = isset($this->server['REMOTE_ADDR'])
? $this->server['REMOTE_ADDR']
: '';
$this->session['sessionuseragent'] = isset($this->server['HTTP_USER_AGENT'])
? $this->server['HTTP_USER_AGENT']
: '';
 
// This should be set by the container to something more safe
// Like md5(passwd.microtime)
if(empty($this->session['challengekey'])) {
$this->session['challengekey'] = md5($username.microtime());
}
 
$this->session['challengecookie'] = md5($this->session['challengekey'].microtime());
setcookie('authchallenge', $this->session['challengecookie']);
 
$this->session['registered'] = true;
$this->session['username'] = $username;
$this->session['timestamp'] = time();
$this->session['idle'] = time();
}
 
// }}}
// {{{ setAdvancedSecurity()
/**
* Enables advanced security checks
*
* Currently only ip change and useragent change
* are detected
* @todo Add challenge cookies - Create a cookie which changes every time
* and contains some challenge key which the server can verify with
* a session var cookie might need to be crypted (user pass)
* @param bool Enable or disable
* @return void
* @access public
*/
function setAdvancedSecurity($flag=true)
{
$this->advancedsecurity = $flag;
}
 
// }}}
// {{{ checkAuth()
 
/**
* Checks if there is a session with valid auth information.
*
* @access public
* @return boolean Whether or not the user is authenticated.
*/
function checkAuth()
{
$this->authChecks++;
if (isset($this->session)) {
// Check if authentication session is expired
if ( $this->expire > 0
&& isset($this->session['timestamp'])
&& ($this->session['timestamp'] + $this->expire) < time()) {
$this->expired = true;
$this->status = AUTH_EXPIRED;
$this->logout();
return false;
}
 
// Check if maximum idle time is reached
if ( $this->idle > 0
&& isset($this->session['idle'])
&& ($this->session['idle'] + $this->idle) < time()) {
$this->idled = true;
$this->status = AUTH_IDLED;
$this->logout();
return false;
}
 
if ( isset($this->session['registered'])
&& isset($this->session['username'])
&& $this->session['registered'] == true
&& $this->session['username'] != '') {
Auth::updateIdle();
 
if ($this->advancedsecurity) {
// Only Generate the challenge once
if($this->authChecks == 1) {
$this->session['challengecookieold'] = $this->session['challengecookie'];
$this->session['challengecookie'] = md5($this->session['challengekey'].microtime());
setcookie('authchallenge', $this->session['challengecookie']);
}
// Check for ip change
if ( isset($this->server['REMOTE_ADDR'])
&& $this->session['sessionip'] != $this->server['REMOTE_ADDR']) {
// Check if the IP of the user has changed, if so we
// assume a man in the middle attack and log him out
$this->expired = true;
$this->status = AUTH_SECURITY_BREACH;
$this->logout();
return false;
}
// Check for useragent change
if ( isset($this->server['HTTP_USER_AGENT'])
&& $this->session['sessionuseragent'] != $this->server['HTTP_USER_AGENT']) {
// Check if the User-Agent of the user has changed, if
// so we assume a man in the middle attack and log him out
$this->expired = true;
$this->status = AUTH_SECURITY_BREACH;
$this->logout();
return false;
}
// Check challenge cookie here, if challengecookieold is not set
// this is the first time and check is skipped
// TODO when user open two pages similtaneuly (open in new window,open
// in tab) auth breach is caused find out a way around that if possible
if ( isset($this->session['challengecookieold'])
&& $this->session['challengecookieold'] != $this->cookie['authchallenge']) {
$this->expired = true;
$this->status = AUTH_SECURITY_BREACH;
$this->logout();
$this->login();
return false;
}
}
 
if (is_callable($this->checkAuthCallback)) {
$checkCallback = call_user_func_array($this->checkAuthCallback, array($this->username, &$this));
if ($checkCallback == false) {
$this->expired = true;
$this->status = AUTH_CALLBACK_ABORT;
$this->logout();
return false;
}
}
 
return true;
}
}
return false;
}
 
// }}}
// {{{ staticCheckAuth() [static]
 
/**
* Statically checks if there is a session with valid auth information.
*
* @access public
* @see checkAuth
* @return boolean Whether or not the user is authenticated.
* @static
*/
function staticCheckAuth($options = null)
{
static $staticAuth;
if(!isset($staticAuth)) {
$staticAuth = new Auth('null', $options);
}
return $staticAuth->checkAuth();
}
 
// }}}
// {{{ getAuth()
 
/**
* Has the user been authenticated?
*
* @access public
* @return bool True if the user is logged in, otherwise false.
*/
function getAuth()
{
return $this->checkAuth();
}
 
// }}}
// {{{ logout()
 
/**
* Logout function
*
* This function clears any auth tokens in the currently
* active session and executes the logout callback function,
* if any
*
* @access public
* @return void
*/
function logout()
{
if (is_callable($this->logoutCallback)) {
call_user_func_array($this->logoutCallback, array($this->session['username'], &$this));
}
 
$this->username = '';
$this->password = '';
$this->session = null;
}
 
// }}}
// {{{ updateIdle()
 
/**
* Update the idletime
*
* @access private
* @return void
*/
function updateIdle()
{
$this->session['idle'] = time();
}
 
// }}}
// {{{ getUsername()
 
/**
* Get the username
*
* @return string
* @access public
*/
function getUsername()
{
if (isset($this->session['username'])) {
return($this->session['username']);
}
return('');
}
 
// }}}
// {{{ getStatus()
 
/**
* Get the current status
*
* @return string
* @access public
*/
function getStatus()
{
return $this->status;
}
 
// }}}
// {{{ getPostUsernameField()
/**
* Gets the post varible used for the username
*
* @return string
* @access public
*/
function getPostUsernameField()
{
return($this->_postUsername);
}
 
// }}}
// {{{ getPostPasswordField()
 
/**
* Gets the post varible used for the username
*
* @return string
* @access public
*/
function getPostPasswordField()
{
return($this->_postPassword);
}
 
// }}}
// {{{ sessionValidThru()
 
/**
* Returns the time up to the session is valid
*
* @access public
* @return integer
*/
function sessionValidThru()
{
if (!isset($this->session['idle'])) {
return 0;
}
if ($this->idle == 0) {
return 0;
}
return ($this->session['idle'] + $this->idle);
}
 
// }}}
// {{{ listUsers()
 
/**
* List all users that are currently available in the storage
* container
*
* @access public
* @return array
*/
function listUsers()
{
$this->_loadStorage();
return $this->storage->listUsers();
}
 
// }}}
// {{{ addUser()
 
/**
* Add user to the storage container
*
* @access public
* @param string Username
* @param string Password
* @param mixed Additional parameters
* @return mixed True on success, PEAR error object on error
* and AUTH_METHOD_NOT_SUPPORTED otherwise.
*/
function addUser($username, $password, $additional = '')
{
$this->_loadStorage();
return $this->storage->addUser($username, $password, $additional);
}
 
// }}}
// {{{ removeUser()
 
/**
* Remove user from the storage container
*
* @access public
* @param string Username
* @return mixed True on success, PEAR error object on error
* and AUTH_METHOD_NOT_SUPPORTED otherwise.
*/
function removeUser($username)
{
$this->_loadStorage();
return $this->storage->removeUser($username);
}
 
// }}}
// {{{ changePassword()
 
/**
* Change password for user in the storage container
*
* @access public
* @param string Username
* @param string The new password
* @return mixed True on success, PEAR error object on error
* and AUTH_METHOD_NOT_SUPPORTED otherwise.
*/
function changePassword($username, $password)
{
$this->_loadStorage();
return $this->storage->changePassword($username, $password);
}
 
// }}}
 
}
?>
/trunk/api/pear/A_LIRE.txt
1,7 → 1,7
Liste des packages PEAR :
==============================
Package Version State
Auth 1.2.3 stable
Auth 1.4.3 stable
Calendar 0.5.2 beta
DB 1.7.6 stable
HTML_Common 1.2.1 stable
12,5 → 12,5
Net_SMTP 1.2.6 stable
Net_Socket 1.0.6 stable
Net_URL 1.0.14 stable
PEAR 1.3.5 stable
PEAR 1.4.11 stable
Text_Wiki 1.0.0 stable
/trunk/api/pear/DB.php
20,7 → 20,7
* @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: DB.php,v 1.2 2005-09-20 17:01:22 ddelon Exp $
* @version CVS: $Id: DB.php,v 1.3 2006-12-14 15:04:28 jp_milcent Exp $
* @link http://pear.php.net/package/DB
*/
 
426,7 → 426,7
* @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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
*/
class DB
572,7 → 572,7
*/
function apiVersion()
{
return '@package_version@';
return '1.7.6';
}
 
// }}}
759,7 → 759,7
 
// Get phptype and dbsyntax
// $str => phptype(dbsyntax)
if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
if (preg_match('/^(.+?)\((.*?)\)$/', $str, $arr)) {
$parsed['phptype'] = $arr[1];
$parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2];
} else {
862,7 → 862,7
* @author Stig Bakken <ssb@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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
*/
class DB_Error extends PEAR_Error
909,7 → 909,7
* @author Stig Bakken <ssb@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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
*/
class DB_result
1351,7 → 1351,7
* @author Stig Bakken <ssb@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@
* @version Release: 1.7.6
* @link http://pear.php.net/package/DB
* @see DB_common::setFetchMode()
*/