<?php
/* This file is part of testMaker.
testMaker is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation.
testMaker 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 this program. If not, see <http://www.gnu.org/licenses/>. */
/**
* @package Core
*/
/**
* Needs the class it inherites from
*/
require_once(dirname(__FILE__).'/GraphNode.php');
/**
* This class is an abstract concept for nodes.
*
* @abstract
* @package Core
*/
class ParentGraphNode extends GraphNode
{
/**#@+
* @access private
*/
var $childrenSequence;
/**#@-*/
/**
* This constructor has to be overwritten.
* In the overwriting constructor the variable $this->table, $this->connectTable, $this->connectId, $this->connectParentId and $this->childrenSequence have to be set.
* After initializing this variable the overwring constructor has to call this overwritten constructor.
* @param integer ID of the node
*/
function ParentGraphNode($id)
{
$this->db = &$GLOBALS['dao']->getConnection();
if(!isset($this->childrenSequence))
{
trigger_error('<b>ParentGraphNode</b>: $this->childrenSequence was not set');
}
$this->GraphNode($id);
}
function _getTableByType($type)
{
return $this->table;
}
/**
* Returns the child nodes of the current node ordered by position
* @return GraphNode[]
*/
function &getChildren()
{
//Thr ObjectCache don't work correct. Until the problem is fixed its comment out.
/*if ($res = retrieve(get_class($this) .'Children', $this->id)) {
return $res;
}*/
$nodes = array();
$query = 'SELECT '.$this->connectId.' FROM '.$this->connectTable.' WHERE '.$this->connectParentId.' = ? ORDER BY pos';
$ids = $this->db->getAll($query, array($this->id));
if($this->db->isError($ids)) {
return false;
}
for($i = 0; $i < count($ids); $i++)
{
if($ids[$i][$this->connectId] != 0)
{
$nodes[] = $this->_returnNode($ids[$i][$this->connectId]);
}
}
store(get_class($this) .'Children', $this->id, $nodes);
return $nodes;
}
/**
* Returns the number of children of this node.
* @return integer
*/
function getChildrenCount()
{
return $this->db->getOne('SELECT COUNT(*) FROM '.$this->connectTable.' WHERE '.$this->connectParentId.' = ?', array($this->id));
}
/**
* Returns the parent nodes of the current node
* @return GraphNode[]
*/
function getChildById($child)
{
if(!$this->existsChild($child))
{
trigger_error('<b>ParentGraphNode:getChildById</b>: $parent is no parent of node');
return false;
}
return $this->_returnNode($child);
}
/**
* Returns the child at given position
* @param integer position of the block
* @return GraphNode
*/
function getChildByPosition($position)
{
if(!$this->existsChildAtPosition($position)) {
trigger_error('<b>ParentGraphNode:getChild</b>: $position at current is empty!');
return false;
}
$query = 'SELECT '.$this->connectId.' FROM '.$this->connectTable.' WHERE '.$this->connectParentId.' = ? AND pos = ?';
$id = $this->db->getOne($query, array($this->id, $position));
if($this->db->isError($id)) {
return false;
}
return $this->_returnNode($id);
}
/**
* Returns if the given node is a child of current node
* @param integer ID of the node to check if it is a child
* @return integer
*/
function existsChild($child)
{
$query = 'SELECT count('.$this->connectId.') FROM '.$this->connectTable.' WHERE '.$this->connectId.' = ? AND '.$this->connectParentId.' = ?';
$num = $this->db->getOne($query, array($child, $this->id));
if($this->db->isError($num)) {
return false;
}
if($num == 1)
{
return true;
}
elseif ($num == 0)
{
return false;
}
else
{
trigger_error('<b>ParentGraphNode:existsChild</b>: database is inconsistent');
return false;
}
}
/**
* Returns if any child exists at given position
* @param integer position of node in current node to check if it exists
* @return integer
*/
function existsChildAtPosition($position)
{
$query = 'SELECT count('.$this->connectId.') FROM '.$this->connectTable.' WHERE '.$this->connectParentId.' = ? AND pos = ?';
$num = $this->db->getOne($query, array($this->id, $position));
if($this->db->isError($num)) {
return false;
}
if($num == 1)
{
return true;
}
elseif ($num == 0)
{
return false;
}
else
{
trigger_error('<b>ParentGraphNode:existsChildAtPosition</b>: database is inconsistent');
return false;
}
}
/**
* returns the next free position for a child node
* @return integer
*/
function getNextFreePosition()
{
$query = 'SELECT pos FROM '.$this->connectTable.' WHERE '.$this->connectParentId.' = ? ORDER BY pos DESC LIMIT 1';
if(!($position = $this->db->getOne($query, array($this->id))))
{
return 1;
}
else
{
if($this->db->isError($position)) {
return false;
}
return (((int) $position) + 1);
}
}
/**
* Creates and returns a new node of the given type
* @param integer $position position of node in parent node
* @param mixed[] $optionalinfo associative array with optionalinformations (title, description)
* @return GraphNode
*/
function _createChild($type, $data = array())
{
if(!is_array($data))
{
trigger_error('<b>ParentGraphNode::createChild</b>: $data is no array');
return false;
}
if(!array_key_exists('pos', $data) || $data['pos'] == NULL)
{
$position = $this->getNextFreePosition();
}
elseif($this->existsChildAtPosition($data['pos']))
{
$tmpblock = $this->getChildByPosition($data['pos']);
$tmpblock->setPosition(($tmpblock->getPosition($parent) + 1), $this->id);
$position = $data['pos'];
}
else
{
$position = $data['pos'];
}
if(!array_key_exists('id', $data) || !preg_match('/^[0-9]+$/', $data['id']))
{
$id = $this->db->nextId($this->childrenSequence);
if($this->db->isError($id)) {
return false;
}
$data['id'] = $id;
}
$owner = $this->getOwner();
$data['owner'] = $owner->getId();
$data['t_created'] = time();
$data['t_modified'] = time();
$data['u_created'] = $GLOBALS['PORTAL']->getUserId();
$data['u_modified'] = $GLOBALS['PORTAL']->getUserId();
$values = '';
$columns = '';
for(reset($data); list($column, $value) = each($data); )
{
if(strlen($columns) > 0)
{
$columns .= ', ';
}
if(strlen($values) > 0)
{
$values .= ', ';
}
$columns .= $column;
$values .= $this->db->quoteSmart($value);
}
$query = 'INSERT INTO '.$this->_getTableByType($type).' ('.$columns.') VALUES ('.$values.')';
$result = $this->db->query($query);
if($this->db->isError($result)) {
return false;
}
$query = 'INSERT INTO '.$this->connectTable.' (id, parent_id, pos) VALUES (?, ?, ?)';
$result = $this->db->query($query, array($data['id'], $this->id, $position));
if($this->db->isError($result)) {
return false;
}
return $this->_returnNode($data['id']);
}
/**
* deletes the given node from the current node and removes all children if they are not children of any other block
* @param integer $id id of node to delete
* @return boolean
*/
function deleteChild($id)
{
if(!$this->existsChild($id))
{
trigger_error('<b>ParentGraphNode:deleteChild</b>: $id does not exist');
return false;
}
$block = $this->getChildById($id);
$parentBlocks = $block->getParents();
$foundDifferentParent = false;
for($i = 0; $i < count($parentBlocks); $i++)
{
if($parentBlocks[$i]->getId() != $this->id)
{
$foundDifferentParent = true;
}
}
$query = 'DELETE FROM '.$this->connectTable.' WHERE '.$this->connectId.' = ? AND '.$this->connectParentId.' = ?';
$result = $this->db->query($query, array($id, $this->id));
if($this->db->isError($result)) {
return false;
}
if(!$foundDifferentParent)
{
$block->cleanUp();
$query = 'DELETE FROM '.$this->_getTableByType($block->getBlockType()).' WHERE id = ?';
$result = $this->db->query($query, array($id));
if($this->db->isError($result)) {
return false;
}
}
return true;
}
/**
* Returns an array of all nodes inside the current node in correct order
* The modifications array has to be orgnaized as an 2 dimensional array,
* in the first dimension all modifications are listed. In the second dimension the fields
* 'id', 'parent', 'position' and 'type' have to be given. In 'id', 'parent' (and 'position') is the new/old position of the added/removed node given.
* The field 'type' is the kind of modification (MODIFICATION_ADD = add, MODIFICATION_REMOVE = remove)
* Example: $modifications = array(0 => array('id' => 5, 'parent' => 1, 'type' = 2), 1 => array('id' => 5, 'parent' => 1, 'position' => 2, 'type' = 1));
* @param $modifications array of modifications to be checked
* @return mixed[]
*/
function listModifiedNode($modifications)
{
if(!is_array($modifications)) {
trigger_error('<b>GraphNode:listModifiedNode()</b>: $modifications is no valid array');
return false;
}
usort($modifications, array(get_class($this), '_compareModifications'));
$children = $this->getChildren();
$newIds = array();
//add insert blocks at beginning
for($j = 0; $j < count($modifications); $j++)
{
if(!isset($modifications[$j]['id']) || !isset($modifications[$j]['parent'])) {
trigger_error('<b>GraphNode:listModifiedNode()</b>: $modifications['.$j.'] is no valid modification array');
return false;
}
if($modifications[$j]['type'] == MODIFICATION_ADD) {
if($modifications[$j]['parent'] == $this->id) {
if(!isset($modifications[$j]['position'])) {
trigger_error('<b>GraphNode:listModifiedNode()</b>: $modifications['.$j.'] is no valid modification array');
return false;
}
if($modifications[$j]['position'] == 1)
{
$newIds[] = $modifications[$j]['id'];
}
}
}
}
$lastPosition = 0;
for($i = 0; $i < count($children); $i++)
{
$lastPosition = $children[$i]->getPosition($this->id);
$continue = false;
for($j = 0; $j < count($modifications); $j++)
{
if(!isset($modifications[$j]['id']) || !isset($modifications[$j]['parent'])) {
trigger_error('<b>GraphNode:listModifiedNode()</b>: $modifications['.$j.'] is no valid modification array');
return false;
}
switch($modifications[$j]['type']) {
case MODIFICATION_ADD:
if($modifications[$j]['parent'] == $this->id) {
if(!isset($modifications[$j]['position'])) {
trigger_error('<b>GraphNode:listModifiedNode()</b>: $modifications['.$j.'] is no valid modification array');
return false;
}
if(($modifications[$j]['position'] >= $children[$i]->getPosition($this->id)) && ($modifications[$j]['position'] < $children[$i]->getNextUsedPosition($this->id)))
{
$newIds[] = $modifications[$j]['id'];
}
}
break;
case MODIFICATION_REMOVE:
if($modifications[$j]['parent'] == $this->id) {
if($modifications[$j]['id'] == $children[$i]->getId())
{
$continue = true;
}
}
break;
}
}
if($continue == true) {
continue;
}
$newIds[] = $children[$i]->getId();
}
//add insert blocks at end
for($j = 0; $j < count($modifications); $j++)
{
if(!isset($modifications[$j]['id']) || !isset($modifications[$j]['parent'])) {
trigger_error('<b>GraphNode:listModifiedNode()</b>: $modifications['.$j.'] is no valid modification array');
return false;
}
if($modifications[$j]['type'] == MODIFICATION_ADD)
{
if($modifications[$j]['parent'] == $this->id) {
if(!isset($modifications[$j]['position'])) {
trigger_error('<b>GraphNode:listModifiedNode()</b>: $modifications['.$j.'] is no valid modification array');
return false;
}
if($modifications[$j]['position'] > $lastPosition)
{
$lastPosition = $modifications[$j]['position'];
$newIds[] = $modifications[$j]['id'];
}
}
}
}
$list = array();
for($i = 0; $i < count($newIds); $i++)
{
$currentNode = $this->_returnNode($newIds[$i]);
$entry = array('id' => $newIds[$i], 'type' => $currentNode->getBlockType());
$list[] = $entry;
$list = array_merge($list, $currentNode->listModifiedNode($modifications));
}
return $list;
}
/**
* prepares everything to delete this block itself
* @return boolean
*/
function cleanUp()
{
$children = $this->getChildren();
for($i = 0; $i < count($children); $i++) {
$this->deleteChild($children[$i]->getId());
}
return parent::cleanUp();
}
/**
* Returns a copy of the current node
* @param target of target node
* @param 2-dimensional array of changed ids. First dimension containing node type as name and seconde dimension assigns old id to new id.
ß * @return Node
*/
function copyNode($parentId, &$changedIds)
{
$newNode = parent::copyNode($parentId, $changedIds);
$children = $this->getChildren();
for($i = 0; $i < count($children); $i++)
{
$children[$i]->copyNode($newNode->getId(), $changedIds);
}
return $newNode;
}
}
?>