* Invision Power Services * IP.Board v3.3.4 * Very lightweight simple, basic node class * Owner: Matt Mecham * Last Updated: $Date: 2012-06-12 10:14:49 -0400 (Tue, 12 Jun 2012) $ * * @author $Author: bfarber $ * @copyright (c) 2001 - 2009 Invision Power Services, Inc. * @license http://www.invisionpower.com/company/standards.php#license * @package IP.Board * @link http://www.invisionpower.com * @since 9th March 2005 11:03 * @version $Revision: 10914 $ */ class classNodes { /** * Registry object * * @access protected * @var object */ protected $registry; /** * Database object * * @access protected * @var object */ protected $DB; /** * Settings object * * @access protected * @var object */ protected $settings; /** * Request object * * @access protected * @var object */ protected $request; /** * Language object * * @access protected * @var object */ protected $lang; /** * Member object * * @access protected * @var object */ protected $member; protected $memberData; /** * Cache object * * @access protected * @var object */ protected $cache; protected $caches; /** * Item Cache * * @access protected * @var array */ protected $_nodeCache = array(); /** * FIELD: item ID * * @access protected * @var string */ protected $_sqlPrimaryID = ''; /** * FIELD: parent ID * * @access protected * @var string */ protected $_sqlParentID = ''; /** * FIELD: Is folder flag * * @access protected * @var string */ protected $_sqlFolderFlag = ''; /** * FIELD: Item name field * * @access protected * @var string */ protected $_sqlTitle = ''; /** * SQL Field: From * * @access protected * @var string */ protected $_sqlTable = ''; /** * SQL Fields: Additional 'where' information * * @access protected * @var string */ protected $_sqlWhere = ''; /** * SQL Fields: Select information * * @access protected * @var string */ protected $_sqlSelect = '*'; /** * SQL Fields: Node path * * @access protected * @var string */ protected $_sqlNodeLevel = ''; /** * SQL Fields: Node left * * @access protected * @var string */ protected $_sqlNodeLeft = ''; /** * SQL Fields: Node right * * @access protected * @var string */ protected $_sqlNodeRight = ''; /** * SQL Fields: Order field * * @access protected * @var string */ protected $_sqlOrder = ''; /** * SQL Fields: Pass IPB formatted add_join array * * @access protected * @var string */ protected $_sqlJoins = ''; /** * SQL Fields: SQL limit * * @access protected * @var string */ protected $_sqlLimit = null; /** * SQL Fields: Tbl prefix used when using joins * * @access protected * @var string */ protected $_sqlTblPrefix = ''; /** * Debug cache * Stores random debug messages and whatnot * * @access protected * @var array */ protected $_debugMsgs = array(); /** * Method constructor * * @access public * @param object Registry Object * @param array Array of settings: * [ 'sqlPrimaryID' (the SQL table field for the item ID) * 'sqlParentID' (the SQL table field for the parent ID) * 'sqlTitle' (the SQL table field for the item title) * 'sqlSelect' (the SQL table select fields (* by default)) * 'sqlTable' (the SQL table name) * 'sqlNodeLevel' (the SQL table field for the node level) * 'sqlNodeLeft' (the SQL table field for node left) * 'sqlNodeRight' (the SQL table field for node right) * 'sqlWhere' (Any additional 'where' information *Optional) ] * @return @e void */ public function __construct() { /* Make object */ $this->registry = ipsRegistry::instance(); $this->DB = $this->registry->DB(); $this->settings =& $this->registry->fetchSettings(); $this->request =& $this->registry->fetchRequest(); $this->member = $this->registry->member(); $this->memberData =& $this->registry->member()->fetchMemberData(); $this->cache = $this->registry->cache(); $this->caches =& $this->registry->cache()->fetchCaches(); } public function init( array $settings ) { /* Sort out settings */ $this->_sqlPrimaryID = isset( $settings['sqlPrimaryID'] ) ? $settings['sqlPrimaryID'] : ''; $this->_sqlParentID = isset( $settings['sqlParentID'] ) ? $settings['sqlParentID'] : ''; $this->_sqlNodeLevel = isset( $settings['sqlNodeLevel'] ) ? $settings['sqlNodeLevel'] : ''; $this->_sqlNodeRight = isset( $settings['sqlNodeRight'] ) ? $settings['sqlNodeRight'] : ''; $this->_sqlNodeLeft = isset( $settings['sqlNodeLeft'] ) ? $settings['sqlNodeLeft'] : ''; $this->_sqlOrder = isset( $settings['sqlOrder'] ) ? $settings['sqlOrder'] : ''; $this->_sqlTitle = isset( $settings['sqlTitle'] ) ? $settings['sqlTitle'] : ''; $this->_sqlTable = isset( $settings['sqlTable'] ) ? $settings['sqlTable'] : ''; $this->_sqlWhere = isset( $settings['sqlWhere'] ) ? $settings['sqlWhere'] : ''; $this->_sqlSelect = isset( $settings['sqlSelect'] ) ? $settings['sqlSelect'] : $this->_sqlPrimaryID . ', ' . $this->_sqlParentID . ',' . $this->_sqlTitle . ',' . $this->_sqlNodeLeft . ',' . $this->_sqlOrder . ',' . $this->_sqlNodeLevel . ',' . $this->_sqlNodeRight; $this->_sqlJoins = isset( $settings['sqlJoins'] ) ? $settings['sqlJoins'] : array(); $this->_sqlTblPrefix = isset( $settings['sqlTblPrefix'] ) ? $settings['sqlTblPrefix'] : 'xyxy'; $this->_sqlLimit = isset( $settings['sqlLimit'] ) ? $settings['sqlLimit'] : null; } /** * Allow set/get data * */ public function __call( $method, $arguments ) { $firstBit = substr( $method, 0, 3 ); $theRest = substr( $method, 3 ); if ( $firstBit == 'set' ) { switch( $theRest ) { case 'Order': $this->_sqlOrder = $arguments[0]; break; case 'Where': $this->_sqlWhere = $arguments[0]; break; case 'Select': $this->_sqlSelect = $arguments[0]; break; case 'Limit': $this->_sqlLimit = $arguments[0]; break; case 'Joins': $this->_sqlJoins = $arguments[0]; break; case 'TblPrefix': $this->_sqlTblPrefix = $arguments[0]; break; } } else if ( $firstBit == 'get' ) { switch( $theRest ) { case 'Order': return $this->_sqlOrder; break; case 'Where': return $this->_sqlWhere; break; case 'Select': return $this->_sqlSelect; break; case 'Limit': return $this->_sqlLimit; break; case 'Joins': return $this->_sqlJoins; break; case 'TblPrefix': return $this->_sqlTblPrefix; break; } } else { trigger_error( "Call to undefined function " . $method . " in classNodes.php" ); } } /** * Fetches a single node's content * * @access public * @param int * @return array */ public function fetchNodeContent( $nodeId ) { $fetch = ( is_numeric( $nodeId ) ) ? '=' . intval( $nodeId ) : ' IN (' . implode( ',', IPSLib::cleanIntArray( (array) $nodeId ) ) . ')'; $gotCache = array(); if ( ! is_numeric( $nodeId ) ) { foreach( $nodeId as $id ) { if ( isset( $this->_nodeCache[ $id ] ) ) { $gotCache[ $id ] = $this->_nodeCache[ $id ]; } } if ( count( $gotCache ) == count( $nodeId ) ) { return $gotCache; } else { $gotCache = array(); } } else { if ( isset( $this->_nodeCache[ $nodeId ] ) ) { return $this->_nodeCache[ $nodeId ]; } } /* Still here? */ if ( is_numeric( $nodeId ) OR ! count( $gotCache ) ) { if ( is_array( $this->_sqlJoins ) and count( $this->_sqlJoins ) ) { $this->DB->build( array( 'select' => $this->_sqlTblPrefix . '.' . $this->_sqlSelect, 'from' => array( $this->_sqlTable => $this->_sqlTblPrefix ), 'where' => $this->_sqlTblPrefix . '.' . $this->_sqlPrimaryID . $fetch, 'add_join' => $this->_sqlJoins ) ); } else { $this->DB->build( array( 'select' => $this->_sqlSelect, 'from' => $this->_sqlTable, 'where' => $this->_sqlPrimaryID . $fetch ) ); } $o = $this->DB->execute(); while( $row = $this->DB->fetch( $o ) ) { $return[ $row[ $this->_sqlPrimaryID ] ] = $row; $this->_nodeCache[ $row[ $this->_sqlPrimaryID ] ] = $row; } } return ( is_numeric( $nodeId ) ) ? $this->_nodeCache[ $nodeId ] : $return; } /** * Fetches left/right/level data * * @access public * @param int * @return array - left, right, level */ public function fetchNodeInfo( $nodeId ) { /* If this is ID 0... */ if ( $nodeId == 0 ) { $left = $this->DB->buildAndFetch( array( 'select' => 'MIN(' . $this->_sqlNodeLeft . ') as nleft', 'from' => $this->_sqlTable, 'where' => $this->_sqlNodeLevel . '=0' ) ); $right = $this->DB->buildAndFetch( array( 'select' => 'MAX(' . $this->_sqlNodeRight . ') as nright', 'from' => $this->_sqlTable, 'where' => $this->_sqlNodeLevel . '=0' ) ); return array( $left['nleft'], $right['nright'], 0 ); } else { $data = $this->fetchNodeContent( $nodeId ); return array( $data[ $this->_sqlNodeLeft ], $data[ $this->_sqlNodeRight ], $data[ $this->_sqlNodeLevel ] ); } } /** * Fetches parent info * * @access public * @param int Node id * @param string Where data * @return DB row */ public function fetchParentInfo( $nodeId, $where = '') { /* Init where */ $where = ( $where ) ? $where : $this->_sqlWhere; /* Fetch noded data */ $nodeData = $this->fetchNodeInfo( $nodeId ); if ( count( $nodeData ) ) { return false; } /* Lets go */ list( $leftId, $rightId, $level ) = $nodeData; /* Jump up a level */ $level--; $where = ( $where ) ? ' AND (' . $where . ')' : ''; /* Fetch data */ if ( is_array( $this->_sqlJoins ) and count( $this->_sqlJoins ) ) { $this->DB->build( array( 'select' => $this->_sqlTblPrefix . '.' . $this->_sqlSelect, 'from' => array( $this->_sqlTable => $this->_sqlTblPrefix ), 'where' => $this->_sqlTblPrefix . '.' . $this->_sqlNodeLeft . ' < ' . $leftId . ' AND ' . $this->_sqlTblPrefix . '.' . $this->_sqlNodeLeft . ' > ' . $rightId . ' AND ' . $this->_sqlTblPrefix . '.' . $this->_sqlNodeLevel . ' = ' . $level . $where, 'order' => $this->_sqlTblPrefix . '.' . $this->_sqlNodeLeft, 'add_join' => $this->_sqlJoins ) ); } else { $this->DB->build( array( 'select' => $this->_sqlSelect, 'from' => $this->_sqlTable, 'where' => $this->_sqlNodeLeft . ' < ' . $leftId . ' AND ' . $this->_sqlNodeLeft . ' > ' . $rightId . ' AND ' . $this->_sqlNodeLevel . ' = ' . $level . $where, 'order' => $this->_sqlNodeLeft ) ); } $o = $this->DB->execute(); $this->_nodeCache[ $nodeId ] = $row = $this->DB->fetch( $o ); return ( count( $row ) ) ? $row : false; } /** * Returns DB resource to all nodes * * @access public * @param string Where string * @return boolean */ public function fetchTree( $where='' ) { /* Init where */ $where = ( $where ) ? $where : $this->_sqlWhere; /* Fetch data */ if ( is_array( $this->_sqlJoins ) and count( $this->_sqlJoins ) OR ! empty( $this->_sqlTblPrefix ) ) { $_order = $this->getOrder(); $order = ( ! empty( $_order ) ) ? $_order : $this->_sqlTblPrefix . '.' . $this->_sqlNodeLeft; $this->DB->build( array( 'select' => $this->_sqlTblPrefix . '.' . $this->_sqlSelect, 'from' => array( $this->_sqlTable => $this->_sqlTblPrefix ), 'where' => $where, 'limit' => $this->getLimit(), 'order' => $order, 'add_join' => $this->_sqlJoins ) ); } else { $_order = $this->getOrder(); $order = ( ! empty( $_order ) ) ? $_order : $this->_sqlNodeLeft; $this->DB->build( array( 'select' => $this->_sqlSelect, 'from' => $this->_sqlTable, 'where' => $where, 'limit' => $this->getLimit(), 'order' => $order ) ); } /* Set up handler */ $this->_dbHandle = $this->DB->execute(); return true; } /** * Fetch the branch starting with $nodeId * * @access public * @param int Node Id * @param string Where clause * @return boolean */ public function fetchBranch( $nodeId, $inWhere='' ) { /* Init where */ $inWhere = ( $inWhere ) ? $inWhere : $this->_sqlWhere; /* Set up tbl prefix */ $a = ( is_array( $this->_sqlJoins ) and count( $this->_sqlJoins ) ) ? $this->_sqlTblPrefix : 'aaa'; /* Set up select */ $select = $a . '.' . $this->_sqlSelect; $where = 'bbb.' . $this->_sqlPrimaryID . '=' . $nodeId . ' AND ' . $a . '.' . $this->_sqlNodeLeft . ' >= bbb.' . $this->_sqlNodeLeft . ' AND ' . $a . '.' . $this->_sqlNodeRight . ' <= bbb.' . $this->_sqlNodeRight; /* Custom where? */ $where .= ( $inWhere ) ? ' AND (' . $inWhere . ')' : ''; $_joins = $this->_sqlJoins; $_joins[] = array( 'from' => array( $this->_sqlTable => 'bbb' ), 'type' => 'inner' ); $this->DB->build( array( 'select' => $select, 'from' => array( $this->_sqlTable => $a ), 'where' => $where, 'order' => $a . '.' . $this->_sqlNodeLeft, 'add_join' => $_joins ) ); /* Set up handler */ $this->_dbHandle = $this->DB->execute(); return true; } /** * Fetch the parents of $nodeId * * @access public * @param int Node Id * @param string Where clause * @return boolean */ public function fetchParents( $nodeId, $inWhere='' ) { /* Init where */ $inWhere = ( $inWhere ) ? $inWhere : $this->_sqlWhere; /* Set up tbl prefix */ $a = ( is_array( $this->_sqlJoins ) and count( $this->_sqlJoins ) ) ? $this->_sqlTblPrefix : 'aaa'; /* Set up select */ $select = $a . '.' . $this->_sqlSelect; $where = 'bbb.' . $this->_sqlPrimaryID . '=' . $nodeId . ' AND bbb.' . $this->_sqlNodeLeft . ' BETWEEN ' . $a . '.' . $this->_sqlNodeLeft . ' AND ' . $a . '.' . $this->_sqlNodeRight; /* Custom where? */ $where .= ( $inWhere ) ? ' AND (' . $inWhere . ')' : ''; $_joins = $this->_sqlJoins; $_joins[] = array( 'from' => array( $this->_sqlTable => 'bbb' ), 'type' => 'inner' ); $this->DB->build( array( 'select' => $select, 'from' => array( $this->_sqlTable => $a ), 'where' => $where, 'order' => $a . '.' . $this->_sqlNodeLeft, 'add_join' => $_joins ) ); /* Set up handler */ $this->_dbHandle = $this->DB->execute(); return true; } /** * Fetch the children of $nodeId * * @access public * @param int Node Id * @param string Where clause * @return boolean */ public function fetchChildren( $nodeId, $inWhere='' ) { /* Init where */ $inWhere = ( $inWhere ) ? $inWhere : $this->_sqlWhere; /* Set up tbl prefix */ $a = ( is_array( $this->_sqlJoins ) and count( $this->_sqlJoins ) ) ? $this->_sqlTblPrefix : 'aaa'; /* Set up select */ $select = $a . '.' . $this->_sqlSelect; if ( $nodeId == 0 ) { $where = '(1=1)'; } else { $where = 'bbb.' . $this->_sqlPrimaryID . '=' . $nodeId . ' AND ' . $a . '.' . $this->_sqlNodeLeft . ' > ' . 'bbb.' . $this->_sqlNodeLeft . ' AND ' . $a . '.' . $this->_sqlNodeRight . ' < bbb.' . $this->_sqlNodeRight; } /* Custom where? */ $where .= ( $inWhere ) ? ' AND (' . $inWhere . ')' : ''; $_joins = $this->_sqlJoins; if ( $nodeId > 0 ) { $_joins[] = array( 'from' => array( $this->_sqlTable => 'bbb' ), 'type' => 'inner' ); } $this->DB->build( array( 'select' => $select, 'from' => array( $this->_sqlTable => $a ), 'where' => $where, 'order' => $a . '.' . $this->_sqlNodeLeft, 'limit' => $this->getLimit(), 'add_join' => $_joins ) ); /* Set up handler */ $this->_dbHandle = $this->DB->execute(); return true; } /** * Returns an item with parents and children * * @access public * @param int Node Id * @param string Where clause * @return boolean */ public function fetchSlice( $nodeId, $inWhere='' ) { /* Init where */ $inWhere = ( $inWhere ) ? $inWhere : $this->_sqlWhere; /* Init */ $i = 0; /* Set up tbl prefix */ $a = ( is_array( $this->_sqlJoins ) and count( $this->_sqlJoins ) ) ? $this->_sqlTblPrefix : 'aaa'; /* Select */ $select = $a . '.' . $this->_sqlNodeLeft . ', ' . $a . '.' . $this->_sqlNodeRight . ', ' . $a . '.' . $this->_sqlNodeLevel; $where = 'bbb.' . $this->_sqlPrimaryID . '=' . $nodeId . ' AND bbb.' . $this->_sqlNodeLeft . ' BETWEEN ' . $a . '.' . $this->_sqlNodeLeft . ' AND ' . $a . '.' . $this->_sqlNodeRight; /* Custom where? */ $where .= ( $inWhere ) ? ' AND (' . $inWhere . ')' : ''; $_joins = $this->_sqlJoins; $_joins[] = array( 'from' => array( $this->_sqlTable => 'bbb' ), 'type' => 'inner' ); $this->DB->build( array( 'select' => $select, 'from' => array( $this->_sqlTable => $a ), 'where' => $where, 'order' => $a . '.' . $this->_sqlNodeLeft, 'add_join' => $_joins ) ); /* Set up handler */ $this->_dbHandle = $this->DB->execute(); $len = $this->recordCount(); /* Set up tbl prefix */ $a = ( is_array( $this->_sqlJoins ) and count( $this->_sqlJoins ) ) ? $this->_sqlTblPrefix : 'aaa'; $_where = ( $inWhere ) ? ' AND (' . $inWhere . ')' : ''; /* start main where */ $where = '(' . $a . '.' . $this->_sqlNodeLevel . ' =1'; while( $row = $this->DB->fetch( $this->_dbHandle ) ) { if ( ( ++$i == $len ) && ( $row[ $this->_sqlNodeLeft ] + 1 ) == $row[ $this->_sqlNodeRight ] ) { break; } $where .= ' OR (' . $a .'.'. $this->_sqlNodeLevel . ' = ' . ( $row[$this->_sqlNodeLevel] + 1 ) . ' AND ' . $a .'.'. $this->_sqlNodeLeft . ' > ' . $row[$this->_sqlNodeLeft] . ' AND ' . $a .'.'. $this->_sqlNodeRight . ' < ' . $row[$this->_sqlNodeRight] . ')'; } $where .= ")" . $_where; /* Fetch data */ if ( is_array( $this->_sqlJoins ) and count( $this->_sqlJoins ) ) { $this->DB->build( array( 'select' => $this->_sqlTblPrefix . '.' . $this->_sqlSelect, 'from' => array( $this->_sqlTable => $this->_sqlTblPrefix ), 'where' => $where, 'order' => $this->_sqlTblPrefix . '.' . $this->_sqlNodeLeft, 'add_join' => $this->_sqlJoins ) ); } else { $this->DB->build( array( 'select' => $a . '.' . $this->_sqlSelect, 'from' => $this->_sqlTable . ' ' . $a, 'where' => $where, 'order' => $a . '.' . $this->_sqlNodeLeft ) ); } /* Set up handler */ $this->_dbHandle = $this->DB->execute(); return true; } /** * Is descendant of * Checks to see if a node is a descendant of another node. Simple really * @param array node (descendant) * @param array node (root parent) */ public function isDescendantOf( $desc, $parent ) { if ( ! isset( $desc[ $this->_sqlNodeLeft ] ) && ! isset( $desc[ $this->_sqlNodeRight ] ) ) { return false; } if ( ! isset( $parent[ $this->_sqlNodeLeft ] ) && ! isset( $parent[ $this->_sqlNodeRight ] ) ) { return false; } if ( $desc[ $this->_sqlNodeLeft ] > $parent[ $this->_sqlNodeLeft ] && $desc[ $this->_sqlNodeLeft ] < $parent[ $this->_sqlNodeRight ] ) { return true; } else { return false; } } /** * Add node * * @param int Parent Node ID * @param array Array of fields to insert * @return int Insert ID */ public function addNode( $parentId, $data ) { $nInfo = $this->fetchNodeInfo( $parentId ); if ( ! isset( $nInfo[0] ) OR empty( $parentId ) ) { /* Add without parents? */ if ( empty( $parentId ) ) { /* Find the highest right value */ $right = $this->DB->buildAndFetch( array( 'select' => '*', 'from' => $this->_sqlTable, 'order' => $this->_sqlNodeRight . ' DESC', 'limit' => array( 0, 1 ) ) ); $leftId = $right[ $this->_sqlNodeRight ] + 1; $rightId = $right[ $this->_sqlNodeRight ] + 2; $level = 0; $data[ $this->_sqlNodeLeft ] = $leftId; $data[ $this->_sqlNodeRight ] = $rightId; $data[ $this->_sqlNodeLevel ] = $level; } else { return false; } } else { list( $leftId, $rightId, $level ) = $nInfo; $data[ $this->_sqlNodeLeft ] = $rightId; $data[ $this->_sqlNodeRight ] = ( $rightId + 1 ); $data[ $this->_sqlNodeLevel ] = ( $level + 1 ); /* Set data: Shift over the elements */ $set = $this->_sqlNodeLeft . ' = CASE WHEN ' . $this->_sqlNodeLeft . '>' . $rightId . ' THEN ' . $this->_sqlNodeLeft . '+2 ELSE ' . $this->_sqlNodeLeft . ' END, ' . $this->_sqlNodeRight . ' = CASE WHEN ' . $this->_sqlNodeRight . '>=' . $rightId . ' THEN ' . $this->_sqlNodeRight . '+2 ELSE ' . $this->_sqlNodeRight . ' END '; /* Pre formatted */ $this->DB->update( $this->_sqlTable, $set, $this->_sqlNodeRight . '>=' . $rightId, false, true ); } /* Insert new row */ $this->DB->insert( $this->_sqlTable, $data ); return $this->DB->getInsertId(); } /** * Move and node and all children to a new parent node * * @param int Node ID * @param int New parent ID * @return int Insert ID */ public function moveNode( $nodeId, $newParentId ) { $nInfo = $this->fetchNodeInfo( $nodeId ); if ( ! isset( $nInfo[0] ) ) { return false; } list( $leftId, $rightId, $level ) = $nInfo; $nInfo = $this->fetchNodeInfo( $newParentId ); if ( ! isset( $nInfo[0] ) ) { return false; } list( $leftIdP, $rightIdP, $levelP ) = $nInfo; /* Make sure it can't be moved into itself or into an invalid node */ if ( $nodeId == $newParentId || $leftId == $leftIdP || ( $leftIdP >= $leftId && $leftIdP <= $rightId ) || ( $newParentId > 0 && $level == $levelP + 1 && $leftId > $leftIdP && $rightId < $rightIdP ) ) { return false; } if ( $newParentId == 0 ) { /* set the math right */ $levelP = -1; $set = $this->_sqlNodeLevel . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_sqlNodeLevel . sprintf( '%+d', - ( $level - 1 ) + $levelP ) . ' ELSE ' . $this->_sqlNodeLevel . ' END, ' . $this->_sqlNodeLeft . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $rightId . ' AND ' . $rightIdP . ' THEN ' . $this->_sqlNodeLeft . '-' . ( $rightId - $leftId + 1 ) . ' ' . 'WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_sqlNodeLeft . '+' . ( $rightIdP - $rightId ) . ' ELSE ' . $this->_sqlNodeLeft . ' END, ' . $this->_sqlNodeRight . ' = CASE WHEN ' . $this->_sqlNodeRight . ' BETWEEN ' . ( $rightId + 1 ) . ' AND ' . ( $rightIdP ) . ' THEN ' . $this->_sqlNodeRight . '-' . ( $rightId - $leftId + 1 ) . ' ' . 'WHEN ' . $this->_sqlNodeRight . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_sqlNodeRight . '+' . ( $rightIdP - $rightId ) . ' ELSE ' . $this->_sqlNodeRight . ' END '; $whr = '(' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId . ' AND ' . $rightIdP . ' ' . 'OR ' . $this->_sqlNodeRight . ' BETWEEN ' . $leftId . ' AND ' . $rightIdP . ')'; } else if ( $leftIdP < $leftId && $rightIdP > $rightId && ( $levelP < $level - 1 ) ) { $set = $this->_sqlNodeLevel . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_sqlNodeLevel . sprintf('%+d', - ( $level - 1) + $levelP ) . ' ELSE ' . $this->_sqlNodeLevel . ' END, ' . $this->_sqlNodeRight . ' = CASE WHEN ' . $this->_sqlNodeRight . ' BETWEEN ' . ( $rightId + 1 ) . ' AND ' . ( $rightIdP - 1 ) . ' THEN ' . $this->_sqlNodeRight . '-' . ( $rightId - $leftId + 1) . ' ' . 'WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_sqlNodeRight . '+' . ( ( ( $rightIdP - $rightId - $level + $levelP ) / 2 ) * 2 + $level - $levelP - 1 ) . ' ELSE ' . $this->_sqlNodeRight . ' END, ' . $this->_sqlNodeLeft . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . ( $rightId + 1 ) . ' AND ' . ( $rightIdP - 1 ) . ' THEN ' . $this->_sqlNodeLeft . '-' . ( $rightId - $leftId + 1 ) . ' ' . 'WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_sqlNodeLeft . '+' . ( ( ( $rightIdP - $rightId - $level + $levelP ) / 2 ) * 2 + $level - $levelP - 1 ) . ' ELSE ' . $this->_sqlNodeLeft . ' END '; $whr = $this->_sqlNodeLeft . ' BETWEEN ' . ( $leftIdP + 1 ) . ' AND ' . ( $rightIdP - 1 ); } else if ( $leftIdP < $leftId ) { $set = $this->_sqlNodeLevel . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_sqlNodeLevel . sprintf( '%+d', - ( $level - 1 ) + $levelP ) . ' ELSE ' . $this->_sqlNodeLevel . ' END, ' . $this->_sqlNodeLeft . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $rightIdP . ' AND ' . ( $leftId - 1 ) . ' THEN ' . $this->_sqlNodeLeft . '+' . ( $rightId - $leftId + 1 ) . ' ' . 'WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_sqlNodeLeft . '-' . ( $leftId - $rightIdP ) . ' ELSE ' . $this->_sqlNodeLeft . ' END, ' . $this->_sqlNodeRight . ' = CASE WHEN ' . $this->_sqlNodeRight . ' BETWEEN ' . $rightIdP . ' AND ' . $leftId . ' THEN ' . $this->_sqlNodeRight . '+' . ( $rightId - $leftId + 1) . ' ' . 'WHEN ' . $this->_sqlNodeRight . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_sqlNodeRight . '-' . ( $leftId - $rightIdP ) . ' ELSE ' . $this->_sqlNodeRight . ' END '; $whr = '(' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftIdP . ' AND ' . $rightId. ' ' . 'OR ' . $this->_sqlNodeRight . ' BETWEEN ' . $leftIdP . ' AND ' . $rightId . ')'; } else { $set = $this->_sqlNodeLevel . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_sqlNodeLevel . sprintf( '%+d', - ( $level - 1 ) + $levelP ) . ' ELSE ' . $this->_sqlNodeLevel . ' END, ' . $this->_sqlNodeLeft . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $rightId . ' AND ' . $rightIdP . ' THEN ' . $this->_sqlNodeLeft . '-' . ( $rightId - $leftId + 1 ) . ' ' . 'WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_sqlNodeLeft . '+' . ( $rightIdP - 1 - $rightId ) . ' ELSE ' . $this->_sqlNodeLeft . ' END, ' . $this->_sqlNodeRight . ' = CASE WHEN ' . $this->_sqlNodeRight . ' BETWEEN ' . ( $rightId + 1 ) . ' AND ' . ( $rightIdP - 1 ) . ' THEN ' . $this->_sqlNodeRight . '-' . ( $rightId - $leftId + 1 ) . ' ' . 'WHEN ' . $this->_sqlNodeRight . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_sqlNodeRight . '+' . ( $rightIdP - 1 - $rightId ) . ' ELSE ' . $this->_sqlNodeRight . ' END '; $whr = '(' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId . ' AND ' . $rightIdP . ' ' . 'OR ' . $this->_sqlNodeRight . ' BETWEEN ' . $leftId . ' AND ' . $rightIdP . ')'; } //print "
";
//print wordwrap( $set, 100, '
' );
//print "\n" . wordwrap( $whr, 100, '
' );
//exit();
/* Pre formatted */
$this->DB->update( $this->_sqlTable, $set, $whr, false, true );
return true;
}
/**
* Delete a node. Any children are moved to root.
*
* @param int Node ID
* @return boolean
*/
public function deleteNode( $nodeId )
{
$nInfo = $this->fetchNodeInfo( $nodeId );
if ( ! isset( $nInfo[0] ) )
{
return false;
}
list( $leftId, $rightId, $level ) = $nInfo;
/* Delete from DB */
$this->DB->delete( $this->_sqlTable, $this->_sqlPrimaryID . '=' . intval( $nodeId ) );
/* Move other nodes down */
$set = $this->_sqlNodeLevel . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_sqlNodeLevel . ' - 1 ELSE ' . $this->_sqlNodeLevel . ' END, '
. $this->_sqlNodeRight . ' = CASE WHEN ' . $this->_sqlNodeRight . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_sqlNodeRight . ' - 1 '
. 'WHEN ' . $this->_sqlNodeRight . ' > ' . $rightId . ' THEN ' . $this->_sqlNodeRight . ' - 2 ELSE ' . $this->_sqlNodeRight . ' END, '
. $this->_sqlNodeLeft . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_sqlNodeLeft . ' - 1 '
. 'WHEN ' . $this->_sqlNodeLeft . ' > ' . $rightId . ' THEN ' . $this->_sqlNodeLeft . ' - 2 ELSE ' . $this->_sqlNodeLeft . ' END ';
$whr = $this->_sqlNodeRight . ' > ' . $leftId;
/* Pre formatted */
$this->DB->update( $this->_sqlTable, $set, $whr, false, true );
return true;
}
/**
* Delete a node branch (node + children)
*
* @param int Node ID
* @return boolean
*/
public function deleteBranch( $nodeId )
{
$nInfo = $this->fetchNodeInfo( $nodeId );
if ( ! isset( $nInfo[0] ) )
{
return false;
}
list( $leftId, $rightId, $level ) = $nInfo;
/* Delete from DB */
$this->DB->delete( $this->_sqlTable, $this->_sqlNodeLeft . ' BETWEEN ' . $leftId . ' AND ' . $rightId );
/* Fix position */
$deltaId = ( ( $rightId - $leftId ) + 1 );
$set = $this->_sqlNodeLeft . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' > ' . $leftId.' THEN ' . $this->_sqlNodeLeft . ' - ' . $deltaId . ' ELSE ' . $this->_sqlNodeLeft . ' END, '
. $this->_sqlNodeRight . ' = CASE WHEN ' . $this->_sqlNodeRight . ' > ' . $leftId . ' THEN ' . $this->_sqlNodeRight . ' - ' . $deltaId . ' ELSE ' . $this->_sqlNodeRight . ' END ';
$whr = $this->_sqlNodeRight . ' > ' . $rightId;
/* Pre formatted */
$this->DB->update( $this->_sqlTable, $set, $whr, false, true );
return true;
}
/**
* Switch a node's position within it's level
*
* @access public
* @param int ID1
* @param int ID 2
* @param string position (before/after)
* @return boolean
*/
public function movePosition( $id1, $id2, $position='after')
{
$nInfo = $this->fetchNodeInfo( $id1 );
if ( ! isset( $nInfo[0] ) )
{
return false;
}
list( $leftId1, $rightId1, $level1 ) = $nInfo;
$nInfo = $this->fetchNodeInfo( $id2 );
if ( ! isset( $nInfo[0] ) )
{
return false;
}
list( $leftId2, $rightId2, $level2 ) = $nInfo;
/* On the level? */
if ( $level1 != $level2 )
{
return false;
}
/* Before? */
if ( $position == 'before' )
{
if ( $leftId1 > $leftId2 )
{
$set = $this->_sqlNodeRight . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId1 . ' AND ' . $rightId1 . ' THEN ' . $this->_sqlNodeRight . ' - ' . ($leftId1 - $leftId2) . ' '
. 'WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId2 . ' AND ' . ($leftId1 - 1) . ' THEN ' . $this->_sqlNodeRight . ' + ' . ( $rightId1 - $leftId1 + 1 ) . ' ELSE ' . $this->_sqlNodeRight . ' END, '
. $this->_sqlNodeLeft . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId1 . ' AND ' . $rightId1 . ' THEN ' . $this->_sqlNodeLeft . ' - ' . ($leftId1 - $leftId2) . ' '
. 'WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId2 . ' AND ' . ($leftId1 - 1) . ' THEN ' . $this->_sqlNodeLeft . ' + ' . ( $rightId1 - $leftId1 + 1 ) . ' ELSE ' . $this->_sqlNodeLeft . ' END ';
$whr = $this->_sqlNodeLeft . ' BETWEEN ' . $leftId2 . ' AND ' . $rightId1;
}
else
{
$set = $this->_sqlNodeRight . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId1 . ' AND ' . $rightId1 . ' THEN ' . $this->_sqlNodeRight . ' + ' . ( ( $leftId2 - $leftId1 ) - ( $rightId1 - $leftId1 + 1 ) ) . ' '
. 'WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . ( $rightId1 + 1 ) . ' AND ' . ($leftId2 - 1) . ' THEN ' . $this->_sqlNodeRight . ' - ' . ( ( $rightId1 - $leftId1 + 1 ) ) . ' ELSE ' . $this->_sqlNodeRight . ' END, '
. $this->_sqlNodeLeft . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId1 . ' AND ' . $rightId1 . ' THEN ' . $this->_sqlNodeLeft . ' + ' . ( ( $leftId2 - $leftId1 ) - ( $rightId1 - $leftId1 + 1 ) ) . ' '
. 'WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . ( $rightId1 + 1 ) . ' AND ' . ($leftId2 - 1) . ' THEN ' . $this->_sqlNodeLeft . ' - ' . ( $rightId1 - $leftId1 + 1 ) . ' ELSE ' . $this->_sqlNodeLeft . ' END ';
$whr = $this->_sqlNodeLeft . ' BETWEEN ' . $leftId1 . ' AND ' . ($leftId2 - 1);
}
}
if ( $position == 'after' )
{
if ( $leftId1 > $leftId2 )
{
$set = $this->_sqlNodeRight . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId1 . ' AND ' . $rightId1 . ' THEN ' . $this->_sqlNodeRight . ' - ' . ( $leftId1 - $leftId2 - ( $rightId2 - $leftId2 + 1 ) ) . ' '
. 'WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . ( $rightId2 + 1 ) . ' AND ' . ( $leftId1 - 1 ) . ' THEN ' . $this->_sqlNodeRight . ' + ' . ( $rightId1 - $leftId1 + 1) . ' ELSE ' . $this->_sqlNodeRight . ' END, '
. $this->_sqlNodeLeft . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId1 . ' AND ' . $rightId1 . ' THEN ' . $this->_sqlNodeLeft . ' - ' . ($leftId1 - $leftId2 - ( $rightId2 - $leftId2 + 1 ) ) . ' '
. 'WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . ( $rightId2 + 1 ) . ' AND ' . ( $leftId1 - 1 ) . ' THEN ' . $this->_sqlNodeLeft . ' + ' . ( $rightId1 - $leftId1 + 1) . ' ELSE ' . $this->_sqlNodeLeft . ' END ';
$whr = $this->_sqlNodeLeft . ' BETWEEN ' . ($rightId2 + 1) . ' AND ' . $rightId1;
}
else
{
$set = $this->_sqlNodeRight . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId1 . ' AND ' . $rightId1 . ' THEN ' . $this->_sqlNodeRight . ' + ' . ($rightId2 - $rightId1) . ' '
. 'WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . ( $rightId1 + 1 ) . ' AND ' . $rightId2 . ' THEN ' . $this->_sqlNodeRight . ' - ' . ( ( $rightId1 - $leftId1 + 1 ) ) . ' ELSE ' . $this->_sqlNodeRight . ' END, '
. $this->_sqlNodeLeft . ' = CASE WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . $leftId1 . ' AND ' . $rightId1 . ' THEN ' . $this->_sqlNodeLeft . ' + ' . ($rightId2 - $rightId1) . ' '
. 'WHEN ' . $this->_sqlNodeLeft . ' BETWEEN ' . ( $rightId1 + 1 ) . ' AND ' . $rightId2 . ' THEN ' . $this->_sqlNodeLeft . ' - ' . ( $rightId1 - $leftId1 + 1 ) . ' ELSE ' . $this->_sqlNodeLeft . ' END ';
$where = $this->_sqlNodeLeft . ' BETWEEN ' . $leftId1 . ' AND ' . $rightId2;
}
}
/* Pre formatted */
$this->DB->update( $this->_sqlTable, $set, $whr, false, true );
return true;
}
/**
* Returns record count
*
* @return int
*/
public function recordCount()
{
return $this->DB->getTotalRows( $this->_dbHandle );
}
/**
* Returns the current row.
*
* @return array
*/
public function nextRow()
{
return $this->DB->fetch( $this->_dbHandle );
}
/**
* Rebuild the tree
*
* @access public
* @param int Root ID
* @param int Left value
* @param int Depth
* @return nothing
*/
function rebuildTree( $root=0, $left=1, $depth=-1 )
{
$right = $left + 1;
$nDepth = $depth + 1;
$this->DB->build( array( 'select' => '*',
'from' => $this->_sqlTable,
'where' => $this->_sqlParentID . '=' . intval( $root ),
'order' => $this->_sqlOrder ) );
$o = $this->DB->execute();
while( $row = $this->DB->fetch( $o ) )
{
$right = $this->rebuildTree( $row[ $this->_sqlPrimaryID ], $right, $nDepth );
}
$this->DB->update( $this->_sqlTable, array( $this->_sqlNodeLeft => $left,
$this->_sqlNodeRight => $right,
$this->_sqlNodeLevel => $depth ), $this->_sqlPrimaryID . '=' . $root );
return $right + 1;
}
/**
* Drop the node cache
*
*/
public function dropCache()
{
$this->_nodeCache = array();
}
/**
* Show the entire tree as HITMUHL
*
* @access public
*/
public function debugShowTree()
{
/* Init */
$content = '';
/* reset params */
$this->setOrder(null);
$this->setWhere(null);
/* Load entire tree */
$this->fetchTree();
while( $item = $this->nextRow() )
{
$content .= str_repeat( ' ', 6 * $item[ $this->_sqlNodeLevel ] ) . '(' . $item[ $this->_sqlPrimaryID ] . ')' . $item[ $this->_sqlTitle ] . '
';
}
return $content;
}
}
/**
* Static class of functions for post processing node data
*/
class nodeFunctions
{
/**
* Create a nestled array of data from a tree or branch
*
* @access public
* @param array array of data from classNodes::fetch*
* @param string Field name for 'right'
* @return Array
*/
static public function nestle( array $array, $nDepthField='level' )
{
/* Init */
$nested = array();
$depths = array();
foreach( $array as $key => $data )
{
if ( $data[ $nDepthField ] == 0 )
{
$nested[ $key ] = $data;
$depths[ $data[ $nDepthField ] + 1] = $key;
}
else
{
$parent =& $nested;
for( $i = 1; $i <= ( $data[ $nDepthField ] ); $i++ )
{
$parent =& $parent[ $depths[ $i ] ];
}
$parent['_children_'][ $key ] = $data;
$depths[ $data[ $nDepthField ] + 1 ] = $key;
}
}
return $nested;
}
}