<?php
/**
 * Horde Tree Class
 *
 * The Horde_Tree:: class provides a tree view of hierarchical information. It
 * allows for expanding/collapsing of branches and maintains their state. It can * work together with the Horde_Tree javascript class to achieve this in DHTML
 * on supported browsers.
 *
 * Copyright 2003 Marko Djukic <marko@oblo.com>
 *
 * See the enclosed file COPYING for license information (GPL). If you did not
 * receive this file, see http://www.fsf.org/copyleft/gpl.html.
 *
 * $Horde: horde/lib/Tree.php,v 1.15 2003/07/29 23:56:22 chuck Exp $
 *
 * @author  Marko Djukic <marko@oblo.com>
 * @version $Revision: 1.15 $
 * @package horde
 */
class Horde_Tree {

    /** The name of this instance. */
    var $_instance = null;

    /** An array containing all the tree nodes. */
    var $nodes = array();

    /** The root node to this tree. */
    var $_root_node_id = null;

    /** Keep count of how many extra columns there are. */
    var $_extra_cols = 0;

    /**
     * Option values.
     * @var array $_options
     */
    var $_options = array();

    function Horde_Tree($tree_name)
    {
        $this->_instance = $tree_name;

        /* Set up the session for later to save tree states. */
        if (!isset($_SESSION['horde_tree'][$this->_instance])) {
            $_SESSION['horde_tree'][$this->_instance] = array();
        }
    }

    function &factory($tree_name, $renderer = 'javascript')
    {
        if (!$GLOBALS['browser']->hasFeature('javascript')) {
            /* If no javascript available default to html renderer. */
            $renderer = 'html';
        } elseif (!@file_exists(dirname(__FILE__) . '/Tree/' . $renderer . '.php')) {
            /* If renderer does not exist default to javascript renderer. */
            $renderer = 'javascript';
        }

        /* Require the renderer lib. */
        include_once dirname(__FILE__) . '/Tree/' . $renderer . '.php';

        $class = 'Horde_Tree_' . $renderer;
        if (class_exists($class)) {
            return new $class($tree_name);
        } else {
            return PEAR::raiseError(sprintf(_("'%s' tree renderer not found."), $renderer));
        }

    }

    function &singleton($tree_name, $renderer = 'javascript')
    {
        static $instance = array();
        $id = $tree_name . ':' . $renderer;

        if (isset($instance[$id])) {
            return $instance[$id];
        }

        $instance[$id] = &Horde_Tree::factory($tree_name, $renderer);

        return $instance[$id];
    }

    /**
     * Set an option.
     *
     * @param mixed $option        The option name.
     * @param optional mixed $val  The option's value.
     */
    function setOption($options, $value = null)
    {
        if (!is_array($options)) {
            $options = array($options => $value);
        }

        foreach ($options as $option => $value) {
            $this->_options[$option] = $value;
        }
    }

    /**
     * Get an option's value.
     *
     * @param string $option  The option name.
     *
     * @return mixed  The option's value.
     */
    function getOption($option, $html = false)
    {
        if (isset($this->_options[$option])) {
            if ($html) {
                return ' ' . $option . '="' . $this->_options[$option] . '"';
            } else {
                return $this->_options[$option];
            }
        }

        return null;
    }

    /**
     * Adds a node to the node tree array.
     *
     * @access public
     *
     * @param string $id                  The unique node id.
     * @param string $parent              The parent's unique node id.
     * @param string $label               The text label for the node.
     * @param string $indent              The level of indentation.
     * @param optional boolean $expanded  Is this level expanded or not, defaults
     *                                    to true.
     * @param optional array $params      Any other parameters to set, see the
     *                                    addNodeParams() function for full
     *                                    details of these parameters.
     * @param optional array $extra       Any other columns to display.
     */
    function addNode($id, $parent, $label, $indent, $expanded = true, $params = array(), $extra = array())
    {
        $this->nodes[$id]['label'] = $label;
        $this->nodes[$id]['indent'] = $indent;

        $toggle_id = Horde::getFormData('toggle_' . $this->_instance);
        $session_state = $_SESSION['horde_tree'][$this->_instance];
        if ($id == $toggle_id) {
            /* We have a url toggle request for this node. */
            if (isset($session_state['expanded'][$id])) {
                /* Use session state if it is set. */
                $expanded = (!$session_state['expanded'][$id]);
            } else {
                /* Otherwise use what was passed through the function. */
                $expanded = (!$expanded);
            }
            /* Save this state to session. */
            $_SESSION['horde_tree'][$this->_instance]['expanded'][$id] = $expanded;
        } elseif (isset($session_state['expanded'][$id])) {
            /* If we have a saved session state use it. */
            $expanded = $session_state['expanded'][$id];
        }

        $this->nodes[$id]['expanded'] = $expanded;

        /* If any params included here add them now. */
        if (!empty($params)) {
            $this->addNodeParams($id, $params);
        }

        /* If any extra columns included here add them now. */
        if (!empty($extra)) {
            $this->addNodeExtra($id, $extra);
        }

        if (is_null($parent)) {
            $this->_root_node_id = $id;
        } else {
            $this->nodes[$parent]['children'][] = $id;
        }
    }


    /**
     * Adds additional parameters to a node.
     *
     * @access public
     *
     * @param string $id      The unique node id.
     * @param array  $params  (optional) Any other parameters to set,
     *          - icondir  - icon directory
     *          - icon     - icon to display next node
     *          - iconopen - icon to indicate this node as expanded
     *          - class    - CSS class to use with this node
     *          - url      - url to link the node to
     *          - onclick  - onclick event attached to this node
     */
    function addNodeParams($id, $params = array())
    {
        if (!is_array($params)) {
            $params = array($params);
        }
        foreach ($params as $param_id => $param_val) {
            /* Set only allowed params, and only if not empty. */
            if (($param_id == 'icondir' ||
                 $param_id == 'icon' ||
                 $param_id == 'iconopen' ||
                 $param_id == 'class' ||
                 $param_id == 'url' ||
                 $param_id == 'onclick') &&
                !empty($param_val)) {
                $this->nodes[$id][$param_id] = $param_val;
            }
        }
    }

    /**
     * Adds a extra columns to be displayed to the side of the node.
     *
     * @access public
     *
     * @param mixed $id     The unique node id.
     * @param array $extra  (optional) Extra columns to display to the
     *                      right of the node label, as an array.
     */
    function addNodeExtra($id, $extra)
    {
        if (!is_array($extra)) {
            $extra = array($extra);
        }

        $this->nodes[$id]['extra'] = $extra;

        if (count($extra) > $this->_extra_cols) {
            $this->_extra_cols = count($extra);
        }
    }

}
