/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/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/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 "table2_id", this method will join |
* "WHERE table1.table2_id=table2.id". |
* 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/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/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/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/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 |
*/ |