<?php
/*
Copyright 2008 Josh Heidenreich
This file is part of Pelzini.
Pelzini is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Pelzini is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Pelzini. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* A simple tree system
*
* @author Josh
* @package Viewer
* @since 0.2
**/
/**
* A node in a tree
**/
class TreeNode implements ArrayAccess {
private $children = array(); private $parent = null;
/**
* Adds a child node to this node
**/
public function addChild(TreeNode $child)
{
$this->children[] = $child;
$child->parent = $this;
}
/**
* Returns a list of all the child nodes of this node
**/
public function getChildren()
{
return $this->children;
}
/**
* Returns all of the data of this node
**/
public function getData()
{
return $this->data;
}
/**
* Returns true if a specific data field exists, and false otherwise
**/
public function offsetExists($index)
{
return isset ($this->data[$index]); }
/**
* Returns the value of a specific data field
**/
public function offsetGet($index)
{
return $this->data[$index];
}
/**
* Sets the value of a specific data field
**/
public function offsetSet($index, $value)
{
$this->data[$index] = $value;
}
/**
* Removes a specific data field
**/
public function offsetUnset($index)
{
unset ($this->data[$index]); }
/**
* Finds a node in the database
*
* @param TreeNodeMatcher $matcher The class which is used to determine if a node should be found or not
**/
public function findNode(TreeNodeMatcher $matcher)
{
$res = $matcher->match ($this);
if ($res) return $this;
foreach ($this->children as $node) {
$res = $node->findNode ($matcher);
if ($res) return $res;
}
return null;
}
/**
* Returns an array of all the the ancestors of this node
**/
public function findAncestors()
{
$node = $this;
while (! $node instanceof RootTreeNode) {
$ancestors[] = $node;
$node = $node->parent;
}
return $ancestors;
}
/**
* Used for debugging only
**/
public function dump()
{
echo '<div style="border: 1px black solid; padding: 0.5em; margin: 0.5em;">';
foreach ($this->data as $key => $val) {
echo "<p>'{$key}' = '{$val}'</p>";
}
foreach ($this->children as $node) {
$node->dump();
}
echo '</div>';
}
}
/**
* The root node in a tree
**/
class RootTreeNode extends TreeNode {
/**
* Creates a tree based on a set of nodes in the database
*
* @param resource $res A database resource created with db_query
* @param string $id_col The name of the identification column in the database query
* @param string $parent_col The name of the column in the database that references the id column of another record
* @returns The loaded tree
**/
static function createFromDatabase($res, $id_col, $parent_col)
{
// load query results into tree
$root = new RootTreeNode;
$remnant_nodes = array();
while ($row = db_fetch_assoc ($res)) {
$id = $row[$id_col];
$parent = $row[$parent_col];
$node = new TreeNode ();
foreach ($row as $key => $val) {
$node[$key] = $val;
}
if ($parent == null or $parent == '') {
$root->addChild ($node);
$known_nodes[$id] = $node;
} else {
$remnant_nodes[] = array ('id' => $id, 'node' => $node, 'parent' => $parent); }
}
$nodes_added_this_pass = count($root->getChildren ());
while (count($remnant_nodes) > 0 and
$nodes_added_this_pass > 0) { $nodes_added_this_pass = 0;
foreach ($remnant_nodes as $remnant_id => $remnant) {
foreach ($known_nodes as $known_id => $known_node) {
if ($known_id == $remnant['parent']) {
$known_node->addChild ($remnant['node']);
$known_nodes[$remnant['id']] = $remnant['node'];
unset ($remnant_nodes[$remnant_id]); $nodes_added_this_pass++;
break;
}
}
}
// Looks for reminant nodes that have parents that are nodes that do not exist
// If any of these are found, the parent is created
if ($nodes_added_this_pass == 0) {
$remnant_nodes[] = $remnant;
$found = false;
foreach ($known_nodes as $known_id => $known_node) {
if ($known_id == $remnant['parent']) {
$found = true;
}
}
if (! $found) {
$node = new TreeNode ();
$node[$id_col] = $remnant['parent'];
$root->addChild ($node);
$known_nodes[$remnant['parent']] = $node;
$nodes_added_this_pass++;
}
}
}
return $root;
}
}
/**
* The generic interface for classes which look for nodes that match specific conditions
**/
interface TreeNodeMatcher {
/**
* Will return true if the node matches the specified condition, or false otherwise
*
* @param TreeNode $node The node to check
* @return boolean True if the node matches, false otherwise
**/
public function match($node);
}
/**
* Finds nodes in the tree which have a specified field which matches a specified value
**/
class FieldTreeNodeMatcher implements TreeNodeMatcher {
private $field;
private $value;
public function __construct($field, $value)
{
$this->field = $field;
$this->value = $value;
}
/**
* Will return true if the node matches the specified condition, or false otherwise
*
* @param TreeNode $node The node to check
* @return boolean True if the node matches, false otherwise
**/
public function match($node)
{
$data = $node->getData();
foreach ($data as $field => $value) {
if ($this->field == $field and $this->value == $value) return true;
}
return false;
}
}
/**
* Loads a tree of the classes, based on 'extends'
**/
function create_classes_tree()
{
global $project;
$q = "SELECT id, name, extends FROM classes WHERE projectid = {$project['id']}";
$res = db_query ($q);
$root = RootTreeNode::createFromDatabase ($res, 'name', 'extends');
return $root;
}
?>