| [ Index ] |
PHP Cross Reference of Limb3 |
[Summary view] [Print] [Text view]
1 <?php 2 /* 3 * Limb PHP Framework 4 * 5 * @link http://limb-project.com 6 * @copyright Copyright © 2004-2007 BIT(http://bit-creative.com) 7 * @license LGPL http://www.gnu.org/copyleft/lesser.html 8 */ 9 lmb_require('limb/core/src/lmbCollection.class.php'); 10 lmb_require('limb/dbal/src/criteria/lmbSQLFieldCriteria.class.php'); 11 lmb_require('limb/dbal/src/lmbTableGateway.class.php'); 12 lmb_require('limb/tree/src/lmbTree.interface.php'); 13 lmb_require('limb/tree/src/exception/lmbTreeException.class.php'); 14 lmb_require('limb/tree/src/exception/lmbTreeInvalidNodeException.class.php'); 15 lmb_require('limb/tree/src/exception/lmbTreeConsistencyException.class.php'); 16 17 /** 18 * class lmbMPTree. 19 * 20 * @package tree 21 * @version $Id$ 22 */ 23 class lmbMPTree implements lmbTree 24 { 25 protected $_conn; 26 27 protected $_system_columns = array(); 28 protected $_column_map; 29 protected $_select_columns = ''; 30 31 //convenience aliases for physical table column names 32 protected $_id; 33 protected $_parent_id; 34 protected $_identifier; 35 protected $_level; 36 protected $_path; 37 38 protected $_db_table; 39 40 function __construct($node_table = 'mp_tree', 41 $conn = null, 42 $column_map = array('id' => 'id', 'parent_id' => 'parent_id', 43 'level' => 'level', 'identifier' => 'identifier', 44 'path' => 'path')) 45 { 46 $this->_mapColumns($column_map); 47 48 if(!$conn) 49 $this->_conn = lmbToolkit :: instance()->getDefaultDbConnection(); 50 else 51 $this->_conn = $conn; 52 53 $this->_node_table = $node_table; 54 $this->_db_table = new lmbTableGateway($this->_node_table, $this->_conn); 55 } 56 57 protected function _mapColumns($column_map) 58 { 59 $this->_id = isset($column_map['id']) ? $column_map['id'] : 'id'; 60 $this->_parent_id = isset($column_map['parent_id']) ? $column_map['parent_id'] : 'parent_id'; 61 $this->_level = isset($column_map['level']) ? $column_map['level'] : 'level'; 62 $this->_identifier = isset($column_map['identifier']) ? $column_map['identifier'] : 'identifier'; 63 $this->_path = isset($column_map['path']) ? $column_map['path'] : 'path'; 64 65 $this->_system_columns = array($this->_id, $this->_parent_id, $this->_level, $this->_path); 66 67 $this->_column_map = array('id' => $this->_id, 'parent_id' => $this->_parent_id, 68 'level' => $this->_level, 'identifier' => $this->_identifier, 69 'path' => $this->_path); 70 } 71 72 function initTree() 73 { 74 $stmt = $this->_conn->newStatement("DELETE FROM {$this->_node_table}"); 75 $stmt->execute(); 76 return $this->_createRootNode(); 77 } 78 79 function getRootNode() 80 { 81 $sql = "SELECT " . $this->_getSelectFields() . " FROM {$this->_node_table} WHERE {$this->_level}=0"; 82 $stmt = $this->_conn->newStatement($sql); 83 84 if($root_node = $stmt->getOneRecord()) 85 return $root_node; 86 87 return null; 88 } 89 90 function setNodeTable($table_name) 91 { 92 $this->_node_table = $table_name; 93 } 94 95 function getNodeTable() 96 { 97 return $this->_node_table; 98 } 99 100 function _getSelectFields() 101 { 102 if($this->_select_columns) 103 return $this->_select_columns; 104 105 $flipped = array_flip($this->_column_map); 106 107 foreach($this->_db_table->getColumnsForSelect() as $name) 108 { 109 if(isset($flipped[$name])) 110 $alias = $flipped[$name]; 111 else 112 $alias = $name; 113 114 $sql_exec_fields[] = "{$this->_node_table}.{$name} AS {$alias}"; 115 } 116 117 $this->_select_columns = implode(', ', $sql_exec_fields); 118 return $this->_select_columns; 119 } 120 121 function _processUserValues($values) 122 { 123 $processed = array(); 124 foreach($values as $name => $value) 125 { 126 if(isset($this->_column_map[$name])) 127 $column = $this->_column_map[$name]; 128 else 129 $column = $name; 130 131 if(in_array($column, $this->_system_columns)) 132 continue; 133 134 $processed[$column] = $value; 135 } 136 return $processed; 137 } 138 139 function getParents($node) 140 { 141 $child = $this->_ensureNode($node); 142 143 if($child['level'] < 1) 144 return null; 145 146 $join_table = $this->_node_table . '2'; 147 $concat = $this->_dbConcat(array($this->_node_table . '.' . $this->_path, "'%'")); 148 149 $sql = "SELECT " . $this->_getSelectFields() . " 150 FROM {$this->_node_table}, {$this->_node_table} AS {$join_table} 151 WHERE 152 {$join_table}.{$this->_path} LIKE {$concat} AND 153 {$this->_node_table}.{$this->_level} < :level: AND 154 {$join_table}.{$this->_id} = :id: 155 ORDER BY {$this->_node_table}.{$this->_level} ASC"; 156 157 $stmt = $this->_conn->newStatement($sql); 158 $stmt->setVarChar('level', $child['level']); 159 $stmt->setVarChar('id', $child['id']); 160 161 return $stmt->getRecordSet(); 162 } 163 164 function getParent($node) 165 { 166 $child = $this->_ensureNode($node); 167 return $this->getNode($child['parent_id']); 168 } 169 170 function getSiblings($node) 171 { 172 $me = $this->_ensureNode($node); 173 174 if(!$me['parent_id']) 175 return new lmbCollection(array($me)); 176 177 return $this->getChildren($me['parent_id']); 178 } 179 180 function getChildren($node, $depth = 1) 181 { 182 $parent = $this->_ensureNode($node); 183 184 if($depth == 1) 185 { 186 $sql = "SELECT " . $this->_getSelectFields() . " 187 FROM {$this->_node_table} 188 WHERE {$this->_parent_id} = :parent_id:"; 189 190 $stmt = $this->_conn->newStatement($sql); 191 $stmt->setInteger('parent_id', $parent['id']); 192 } 193 else 194 { 195 $sql = "SELECT " . $this->_getSelectFields() . " 196 FROM {$this->_node_table} 197 WHERE {$this->_path} LIKE '{$parent['path']}%' 198 AND {$this->_id} != {$parent['id']}"; 199 if($depth != -1) 200 $sql .= " AND {$this->_level} < ". ($parent['level'] + 1 + $depth); 201 $stmt = $this->_conn->newStatement($sql); 202 } 203 204 $rs = $stmt->getRecordSet(); 205 $rs->sort(array($this->_path => 'ASC')); 206 return $rs; 207 } 208 209 function getChildrenAll($node) 210 { 211 $node = $this->_ensureNode($node); 212 213 $sql = "SELECT " . $this->_getSelectFields() . " 214 FROM {$this->_node_table} 215 WHERE {$this->_path} LIKE '{$node['path']}%' 216 AND {$this->_id} != {$node['id']}"; 217 218 $stmt = $this->_conn->newStatement($sql); 219 220 $rs = $stmt->getRecordSet(); 221 $rs->sort(array($this->_path => 'ASC')); 222 return $rs; 223 } 224 225 function countChildren($node, $depth = 1) 226 { 227 $parent = $this->_ensureNode($node); 228 229 if($depth == 1) 230 { 231 $sql = "SELECT count({$this->_id}) as counter FROM {$this->_node_table} 232 WHERE {$this->_parent_id} = :parent_id:"; 233 234 $stmt = $this->_conn->newStatement($sql); 235 $stmt->setInteger('parent_id', $parent['id']); 236 } 237 else 238 { 239 $sql = "SELECT count({$this->_id}) as counter 240 FROM {$this->_node_table} 241 WHERE {$this->_path} LIKE '{$parent['path']}%' 242 AND {$this->_id} != {$parent['id']}"; 243 if($depth !=-1) 244 $sql .= " AND {$this->_level} < " . ($parent['level'] + 1 + $depth); 245 $stmt = $this->_conn->newStatement($sql); 246 } 247 return $stmt->getOneValue(); 248 } 249 250 function countChildrenAll($node) 251 { 252 $parent = $this->_ensureNode($node); 253 254 $sql = "SELECT count({$this->_id}) as counter 255 FROM {$this->_node_table} 256 WHERE {$this->_path} LIKE '{$parent['path']}%' 257 AND {$this->_id} != {$parent['id']}"; 258 259 $stmt = $this->_conn->newStatement($sql); 260 return $stmt->getOneValue(); 261 } 262 263 function getNode($node) 264 { 265 if(is_string($node) && !is_numeric($node)) 266 { 267 if(!$res = $this->getNodeByPath($node)) 268 return null; 269 return $res; 270 } 271 elseif(is_array($node) || is_object($node)) 272 { 273 if(isset($node['id'])) 274 $id = $node['id']; 275 else 276 return null; 277 } 278 else 279 $id = $node; 280 281 $sql = "SELECT " . $this->_getSelectFields() . " 282 FROM {$this->_node_table} WHERE {$this->_id}=:id:"; 283 284 $stmt = $this->_conn->newStatement($sql); 285 $stmt->setInteger($this->_id, $id); 286 287 if($node = $stmt->getOneRecord()) 288 return $node; 289 290 return null; 291 } 292 293 function getNodeByPath($path) 294 { 295 $path = preg_replace('~\/+~', '/', $path); 296 297 if($path == '/') 298 return $this->getRootNode(); 299 300 $path_array = explode('/', $path); 301 302 array_shift($path_array);//skip first item 303 if(end($path_array) == '')//ending slash 304 array_pop($path_array); 305 306 if(!$path_array) 307 return null; 308 309 $level = sizeof($path_array); 310 311 $in_condition = $this->_dbIn($this->_identifier, array_unique($path_array)); 312 313 $sql = "SELECT " . $this->_getSelectFields() . " 314 FROM {$this->_node_table} 315 WHERE 316 {$in_condition} 317 AND {$this->_level} <= {$level} 318 ORDER BY {$this->_path}"; 319 320 $stmt = $this->_conn->newStatement($sql); 321 $rs = $stmt->getRecordSet(); 322 323 $curr_level = 0; 324 $parent_id = null; 325 $path_to_node = ''; 326 327 foreach($rs as $node) 328 { 329 if($node['level'] < $curr_level) 330 continue; 331 332 if($node['identifier'] == $path_array[$curr_level] && 333 (!$parent_id || 334 $node['parent_id'] == $parent_id)) 335 { 336 $parent_id = $node['id']; 337 338 $curr_level++; 339 $path_to_node .= '/' . $node['identifier']; 340 341 if($curr_level == $level) 342 return $node; 343 } 344 } 345 return null; 346 } 347 348 function getPathToNode($node) 349 { 350 $node = $this->_ensureNode($node); 351 352 if(!$parents = $this->getParents($node['id'])) 353 return '/'; 354 355 $path = ''; 356 foreach($parents as $parent) 357 $path .= $parent['identifier'] . '/'; 358 359 return $path .= $node['identifier']; 360 } 361 362 function getNodesByIds($ids) 363 { 364 if(!$ids) 365 return new lmbCollection(); 366 367 $sql = "SELECT " . $this->_getSelectFields() . " 368 FROM {$this->_node_table} 369 WHERE " . $this->_dbIn($this->_id, $ids) . " 370 ORDER BY {$this->_path}"; 371 372 $stmt = $this->_conn->newStatement($sql); 373 return $stmt->getRecordSet(); 374 } 375 376 function isNode($node) 377 { 378 return ($this->getNode($node) !== null); 379 } 380 381 function updateNode($node, $user_values, $internal = false) 382 { 383 $node = $this->_ensureNode($node); 384 385 if(isset($user_values['identifier'])) 386 { 387 if($node['parent_id'] == 0 && $user_values['identifier'])//root check 388 throw new lmbTreeConsistencyException('Root node is forbidden to have an identifier'); 389 390 if($node['identifier'] != $user_values['identifier']) 391 $this->_ensureUniqueSiblingIdentifier($user_values['identifier'], $node['parent_id']); 392 } 393 394 if(!$internal) 395 $values = $this->_processUserValues($user_values); 396 else 397 $values = $user_values; 398 399 if(!$values) 400 return; 401 402 $this->_db_table->updateById($node['id'], $values); 403 } 404 405 function _getNextNodeInsertId() 406 { 407 //if field is autoincremented why do we need it? 408 $sql = "SELECT MAX({$this->_id}) as m FROM {$this->_node_table}"; 409 $stmt = $this->_conn->newStatement($sql); 410 $max = $stmt->getOneValue(); 411 return isset($max) ? $max + 1 : 1; 412 } 413 414 function _dbConcat($values) 415 { 416 switch($this->_conn->getType()) 417 { 418 case 'mysql': 419 $str = implode(',', $values); 420 return " CONCAT({