| 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;	}}?>
 |