PageRenderTime 21ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/library/classes/Tree.class.php

https://github.com/md-tech/openemr
PHP | 355 lines | 183 code | 64 blank | 108 comment | 41 complexity | 755fc23708ce7e02074600e9dd247e29 MD5 | raw file
  1. <?php
  2. define("ROOT_TYPE_ID",1);
  3. define("ROOT_TYPE_NAME",2);
  4. /**
  5. * class Tree
  6. * This class is a clean implementation of a modified preorder tree traversal hierachy to relational model
  7. * Don't use this class directly as it won't work, extend it and set the $this->_table variable, currently
  8. * this class needs its own sequence per table. MPTT uses a lot of self referential parent child relationships
  9. * and having ids that are more or less sequential makes human reading, fixing and reconstruction much easier.
  10. */
  11. class Tree {
  12. /*
  13. * This is the name of the table this tree is stored in
  14. * @var string
  15. */
  16. var $_table;
  17. /*
  18. * This is a lookup table so that you can get a node name or parent id from its id
  19. * @var array
  20. */
  21. var $_id_name;
  22. /*
  23. * This is a db abstraction object compatible with ADODB
  24. * @var object the constructor expects it to be available as $GLOBALS['adodb']['db']
  25. */
  26. var $_db;
  27. /*
  28. * The constructor takes a value and a flag determining if the value is the id of a the desired root node or the name
  29. * @param mixed $root name or id of desired root node
  30. * @param int $root_type optional flag indicating if $root is a name or id, defaults to id
  31. */
  32. function Tree($root,$root_type = ROOT_TYPE_ID) {
  33. $this->_db = $GLOBALS['adodb']['db'];
  34. $this->_root = mysql_real_escape_string($root);
  35. $this->_root_type = mysql_real_escape_string($root_type);
  36. $this->load_tree();
  37. }
  38. function load_tree() {
  39. $root = $this->_root;
  40. $tree = array();
  41. $tree_tmp = array();
  42. //get the left and right value of the root node
  43. $sql = "SELECT * FROM " . $this->_table . " WHERE id='".$root."'";
  44. if ($this->root_type == ROOT_TYPE_NAME) {
  45. $sql = "SELECT * FROM " . $this->_table . " WHERE name='".$root."'";
  46. }
  47. $result = $this->_db->Execute($sql) or die("Error: " . $this->_db->ErrorMsg());
  48. $row = array();
  49. if($result && !$result->EOF) {
  50. $row = $result->fields;
  51. }
  52. else {
  53. $this->tree = array();
  54. }
  55. // start with an empty right stack
  56. $right = array();
  57. // now, retrieve all descendants of the root node
  58. $sql = "SELECT * FROM " . $this->_table . " WHERE lft BETWEEN " . $row['lft'] . " AND " . $row['rght'] . " ORDER BY parent,name ASC;";
  59. $result = $this->_db->Execute($sql);
  60. $this->_id_name = array();
  61. while ($result && !$result->EOF) {
  62. $ar = array();
  63. $row = $result->fields;
  64. //create a lookup table of id to name for every node that will end up in this tree, this is used
  65. //by the array building code below to find the chain of parents for each node
  66. // ADDED below by BM on 06-2009 to translate categories, if applicable
  67. if ($this->_table == "categories") {
  68. $this->_id_name[$row['id']] = array("id" => $row['id'], "name" => xl_document_category($row['name']), "parent" => $row['parent']);
  69. }
  70. else {
  71. $this->_id_name[$row['id']] = array("id" => $row['id'], "name" => $row['name'], "parent" => $row['parent']);
  72. }
  73. // only check stack if there is one
  74. if (count($right)>0) {
  75. // check if we should remove a node from the stack
  76. while ($right[count($right)-1]<$row['rght']) {
  77. array_pop($right);
  78. }
  79. }
  80. //set up necessary variables to then determine the chain of parents for each node
  81. $parent = $row['parent'];
  82. $loop = 0;
  83. //this is a string that gets evaled below to create the array representing the tree
  84. $ar_string = "[\"".($row['id']) ."\"] = \$row[\"value\"]";
  85. //if parent is 0 then the node has no parents, the number of nodes in the id_name lookup always includes any nodes
  86. //that could be the parent of any future node in the record set, the order is deterministic because of the algorithm
  87. while($parent != 0 && $loop < count($this->_id_name)) {
  88. $ar_string = "[\"" . ($this->_id_name[$parent]['id']) . "\"]" . $ar_string;
  89. $loop++;
  90. $parent = $this->_id_name[$parent]['parent'];
  91. }
  92. $ar_string = '$ar' . $ar_string . ";";
  93. //echo $ar_string;
  94. //now eval the string to create the tree array
  95. //there must be a more efficient way to do this than eval?
  96. eval($ar_string);
  97. //merge the evaled array with all of the already exsiting tree elements,
  98. //merge recursive is used so that no keys are replaced in other words a key
  99. //with a specific value will not be replace but instead that value will be turned into an array
  100. //consisting of the previous value and the new value
  101. $tree = array_merge_n($tree,$ar);
  102. // add this node to the stack
  103. $right[] = $row['rght'];
  104. $result->MoveNext();
  105. }
  106. $this->tree = $tree;
  107. }
  108. /*
  109. * This function completely rebuilds a tree starting from parent to ensure that all of its preorder values
  110. * are integrous.
  111. * Upside is that it fixes any kind of goofiness, downside is that it is recursive and consequently
  112. * exponentially expensive with the size of the tree.
  113. * On adds and deletes the tree does dynamic updates as appropriate to maintain integrity of the algorithm,
  114. * however you can still force it to do goofy things and afterwards you will need this function to fix it.
  115. * If you need to do a huge number of adds or deletes it will be much faster to act directly on the db and then
  116. * call this to fix the mess than to use the add and delete functions.
  117. * @param int $parent id of the node you would like to rebuild all nodes below
  118. * @param int $left optional proper left value of the node you are rebuilding below, then used recursively
  119. */
  120. function rebuild_tree($parent, $left = null) {
  121. //if no left is supplied assume the existing left is proper
  122. if ($left == null) {
  123. $sql = "SELECT lft FROM " . $this->_table . " WHERE id='" . $parent . "';";
  124. $result = $this->_db->Execute($sql) or die("Error: " . $this->_db->ErrorMsg());
  125. if($result && !$result->EOF) {
  126. $left = $result->fields['lft'];
  127. }
  128. else {
  129. //the node you are rebuilding below if goofed up and you didn't supply a proper value
  130. //nothing we can do so error
  131. die("Error: The node you are rebuilding from could not be found, please supply an existing node id.");
  132. }
  133. }
  134. // get all children of this node
  135. $sql = "SELECT id FROM " . $this->_table . " WHERE parent='" . $parent . "' ORDER BY id;";
  136. $result = $this->_db->Execute($sql) or die("Error: " . $this->_db->ErrorMsg());
  137. // the right value of this node is the left value + 1
  138. $right = $left+1;
  139. while ($result && !$result->EOF) {
  140. $row = $result->fields;
  141. // recursive execution of this function for each
  142. // child of this node
  143. // $right is the current right value, which is
  144. // incremented by the rebuild_tree function
  145. $right = $this->rebuild_tree($row['id'], $right);
  146. $result->MoveNext();
  147. }
  148. // we've got the left value, and now that we've processed
  149. // the children of this node we also know the right value
  150. $sql = "UPDATE " . $this->_table . " SET lft=".$left.", rght=".$right." WHERE id='".$parent."';";
  151. //echo $sql . "<br>";
  152. $this->_db->Execute($sql) or die("Error: $sql" . $this->_db->ErrorMsg());
  153. // return the right value of this node + 1
  154. return $right+1;
  155. }
  156. /*
  157. * Call this to add a new node to the tree
  158. * @param int $parent id of the node you would like the new node to have as its parent
  159. * @param string $name the name of the new node, it will be used to reference its value in the tree array
  160. * @param string $value optional value this node is to contain
  161. * @return int id of newly added node
  162. */
  163. function add_node($parent_id,$name,$value="") {
  164. $sql = "SELECT * from " . $this->_table . " where parent = '" . $parent_id . "' and name='" . $name . "'";
  165. $result = $this->_db->Execute($sql) or die("Error: " . $this->_db->ErrorMsg());
  166. if ($result && !$result->EOF) {
  167. die("You cannot add a node with the name '" . $name ."' because one already exists under parent " . $parent_id . "<br>");
  168. }
  169. $sql = "SELECT * from " . $this->_table . " where id = '" . $parent_id . "'";
  170. $result = $this->_db->Execute($sql) or die("Error: " . $this->_db->ErrorMsg());
  171. $next_right = 0;
  172. if ($result && !$result->EOF) {
  173. $next_right = $result->fields['rght'];
  174. }
  175. $sql = "UPDATE " . $this->_table . " SET rght=rght+2 WHERE rght>=" . $next_right;
  176. $this->_db->Execute($sql) or die("Error: " . $this->_db->ErrorMsg());
  177. $sql = "UPDATE " . $this->_table . " SET lft=lft+2 WHERE lft>=" . $next_right;
  178. $this->_db->Execute($sql) or die("Error: " . $this->_db->ErrorMsg());
  179. $id = $this->_db->GenID($this->_table . "_seq");
  180. $sql = "INSERT INTO " . $this->_table . " SET name='" . $name . "', value='" . $value . "', lft='" . $next_right . "', rght='" . ($next_right + 1) . "', parent='" . $parent_id . "', id='" . $id . "'";
  181. $this->_db->Execute($sql) or die("Error: $sql :: " . $this->_db->ErrorMsg());
  182. //$this->rebuild_tree(1,1);
  183. $this->load_tree();
  184. return $id;
  185. }
  186. /*
  187. * Call this to delete a node from the tree, the nodes children (and their children, etc) will become children
  188. * of the deleted nodes parent
  189. * @param int $id id of the node you want to delete
  190. */
  191. function delete_node($id) {
  192. $sql = "SELECT * from " . $this->_table . " where id = '" . $id . "'";
  193. //echo $sql . "<br>";
  194. $result = $this->_db->Execute($sql) or die("Error: " . $this->_db->ErrorMsg());
  195. $left = 0;
  196. $right = 1;
  197. $new_parent = 0;
  198. if ($result && !$result->EOF) {
  199. $left = $result->fields['lft'];
  200. $right = $result->fields['rght'];
  201. $new_parent = $result->fields['parent'];
  202. }
  203. $sql = "UPDATE " . $this->_table . " SET rght=rght-2 WHERE rght>" . $right;
  204. //echo $sql . "<br>";
  205. $this->_db->Execute($sql) or die("Error: " . $this->_db->ErrorMsg());
  206. $sql = "UPDATE " . $this->_table . " SET lft=lft-2 WHERE lft>" . $right;
  207. //echo $sql . "<br>";
  208. $this->_db->Execute($sql) or die("Error: " . $this->_db->ErrorMsg());
  209. $sql = "UPDATE " . $this->_table . " SET lft=lft-1, rght=rght-1 WHERE lft>" . $left . " and rght < " . $right;
  210. //echo $sql . "<br>";
  211. $this->_db->Execute($sql) or die("Error: " . $this->_db->ErrorMsg());
  212. //only update the childrens parent setting if the node has children
  213. if ($right > ($left +1)) {
  214. $sql = "UPDATE " . $this->_table . " SET parent='" . $new_parent . "' WHERE parent='" . $id . "'";
  215. //echo $sql . "<br>";
  216. $this->_db->Execute($sql) or die("Error: " . $this->_db->ErrorMsg());
  217. }
  218. $sql = "DELETE FROM " . $this->_table . " where id='" . $id . "'";
  219. //echo $sql . "<br>";
  220. $this->_db->Execute($sql) or die("Error: " . $this->_db->ErrorMsg());
  221. $this->load_tree();
  222. return true;
  223. }
  224. function get_node_info($id) {
  225. if(!empty($this->_id_name[$id])) {
  226. return $this->_id_name[$id];
  227. }
  228. else {
  229. return array();
  230. }
  231. }
  232. function get_node_name($id) {
  233. if(!empty($this->_id_name[$id])) {
  234. return $this->_id_name[$id]['name'];
  235. }
  236. else {
  237. return false;
  238. }
  239. }
  240. }
  241. function array_merge_2(&$array, &$array_i) {
  242. // For each element of the array (key => value):
  243. foreach ($array_i as $k => $v) {
  244. // If the value itself is an array, the process repeats recursively:
  245. if (is_array($v)) {
  246. if (!isset($array[$k])) {
  247. $array[$k] = array();
  248. }
  249. array_merge_2($array[$k], $v);
  250. // Else, the value is assigned to the current element of the resulting array:
  251. } else {
  252. if (isset($array[$k]) && is_array($array[$k])) {
  253. $array[$k][0] = $v;
  254. } else {
  255. if (isset($array) && !is_array($array)) {
  256. $temp = $array;
  257. $array = array();
  258. $array[0] = $temp;
  259. }
  260. $array[$k] = $v;
  261. }
  262. }
  263. }
  264. }
  265. function array_merge_n() {
  266. // Initialization of the resulting array:
  267. $array = array();
  268. // Arrays to be merged (function's arguments):
  269. $arrays =& func_get_args();
  270. // Merging of each array with the resulting one:
  271. foreach ($arrays as $array_i) {
  272. if (is_array($array_i)) {
  273. array_merge_2($array, $array_i);
  274. }
  275. }
  276. return $array;
  277. }
  278. /*
  279. $db = $GLOBALS['adodb']['db'];
  280. $sql = "USE document;";
  281. $db->Execute($sql);
  282. $t = new Tree(1);
  283. echo "<pre>";
  284. print_r($t->tree);
  285. //$nid = $t->add_node(0,"test node2","test value");
  286. //$t->add_node($nid,"test child","another value");
  287. //$t->add_node($nid,"test child");
  288. print_r($t->tree);
  289. echo "</pre>";
  290. */
  291. ?>