123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- <?
- class tree {
- // Structure table and fields
- var $s_table = "";
- var $s_fields = array(
- "id" => false,
- "parent_id" => false,
- "position" => false,
- "left" => false,
- "right" => false,
- "level" => false
- );
- // Additional fields (stored in format `table_name.field_name`)
- var $d_fields = array();
- // Tree type (or types)
- var $adjacency = false;
- var $nestedset = false;
- // Database
- var $db = false;
- // Constructor
- function __construct($tables = array()) {
- if(!is_array($tables) || !count($tables)) return;
- foreach($tables as $table_name => $fields) {
- if(is_array($fields)) {
- foreach($fields as $key => $field) {
- switch($key) {
- case "id":
- case "parent_id":
- case "position":
- case "left":
- case "right":
- case "level":
- $this->s_table = $table_name;
- $this->s_fields[$key] = $field;
- break;
- default:
- $this->d_fields[] = $table_name.".".$field;
- break;
- }
- }
- }
- }
- // Determine what kind of a tree is used (or both)
- if($this->s_fields["id"] && $this->s_fields["position"]) $this->adjacency = true;
- if($this->s_fields["id"] && $this->s_fields["left"] && $this->s_fields["right"] && $this->s_fields["level"]) $this->nestedset = true;
- // Database
- $this->db = new DB;
- }
- function tree($tables = array()) { return $this->__construct($tables); } // PHP 4 compatibilty
- // WRITING FUNCTIONS
- // Function for moving nodes
- // ID is the node that is being moved - 0 is creating a new NODE
- // REF_ID is the reference node in the move
- // TYPE is one of "after", "before" or "inside"
- function move($id, $ref_id, $type, $mode = "move") {
- if(!in_array($type, array("after", "before", "inside"))) return false;
- // Queries executed at the end
- $sql = array();
- if(!(int)$id) $mode = "create";
- if($mode == "create") {
- // Fields and values that will be inserted
- $fields = array();
- $values = array();
- // Inserting an ID
- $fields[] = "`".$this->s_fields["id"]."`";
- $values[] = "NULL";
- }
- // If the tree maintains an ID->PARENT_ID relation
- if($this->adjacency) {
- $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);
- $this->db->nextr();
- // Determine new parent and position
- if($type == "inside") {
- $new_parent_id = $ref_id;
- $new_position = 1;
- }
- else {
- $new_parent_id = (int)$this->db->f(0);
- if($type == "before") $new_position = $this->db->f(1);
- if($type == "after") $new_position = $this->db->f(1) + 1;
- }
- // Cleanup old parent
- if($mode == "create") {
- $old_parent_id = -1;
- $old_position = 0;
- }
- else {
- $this->db->query("SELECT `".$this->s_fields["parent_id"]."`, `".$this->s_fields["position"]."` FROM `".$this->s_table."` WHERE `".$this->s_fields["id"]."` = ".(int)$id);
- $this->db->nextr();
- $old_parent_id = $this->db->f(0);
- $old_position = $this->db->f(1);
- }
- // A reorder was made
- if($old_parent_id == $new_parent_id) {
- if($new_position > $old_position) {
- $new_position = $new_position - 1;
- $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;
- }
- if($new_position < $old_position) {
- $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);
- }
- }
- else {
- // Fix old parent (move siblings up)
- $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;
- // Prepare new parent (move sibling down)
- $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;
- }
- // Move the node to the new position
- if($mode == "create") {
- $fields[] = "`".$this->s_fields["parent_id"]."`";
- $fields[] = "`".$this->s_fields["position"]."`";
- $values[] = $new_parent_id;
- $values[] = $new_position;
- }
- else {
- $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;
- }
- }
- // If the tree maintains a nested set
- if($this->nestedset) {
- $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.")");
- while($this->db->nextr()) {
- if($id == $this->db->f("id")) {
- $nod_lft = (int)$this->db->f("lft");
- $nod_rgt = (int)$this->db->f("rgt");
- $dif = $nod_rgt - $nod_lft + 1;
- }
- if($ref_id == $this->db->f("id")) {
- $ref_lft = (int)$this->db->f("lft");
- $ref_rgt = (int)$this->db->f("rgt");
- $ref_lvl = (int)$this->db->f("lvl");
- }
- }
- if($mode == "move") {
- $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["left"]."` = `".$this->s_fields["left"]."` - ".$dif." WHERE `".$this->s_fields["left"]."` > ".$nod_rgt;
- $sql[] = "UPDATE `".$this->s_table."` SET `".$this->s_fields["right"]."` = `".$this->s_fields["right"]."` - ".$dif." WHERE `".$this->s_fields["right"]."` > ".$nod_rgt;
- if($ref_lft > $nod_rgt) $ref_lft -= $dif;
- if($ref_rgt > $nod_rgt) $ref_rgt -= $dif;
- }
- else $dif = 2;
- $ids = array();
- if($mode == "move") {
- $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);
- while($this->db->nextr()) $ids[] = (int)$this->db->f(0);
- }
- else $ids[] = -1;
- switch($type) {
- case "before":
- $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).") ";
- $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).") ";
- if($mode == "move") {
- $dif = $ref_lft - $nod_lft;
- $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).") ";
- }
- else {
- $fields[] = "`".$this->s_fields["level"]."`";
- $fields[] = "`".$this->s_fields["left"]."`";
- $fields[] = "`".$this->s_fields["right"]."`";
- $values[] = (int)$ref_lvl;
- $values[] = (int)$ref_lft;
- $values[] = ((int)$ref_lft + 2);
- }
- break;
- case "after":
- $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).") ";
- $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).") ";
- if($mode == "move") {
- $dif = ($ref_rgt + 1) - $nod_lft;
- $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).") ";
- } else {
- $fields[] = "`".$this->s_fields["level"]."`";
- $fields[] = "`".$this->s_fields["left"]."`";
- $fields[] = "`".$this->s_fields["right"]."`";
- $values[] = (int)$ref_lvl;
- $values[] = ((int)$ref_rgt + 1);
- $values[] = ((int)$ref_rgt + 3);
- }
- break;
- case "inside":
- default:
- $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).") ";
- $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).") ";
- if($mode == "move") {
- $dif = ($ref_lft + 1) - $nod_lft;
- $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).") ";
- }
- else {
- $fields[] = "`".$this->s_fields["level"]."`";
- $fields[] = "`".$this->s_fields["left"]."`";
- $fields[] = "`".$this->s_fields["right"]."`";
- $values[] = ((int)$ref_lvl + 1);
- $values[] = ((int)$ref_lft + 1);
- $values[] = ((int)$ref_lft + 3);
- }
- break;
- }
- }
-
- // If creating a new node
- if($mode == "create") $sql[] = "INSERT INTO `".$this->s_table."` (".implode(",",$fields).") VALUES (".implode(",",$values).")";
- // Applying all changes - there should be a transaction here
- foreach($sql as $q) { $this->db->query($q); }
- if($mode == "create") return mysql_insert_id();
- }
- // Function for removing nodes
- // ID is the node (or array of nodes) that is being removed
- function remove($id) {
- if(is_array($id)) {
- foreach($id as $i) { $this->remove($i); }
- return;
- }
- if(!(int)$id) return false;
- // Take care of nested sets (and adjacency at the same time if applicable)
- if($this->nestedset) {
- $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);
- $this->db->nextr();
- if($this->adjacency) {
- $pid = (int)$this->db->f("pid");
- $pos = (int)$this->db->f("pos");
- }
- $lft = (int)$this->db->f("lft");
- $rgt = (int)$this->db->f("rgt");
- $dif = $rgt - $lft + 1;
- $this->db->query("DELETE FROM `".$this->s_table."` WHERE `".$this->s_fields["left"]."` >= ".$lft." AND `".$this->s_fields["right"]."` <= ".$rgt);
- $this->db->query("UPDATE `".$this->s_table."` SET `".$this->s_fields["left"]."` = `".$this->s_fields["left"]."` - ".$dif." WHERE `".$this->s_fields["left"]."` > ".$rgt);
- $this->db->query("UPDATE `".$this->s_table."` SET `".$this->s_fields["right"]."` = `".$this->s_fields["right"]."` - ".$dif." WHERE `".$this->s_fields["right"]."` > ".$lft);
- if($this->adjacency) {
- $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);
- }
- return;
- }
- // Only end up here if the tree is adjacency only
- if($this->adjacency) {
- $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);
- $this->db->nextr();
- $pid = (int)$this->db->f("pid");
- $pos = (int)$this->db->f("pos");
- $tmp = array($id);
- $ids = array($id);
- while(count($tmp)) {
- $t = array_shift($tmp);
- if($t) {
- $this->db->query("SELECT `".$this->s_fields["id"]."` FROM `".$this->s_table."` WHERE `".$this->s_fields["parent_id"]."` = ".(int)$t);
- while($this->db->nextr()) {
- array_push($ids, $this->db->f(0));
- array_push($tmp, $this->db->f(0));
- }
- }
- }
- $this->db->query("DELETE FROM `".$this->s_table."` WHERE `".$this->s_fields["id"]."` IN (".implode(",",$ids).")");
- $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);
- }
- }
- function reconstruct() {
- if(!$this->adjacency || !$this->nestedset) return;
- // не знам защо да не е persistent
- $this->db->pcn = false;
- $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";
- $this->db->query($q);
- $q = "INSERT INTO temp_tree SELECT ".$this->s_fields["id"].", ".$this->s_fields["parent_id"].", ".$this->s_fields["position"]." FROM ".$this->s_table;
- $this->db->query($q);
- $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";
- $this->db->query($q);
- $counter = 2;
- $q = "SELECT COUNT(*) as maxcounter FROM temp_tree";
- $this->db->query($q);
- $this->db->nextr();
- $maxcounter = (int) $this->db->f("maxcounter") * 2;
- $currenttop = 1;
- $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";
- $this->db->query($q);
- $q = "DELETE FROM temp_tree WHERE ".$this->s_fields["parent_id"]." = 0";
- $this->db->query($q);
- while ($counter <= $maxcounter) {
- $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";
- $this->db->query($q);
- if ($this->db->nextr()) {
- $tmp = $this->db->f("tempmin");
- $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").")";
- $this->db->query($q);
- $q = "DELETE FROM temp_tree WHERE ".$this->s_fields["id"]." = ".$tmp;
- $this->db->query($q);
- $counter++;
- $currenttop++;
- }
- else {
- $q = "UPDATE temp_stack SET ".$this->s_fields["right"]." = ".$counter.", stack_top = -stack_top WHERE stack_top = ".$currenttop;
- $this->db->query($q);
- $counter++;
- $currenttop--;
- }
- }
- $q = "TRUNCATE TABLE ".$this->s_table;
- $this->db->query($q);
- $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"];
- $this->db->query($q);
- }
- function analyze() {
- $this->errors = array();
- if($this->adjacency) {
- $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 ");
- $this->db->nextr();
- if($this->db->f(0) > 0) $this->errors[] = "Missing parents.";
- }
- if($this->nestedset) {
- $this->db->query("SELECT MAX(".$this->s_fields["right"].") FROM ".$this->s_table);
- $this->db->nextr();
- $n = $this->db->f(0);
- $this->db->query("SELECT COUNT(*) FROM ".$this->s_table);
- $this->db->nextr();
- $c = $this->db->f(0);
- if($n/2 != $c) $this->errors[] = "Right index does not match node count.";
- }
- if($this->adjacency && $this->nestedset) {
- $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"].") ");
- $this->db->nextr();
- if($this->db->f(0) > 0) $this->errors = "Adjacency and nested set do not match.";
- }
- return $error;
- }
- }
- ?>
|