<?php

/**
 * Project: 
 * Contenido Content Management System
 * 
 * Description: 
 * Logical cTreeItem
 * 
 * Requirements: 
 * @con_php_req 5.0
 * 
 *
 * @package    Contenido Backend classes
 * @version    1.12
 * @author     Bjoern Behrens
 * @copyright  four for business AG <www.4fb.de>
 * @license    http://www.contenido.org/license/LIZENZ.txt
 * @link       http://www.4fb.de
 * @link       http://www.contenido.org
 * 
 * {@internal 
 *   created 2006-10-05
 *   
 *   $Id$
 * }}
 * 
 */
if (!defined('CON_FRAMEWORK')) {
    die('Illegal call');
}

/**
 * class cTreeItem
 * 
 */
class cTreeItem {

    /**
     * Sub Items for this tree item
     * @var array
     */
    var $_subitems;

    /**
     * Determinates if this tree item is collapsed
     * @var boolean
     */
    var $_collapsed;

    /**
     * ID for this item
     * @var string
     */
    var $_id;

    /**
     * Name for this item
     * @var string
     */
    var $_name;

    /**
     * Contains the level of this item
     * @var integer
     */
    var $_level;

    /**
     * Contains custom entries
     * @var array
     */
    var $_attributes;

    /**
     * Contains the parent of this item
     * @var array
     */
    var $_parent;

    /**
     * Contains the next item
     * @var array
     */
    var $_next;

    /**
     * Contains the previous item
     * @var array
     */
    var $_previous;

    function __construct($id = "", $name = "", $collapsed = false) {
        $this->_id = $id;
        $this->_name = $name;
        $this->_collapsed = $collapsed;
        $this->_subitems = array();
        $this->_parent = false;
        $this->_next = false;
        $this->_previous = false;
        $this->_attributes = array();
    }

    /**
     * Imports a table from an array of arrays.  Array format:
     * array(
     *        array("id" => "Item ID", "name" => "Item name", "level" => 1, "collapsed" => true|false, "attributes" => array("attr_name" => "attr_value"))   
     *     );  
     *
     * The entries "collapsed" and "attributes" are optional!
     *
     * @param array flat_array See above
     * @return void
     * @access public
     */
    function importTable($flat_array) {
        $lastobj[0] = $this->_id;
        $currentlevel = 1;

        if (!is_array($flat_array)) {
            return false;
        }

        foreach ($flat_array as $item) {
            $mitem[$item["id"]] = new cTreeItem($item["id"], $item["name"]);

            if ($item["level"] > $currentlevel) {
                $currentlevel++;
            }

            if ($item["level"] < $currentlevel) {
                $currentlevel = $item["level"];
            }

            if (is_array($item["attributes"])) {
                $mitem[$item["id"]]->setAttributes($item["attributes"]);
            }

            if (array_key_exists("collapsed", $item)) {
                $mitem[$item["id"]]->setCollapsed($item["collapsed"]);
            }

            /* Set payload object */
            if (array_key_exists("payload", $item)) {
                $mitem[$item["id"]]->setPayloadObject($item["payload"]);
            }


            if (is_object($mitem[$lastobj[$currentlevel - 1]])) {
                $mitem[$lastobj[$currentlevel - 1]]->addItem($mitem[$item["id"]]);
            } else {
                $this->addItemToID($lastobj[$currentlevel - 1], $mitem[$item["id"]]);
            }

            $lastobj[$currentlevel] = $item["id"];
        }
    }

// end of member function importTree

    function importStructuredArray($array) {
        $i = array();

        $lastid = 1;
        $level = 1;

        $this->_flattenArray($array, $i, $lastid, $level);


        $this->importTable($i);
    }

    function _flattenArray($sourcearray, &$destarray, &$lastid, &$level) {
        if ($lastid == false) {
            $lastid = 1;
        }

        if ($level == false) {
            $level = 1;
        }

        if (!is_array($sourcearray)) {
            return false;
        }

        foreach ($sourcearray as $id => $item) {
            $lastid++;
            $destarray[$lastid]["id"] = $item["class"] . "." . $id;

            /* Name should be fetched via the meta object */
            $meta = $item["object"]->getMetaObject();

            if (is_object($meta)) {
                $destarray[$lastid]["name"] = $meta->getName();
            }

            $destarray[$lastid]["level"] = $level;
            $destarray[$lastid]["payload"] = $item["object"];

            if (count($item["items"]) > 0) {
                $level++;
                $this->_flattenArray($item["items"], $destarray, $lastid, $level);
                $level--;
            }
        }
    }

    /**
     * Exports a tree as an array of arrays.  Array format:  
     * array(
     *        array("id" => "Item ID", "name" => "Item name", "level" => 1, "attributes" => array("attr_name" => "attr_value"))   
     *     );  
     *
     * @return array
     * @access public
     */
    function exportTree() {
        /* TODO: Function can't work... work in progress...
          $myobj->traverse($objlist);
         */
    }

// end of member function exportTree

    /**
     * adds an item as a subitem to the current item
     *
     * @param cTreeItem item item object to add
     * @return void
     * @access public
     */
    function addItem(&$item) {
        /* Update last item */
        if ($lastitem = end($this->_subitems) !== false) {
            $this->_subitems[key($this->_subitems)]->_next = $item->_id;
        }

        $this->_subitems[count($this->_subitems)] = &$item;
        $item->_parent = $this->_id;
        $item->_previous = $lastitem->_id;
    }

// end of member function addItem

    /**
     * adds an item to a specific ID
     *
     * @param string id ID to add the item to
     * @param cTreeItem item Item to add
     * @return void
     * @access public
     */
    function addItemToID($id, &$item) {
        if ($this->_id == $id) {

            /* Update last item */
            if ($lastitem = end($this->_subitems) !== false) {
                $this->_subitems[key($this->_subitems)]->_next = $item->_id;
            }

            $this->_subitems[count($this->_subitems)] = &$item;
            $item->_parent = $this->_id;
            $item->_previous = $lastitem->_id;
            return true;
        } else {
            foreach (array_keys($this->_subitems) as $key) {
                $result = $this->_subitems[$key]->addItemToID($id, $item);

                if ($result == true) {
                    return true;
                }
            }
        }

        return false;
    }

// end of member function addItemToID

    /**
     * moves an item to another object
     *
     * @param cTreeItem targetItem Item to move the subitem to
     * @param mixed itemToMove cTreeItem-Object or id of object to move
     * @return void
     * @access public
     */
    function moveItem($targetItem, $itemToMove) {
        
    }

// end of member function moveItem

    /**
     * deletes a subitem
     *
     * @param mixed item object or ID to delete
     * @return deleted object
     * @access public
     */
    function deleteItem($id) {
        foreach (array_keys($this->_subitems) as $key) {
            if ($this->_subitems[$key]->_id == $id) {
                /* Fetch next item, reset to current item */
                $nextitem = next($this->_subitems);
                $nkey = key($this->_subitems);
                prev($this->_subitems);

                $previtem = &prev($this->_subitems);
                $pkey = key($this->_subitems);
                next($this->_subitems);


                if ($nextitem !== false) {
                    if ($previtem !== false) {
                        $this->_subitems[$nkey]->_previous = $this->_subitems[$pkey]->_id;
                    }
                }

                if ($previtem !== false) {
                    if ($nextitem !== false) {
                        $this->_subitems[$pkey]->_next = $this->_subitems[$nkey]->_id;
                    }
                }

                $itemcopy = $this->_subitems[$key];
                unset($this->_subitems[$key]);

                return ($itemcopy);
            } else {
                $this->_subitems[$key]->deleteItem($id);
            }
        }
    }

// end of member function deleteItem

    /**
     * Retrieves a specific item by its ID. Note that this
     * function traverses all subitems to find the correct item.
     *
     * @param string id ID to retrieve
     * @return cTreeItem
     * @access public
     */
    function &getItemByID($id) {
        if ($this->_id == $id) {
            return ($this);
        } else {
            foreach (array_keys($this->_subitems) as $key) {
                $retObj = &$this->_subitems[$key]->getItemByID($id);
                if ($retObj->_id == $id) {
                    return ($retObj);
                }
            }
        }

        return false;
    }

// end of member function getItemByID

    /**
     * sets a custom attribute for this TreeItem
     *
     * @param string attributeName 
     * @param array attributeValue The value(s) of the attribute
     * @return void
     * @access public
     */
    function setAttribute($attributeName, $attributeValue) {
        $this->_attributes[$attributeName] = $attributeValue;
    }

// end of member function setAttribute

    /**
     * sets a bunch of attributes
     *
     * @param string attributeName 
     * @param array attributeValue The value(s) of the attribute
     * @return void
     * @access public
     */
    function setAttributes($aAttributeArray) {
        $this->_attributes = array_merge($aAttributeArray, $this->_attributes);
    }

// end of member function setAttribute

    /**
     * returns an attribute 
     *
     * @param string attributeName 
     * @return mixed
     * @access public
     */
    function getAttribute($attributeName) {
        if (array_key_exists($attributeName, $this->_attributes)) {
            return ($this->_attributes[$attributeName]);
        } else {
            return false;
        }
    }

// end of member function getAttribute

    /**
     * deletes an attribute
     *
     * @param string attributeName 
     * @return void
     * @access public
     */
    function deleteAttribute($attributeName) {
        if (array_key_exists($attributeName, $this->_attributes)) {
            unset($this->_attributes[$attributeName]);
            return true;
        } else {
            return false;
        }
    }

// end of member function deleteAttribute

    function hasAttribute($attributeName, $bRecursive = false) {
        if (array_key_exists($attributeName, $this->_attributes)) {
            return true;
        } else {
            if ($bRecursive == true) {
                if (count($this->_subitems) > 0) {
                    foreach ($this->_subitems as $oSubitem) {
                        $bFound = $oSubitem->hasAttribute($attributeName, true);

                        if ($bFound == true) {
                            return true;
                        }
                    }
                }

                return false;
            } else {
                return false;
            }
        }
    }

    /**
     * 
     *
     * @param mixed expand ID of item to expand or array of item ID's to expand
     * @return void
     * @access public
     */
    function setExpanded($id) {
        if (is_array($id)) {
            if (in_array($this->_id, $id, true)) {
                $this->_collapsed = false;
            }

            foreach (array_keys($this->_subitems) as $key) {
                $this->_subitems[$key]->setExpanded($id);
            }
        } else {
            if ($this->_id === $id) {
                $this->_collapsed = false;
                return true;
            } else {
                foreach (array_keys($this->_subitems) as $key) {
                    $this->_subitems[$key]->setExpanded($id);
                }
            }
        }
    }

// end of member function setExpanded

    /**
     * 
     *
     * @param mixed collapse ID to collapse or an array with items to collapse
     * @return void
     * @access public
     */
    function setCollapsed($id) {
        if (is_array($id)) {
            if (in_array($this->_id, $id, true)) {
                $this->_collapsed = true;
            }

            foreach (array_keys($this->_subitems) as $key) {
                $this->_subitems[$key]->setCollapsed($id);
            }
        } else {
            if ($this->_id === $id) {
                $this->_collapsed = true;
                return true;
            } else {
                foreach (array_keys($this->_subitems) as $key) {
                    $this->_subitems[$key]->setCollapsed($id);
                }
            }
        }
    }

// end of member function setCollapsed

    /**
     * 
     *
     * @param int leveloffset Level offset. Ignores all expand operations below the offset.
     * @return void
     * @access public
     */
    function expandBelowLevel($leveloffset) {
        if ($leveloffset > 0) {
            $leveloffset--;
        } else {
            $this->_collapsed = false;
        }

        foreach (array_keys($this->_subitems) as $key) {
            $this->_subitems[$key]->expandBelowLevel($leveloffset);
        }
    }

// end of member function expandBelowLevel

    /**
     * 
     *
     * @param int leveloffset Level offset. Ignores all expand operations below the offset.
     * @return void
     * @access public
     */
    function collapseBelowLevel($leveloffset) {
        if ($leveloffset > 0) {
            $leveloffset--;
        } else {
            $this->_collapsed = true;
        }

        foreach (array_keys($this->_subitems) as $key) {
            $this->_subitems[$key]->collapseBelowLevel($leveloffset);
        }
    }

// end of member function expandBelowLevel

    /**
     * 
     *
     * @param int leveloffset Level offset. Ignores all expand operations below the offset.
     * @return void
     * @access public
     */
    function expandBelowID($id, $found = false) {
        if ($found === true) {
            $this->_collapsed = false;
        }

        if ($this->_id == $id) {
            $found = true;
            $this->_collapsed = false;
        }

        foreach (array_keys($this->_subitems) as $key) {
            $this->_subitems[$key]->expandBelowID($id, $found);
        }
    }

// end of member function expandBelowLevel

    /**
     * 
     *
     * @param int leveloffset Level offset. Ignores all expand operations below the offset.
     * @return void
     * @access public
     */
    function collapseBelowID($id, $found = false) {
        if ($found === true) {
            $this->_collapsed = true;
        }

        if ($this->_id == $id) {
            $found = true;
            $this->_collapsed = true;
        }

        foreach (array_keys($this->_subitems) as $key) {
            $this->_subitems[$key]->collapseBelowID($id, $found);
        }
    }

// end of member function expandBelowLevel

    /**
     * getCollapsedList
     * Returns all items (as ID array) which are collapsed.
     * @param array $list Contains the list with all collapsed items
     */
    function getCollapsedList(&$list) {
        if (!is_array($list)) {
            $list = array();
        }

        if ($this->_collapsed == true) {
            $list[] = $this->_id;
        }

        foreach (array_keys($this->_subitems) as $key) {
            $this->_subitems[$key]->getCollapsedList($list);
        }
    }

    /**
     * getExpandedList
     * Returns all items (as ID array) which are expanded.
     * @param array $list Contains the list with all expanded items
     */
    function getExpandedList(&$list) {
        if (!is_array($list)) {
            $list = array();
        }

        if ($this->_collapsed == false && !in_array($this->_id, $list)) {
            $list[] = $this->_id;
        }

        foreach (array_keys($this->_subitems) as $key) {
            $this->_subitems[$key]->getExpandedList($list);
        }
    }

    /**
     * sets a payload object for later reference
     *
     * @param object payload The object to payload
     * @return void
     * @access public
     */
    function setPayloadObject($payload) {
        $this->payload = $payload;
    }

// end of member function setPayloadObject

    /**
     * unsets a payload object
     *
     * @return object
     * @access public
     */
    function unsetPayloadObject() {
        
    }

// end of member function unsetPayloadObject

    /**
     * traverse
     * traverses the tree starting from this item, and returning
     * all objects as $objects in a nested array.
     * @param object $objects all found objects
     * @param integer $level Level to start on
     */
    function traverse(&$objects, $level = 0) {
        $objects[count($objects)] = &$this;
        $this->_level = $level;

        if ($this->_collapsed == false) {
            foreach (array_keys($this->_subitems) as $key) {
                $this->_subitems[$key]->traverse($objects, $level + 1);
            }
        }
    }

    /**
     * flatTraverse
     * traverses the tree starting from this item, and returning
     * all objects as $objects in a flat array.
     * @param object $objects all found objects
     * @param integer $level Level to start on
     */
    function flatTraverse($level = 0) {
        $objects[] = &$this;
        $this->_level = $level;

        if ($this->_collapsed == false) {
            foreach (array_keys($this->_subitems) as $key) {
                $objects = array_merge($objects, $this->_subitems[$key]->flatTraverse($level + 1));
            }
        }

        return $objects;
    }

    /**
     * setName
     * sets the Name for this item.
     * @param string $name New name for this item
     * @return none
     */
    function setName($name) {
        $this->_name = $name;
    }

}

// end of cTreeItem
?>