class.tree.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <?
  2. class tree {
  3. // Structure table and fields
  4. var $s_table = "";
  5. var $s_fields = array(
  6. "id" => false,
  7. "parent_id" => false,
  8. "position" => false,
  9. "left" => false,
  10. "right" => false,
  11. "level" => false
  12. );
  13. // Additional fields (stored in format `table_name.field_name`)
  14. var $d_fields = array();
  15. // Tree type (or types)
  16. var $adjacency = false;
  17. var $nestedset = false;
  18. // Database
  19. var $db = false;
  20. // Constructor
  21. function __construct($tables = array()) {
  22. if(!is_array($tables) || !count($tables)) return;
  23. foreach($tables as $table_name => $fields) {
  24. if(is_array($fields)) {
  25. foreach($fields as $key => $field) {
  26. switch($key) {
  27. case "id":
  28. case "parent_id":
  29. case "position":
  30. case "left":
  31. case "right":
  32. case "level":
  33. $this->s_table = $table_name;
  34. $this->s_fields[$key] = $field;
  35. break;
  36. default:
  37. $this->d_fields[] = $table_name.".".$field;
  38. break;
  39. }
  40. }
  41. }
  42. }
  43. // Determine what kind of a tree is used (or both)
  44. if($this->s_fields["id"] && $this->s_fields["position"]) $this->adjacency = true;
  45. if($this->s_fields["id"] && $this->s_fields["left"] && $this->s_fields["right"] && $this->s_fields["level"]) $this->nestedset = true;
  46. // Database
  47. $this->db = new DB;
  48. }
  49. function tree($tables = array()) { return $this->__construct($tables); } // PHP 4 compatibilty
  50. // WRITING FUNCTIONS
  51. // Function for moving nodes
  52. // ID is the node that is being moved - 0 is creating a new NODE
  53. // REF_ID is the reference node in the move
  54. // TYPE is one of "after", "before" or "inside"
  55. function move($id, $ref_id, $type, $mode = "move") {
  56. if(!in_array($type, array("after", "before", "inside"))) return false;
  57. // Queries executed at the end
  58. $sql = array();
  59. if(!(int)$id) $mode = "create";
  60. if($mode == "create") {
  61. // Fields and values that will be inserted
  62. $fields = array();
  63. $values = array();
  64. // Inserting an ID
  65. $fields[] = "`".$this->s_fields["id"]."`";
  66. $values[] = "NULL";
  67. }
  68. // If the tree maintains an ID->PARENT_ID relation
  69. if($this->adjacency) {
  70. $this->db->query("SELECT `".$this->s_fields["parent_id"]."`, `".$this->s_fields["position"]."` FROM `".$this->s_table."` WHERE `".$this->s_fields["id"]."` = ".(int)$ref_id);
  71. $this->db->nextr();
  72. // Determine new parent and position
  73. if($type == "inside") {
  74. $new_parent_id = $ref_id;
  75. $new_position = 1;
  76. }
  77. else {
  78. $new_parent_id = (int)$this->db->f(0);
  79. if($type == "before") $new_position = $this->db->f(1);
  80. if($type == "after") $new_position = $this->db->f(1) + 1;
  81. }
  82. // Cleanup old parent
  83. if($mode == "create") {
  84. $old_parent_id = -1;
  85. $old_position = 0;
  86. }
  87. else {
  88. $this->db->query("SELECT `".$this->s_fields["parent_id"]."`, `".$this->s_fields["position"]."` FROM `".$this->s_table."` WHERE `".$this->s_fields["id"]."` = ".(int)$id);
  89. $this->db->nextr();
  90. $old_parent_id = $this->db->f(0);
  91. $old_position = $this->db->f(1);
  92. }
  93. // A reorder was made
  94. if($old_parent_id == $new_parent_id) {
  95. if($new_position > $old_position) {
  96. $new_position = $new_position - 1;
  97. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["position"]."` = `".$this->s_fields["position"]."` - 1 WHERE `".$this->s_fields["parent_id"]."` = ".$old_parent_id." AND `".$this->s_fields["position"]."` BETWEEN ".($old_position + 1)." AND ".$new_position;
  98. }
  99. if($new_position < $old_position) {
  100. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["position"]."` = `".$this->s_fields["position"]."` + 1 WHERE `".$this->s_fields["parent_id"]."` = ".$old_parent_id." AND `".$this->s_fields["position"]."` BETWEEN ".$new_position." AND ".($old_position - 1);
  101. }
  102. }
  103. else {
  104. // Fix old parent (move siblings up)
  105. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["position"]."` = `".$this->s_fields["position"]."` - 1 WHERE `".$this->s_fields["parent_id"]."` = ".$old_parent_id." AND `".$this->s_fields["position"]."` > ".$old_position;
  106. // Prepare new parent (move sibling down)
  107. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["position"]."` = `".$this->s_fields["position"]."` + 1 WHERE `".$this->s_fields["parent_id"]."` = ".$new_parent_id." AND `".$this->s_fields["position"]."` >".($type != "after" ? "=" : "")." ".$new_position;
  108. }
  109. // Move the node to the new position
  110. if($mode == "create") {
  111. $fields[] = "`".$this->s_fields["parent_id"]."`";
  112. $fields[] = "`".$this->s_fields["position"]."`";
  113. $values[] = $new_parent_id;
  114. $values[] = $new_position;
  115. }
  116. else {
  117. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["position"]."` = ".$new_position.", `".$this->s_fields["parent_id"]."` = ".$new_parent_id." WHERE `".$this->s_fields["id"]."` = ".(int)$id;
  118. }
  119. }
  120. // If the tree maintains a nested set
  121. if($this->nestedset) {
  122. $this->db->query("SELECT `".$this->s_fields["id"]."` AS id, `".$this->s_fields["left"]."` AS lft, `".$this->s_fields["right"]."` AS rgt, `".$this->s_fields["level"]."` AS lvl FROM `".$this->s_table."` WHERE `".$this->s_fields["id"]."` IN(".(int)$id.",".(int)$ref_id.")");
  123. while($this->db->nextr()) {
  124. if($id == $this->db->f("id")) {
  125. $nod_lft = (int)$this->db->f("lft");
  126. $nod_rgt = (int)$this->db->f("rgt");
  127. $dif = $nod_rgt - $nod_lft + 1;
  128. }
  129. if($ref_id == $this->db->f("id")) {
  130. $ref_lft = (int)$this->db->f("lft");
  131. $ref_rgt = (int)$this->db->f("rgt");
  132. $ref_lvl = (int)$this->db->f("lvl");
  133. }
  134. }
  135. if($mode == "move") {
  136. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["left"]."` = `".$this->s_fields["left"]."` - ".$dif." WHERE `".$this->s_fields["left"]."` > ".$nod_rgt;
  137. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["right"]."` = `".$this->s_fields["right"]."` - ".$dif." WHERE `".$this->s_fields["right"]."` > ".$nod_rgt;
  138. if($ref_lft > $nod_rgt) $ref_lft -= $dif;
  139. if($ref_rgt > $nod_rgt) $ref_rgt -= $dif;
  140. }
  141. else $dif = 2;
  142. $ids = array();
  143. if($mode == "move") {
  144. $this->db->query("SELECT `".$this->s_fields["id"]."` FROM `".$this->s_table."` WHERE `".$this->s_fields["left"]."` >= ".$nod_lft." AND `".$this->s_fields["right"]."` <= ".$nod_rgt);
  145. while($this->db->nextr()) $ids[] = (int)$this->db->f(0);
  146. }
  147. else $ids[] = -1;
  148. switch($type) {
  149. case "before":
  150. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["left"]."` = `".$this->s_fields["left"]."` + ".$dif." WHERE `".$this->s_fields["left"]."` >= ".$ref_lft." AND `".$this->s_fields["id"]."` NOT IN(".implode(",",$ids).") ";
  151. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["right"]."` = `".$this->s_fields["right"]."` + ".$dif." WHERE `".$this->s_fields["right"]."` > ".$ref_lft." AND `".$this->s_fields["id"]."` NOT IN(".implode(",",$ids).") ";
  152. if($mode == "move") {
  153. $dif = $ref_lft - $nod_lft;
  154. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["level"]."` = ".(int)$ref_lvl.", `".$this->s_fields["left"]."` = `".$this->s_fields["left"]."` + (".$dif."), `".$this->s_fields["right"]."` = `".$this->s_fields["right"]."` + (".$dif.") WHERE `".$this->s_fields["id"]."` IN (".implode(",",$ids).") ";
  155. }
  156. else {
  157. $fields[] = "`".$this->s_fields["level"]."`";
  158. $fields[] = "`".$this->s_fields["left"]."`";
  159. $fields[] = "`".$this->s_fields["right"]."`";
  160. $values[] = (int)$ref_lvl;
  161. $values[] = (int)$ref_lft;
  162. $values[] = ((int)$ref_lft + 2);
  163. }
  164. break;
  165. case "after":
  166. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["left"]."` = `".$this->s_fields["left"]."` + ".$dif." WHERE `".$this->s_fields["left"]."` > ".$ref_rgt." AND `".$this->s_fields["id"]."` NOT IN(".implode(",",$ids).") ";
  167. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["right"]."` = `".$this->s_fields["right"]."` + ".$dif." WHERE `".$this->s_fields["right"]."` > ".$ref_rgt." AND `".$this->s_fields["id"]."` NOT IN(".implode(",",$ids).") ";
  168. if($mode == "move") {
  169. $dif = ($ref_rgt + 1) - $nod_lft;
  170. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["level"]."` = ".(int)$ref_lvl.", `".$this->s_fields["left"]."` = `".$this->s_fields["left"]."` + (".$dif."), `".$this->s_fields["right"]."` = `".$this->s_fields["right"]."` + (".$dif.") WHERE `".$this->s_fields["id"]."` IN (".implode(",",$ids).") ";
  171. } else {
  172. $fields[] = "`".$this->s_fields["level"]."`";
  173. $fields[] = "`".$this->s_fields["left"]."`";
  174. $fields[] = "`".$this->s_fields["right"]."`";
  175. $values[] = (int)$ref_lvl;
  176. $values[] = ((int)$ref_rgt + 1);
  177. $values[] = ((int)$ref_rgt + 3);
  178. }
  179. break;
  180. case "inside":
  181. default:
  182. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["left"]."` = `".$this->s_fields["left"]."` + ".$dif." WHERE `".$this->s_fields["left"]."` > ".$ref_lft." AND `".$this->s_fields["id"]."` NOT IN(".implode(",",$ids).") ";
  183. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["right"]."` = `".$this->s_fields["right"]."` + ".$dif." WHERE `".$this->s_fields["right"]."` > ".$ref_lft." AND `".$this->s_fields["id"]."` NOT IN(".implode(",",$ids).") ";
  184. if($mode == "move") {
  185. $dif = ($ref_lft + 1) - $nod_lft;
  186. $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["level"]."` = ".(int)($ref_lvl + 1).", `".$this->s_fields["left"]."` = `".$this->s_fields["left"]."` + (".$dif."), `".$this->s_fields["right"]."` = `".$this->s_fields["right"]."` + (".$dif.") WHERE `".$this->s_fields["id"]."` IN (".implode(",",$ids).") ";
  187. }
  188. else {
  189. $fields[] = "`".$this->s_fields["level"]."`";
  190. $fields[] = "`".$this->s_fields["left"]."`";
  191. $fields[] = "`".$this->s_fields["right"]."`";
  192. $values[] = ((int)$ref_lvl + 1);
  193. $values[] = ((int)$ref_lft + 1);
  194. $values[] = ((int)$ref_lft + 3);
  195. }
  196. break;
  197. }
  198. }
  199. // If creating a new node
  200. if($mode == "create") $sql[] = "INSERT INTO `".$this->s_table."` (".implode(",",$fields).") VALUES (".implode(",",$values).")";
  201. // Applying all changes - there should be a transaction here
  202. foreach($sql as $q) { $this->db->query($q); }
  203. if($mode == "create") return mysql_insert_id();
  204. }
  205. // Function for removing nodes
  206. // ID is the node (or array of nodes) that is being removed
  207. function remove($id) {
  208. if(is_array($id)) {
  209. foreach($id as $i) { $this->remove($i); }
  210. return;
  211. }
  212. if(!(int)$id) return false;
  213. // Take care of nested sets (and adjacency at the same time if applicable)
  214. if($this->nestedset) {
  215. $this->db->query("SELECT `".$this->s_fields["left"]."` AS lft, `".$this->s_fields["right"]."` AS rgt ".( ($this->adjacency) ? " , `".$this->s_fields["parent_id"]."` AS pid, `".$this->s_fields["position"]."` AS pos " : "" )." FROM `".$this->s_table."` WHERE `".$this->s_fields["id"]."` = ".(int)$id);
  216. $this->db->nextr();
  217. if($this->adjacency) {
  218. $pid = (int)$this->db->f("pid");
  219. $pos = (int)$this->db->f("pos");
  220. }
  221. $lft = (int)$this->db->f("lft");
  222. $rgt = (int)$this->db->f("rgt");
  223. $dif = $rgt - $lft + 1;
  224. $this->db->query("DELETE FROM `".$this->s_table."` WHERE `".$this->s_fields["left"]."` >= ".$lft." AND `".$this->s_fields["right"]."` <= ".$rgt);
  225. $this->db->query("UPDATE `".$this->s_table."` SET `".$this->s_fields["left"]."` = `".$this->s_fields["left"]."` - ".$dif." WHERE `".$this->s_fields["left"]."` > ".$rgt);
  226. $this->db->query("UPDATE `".$this->s_table."` SET `".$this->s_fields["right"]."` = `".$this->s_fields["right"]."` - ".$dif." WHERE `".$this->s_fields["right"]."` > ".$lft);
  227. if($this->adjacency) {
  228. $this->db->query("UPDATE `".$this->s_table."` SET `".$this->s_fields["position"]."` = `".$this->s_fields["position"]."` - 1 WHERE `".$this->s_fields["parent_id"]."` = ".$pid." AND `".$this->s_fields["position"]."` > ".$pos);
  229. }
  230. return;
  231. }
  232. // Only end up here if the tree is adjacency only
  233. if($this->adjacency) {
  234. $this->db->query("SELECT `".$this->s_fields["parent_id"]."` AS pid, `".$this->s_fields["position"]."` AS pos FROM `".$this->s_table."` WHERE `".$this->s_fields["id"]."` = ".(int)$id);
  235. $this->db->nextr();
  236. $pid = (int)$this->db->f("pid");
  237. $pos = (int)$this->db->f("pos");
  238. $tmp = array($id);
  239. $ids = array($id);
  240. while(count($tmp)) {
  241. $t = array_shift($tmp);
  242. if($t) {
  243. $this->db->query("SELECT `".$this->s_fields["id"]."` FROM `".$this->s_table."` WHERE `".$this->s_fields["parent_id"]."` = ".(int)$t);
  244. while($this->db->nextr()) {
  245. array_push($ids, $this->db->f(0));
  246. array_push($tmp, $this->db->f(0));
  247. }
  248. }
  249. }
  250. $this->db->query("DELETE FROM `".$this->s_table."` WHERE `".$this->s_fields["id"]."` IN (".implode(",",$ids).")");
  251. $this->db->query("UPDATE `".$this->s_table."` SET `".$this->s_fields["position"]."` = `".$this->s_fields["position"]."` - 1 WHERE `".$this->s_fields["parent_id"]."` = ".$pid." AND `".$this->s_fields["position"]."` > ".$pos);
  252. }
  253. }
  254. function reconstruct() {
  255. if(!$this->adjacency || !$this->nestedset) return;
  256. // не знам защо да не е persistent
  257. $this->db->pcn = false;
  258. $q = "CREATE TEMPORARY TABLE temp_tree (".$this->s_fields["id"]." INTEGER NOT NULL, ".$this->s_fields["parent_id"]." INTEGER NOT NULL, ". $this->s_fields["position"]." INTEGER NOT NULL) type=HEAP";
  259. $this->db->query($q);
  260. $q = "INSERT INTO temp_tree SELECT ".$this->s_fields["id"].", ".$this->s_fields["parent_id"].", ".$this->s_fields["position"]." FROM ".$this->s_table;
  261. $this->db->query($q);
  262. $q = "CREATE TEMPORARY TABLE temp_stack (".$this->s_fields["id"]." INTEGER NOT NULL, ".$this->s_fields["left"]." INTEGER, ".$this->s_fields["right"]." INTEGER, ".$this->s_fields["level"]." INTEGER, stack_top INTEGER NOT NULL, ".$this->s_fields["parent_id"]." INTEGER, ".$this->s_fields["position"]." INTEGER) type=HEAP";
  263. $this->db->query($q);
  264. $counter = 2;
  265. $q = "SELECT COUNT(*) as maxcounter FROM temp_tree";
  266. $this->db->query($q);
  267. $this->db->nextr();
  268. $maxcounter = (int) $this->db->f("maxcounter") * 2;
  269. $currenttop = 1;
  270. $q = "INSERT INTO temp_stack SELECT ".$this->s_fields["id"].", 1, NULL, 0, 1, ".$this->s_fields["parent_id"].", ".$this->s_fields["position"]." FROM temp_tree WHERE ".$this->s_fields["parent_id"]." = 0";
  271. $this->db->query($q);
  272. $q = "DELETE FROM temp_tree WHERE ".$this->s_fields["parent_id"]." = 0";
  273. $this->db->query($q);
  274. while ($counter <= $maxcounter) {
  275. $q = "SELECT temp_tree.".$this->s_fields["id"]." AS tempmin, temp_tree.".$this->s_fields["parent_id"]." AS pid, temp_tree.".$this->s_fields["position"]." AS lid FROM temp_stack, temp_tree WHERE temp_stack.".$this->s_fields["id"]." = temp_tree.".$this->s_fields["parent_id"]." AND temp_stack.stack_top = ".$currenttop." ORDER BY temp_tree.".$this->s_fields["position"]." ASC LIMIT 1";
  276. $this->db->query($q);
  277. if ($this->db->nextr()) {
  278. $tmp = $this->db->f("tempmin");
  279. $q = "INSERT INTO temp_stack (stack_top, ".$this->s_fields["id"].", ".$this->s_fields["left"].", ".$this->s_fields["right"].", ".$this->s_fields["level"].", ".$this->s_fields["parent_id"].", ".$this->s_fields["position"].") VALUES(".($currenttop + 1).", ".$tmp.", ".$counter.", NULL, ".$currenttop.", ".$this->db->f("pid").", ".$this->db->f("lid").")";
  280. $this->db->query($q);
  281. $q = "DELETE FROM temp_tree WHERE ".$this->s_fields["id"]." = ".$tmp;
  282. $this->db->query($q);
  283. $counter++;
  284. $currenttop++;
  285. }
  286. else {
  287. $q = "UPDATE temp_stack SET ".$this->s_fields["right"]." = ".$counter.", stack_top = -stack_top WHERE stack_top = ".$currenttop;
  288. $this->db->query($q);
  289. $counter++;
  290. $currenttop--;
  291. }
  292. }
  293. $q = "TRUNCATE TABLE ".$this->s_table;
  294. $this->db->query($q);
  295. $q = "INSERT INTO ".$this->s_table." SELECT ".$this->s_fields["id"].", ".$this->s_fields["parent_id"].", ".$this->s_fields["position"].", ".$this->s_fields["left"].", ".$this->s_fields["right"].", ".$this->s_fields["level"]." FROM temp_stack ORDER BY ".$this->s_fields["id"];
  296. $this->db->query($q);
  297. }
  298. function analyze() {
  299. $this->errors = array();
  300. if($this->adjacency) {
  301. $this->db->query("SELECT COUNT(*) FROM ".$this->s_table." s WHERE ".$this->s_fields["parent_id"]." != 0 AND (SELECT COUNT(*) FROM ".$this->s_table." WHERE ".$this->s_fields["id"]." = s.".$this->s_fields["parent_id"].") = 0 ");
  302. $this->db->nextr();
  303. if($this->db->f(0) > 0) $this->errors[] = "Missing parents.";
  304. }
  305. if($this->nestedset) {
  306. $this->db->query("SELECT MAX(".$this->s_fields["right"].") FROM ".$this->s_table);
  307. $this->db->nextr();
  308. $n = $this->db->f(0);
  309. $this->db->query("SELECT COUNT(*) FROM ".$this->s_table);
  310. $this->db->nextr();
  311. $c = $this->db->f(0);
  312. if($n/2 != $c) $this->errors[] = "Right index does not match node count.";
  313. }
  314. if($this->adjacency && $this->nestedset) {
  315. $this->db->query("SELECT COUNT(".$this->s_fields["id"].") FROM ".$this->s_table." s WHERE (SELECT COUNT(*) FROM ".$this->s_table." WHERE ".$this->s_fields["right"]." < s.".$this->s_fields["right"]." AND ".$this->s_fields["left"]." > s.".$this->s_fields["left"]." AND ".$this->s_fields["level"]." = s.".$this->s_fields["level"]." + 1) != (SELECT COUNT(*) FROM ".$this->s_table." WHERE ".$this->s_fields["parent_id"]." = s.".$this->s_fields["id"].") ");
  316. $this->db->nextr();
  317. if($this->db->f(0) > 0) $this->errors = "Adjacency and nested set do not match.";
  318. }
  319. return $error;
  320. }
  321. }
  322. ?>