Source for file HierarchyCache.class.php

Documentation is available at HierarchyCache.class.php

  1. <?php
  2.  
  3. require_once(HARMONI."oki2/hierarchy/tree/Tree.class.php");
  4. require_once(HARMONI."oki2/hierarchy/HarmoniTraversalInfo.class.php");
  5. require_once(HARMONI."oki2/hierarchy/HarmoniTraversalInfoIterator.class.php");
  6.  
  7. /**
  8. * This class provides a mechanism for caching different parts of a hierarchy and
  9. * acts as an interface between the datastructures and the database. A
  10. * single instance of this class will be included with a single <code>Hierarchy</code>
  11. * object and all its <code>Node</code> objects.<br /><br />
  12. *
  13. * The class maintains a single <code>Tree</code> object called <code>tree</code>
  14. * that will store the parts of the hierarchy already cached. In addition,
  15. * there is a special array called <code>cache</code>. Given an id of a cached node,
  16. * this array enables us to determine whether any of the node's inheritors or ancestors have
  17. * been cached as well. In specific, each element of the array corresponds to a unique
  18. * node (each index of the array is a node id) and is another array storing
  19. * one <code>Node</code> object and two integer values.
  20. * If any of the integer values is positive, then that means that
  21. * we have information about the cache status of the node's immediate ancestors or
  22. * inheritors. Specifically, the two integer values determine the amount of caching, i.e. the first
  23. * integer specifies the number of tree levels cached down the node, and the second integer
  24. * specifies the number of tree levels cached up the node. If any of these values is negative
  25. * then that means that the caching extends all the way up or down. If any of the two
  26. * values is zero, then nothing has been cached in the corresponding direction.<br /><br />
  27. *
  28. * Caching occurs when the user calls the accessor methods of the <code>Hierarchy</code> class,
  29. * i.e. <code>traverse()</code>, <code>getChildren()</code> or <code>getParents()</code>.
  30. *
  31. * @package harmoni.osid_v2.hierarchy
  32. *
  33. * @copyright Copyright &copy; 2005, Middlebury College
  34. * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL)
  35. *
  36. * @version $Id: HierarchyCache.class.php,v 1.39 2007/09/17 16:44:35 adamfranco Exp $
  37. ***/
  38.  
  39. class HierarchyCache {
  40.  
  41.  
  42. /**
  43. * This is the <code>Tree</code> object that will store the cached portions
  44. * of the hierarchy.
  45. * @var object _tree
  46. * @access protected
  47. */
  48. var $_tree;
  49. /**
  50. * Given an id of a cached node,
  51. * this array enables us to determine whether any of the node's inheritors or ancestors have
  52. * been cached as well. In specific, each element of the array corresponds to a unique
  53. * node (each index of the array is a node id) and is another array storing
  54. * one <code>Node</code> object and two integer values.
  55. * If any of the integer values is positive, then that means that
  56. * we have information about the cache status of the node's immediate ancestors or
  57. * inheritors. Specifically, the two integer values determine the amount of caching, i.e. the first
  58. * integer specifies the number of tree levels cached down the node, and the second integer
  59. * specifies the number of tree levels cached up the node. If any of these values is negative
  60. * then that means that the caching extends all the way down or up. If any of the two
  61. * values is zero, then nothing has been cached in the corresponding direction.<br /><br />
  62. * @var array _cache
  63. * @access protected
  64. */
  65. var $_cache;
  66.  
  67.  
  68. /**
  69. * The database connection as returned by the DBHandler.
  70. * @var integer _dbIndex
  71. * @access protected
  72. */
  73. var $_dbIndex;
  74.  
  75. /**
  76. * The id of the hierarchy.
  77. * @var string _$hierarchyId
  78. * @access protected
  79. */
  80. var $_hierarchyId;
  81. /**
  82. * This is a SELECT query that we will often use to get one single
  83. * node from the database.
  84. * @var object _nodeQuery
  85. * @access protected
  86. */
  87. var $_nodeQuery;
  88. /**
  89. * This is true if the hierarchy will allow
  90. * multiple parents.
  91. * @var boolean _allowsMultipleParents
  92. * @access private
  93. */
  94. var $_allowsMultipleParents;
  95. /**
  96. * A cache of the created traversalInfoObjects. Intended to help solve some of
  97. * the memory blowup in Concerto. Checking 20 AZs on about 25 Assets in Concerto
  98. * was traversing such that ~100,000 HarmoniTraversalInfo objects were being created.
  99. * @var array _infoCache
  100. * @access private
  101. * @since 3/28/05
  102. */
  103. var $_infoCache;
  104.  
  105. /**
  106. * Constructor
  107. * @param mixed hierarchyId The id of the corresponding hierarchy.
  108. * @param integer dbIndex The database connection as returned by the DBHandler.
  109. * @param object DateAndTime $lastStructureUpdate
  110. * @access protected
  111. */
  112. function HierarchyCache($hierarchyId, $allowsMultipleParents, $dbIndex) {
  113. // ** parameter validation
  114. ArgumentValidator::validate($hierarchyId,
  115. OrValidatorRule::getRule(
  116. NonzeroLengthStringValidatorRule::getRule(),
  117. IntegerValidatorRule::getRule()),
  118. true);
  119. ArgumentValidator::validate($allowsMultipleParents, BooleanValidatorRule::getRule(), true);
  120. ArgumentValidator::validate($dbIndex, IntegerValidatorRule::getRule(), true);
  121. // ** end of parameter validation
  122.  
  123. $this->_tree = new Tree();
  124. $this->_cache = array();
  125. $this->_dbIndex = $dbIndex;
  126. $this->_hierarchyId = $hierarchyId;
  127. $this->_allowsMultipleParents = $allowsMultipleParents;
  128.  
  129. // initialize a generic SELECT query to fetch one node from the DB
  130. $this->_nodeQuery = new SelectQuery();
  131. $this->_nodeQuery->addColumn("node_id", "id", "node");
  132. $this->_nodeQuery->addColumn("node_display_name", "display_name", "node");
  133. $this->_nodeQuery->addColumn("node_description", "description", "node");
  134. $this->_nodeQuery->addColumn("fk_hierarchy", "hierarchy_id", "node");
  135. $this->_nodeQuery->addColumn("type_domain", "domain", "type");
  136. $this->_nodeQuery->addColumn("type_authority", "authority", "type");
  137. $this->_nodeQuery->addColumn("type_keyword", "keyword", "type");
  138. $this->_nodeQuery->addColumn("type_description", "type_description", "type");
  139. $this->_nodeQuery->addTable("node");
  140. $this->_nodeQuery->addOrderBy("node_id");
  141. $joinc = "fk_type = "."type_id";
  142. $this->_nodeQuery->addTable("type", INNER_JOIN, $joinc);
  143. $this->_infoCache = array();
  144. }
  145. /**
  146. * Answer the Id of the hierarchy
  147. *
  148. * @return object Id
  149. * @access public
  150. * @since 12/20/05
  151. */
  152. function getHierarchyId () {
  153. $idManager = Services::getService("Id");
  154. return $idManager->getId($this->_hierarchyId);
  155. }
  156. /**
  157. * Determines whether a node has been cached down
  158. * @see HierarchyCache::_cache
  159. * @access private
  160. * @param mixed idValue The string id of the node.
  161. * @param integer levels The number of tree levels down to check for caching.
  162. * If negative, then cached all the way down.
  163. * @return boolean If <code>true</code> then <code>levels</code> number of
  164. * levels have been cached down.
  165. ***/
  166. function _isCachedDown($idValue, $levels) {
  167. // ** parameter validation
  168. ArgumentValidator::validate($idValue,
  169. OrValidatorRule::getRule(
  170. NonzeroLengthStringValidatorRule::getRule(),
  171. IntegerValidatorRule::getRule()),
  172. true);
  173. ArgumentValidator::validate($levels, IntegerValidatorRule::getRule(), true);
  174. // ** end of parameter validation
  175.  
  176. if (isset($this->_cache[$idValue]))
  177. return (($this->_cache[$idValue][1] >= $levels) && ($levels >= 0)) ||
  178. ($this->_cache[$idValue][1] < 0);
  179. else
  180. return false;
  181. }
  182.  
  183.  
  184. /**
  185. * Determines whether a node has been cached up
  186. * @see HierarchyCache::_cache
  187. * @access private
  188. * @param mixed idValue The string id of the node.
  189. * @param integer levels The number of tree levels up to check for caching.
  190. * If negative, then cached all the way up.
  191. * @return boolean If <code>true</code> then <code>levels</code> number of
  192. * levels have been cached up.
  193. ***/
  194. function _isCachedUp($idValue, $levels) {
  195. // ** parameter validation
  196. ArgumentValidator::validate($idValue,
  197. OrValidatorRule::getRule(
  198. NonzeroLengthStringValidatorRule::getRule(),
  199. IntegerValidatorRule::getRule()),
  200. true);
  201. ArgumentValidator::validate($levels, IntegerValidatorRule::getRule(), true);
  202. // ** end of parameter validation
  203.  
  204. if (isset($this->_cache[$idValue]))
  205. return (($this->_cache[$idValue][2] >= $levels) && ($levels >= 0)) ||
  206. ($this->_cache[$idValue][2] < 0);
  207. else
  208. return false;
  209. }
  210. /**
  211. * Returns <code>true</code> if the node with the specified string id has
  212. * been cached.
  213. * @access private
  214. * @param mixed idValue The string id of the node.
  215. * @return boolean <code>true</code> if the with the specified string id has
  216. * been cached.
  217. ***/
  218. function _isCached($idValue) {
  219. return (isset($this->_cache[$idValue]));
  220. }
  221. /**
  222. * Makes the first node the parent of the second node.
  223. * @access public
  224. * @param mixed parentIdValue The string id of the node to add as a parent.
  225. * @param mixed childIdValue The string id of the child node.
  226. * @return void
  227. ***/
  228. function addParent($parentIdValue, $childIdValue) {
  229. // ** parameter validation
  230. ArgumentValidator::validate($parentIdValue,
  231. OrValidatorRule::getRule(
  232. NonzeroLengthStringValidatorRule::getRule(),
  233. IntegerValidatorRule::getRule()),
  234. true);
  235. ArgumentValidator::validate($childIdValue,
  236. OrValidatorRule::getRule(
  237. NonzeroLengthStringValidatorRule::getRule(),
  238. IntegerValidatorRule::getRule()),
  239. true);
  240. // ** end of parameter validation
  241. // get the two nodes
  242. $parent =$this->getNode($parentIdValue);
  243. $child =$this->getNode($childIdValue);
  244. // the next two calls make sure everything will go smoothly
  245. // i.e. will help to detect that $parent is not already a parent of $child
  246. $parent->getChildren();
  247. $child->getParents();
  248. $this->traverse($child->getId(), true, -1); // traverse fully down in order to detect cycles
  249. // get the tree nodes
  250. $parentTreeNode =$this->_tree->getNode($parentIdValue);
  251. $childTreeNode =$this->_tree->getNode($childIdValue);
  252.  
  253. // make sure that we are not adding a second parent in a single-parent hierarchy
  254. if (!$this->_allowsMultipleParents)
  255. if ($childTreeNode->getParentsCount() > 1)
  256. throwError(new Error(HierarchyException::SINGLE_PARENT_HIERARCHY(), "HierarchyCache", true));
  257.  
  258.  
  259.  
  260. // IMPORTANT SPECIAL CASES:
  261.  
  262. // A = the number of levels that the parent is cached down
  263. $A = $this->_cache[$parentIdValue][1];
  264. // B = the number of levels that the child is cached down
  265. $B = $this->_cache[$childIdValue][1];
  266. // if B < A-1 AND B >= 0 then cache down the child A-1 levels
  267. if (($B < ($A-1)) && ($B >= 0))
  268. $this->_traverseDown($childIdValue, $A-1);
  269. // if A < 0 AND B >= 0 then cache down the child fully
  270. if (($A < 0) && ($B >= 0))
  271. $this->_traverseDown($childIdValue, -1);
  272.  
  273. // the special cases are symmetric when going up
  274. // A = the number of levels that the child is cached up
  275. $A = $this->_cache[$childIdValue][2];
  276. // B = the number of levels that the parent is cached up
  277. $B = $this->_cache[$parentIdValue][2];
  278. // if B < A-1 AND B >= 0 then cache up the parent A-1 levels
  279. if (($B < ($A-1)) && ($B >= 0))
  280. $this->_traverseUp($parentIdValue, $A-1);
  281. // if A < 0 AND B >= 0 then cache up the parent fully
  282. if (($A < 0) && ($B >= 0))
  283. $this->_traverseUp($parentIdValue, -1);
  284. // now add the new node as a parent
  285. // 1) update the cache
  286. $this->_tree->addNode($childTreeNode, $parentTreeNode, true);
  287.  
  288. // 2) update the database
  289. $dbHandler = Services::getService("DatabaseManager");
  290. $query = new InsertQuery();
  291. $query->setTable("j_node_node");
  292. $columns = array();
  293. $columns[] = "fk_hierarchy";
  294. $columns[] = "fk_parent";
  295. $columns[] = "fk_child";
  296. $query->setColumns($columns);
  297. $values = array();
  298. $values[] = "'".addslashes($this->_hierarchyId)."'";
  299. $values[] = "'".addslashes($parentIdValue)."'";
  300. $values[] = "'".addslashes($childIdValue)."'";
  301. $query->setValues($values);
  302. // echo "<pre>\n";
  303. // echo MySQL_SQLGenerator::generateSQLQuery($query);
  304. // echo "</pre>\n";
  305. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  306. if ($queryResult->getNumberOfRows() != 1)
  307. throwError(new Error(HierarchyException::OPERATION_FAILED(),"HierarchyCache",true));
  308. // Update the ancestory table
  309. $this->rebuildSubtreeAncestory($child->getId());
  310. }
  311. /**
  312. * Removes the first node from the list of parents of the second node.
  313. * @access public
  314. * @param mixed parentIdValue The string id of the node to to remove as a parent.
  315. * @param mixed childIdValue The string id of the child node.
  316. * @return void
  317. ***/
  318. function removeParent($parentIdValue, $childIdValue) {
  319. // ** parameter validation
  320. ArgumentValidator::validate($parentIdValue,
  321. OrValidatorRule::getRule(
  322. NonzeroLengthStringValidatorRule::getRule(),
  323. IntegerValidatorRule::getRule()),
  324. true);
  325. ArgumentValidator::validate($childIdValue,
  326. OrValidatorRule::getRule(
  327. NonzeroLengthStringValidatorRule::getRule(),
  328. IntegerValidatorRule::getRule()),
  329. true);
  330. // ** end of parameter validation
  331.  
  332. // get the two nodes
  333. $parent =$this->getNode($parentIdValue);
  334. $child =$this->getNode($childIdValue);
  335. // the next two calls make sure everything will go smoothly
  336. // i.e. will help to detect that $parent is indeed a parent of $child
  337.  
  338. $parent->getChildren();
  339. $child->getParents();
  340. // now add remove the parent
  341. // 1) update the cache
  342. $parentTreeNode =$this->_tree->getNode($parentIdValue);
  343. $childTreeNode =$this->_tree->getNode($childIdValue);
  344. $parentTreeNode->detachChild($childTreeNode);
  345.  
  346. // 2) update the database
  347. $dbHandler = Services::getService("DatabaseManager");
  348. $query = new DeleteQuery();
  349. $query->setTable("j_node_node");
  350. $query->addWhere("fk_hierarchy= '".addslashes($this->_hierarchyId)."'");
  351. $query->addWhere("fk_parent = '".addslashes($parentIdValue)."'");
  352. $query->addWhere("fk_child = '".addslashes($childIdValue)."'");
  353. // echo "<pre>\n";
  354. // echo MySQL_SQLGenerator::generateSQLQuery($query);
  355. // echo "</pre>\n";
  356. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  357. if ($queryResult->getNumberOfRows() != 1)
  358. throwError(new Error(HierarchyException::OPERATION_FAILED(),"HierarchyCache",true));
  359. // Update the ancestory table
  360. $this->_tree->_traversalCache = array();
  361. $this->rebuildSubtreeAncestory($child->getId());
  362. }
  363. /**
  364. * Gets node(s) from the database that match the criteria specified by
  365. * the given where condition.
  366. * @access public
  367. * @param string where The where condition that will be used to determine
  368. * which nodes to return.
  369. * @return ref object An array of HarmoniNode objects.
  370. ***/
  371. function getNodesFromDB($where) {
  372. // ** parameter validation
  373. ArgumentValidator::validate($where, StringValidatorRule::getRule(), true);
  374. // ** end of parameter validation
  375.  
  376. $dbHandler = Services::getService("DatabaseManager");
  377.  
  378. $this->_nodeQuery->resetWhere();
  379. $this->_nodeQuery->addWhere($where);
  380. $this->_nodeQuery->addWhere("fk_hierarchy = '".addslashes($this->_hierarchyId)."'");
  381. $nodeQueryResult = $dbHandler->query($this->_nodeQuery, $this->_dbIndex);
  382. $result = array();
  383.  
  384. $idManager = Services::getService("Id");
  385. if (!$nodeQueryResult->hasMoreRows()) {
  386. throw new HarmoniException("No nodes found. \$query = ".printpre($this->_nodeQuery->asString(), true));
  387. }
  388. while ($nodeQueryResult->hasMoreRows()) {
  389. $nodeRow = $nodeQueryResult->getCurrentRow();
  390. $idValue = $nodeRow['id'];
  391. $id =$idManager->getId($idValue);
  392. $type = new HarmoniType($nodeRow['domain'], $nodeRow['authority'],
  393. $nodeRow['keyword'], $nodeRow['type_description']);
  394. $node = new HarmoniNode($id, $type,
  395. $nodeRow['display_name'], $nodeRow['description'], $this);
  396. $result[] =$node;
  397. $nodeQueryResult->advanceRow();
  398. }
  399. return $result;
  400. }
  401. /**
  402. * Returns an array of all nodes in this hierarchy.
  403. * @access public
  404. * @return ref array An array of all nodes in this hierarchy.
  405. ***/
  406. function getAllNodes() {
  407. $dbHandler = Services::getService("DatabaseManager");
  408. $idManager = Services::getService("Id");
  409.  
  410. $query = new SelectQuery();
  411. $query->addColumn("node_id", "id", "node");
  412. $query->addColumn("node_display_name", "display_name", "node");
  413. $query->addColumn("node_description", "description", "node");
  414. $query->addColumn("type_domain", "domain", "type");
  415. $query->addColumn("type_authority", "authority", "type");
  416. $query->addColumn("type_keyword", "keyword", "type");
  417. $query->addColumn("type_description", "type_description", "type");
  418. $query->addColumn("fk_parent", "parent_id", "j_node_node");
  419.  
  420. $query->addTable("node");
  421. $joinc = "fk_type = type_id";
  422. $query->addTable("type", INNER_JOIN, $joinc);
  423. $joinc = "node_id = fk_child AND node.fk_hierarchy = j_node_node.fk_hierarchy";
  424. $query->addTable("j_node_node", LEFT_JOIN, $joinc);
  425.  
  426. $query->addWhere("node.fk_hierarchy = '".addslashes($this->_hierarchyId)."'");
  427.  
  428. $query->addOrderBy("node_id");
  429. $nodeQueryResult =$dbHandler->query($query, $this->_dbIndex);
  430. $result = array();
  431. // CLEAR THE CACHE
  432. $this->clearCache();
  433.  
  434. while ($nodeQueryResult->hasMoreRows()) {
  435. $nodeRow = $nodeQueryResult->getCurrentRow();
  436. $idValue = $nodeRow['id'];
  437. $parentIdValue = $nodeRow['parent_id'];
  438. // update cache
  439. // 1) update the tree structure
  440. // create a TreeNode for the current node, if necessary
  441. if ($this->_tree->nodeExists($idValue))
  442. $tn =$this->_tree->getNode($idValue);
  443. else
  444. $tn = new TreeNode($idValue);
  445. // 2) create a TreeNode for the parent node, if necessary
  446. if (is_null($parentIdValue)) {
  447. unset($parent_tn); // this line is very important (without it, the previous
  448. // instance of $parent_tn will be reset to null
  449. $parent_tn = null;
  450. }
  451. else if ($this->_tree->nodeExists($parentIdValue))
  452. $parent_tn =$this->_tree->getNode($parentIdValue);
  453. else {
  454. $parent_tn = new TreeNode($parentIdValue);
  455. $nullValue = NULL; // getting rid of PHP warnings by specifying
  456. // this second argument
  457. $this->_tree->addNode($parent_tn, $nullValue);
  458. }
  459. // 3) insert child-parent relationship into the tree
  460. $this->_tree->addNode($tn, $parent_tn);
  461.  
  462. // 4) update cache of hierarchy nodes
  463. $id =$idManager->getId($idValue);
  464. $type = new HarmoniType($nodeRow['domain'], $nodeRow['authority'],
  465. $nodeRow['keyword'], $nodeRow['type_description']);
  466. $node = new HarmoniNode($id, $type,
  467. $nodeRow['display_name'], $nodeRow['description'], $this);
  468. $this->_cache[$idValue][0] =$node;
  469. $this->_cache[$idValue][1] = -1;
  470. $this->_cache[$idValue][2] = -1;
  471. $result[] =$this->_cache[$idValue][0];
  472. $nodeQueryResult->advanceRow();
  473. }
  474. $nodeQueryResult->free();
  475. return $result;
  476. }
  477. /**
  478. * Returns an array of all root nodes in this hierarchy.
  479. * @access public
  480. * @return ref array An array of all root nodes in this hierarchy.
  481. ***/
  482. function getRootNodes() {
  483. $dbHandler = Services::getService("DatabaseManager");
  484. $idManager = Services::getService("Id");
  485.  
  486. // copy _nodeQuery into a new object
  487. $query = clone $this->_nodeQuery;
  488. $query->resetWhere();
  489. $joinc = "node_id = fk_child and node.fk_hierarchy = j_node_node.fk_hierarchy";
  490. $query->addTable("j_node_node", LEFT_JOIN, $joinc);
  491. $query->addColumn("fk_child", "join_id", "j_node_node");
  492. $query->addWhere("node.fk_hierarchy = '".addslashes($this->_hierarchyId)."'");
  493. $query->addWhere("fk_child IS NULL");
  494. $query->addOrderBy("fk_child");
  495.  
  496. // echo "<pre>\n";
  497. // echo MySQL_SQLGenerator::generateSQLQuery($query);
  498. // echo "</pre>\n";
  499.  
  500. $nodeQueryResult =$dbHandler->query($query, $this->_dbIndex);
  501. $result = array();
  502.  
  503. while ($nodeQueryResult->hasMoreRows()) {
  504. $nodeRow = $nodeQueryResult->getCurrentRow();
  505. $idValue =$nodeRow['id'];
  506. if (!$this->_isCached($idValue)) {
  507. $id =$idManager->getId($idValue);
  508. $type = new HarmoniType($nodeRow['domain'], $nodeRow['authority'],
  509. $nodeRow['keyword'], $nodeRow['type_description']);
  510. $node = new HarmoniNode($id, $type,
  511. $nodeRow['display_name'], $nodeRow['description'], $this);
  512.  
  513. // insert node into cache
  514. $nullValue = NULL; // getting rid of PHP warnings by specifying
  515. // this second argument
  516. $this->_tree->addNode(new TreeNode($idValue), $nullValue);
  517. $this->_cache[$idValue][0] =$node;
  518. $this->_cache[$idValue][1] = 0;
  519. $this->_cache[$idValue][2] = 0;
  520. }
  521. $result[] =$this->_cache[$idValue][0];
  522. $nodeQueryResult->advanceRow();
  523. }
  524. return $result;
  525. }
  526.  
  527. /**
  528. * Returns (and caches if necessary) the node with the specified string id.
  529. * @access public
  530. * @param mixed idValue The string id of the node.
  531. * @return mixed The corresponding <code>Node</code> object.
  532. ***/
  533. function getNode($idValue) {
  534. // ** parameter validation
  535. ArgumentValidator::validate($idValue,
  536. OrValidatorRule::getRule(
  537. NonzeroLengthStringValidatorRule::getRule(),
  538. IntegerValidatorRule::getRule()),
  539. true);
  540. // ** end of parameter validation
  541.  
  542. // if the node has not been already cached, do it
  543. if (!$this->_isCached($idValue)) {
  544. // now fetch the node from the database
  545. $nodes = $this->getNodesFromDB("node_id = '".addslashes($idValue)."'");
  546. // must be only one node
  547. if (count($nodes) != 1) {
  548. throwError(new Error(HierarchyException::OPERATION_FAILED()." - ".count($nodes)." nodes found.", "HierarchyCache", true));
  549. }
  550. $displayName = $nodes[0]->getDisplayName();
  551. // echo "<br />Creating node # <b>$idValue - '$displayName'</b>";
  552. // insert node into cache
  553. $nullValue = NULL; // getting rid of PHP warnings by specifying
  554. // this second argument
  555. $this->_tree->addNode(new TreeNode($idValue), $nullValue);
  556. $this->_cache[$idValue][0] =$nodes[0];
  557. $this->_cache[$idValue][1] = 0;
  558. $this->_cache[$idValue][2] = 0;
  559. }
  560. // now that all nodes are cached, just return all children
  561. $result =$this->_cache[$idValue][0];
  562.  
  563. return $result;
  564. }
  565. /**
  566. * Returns true if the node specified by the idString exists. The node is
  567. * cached for future access if it is found
  568. *
  569. * @access public
  570. * @param mixed idValue The string id of the node.
  571. * @return boolean
  572. ***/
  573. function nodeExists($idValue) {
  574. // ** parameter validation
  575. ArgumentValidator::validate($idValue,
  576. OrValidatorRule::getRule(
  577. NonzeroLengthStringValidatorRule::getRule(),
  578. IntegerValidatorRule::getRule()),
  579. true);
  580. // ** end of parameter validation
  581.  
  582. // if the node has not been already cached, do it
  583. if (!$this->_isCached($idValue)) {
  584. // now fetch the node from the database
  585. $nodes = $this->getNodesFromDB("node_id = '".addslashes($idValue)."'");
  586. // if it isn't in the database, then it doesn't exist
  587. if (count($nodes) < 1) {
  588. return false;
  589. }
  590. if (count($nodes) > 1) {
  591. throwError(new Error(HierarchyException::OPERATION_FAILED(), "HierarchyCache", true));
  592. }
  593. $displayName = $nodes[0]->getDisplayName();
  594. // echo "<br />Creating node # <b>$idValue - '$displayName'</b>";
  595. // insert node into cache
  596. $nullValue = NULL; // getting rid of PHP warnings by specifying
  597. // this second argument
  598. $this->_tree->addNode(new TreeNode($idValue), $nullValue);
  599. $this->_cache[$idValue][0] =$nodes[0];
  600. $this->_cache[$idValue][1] = 0;
  601. $this->_cache[$idValue][2] = 0;
  602. }
  603. return true;
  604. }
  605. /**
  606. * Caches the parents (if not cached already)
  607. * of the given node by fecthing them from the database
  608. * if neccessary, and then inserting them in <code>_tree</code> and updating
  609. * <code>_cache</code>.
  610. * @access public
  611. * @param object node The node object whose parents we must cache.
  612. * @return ref array An array of the parent nodes of the given node.
  613. ***/
  614. function getParents($node) {
  615. // ** parameter validation
  616. ArgumentValidator::validate($node, ExtendsValidatorRule::getRule("HarmoniNode"), true);
  617. // ** end of parameter validation
  618. $idValue = $node->_id->getIdString();
  619.  
  620. // if the children have not been already cached, do it
  621. if (!$this->_isCachedUp($idValue, 1)) {
  622.  
  623. // include the given node in the cache of nodes if necessary
  624. if (!$this->_isCached($idValue)) {
  625. $nullValue = NULL; // getting rid of PHP warnings by specifying
  626. // this second argument
  627. $this->_tree->addNode(new TreeNode($idValue), $nullValue);
  628. $this->_cache[$idValue][0] =$node;
  629. }
  630. // now fetch <code>$node</code>'s parents from the database
  631. // with the exception of those parents that have been already fetched
  632. $treeNode =$this->_tree->getNode($idValue);
  633. $nodesToExclude = (isset($treeNode)) ? ($treeNode->getParents()) : array();
  634. $dbHandler = Services::getService("DatabaseManager");
  635. $idManager = Services::getService("Id");
  636. $query = new SelectQuery();
  637. // set the columns to select
  638. $query->addColumn("node_id", "id", "parents");
  639. $query->addColumn("node_display_name", "display_name", "parents");
  640. $query->addColumn("node_description", "description", "parents");
  641. $query->addColumn("type_domain", "domain", "type");
  642. $query->addColumn("type_authority", "authority", "type");
  643. $query->addColumn("type_keyword", "keyword", "type");
  644. $query->addColumn("type_description", "type_description", "type");
  645. // set the tables
  646. $query->addTable("j_node_node", NO_JOIN, "", "child");
  647. $joinc = "child.fk_parent = "."parents.node_id";
  648. $query->addTable("node", INNER_JOIN, $joinc, "parents");
  649. $joinc = "parents.fk_type = "."type_id";
  650. $query->addTable("type", INNER_JOIN, $joinc);
  651. $where = "child.fk_hierarchy = '".$this->_hierarchyId."' AND child.fk_child = '".addslashes($idValue)."'";
  652. $query->addWhere($where);
  653. $query->addOrderBy("node_id");
  654. if (count($nodesToExclude) > 0) {
  655. $idsToExclude = array_keys($nodesToExclude);
  656. foreach ($idsToExclude as $key => $id)
  657. $idsToExclude[$key] = "'".addslashes($id)."'";
  658. $where = implode(", ",$idsToExclude);
  659. $where = "parents.node_id NOT IN ({$where})";
  660. $query->addWhere($where);
  661. }
  662. // echo "<pre>\n";
  663. // echo MySQL_SQLGenerator::generateSQLQuery($query);
  664. // echo "</pre>\n";
  665. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  666. // for all rows returned by the query
  667. while($queryResult->hasMoreRows()) {
  668. $row = $queryResult->getCurrentRow();
  669. // see if node in current row is in the cache
  670. $parentIdValue = $row['id'];
  671. // if not create it and cache it
  672. if (!$this->_isCached($parentIdValue)) {
  673. $parentId =$idManager->getId($parentIdValue);
  674. $parentType = new HarmoniType($row['domain'], $row['authority'],
  675. $row['keyword'], $row['type_description']);
  676. $parent = new HarmoniNode($parentId, $parentType,
  677. $row['display_name'], $row['description'], $this);
  678. $parentTreeNode = new TreeNode($parentIdValue);
  679. $nullValue = NULL; // getting rid of PHP warnings by specifying
  680. // this second argument
  681. $this->_tree->addNode($parentTreeNode, $nullValue);
  682. $this->_tree->addNode($treeNode, $parentTreeNode);
  683. $this->_cache[$parentIdValue][0] =$parent;
  684. $this->_cache[$parentIdValue][1] = 0;
  685. $this->_cache[$parentIdValue][2] = 0;
  686. }
  687. // if yes, then just update tree stucture
  688. else
  689. $this->_tree->addNode($treeNode, $this->_tree->getNode($parentIdValue));
  690. $queryResult->advanceRow();
  691. }
  692. // finish caching
  693. $this->_cache[$idValue][2] = 1; // parents have been cached
  694. $queryResult->free();
  695. }
  696. // now that all nodes are cached, just return all children
  697. $treeNode =$this->_tree->getNode($idValue);
  698. $result = array();
  699. $parentsIds =$treeNode->getParents();
  700. foreach (array_keys($parentsIds) as $i => $key)
  701. $result[] =$this->_cache[$key][0];
  702. return $result;
  703. }
  704.  
  705. /**
  706. * Caches the children (if not cached already)
  707. * of the given node by fecthing them from the database
  708. * if neccessary, and then inserting them in <code>_tree</code> and updating
  709. * <code>_cache</code>.
  710. * @access public
  711. * @param object node The node object whose children we must cache.
  712. * @return ref array An array of the children nodes of the given node.
  713. ***/
  714. function getChildren($node) {
  715. // ** parameter validation
  716. ArgumentValidator::validate($node, ExtendsValidatorRule::getRule("HarmoniNode"), true);
  717. // ** end of parameter validation
  718. $idValue = $node->_id->getIdString();
  719.  
  720. // if the children have not been already cached, do it
  721. if (!$this->_isCachedDown($idValue, 1)) {
  722.  
  723. // include the given node in the cache of nodes if necessary
  724. if (!$this->_isCached($idValue)) {
  725. $nullValue = NULL; // getting rid of PHP warnings by specifying
  726. // this second argument
  727. $this->_tree->addNode(new TreeNode($idValue), $nullValue);
  728. $this->_cache[$idValue][0] =$node;
  729. }
  730. // now fetch <code>$node</code>'s children from the database
  731. // with the exception of those children that have been already fetched
  732. $treeNode =$this->_tree->getNode($idValue);
  733. $nodesToExclude = (isset($treeNode)) ? ($treeNode->getChildren()) : array();
  734. $dbHandler = Services::getService("DatabaseManager");
  735. $idManager = Services::getService("Id");
  736. $query = new SelectQuery();
  737. // set the columns to select
  738. $query->addColumn("node_id", "id", "children");
  739. $query->addColumn("node_display_name", "display_name", "children");
  740. $query->addColumn("node_description", "description", "children");
  741. $query->addColumn("type_domain", "domain", "type");
  742. $query->addColumn("type_authority", "authority", "type");
  743. $query->addColumn("type_keyword", "keyword", "type");
  744. $query->addColumn("type_description", "type_description", "type");
  745. // set the tables
  746. $query->addTable("j_node_node", NO_JOIN, "", "parent");
  747. $joinc = "parent.fk_child = "."children.node_id";
  748. $query->addTable("node", INNER_JOIN, $joinc, "children");
  749. $joinc = "children.fk_type = "."type_id";
  750. $query->addTable("type", INNER_JOIN, $joinc);
  751. $where = "parent.fk_hierarchy = '".$this->_hierarchyId."' AND parent.fk_parent = '".addslashes($idValue)."'";
  752. $query->addWhere($where);
  753. $query->addOrderBy("node_id");
  754. if (count($nodesToExclude) > 0) {
  755. $idsToExclude = array_keys($nodesToExclude);
  756. foreach ($idsToExclude as $key => $id)
  757. $idsToExclude[$key] = "'".addslashes($id)."'";
  758. $where = implode(", ",$idsToExclude);
  759. $where = "children.node_id NOT IN ({$where})";
  760. $query->addWhere($where);
  761. }
  762. // echo "<pre>\n";
  763. // echo MySQL_SQLGenerator::generateSQLQuery($query);
  764. // echo "</pre>\n";
  765. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  766. // for all rows returned by the query
  767. while($queryResult->hasMoreRows()) {
  768. $row = $queryResult->getCurrentRow();
  769. // see if node in current row is in the cache
  770. $childIdValue = $row['id'];
  771. // if not create it and cache it
  772. if (!$this->_isCached($childIdValue)) {
  773. $childId =$idManager->getId($childIdValue);
  774. $childType = new HarmoniType($row['domain'], $row['authority'],
  775. $row['keyword'], $row['type_description']);
  776. $child = new HarmoniNode($childId, $childType,
  777. $row['display_name'], $row['description'], $this);
  778. $this->_tree->addNode(new TreeNode($childIdValue), $treeNode);
  779. $this->_cache[$childIdValue][0] =$child;
  780. $this->_cache[$childIdValue][1] = 0;
  781. $this->_cache[$childIdValue][2] = 0;
  782. }
  783. // if yes, then just update tree stucture
  784. else
  785. $this->_tree->addNode($this->_tree->getNode($childIdValue), $treeNode);
  786. $queryResult->advanceRow();
  787. }
  788. // finish caching
  789. $this->_cache[$idValue][1] = 1; // children have been cached
  790. $queryResult->free();
  791. }
  792. // now that all nodes are cached, just return all children
  793. $treeNode =$this->_tree->getNode($idValue);
  794. $result = array();
  795. $childrenIds =$treeNode->getChildren();
  796. foreach (array_keys($childrenIds) as $i => $key)
  797. $result[] =$this->_cache[$key][0];
  798. return $result;
  799. }
  800. /**
  801. * Return true if the node is a leaf
  802. *
  803. * @access public
  804. * @param object node The node object to check.
  805. * @return boolean
  806. ***/
  807. function isLeaf($node) {
  808. // ** parameter validation
  809. ArgumentValidator::validate($node, ExtendsValidatorRule::getRule("HarmoniNode"), true);
  810. // ** end of parameter validation
  811. $idValue = $node->_id->getIdString();
  812.  
  813. // if the children has been already cached, use it
  814. if ($this->_isCachedDown($idValue, 1)) {
  815. $treeNode =$this->_tree->getNode($idValue);
  816. return !$treeNode->hasChildren();
  817. } else {
  818. // now fetch <code>$node</code>'s children from the database
  819. // with the exception of those children that have been already fetched
  820. $treeNode =$this->_tree->getNode($idValue);
  821. $nodesToExclude = (isset($treeNode)) ? ($treeNode->getChildren()) : array();
  822. $dbHandler = Services::getService("DatabaseManager");
  823. $idManager = Services::getService("Id");
  824. $query = new SelectQuery();
  825. // set the columns to select
  826. $query->addColumn("count(*)", "count");
  827. // set the tables
  828. $query->addTable("j_node_node");
  829. $where = "j_node_node.fk_hierarchy = '".$this->_hierarchyId."' AND fk_parent = '".addslashes($idValue)."'";
  830. $query->addWhere($where);
  831. // echo "<pre>\n";
  832. // echo MySQL_SQLGenerator::generateSQLQuery($query);
  833. // echo "</pre>\n";
  834. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  835. if ($queryResult->field("count") == '0')
  836. return true;
  837. else
  838. return false;
  839. }
  840. }
  841. /**
  842. * Performs a depth-first pre-order traversal. It either returns the previously cached nodes
  843. * or fetches them from the database and then caches them (depending on whtether
  844. * they had been already cached).
  845. * @access public
  846. * @param object id The id of the node where to start traversal from.
  847. * @param boolean down If <code>true</code>, this argument specifies that the traversal will
  848. * go down the children; if <code>false</code> then it will go up the parents.
  849. * @param integer levels Specifies how many levels of nodes to traverse. If this is negative
  850. * then the traversal will go on until the last level is processed.
  851. * @return ref array An array of all nodes in the tree visited in a pre-order
  852. * manner.
  853. ***/
  854. function traverse($id, $down, $levels) {
  855. // ** parameter validation
  856. ArgumentValidator::validate($id, ExtendsValidatorRule::getRule("Id"), true);
  857. ArgumentValidator::validate($down, BooleanValidatorRule::getRule(), true);
  858. ArgumentValidator::validate($levels, IntegerValidatorRule::getRule(), true);
  859. // ** end of parameter validation
  860. // get the id value
  861. $idValue = $id->getIdString();
  862.  
  863. // see if the nodes have already been cached, if so
  864. // there is no need to access the database, but if not,
  865. // then hell yeah, we gotta access the database.
  866. // the manner of traversing if completely different
  867. // for the 2 directions: therefore, execution splits here
  868. // BIG NOTE: It seems that efficient caching mechanisms with
  869. // partial fetching of the hierarchy (i.e. traversing with levels > 1)
  870. // is not feasible. Thus, this method will only take advantage of the cache
  871. // when traversing all the way down (levels < 0) or when getting the children
  872. // 1) GOING DOWN
  873. if ($down) {
  874. // if not cached, fetch from DB and cache!
  875. if (!$this->_isCachedDown($idValue, $levels))
  876. $this->_traverseDown($idValue, $levels);
  877. }
  878. // 2) GOING UP
  879. else {
  880. // if not cached, fetch from DB and cache!
  881. if (!$this->_isCachedUp($idValue, $levels))
  882. $this->_traverseUpAncestory($idValue, $levels);
  883. // $this->_traverseUp($idValue, $levels);
  884. }
  885.  
  886. // now that all nodes are cached, return them
  887. $treeNode = $this->_tree->getNode($idValue);
  888. $treeNodes = $this->_tree->traverse($treeNode, $down, $levels);
  889. $result = array();
  890. foreach (array_keys($treeNodes) as $i => $key) {
  891. $node = $this->_cache[$key][0];
  892.  
  893. // If the node was deleted, but the cache still has a key for it,
  894. // continue.
  895. if (!is_object($node)) {
  896. continue;
  897. // throwError(new Error("Missing node object", "Hierarchy Cache"));
  898. }
  899.  
  900. $nodeId =$node->getId();
  901. if (!isset($this->_infoCache[$nodeId->getIdString()])) {
  902. $this->_infoCache[$nodeId->getIdString()]
  903. = new HarmoniTraversalInfo($nodeId,
  904. $node->getDisplayName(),
  905. $treeNodes[$key][1]);
  906. }
  907. $result[] =$this->_infoCache[$nodeId->getIdString()];
  908. }
  909. $iterator = new HarmoniTraversalInfoIterator($result);
  910. return $iterator;
  911. }
  912. /**
  913. * Traverses down and caches whatever needs to be cached.
  914. * @access public
  915. * @param string idValue The string id of the node to start traversal from.
  916. * @param integer levels Specifies how many levels of nodes to traverse. If this is negative
  917. * then the traversal will go on until the last level is processed.
  918. * @return void
  919. ***/
  920. function _traverseDown($idValue, $levels) {
  921. $dbHandler = Services::getService("DatabaseManager");
  922. $query = new SelectQuery();
  923. // the original value of levels
  924. $originalLevels = $levels;
  925. // echo "<br /><br /><br /><b>=== Caching node # $idValue, $levels levels down</b><br />";
  926. // MySQL has a limit of 31 tables in a select query, thus essentially
  927. // there is a limit to the max value of $levels.
  928. // if levels > 31 or levels is negative (full traversal)
  929. // then set it to 31
  930. if (($levels > 31) || ($levels < 0))
  931. $levels = 31;
  932. // generate query
  933. $query->addColumn("fk_parent", "level0_id", "level0");
  934. $query->addColumn("fk_child", "level1_id", "level0");
  935. $query->addTable("j_node_node", NO_JOIN, "", "level0");
  936. $query->addOrderBy("level0_id");
  937. $query->addOrderBy("level1_id");
  938. // now left join with itself.
  939. // maximum number of joins is 31, we've used 1 already, so there are 30 left
  940. for ($level = 1; $level <= $levels-1; $level++) {
  941. $joinc = "level".($level-1).".fk_hierarchy = level".($level).".fk_hierarchy AND level".($level-1).".fk_child = level".($level).".fk_parent";
  942. $query->addTable("j_node_node", LEFT_JOIN, $joinc, "level".($level));
  943. $query->addColumn("fk_child", "level".($level+1)."_id", "level".($level));
  944. $query->addOrderBy("level".($level+1)."_id");
  945. }
  946. // this is the where clause
  947. $where = "level0.fk_hierarchy = '".addslashes($this->_hierarchyId)."' AND level0.fk_parent = '".addslashes($idValue)."'";
  948. $query->addWhere($where);
  949. // echo "<pre>\n";
  950. // echo MySQL_SQLGenerator::generateSQLQuery($query);
  951. // echo "</pre>\n";
  952.  
  953. // $timer1 = new Timer;
  954. // $timer1->start();
  955. // execute the query
  956. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  957.  
  958. // $timer1->end();
  959. // printf("<br/>Traversal Query Time: %1.6f", $timer1->printTime());
  960.  
  961. if ($queryResult->getNumberOfRows() == 0) {
  962. $queryResult->free();
  963. return;
  964. }
  965. // note that the query only returns ids of nodes; thus, for each id,
  966. // we would need to fetch the actual node information from the node table.
  967. // $timer1 = new Timer;
  968. // $timer1->start();
  969. // for all rows returned by the query
  970. while($queryResult->hasMoreRows()) {
  971. $row = $queryResult->getCurrentRow();
  972. // check all non-null values in current row
  973. // see if it is cached, if not create a group object and cache it
  974. for ($level = 0; $level <= $levels; $level++) {
  975. $nodeId = $row["level{$level}_id"];
  976.  
  977. // ignore null values
  978. if (is_null($nodeId)) {
  979. // echo "<br />--- skipping to next row (null value encountered)<br />";
  980. break;
  981. }
  982. // echo "<br /><b>Level: $level - Node # $nodeId</b>";
  983.  
  984. // if the node has not been cached, then we must create it
  985. // echo "<br />--- CACHE UPDATE: ";
  986. if (!$this->_isCached($nodeId)) {
  987. $nodes =$this->getNodesFromDB("node_id = '".addslashes($nodeId)."'");
  988. // must be only one node
  989. if (count($nodes) != 1) {
  990. throwError(new Error(HierarchyException::OPERATION_FAILED(), "HierarchyCache", true));
  991. }
  992. // $displayName = $nodes[0]->getDisplayName();
  993. // echo "Creating node # <b>$nodeId - '$displayName'</b>, ";
  994.  
  995. // insert node into cache
  996. $this->_cache[$nodeId][0] =$nodes[0];
  997. $this->_cache[$nodeId][1] = 0;
  998. $this->_cache[$nodeId][2] = 0;
  999. }
  1000. // else
  1001. // echo "Node already in cache, ";
  1002. // update the levels fetched down, if necessary
  1003. if (($this->_cache[$nodeId][1] >= 0) &&
  1004. ($this->_cache[$nodeId][1] < ($levels - $level))) {
  1005. $old = $this->_cache[$nodeId][1];
  1006. if ($originalLevels < 0)
  1007. // if fully, then the node is fetched fully as well
  1008. $this->_cache[$nodeId][1] = -1;
  1009. else
  1010. // if not fully, then set the value appropriately
  1011. $this->_cache[$nodeId][1] = $levels - $level;
  1012.  
  1013. // echo "changing level of caching from <b>$old</b> to <b>".$this->_cache[$nodeId][1]."</b>";
  1014. }
  1015. // else
  1016. // echo "no need to set level of caching";
  1017. // now, update tree structure
  1018. // echo "<br />--- TREE STRUCTURE UPDATE: ";
  1019.  
  1020. // get the current node (create it, if necessary)
  1021. if ($this->_tree->nodeExists($nodeId))
  1022. $node =$this->_tree->getNode($nodeId);
  1023. else {
  1024. // echo "Creating new tree node # <b>$nodeId</b>, ";
  1025. $node = new TreeNode($nodeId);
  1026. $nullValue = NULL; // getting rid of PHP warnings by specifying
  1027. // this second argument
  1028. $this->_tree->addNode($node, $nullValue);
  1029. }
  1030. // does the current node have a parent?
  1031. // if no, there is nothing to update
  1032. if ($level == 0) {
  1033. // echo "Skipping root node, continuing with child";
  1034. continue;
  1035. }
  1036. // if there is a parent, check if it has already been added
  1037. else {
  1038. // get the parent id
  1039. $parentId = $row["level".($level-1)."_id"];
  1040. // get the parent node
  1041. $parent =$this->_tree->getNode($parentId);
  1042. // has the parent been added? if no, add it!
  1043. if (!$node->isParent($parent)) {
  1044. // echo "adding node # <b>$nodeId</b> as a child of node # <b>$parentId</b>";
  1045. $this->_tree->addNode($node, $parent);
  1046. }
  1047. // else
  1048. // echo "already parent";
  1049. }
  1050. }
  1051.  
  1052. $queryResult->advanceRow();
  1053. }
  1054. $queryResult->free();
  1055. // $timer1->end();
  1056. // printf("<br/>Traversal Processing Time: %1.6f", $timer1->printTime());
  1057. }
  1058. /**
  1059. * Traverses up the ancestory table and caches whatever needs to be cached.
  1060. * @access public
  1061. * @param string idValue The string id of the node to start traversal from.
  1062. * @param integer levels Specifies how many levels of nodes to traverse. If this is negative
  1063. * then the traversal will go on until the last level is processed.
  1064. * @return void
  1065. ***/
  1066. function _traverseUpAncestory($idValue, $levels) {
  1067. $dbHandler = Services::getService("DatabaseManager");
  1068. // echo "<br /><br /><br /><b>=== TraverseUpAncestory: Caching node # $idValue, $levels levels up</b><br />";
  1069. $query = new SelectQuery();
  1070. $query->addColumn("*");
  1071. $query->addTable("node_ancestry");
  1072. $query->addTable("j_node_node", LEFT_JOIN, "j_node_node.fk_hierarchy = node_ancestry.fk_hierarchy AND fk_ancestor = fk_child");
  1073. // $query->setGroupBy(array("fk_ancestor"));
  1074. $query->addOrderBy("level", DESCENDING);
  1075. $query->addWhere("node_ancestry.fk_hierarchy = '".addslashes($this->_hierarchyId)."' AND fk_node = '".addslashes($idValue)."'");
  1076. // echo "<pre>\n";
  1077. // echo MySQL_SQLGenerator::generateSQLQuery($query);
  1078. // echo "</pre>\n";
  1079. // execute the query
  1080. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  1081. if ($queryResult->getNumberOfRows() == 0) {
  1082. $queryResult->free();
  1083. return;
  1084. }
  1085. // note that the query only returns ids of nodes; thus, for each id,
  1086. // we would need to fetch the actual node information from the node table.
  1087. // for all rows returned by the query
  1088. while($queryResult->hasMoreRows()) {
  1089. $row = $queryResult->getCurrentRow();
  1090. $level = abs($row["level"]);
  1091. if ($level == 0)
  1092. $nodeId = $row["fk_node"];
  1093. else
  1094. $nodeId = $row["fk_ancestor"];
  1095.  
  1096. // ignore null values
  1097. if (is_null($nodeId)) {
  1098. // echo "<br />--- skipping to next row (null value encountered)<br />";
  1099. $queryResult->advanceRow();
  1100. continue;
  1101. }
  1102. // echo "<br /><b>Level: $level - Node # $nodeId</b>";
  1103.  
  1104. // if the node has not been cached, then we must create it
  1105. // echo "<br />--- CACHE UPDATE: ";
  1106. if (!$this->_isCached($nodeId)) {
  1107. $nodes =$this->getNodesFromDB("node_id = '".addslashes($nodeId)."'");
  1108. // must be only one node
  1109. if (count($nodes) != 1) {
  1110. throwError(new Error(HierarchyException::OPERATION_FAILED().": ".count($nodes)." nodes found.", "HierarchyCache", true));
  1111. }
  1112. $displayName = $nodes[0]->getDisplayName();
  1113. // echo "Creating node # <b>$nodeId - '$displayName'</b>, ";
  1114.  
  1115. // insert node into cache
  1116. $this->_cache[$nodeId][0] =$nodes[0];
  1117. $this->_cache[$nodeId][1] = 0;
  1118. $this->_cache[$nodeId][2] = 0;
  1119. }
  1120. // else
  1121. // echo "Node already in cache, ";
  1122. // update the levels fetched up, if necessary
  1123. $old = $this->_cache[$nodeId][2];
  1124. // print " old=$old, ";
  1125. if ($old >= 0) {
  1126. if ( $levels < 0)
  1127. // if fully, then the node is fetched fully as well
  1128. $this->_cache[$nodeId][2] = -1;
  1129. else if ($old < ($levels - $level))
  1130. // if not fully, then set the value appropriately
  1131. $this->_cache[$nodeId][2] = $levels - $level;
  1132.  
  1133. // echo "changing level of caching from <b>$old</b> to <b>".$this->_cache[$nodeId][2]."</b>";
  1134. }
  1135. // else
  1136. // echo "no need to set level of caching";
  1137. // now, update tree structure
  1138. // echo "<br />--- TREE STRUCTURE UPDATE: ";
  1139.  
  1140. // get the current node (create it, if necessary)
  1141. if ($this->_tree->nodeExists($nodeId))
  1142. $node =$this->_tree->getNode($nodeId);
  1143. else {
  1144. // echo "Creating new tree node # <b>$nodeId</b>, ";
  1145. $node = new TreeNode($nodeId);
  1146. $nullValue = NULL; // getting rid of PHP warnings by specifying
  1147. // this second argument
  1148. $this->_tree->addNode($node, $nullValue);
  1149. }
  1150. // does the current node have a child?
  1151. // if no, there is nothing to update
  1152. if ($level == 0) {
  1153. // echo "Skipping leaf node, continuing with parent";
  1154. // continue;
  1155. }
  1156. // if there is a child, check if it has already been added
  1157. else {
  1158. // get the child id
  1159. $childId = $row["fk_ancestors_child"];
  1160. // print "Checking Child: $childId, ";
  1161. // get the child node (create it, if necessary)
  1162. if ($this->_tree->nodeExists($childId))
  1163. $child =$this->_tree->getNode($childId);
  1164. else {
  1165. // echo "Creating new tree node # <b>$childId</b>, ";
  1166. $child = new TreeNode($childId);
  1167. $nullValue = NULL; // getting rid of PHP warnings by specifying
  1168. // this second argument
  1169. $this->_tree->addNode($child, $nullValue);
  1170. }
  1171. // has the child been added? if no, add it!
  1172. if (!$node->isChild($child)) {
  1173. // echo "adding node # <b>$nodeId</b> as a parent of node # <b>$childId</b>";
  1174. // print_r($child);
  1175. // print_r($node);
  1176. $this->_tree->addNode($child, $node);
  1177. }
  1178. // else
  1179. // echo "already child";
  1180. }
  1181. $queryResult->advanceRow();
  1182. }
  1183. $queryResult->free();
  1184. }
  1185. /**
  1186. * Traverses up and caches whatever needs to be cached.
  1187. * @access public
  1188. * @param string idValue The string id of the node to start traversal from.
  1189. * @param integer levels Specifies how many levels of nodes to traverse. If this is negative
  1190. * then the traversal will go on until the last level is processed.
  1191. * @return void
  1192. ***/
  1193. function _traverseUp($idValue, $levels) {
  1194. $dbHandler = Services::getService("DatabaseManager");
  1195. $query = new SelectQuery();
  1196. // the original value of levels
  1197. $originalLevels = $levels;
  1198. // echo "<br /><br /><br /><b>=== TraverseUp: Caching node # $idValue, $levels levels up</b><br />";
  1199.  
  1200. // MySQL has a limit of 31 tables in a select query, thus essentially
  1201. // there is a limit to the max value of $levels.
  1202. // if levels > 31 or levels is negative (full traversal)
  1203. // then set it to 31
  1204. if (($levels > 31) || ($levels < 0))
  1205. $levels = 31;
  1206. // generate query
  1207. $query->addColumn("fk_child", "level0_id", "level0");
  1208. $query->addColumn("fk_parent", "level1_id", "level0");
  1209. $query->addTable("j_node_node", NO_JOIN, "", "level0");
  1210. $query->addOrderBy("level0_id");
  1211. $query->addOrderBy("level1_id");
  1212. // now left join with itself.
  1213. // maximum number of joins is 31, we've used 1 already, so there are 30 left
  1214. for ($level = 1; $level <= $levels-1; $level++) {
  1215. $joinc = "level".($level-1).".fk_hierarchy = level".($level).".fk_hierarchy AND level".($level-1).".fk_parent = level".($level).".fk_child";
  1216. $query->addTable("j_node_node", LEFT_JOIN, $joinc, "level".($level));
  1217. $query->addColumn("fk_parent", "level".($level+1)."_id", "level".($level));
  1218. $query->addOrderBy("level".($level+1)."_id");
  1219. }
  1220. // this is the where clause
  1221. $where = "level0.fk_hierarchy = '".addslashes($this->_hierarchyId)."' AND level0.fk_child = '".addslashes($idValue)."'";
  1222. $query->addWhere($where);
  1223. // echo "<pre>\n";
  1224. // echo MySQL_SQLGenerator::generateSQLQuery($query);
  1225. // echo "</pre>\n";
  1226. // execute the query
  1227. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  1228. if ($queryResult->getNumberOfRows() == 0) {
  1229. $queryResult->free();
  1230. return;
  1231. }
  1232. // note that the query only returns ids of nodes; thus, for each id,
  1233. // we would need to fetch the actual node information from the node table.
  1234. // for all rows returned by the query
  1235. while($queryResult->hasMoreRows()) {
  1236. $row = $queryResult->getCurrentRow();
  1237. // check all non-null values in current row
  1238. // see if it is cached, if not create a group object and cache it
  1239. for ($level = 0; $level <= $levels; $level++) {
  1240. $nodeId = $row["level{$level}_id"];
  1241.  
  1242. // ignore null values
  1243. if (is_null($nodeId)) {
  1244. // echo "<br />--- skipping to next row (null value encountered)<br />";
  1245. break;
  1246. }
  1247. // echo "<br /><b>Level: $level - Node # $nodeId</b>";
  1248.  
  1249. // if the node has not been cached, then we must create it
  1250. // echo "<br />--- CACHE UPDATE: ";
  1251. if (!$this->_isCached($nodeId)) {
  1252. $nodes =$this->getNodesFromDB("node_id = '".addslashes($nodeId)."'");
  1253. // must be only one node
  1254. if (count($nodes) != 1) {
  1255. throwError(new Error(HierarchyException::OPERATION_FAILED(), "HierarchyCache", true));
  1256. }
  1257. $displayName = $nodes[0]->getDisplayName();
  1258. // echo "Creating node # <b>$nodeId - '$displayName'</b>, ";
  1259.  
  1260. // insert node into cache
  1261. $this->_cache[$nodeId][0] =$nodes[0];
  1262. $this->_cache[$nodeId][1] = 0;
  1263. $this->_cache[$nodeId][2] = 0;
  1264. }
  1265. // else
  1266. // echo "Node already in cache, ";
  1267. // update the levels fetched up, if necessary
  1268. $old = $this->_cache[$nodeId][2];
  1269. // print " old=$old levels=$levels level=$level, ";
  1270. if (($old >= 0) && ($old < ($levels - $level))) {
  1271. if ($originalLevels < 0)
  1272. // if fully, then the node is fetched fully as well
  1273. $this->_cache[$nodeId][2] = -1;
  1274. else
  1275. // if not fully, then set the value appropriately
  1276. $this->_cache[$nodeId][2] = $levels - $level;
  1277.  
  1278. // echo "changing level of caching from <b>$old</b> to <b>".$this->_cache[$nodeId][2]."</b>";
  1279. }
  1280. // else
  1281. // echo "no need to set level of caching";
  1282. // now, update tree structure
  1283. // echo "<br />--- TREE STRUCTURE UPDATE: ";
  1284.  
  1285. // get the current node (create it, if necessary)
  1286. if ($this->_tree->nodeExists($nodeId))
  1287. $node =$this->_tree->getNode($nodeId);
  1288. else {
  1289. // echo "Creating new tree node # <b>$nodeId</b>, ";
  1290. $node = new TreeNode($nodeId);
  1291. $nullValue = NULL; // getting rid of PHP warnings by specifying
  1292. // this second argument
  1293. $this->_tree->addNode($node, $nullValue);
  1294. }
  1295. // does the current node have a child?
  1296. // if no, there is nothing to update
  1297. if ($level == 0) {
  1298. // echo "Skipping leaf node, continuing with parent";
  1299. continue;
  1300. }
  1301. // if there is a child, check if it has already been added
  1302. else {
  1303. // get the child id
  1304. $childId = $row["level".($level-1)."_id"];
  1305. // get the child node
  1306. $child =$this->_tree->getNode($childId);
  1307. // has the child been added? if no, add it!
  1308. if (!$node->isChild($child)) {
  1309. // echo "adding node # <b>$nodeId</b> as a parent of node # <b>$childId</b>";
  1310. // print_r($child);
  1311. // print_r($node);
  1312. $this->_tree->addNode($child, $node);
  1313. }
  1314. // else
  1315. // echo "already child";
  1316. }
  1317. }
  1318.  
  1319. $queryResult->advanceRow();
  1320. }
  1321. $queryResult->free();
  1322. }
  1323. /**
  1324. * Attempts to create the specified node as root node in the database.
  1325. * @access public
  1326. * @param object nodeId The id of the node.
  1327. * @param object type The type of the node.
  1328. * @param string displayName The display name of the node.
  1329. * @param string description The description of the node.
  1330. * @return void
  1331. ***/
  1332. function createRootNode($nodeId, $type, $displayName, $description) {
  1333. // ** parameter validation
  1334. ArgumentValidator::validate($nodeId, ExtendsValidatorRule::getRule("Id"), true);
  1335. ArgumentValidator::validate($type, ExtendsValidatorRule::getRule("Type"), true);
  1336. $stringRule = StringValidatorRule::getRule();
  1337. ArgumentValidator::validate($displayName, $stringRule, true);
  1338. ArgumentValidator::validate($description, $stringRule, true);
  1339. // ** end of parameter validation
  1340. // check that the node does not exist in the cache
  1341. $idValue = $nodeId->getIdString();
  1342. if ($this->_isCached($idValue)) {
  1343. // The node has already been cached!
  1344. throwError(new Error(HierarchyException::OPERATION_FAILED(), "HierarchyCache", true));
  1345. }
  1346. // attempt to insert the node now
  1347. $dbHandler = Services::getService("DatabaseManager");
  1348.  
  1349. // 1. Insert the type
  1350. $domain = $type->getDomain();
  1351. $authority = $type->getAuthority();
  1352. $keyword = $type->getKeyword();
  1353. $typeDescription = $type->getDescription();
  1354.  
  1355. // check whether the type is already in the DB, if not insert it
  1356. $query = new SelectQuery();
  1357. $query->addTable("type");
  1358. $query->addColumn("type_id", "id", "type");
  1359. $where = "type_domain = '".addslashes($domain)."'";
  1360. $where .= " AND type_authority = '".addslashes($authority)."'";
  1361. $where .= " AND type_keyword = '".addslashes($keyword)."'";
  1362. $where .= " AND type_description = '".addslashes($typeDescription)."'";
  1363. $query->addWhere($where);
  1364.  
  1365. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  1366. if ($queryResult->getNumberOfRows() > 0) {// if the type is already in the database
  1367. $typeIdValue = $queryResult->field("id"); // get the id
  1368. $queryResult->free();
  1369. } else { // if not, insert it
  1370. $queryResult->free();
  1371.  
  1372. $query = new InsertQuery();
  1373. $query->setTable("type");
  1374. $query->setAutoIncrementColumn("type_id", "type_type_id_seq");
  1375. $columns = array();
  1376. $columns[] = "type_domain";
  1377. $columns[] = "type_authority";
  1378. $columns[] = "type_keyword";
  1379. $columns[] = "type_description";
  1380. $query->setColumns($columns);
  1381. $values = array();
  1382. $values[] = "'".addslashes($domain)."'";
  1383. $values[] = "'".addslashes($authority)."'";
  1384. $values[] = "'".addslashes($keyword)."'";
  1385. $values[] = "'".addslashes($typeDescription)."'";
  1386. $query->setValues($values);
  1387.  
  1388. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  1389. $typeIdValue = $queryResult->getLastAutoIncrementValue();
  1390. }
  1391. // 2. Now that we know the id of the type, insert the node itself
  1392. $query = new InsertQuery();
  1393. $query->setTable("node");
  1394. $columns = array();
  1395. $columns[] = "node_id";
  1396. $columns[] = "node_display_name";
  1397. $columns[] = "node_description";
  1398. $columns[] = "fk_hierarchy";
  1399. $columns[] = "fk_type";
  1400. $query->setColumns($columns);
  1401. $values = array();
  1402. $values[] = "'".addslashes($idValue)."'";
  1403. $values[] = "'".addslashes($displayName)."'";
  1404. $values[] = "'".addslashes($description)."'";
  1405. $values[] = "'".addslashes($this->_hierarchyId)."'";
  1406. $values[] = "'".addslashes($typeIdValue)."'";
  1407. $query->setValues($values);
  1408.  
  1409. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  1410. if ($queryResult->getNumberOfRows() != 1) {
  1411. //"Could not insert the node (it already exists?)";
  1412. throwError(new Error(HierarchyException::OPERATION_FAILED(), "HierarchyCache", true));
  1413. }
  1414. // create the node object to return
  1415. $node = new HarmoniNode($nodeId, $type, $displayName, $description, $this);
  1416. // then cache it
  1417. $this->_cache[$idValue][0] =$node;
  1418. $this->_cache[$idValue][1] = -1; // fully cached up and down because
  1419. $this->_cache[$idValue][2] = -1; // in fact this node does not have any ancestors or descendents
  1420. // update _tree
  1421. $nullValue = NULL; // getting rid of PHP warnings by specifying
  1422. // this second argument
  1423. $this->_tree->addNode(new TreeNode($idValue), $nullValue);
  1424. return $node;
  1425. }
  1426. /**
  1427. * Attempts to create the specified node in the database and adds the
  1428. * specified parent.
  1429. * @access public
  1430. * @param object nodeId The id of the node.
  1431. * @param object parentId The id of the parent.
  1432. * @param object type The type of the node.
  1433. * @param string displayName The display name of the node.
  1434. * @param string description The description of the node.
  1435. * @return void
  1436. ***/
  1437. function createNode($nodeId, $parentId, $type, $displayName, $description) {
  1438. // create the root node and assign the parent
  1439. $node =$this->createRootNode($nodeId, $type, $displayName, $description);
  1440. $this->addParent($parentId->getIdString(), $nodeId->getIdString());
  1441. return $node;
  1442. }
  1443. /**
  1444. * Attempts to delete the specified node in the database. Only leaf nodes can
  1445. * be deleted.
  1446. * @access public
  1447. * @param mixed idValue The string id of the node to delete.
  1448. * @return void
  1449. ***/
  1450. function deleteNode($idValue) {
  1451. // ** parameter validation
  1452. ArgumentValidator::validate($idValue,
  1453. OrValidatorRule::getRule(
  1454. NonzeroLengthStringValidatorRule::getRule(),
  1455. IntegerValidatorRule::getRule()),
  1456. true);
  1457. // ** end of parameter validation
  1458. // get the node
  1459. $node =$this->getNode($idValue);
  1460. // if not a leaf, cannot delete
  1461. if (!$node->isLeaf()) {
  1462. // "Can not delete non-leaf nodes.";
  1463. throwError(new Error(HierarchyException::OPERATION_FAILED(). " - Cannont delete non-leaf nodes", "HierarchyCache", true));
  1464. }
  1465. // clear the cache and update the _tree structure
  1466.  
  1467. // detach the node from each of its parents and update the join table
  1468. $parents =$node->getParents();
  1469. while ($parents->hasNext()) {
  1470. $parent =$parents->next();
  1471. $node->removeParent($parent->getId());
  1472. }
  1473. // now delete the tree node
  1474. $treeNode =$this->_tree->getNode($idValue);
  1475. $this->_tree->deleteNode($treeNode);
  1476.  
  1477. // -----------------
  1478.  
  1479. // remove from cache
  1480. unset($this->_cache[$idValue]);
  1481. $node = null;
  1482.  
  1483. // now remove from database
  1484. $dbHandler = Services::getService("DatabaseManager");
  1485.  
  1486. // 1. Get the id of the type associated with the node
  1487. $query = new SelectQuery();
  1488. $query->addTable("node");
  1489. $query->addColumn("fk_type", "type_id", "node");
  1490. $query->addWhere("node_id = '".addslashes($idValue)."'");
  1491.  
  1492. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  1493. if ($queryResult->getNumberOfRows() == 0) {
  1494. $queryResult->free();
  1495. throwError(new Error(HierarchyException::OPERATION_FAILED(),"HierarchyCache",true));
  1496. }
  1497. if ($queryResult->getNumberOfRows() > 1) {
  1498. $queryResult->free();
  1499. throwError(new Error(HierarchyException::OPERATION_FAILED() ,"HierarchyCache",true));
  1500. }
  1501. $typeIdValue = $queryResult->field("type_id");
  1502. $queryResult->free();
  1503. // 2. Now delete the node
  1504. $query = new DeleteQuery();
  1505. $query->setTable("node");
  1506. $query->addWhere("node_id = '".addslashes($idValue)."'");
  1507. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  1508. // 3. Now see if any other nodes have the same type
  1509. $query = new SelectQuery();
  1510. $query->addTable("node");
  1511. // count the number of nodes using the same type
  1512. $query->addColumn("COUNT(fk_type)", "num");
  1513. $query->addWhere("fk_type = '".addslashes($typeIdValue)."'");
  1514.  
  1515. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  1516. $num = $queryResult->field("num");
  1517. $queryResult->free();
  1518. if ($num == 0) { // if no other nodes use this type, then delete the type
  1519. $query = new DeleteQuery();
  1520. $query->setTable("type");
  1521. $query->addWhere("type_id = '".addslashes($typeIdValue)."'");
  1522. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  1523. }
  1524. // Delete the node's ancestory from the Ancestory table
  1525. $this->clearNodeAncestory($idValue);
  1526. }
  1527. /**
  1528. * Clears the cache.
  1529. * @access public
  1530. ***/
  1531. function clearCache() {
  1532. $this->_tree = null;
  1533. $this->_cache = null;
  1534. unset($this->_tree);
  1535. unset($this->_cache);
  1536. $this->HierarchyCache($this->_hierarchyId, $this->_allowsMultipleParents,
  1537. $this->_dbIndex);
  1538. }
  1539. /**
  1540. * Build the ancestory rows for a given node
  1541. *
  1542. * @param object Id $id
  1543. * @return void
  1544. * @access public
  1545. * @since 11/4/05
  1546. */
  1547. function rebuildNodeAncestory ( $id ) {
  1548. // print "<hr/><hr/>";
  1549. // print "<strong>"; printpre($id); print "</strong>";
  1550. $idString = $id->getIdString();
  1551. $dbHandler = Services::getService("DatabaseManager");
  1552. // Delete the old ancestory rows
  1553. $this->clearNodeAncestory($idString);
  1554.  
  1555.  
  1556. // Make sure we have traversed the authoratative parent/child hierarchy
  1557. // To determine the new ancestory of the nodes
  1558. if (!$this->_isCachedUp($idString, -1))
  1559. $this->_traverseUp($idString, -1);
  1560. // now that all nodes are cached, add their ids to the ancestor table for
  1561. // easy future searching.
  1562. $query = new InsertQuery;
  1563. $query->setTable("node_ancestry");
  1564. $query->setColumns(array("fk_hierarchy", "fk_node", "fk_ancestor", "level", "fk_ancestors_child"));
  1565. $treeNode =$this->_tree->getNode($idString);
  1566. $treeNodes =$this->_tree->traverse($treeNode, false, -1);
  1567. if (count($treeNodes) > 1) {
  1568. foreach (array_keys($treeNodes) as $i => $key) {
  1569. $node =$this->_cache[$key][0];
  1570. // If the node was deleted, but the cache still has a key for it,
  1571. // continue.
  1572. if (!is_object($node))
  1573. continue;
  1574. $nodeId =$node->getId();
  1575. // printpre($nodeId->getIdString());
  1576. if (!$nodeId->isEqual($id)) {
  1577. foreach ($treeNodes[$key]['children'] as $ancestorChildId) {
  1578. $query->addRowOfValues(array(
  1579. "'".addslashes($this->_hierarchyId)."'",
  1580. "'".addslashes($idString)."'",
  1581. "'".addslashes($nodeId->getIdString())."'",
  1582. "'".addslashes($treeNodes[$key][1])."'",
  1583. "'".addslashes($ancestorChildId)."'"));
  1584. }
  1585. } else {
  1586. $query->addRowOfValues(array(
  1587. "'".addslashes($this->_hierarchyId)."'",
  1588. "'".addslashes($idString)."'",
  1589. "NULL",
  1590. "'0'",
  1591. "NULL"));
  1592. }
  1593. }
  1594. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  1595. // $queryResult->free();
  1596. }
  1597. }
  1598. /**
  1599. * Build the ancestory rows for a node and its decendents
  1600. *
  1601. * @param object Id $id
  1602. * @return void
  1603. * @access public
  1604. * @since 11/4/05
  1605. */
  1606. function rebuildSubtreeAncestory ( $id ) {
  1607. $idString = $id->getIdString();
  1608. // Traverse down to get the nodes in the sub-tree
  1609. if (!$this->_isCachedDown($idString, -1))
  1610. $this->_traverseDown($idString, -1);
  1611. $treeNode =$this->_tree->getNode($idString);
  1612. $treeNodes =$this->_tree->traverse($treeNode, true, -1);
  1613. foreach (array_keys($treeNodes) as $i => $key) {
  1614. $node =$this->_cache[$key][0];
  1615.  
  1616. // If the node was deleted, but the cache still has a key for it,
  1617. // continue.
  1618. if (!is_object($node))
  1619. continue;
  1620. $nodeId =$node->getId();
  1621. $this->rebuildNodeAncestory($nodeId);
  1622. }
  1623. }
  1624. /**
  1625. * Clear the ancestory rows for a given node
  1626. *
  1627. * @param string $idString
  1628. * @return void
  1629. * @access public
  1630. * @since 11/4/05
  1631. */
  1632. function clearNodeAncestory ( $idString ) {
  1633. $dbHandler = Services::getService("DatabaseManager");
  1634. // Delete the old ancestory
  1635. $query = new DeleteQuery;
  1636. $query->setTable("node_ancestry");
  1637. $query->addWhere("node_ancestry.fk_hierarchy = '".addslashes($this->_hierarchyId)."'");
  1638. $query->addWhere("node_ancestry.fk_node = '".addslashes($idString)."'");
  1639. $queryResult =$dbHandler->query($query, $this->_dbIndex);
  1640. // $queryResult->free();
  1641. }
  1642. }
  1643.  
  1644. ?>

Documentation generated on Wed, 19 Sep 2007 10:24:31 -0400 by phpDocumentor 1.3.0RC3