Import Horde_Tree from CVS HEAD
authorMichael M Slusarz <slusarz@curecanti.org>
Wed, 16 Dec 2009 01:00:17 +0000 (18:00 -0700)
committerMichael M Slusarz <slusarz@curecanti.org>
Wed, 16 Dec 2009 01:00:17 +0000 (18:00 -0700)
framework/Tree/lib/Horde/Tree.php [new file with mode: 0644]
framework/Tree/lib/Horde/Tree/Html.php [new file with mode: 0644]
framework/Tree/lib/Horde/Tree/Javascript.php [new file with mode: 0644]
framework/Tree/lib/Horde/Tree/Select.php [new file with mode: 0644]
framework/Tree/package.xml [new file with mode: 0644]

diff --git a/framework/Tree/lib/Horde/Tree.php b/framework/Tree/lib/Horde/Tree.php
new file mode 100644 (file)
index 0000000..c063fd1
--- /dev/null
@@ -0,0 +1,573 @@
+<?php
+/**
+ * The Horde_Tree:: class provides a tree view of hierarchical information. It
+ * allows for expanding/collapsing of branches and maintains their state.
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author   Marko Djukic <marko@oblo.com>
+ * @category Horde
+ * @package  Horde_Tree
+ */
+class Horde_Tree
+{
+    /**
+     * Display extra columns to the left of the main tree.
+     */
+    const EXTRA_LEFT = 0;
+
+    /**
+     * Display extra columns to the right of the main tree.
+     */
+    const EXTRA_RIGHT = 1;
+
+    /**
+     * The preceding text, before the Horde_Tree instance name, used for
+     * collapse/expand submissions.
+     */
+    const TOGGLE = 'ht_toggle_';
+
+    /**
+     * Singleton instances.
+     *
+     * @var array
+     */
+    static protected $_instances = array();
+
+    /**
+     * The name of this instance.
+     *
+     * @var string
+     */
+    protected $_instance = null;
+
+    /**
+     * Hash with header information.
+     *
+     * @var array
+     */
+    protected $_header = array();
+
+    /**
+     * An array containing all the tree nodes.
+     *
+     * @var array
+     */
+    protected $_nodes = array();
+
+    /**
+     * The top-level nodes in the tree.
+     *
+     * @var array
+     */
+    protected $_root_nodes = array();
+
+    /**
+     * Keep count of how many extra columns there are on the left side
+     * of the node.
+     *
+     * @var integer
+     */
+    protected $_extra_cols_left = 0;
+
+    /**
+     * Keep count of how many extra columns there are on the right side
+     * of the node.
+     *
+     * @var integer
+     */
+    protected $_extra_cols_right = 0;
+
+    /**
+     * Option values.
+     *
+     * @var array
+     */
+    protected $_options = array(
+        'lines' => true
+    );
+
+    /**
+      * Image directory location.
+      *
+      * @var string
+      */
+    protected $_img_dir = '';
+
+    /**
+     * Images array.
+     *
+     * @var array
+     */
+    protected $_images = array(
+        'line' => 'line.png',
+        'blank' => 'blank.png',
+        'join' => 'join.png',
+        'join_bottom' => 'joinbottom.png',
+        'plus' => 'plus.png',
+        'plus_bottom' => 'plusbottom.png',
+        'plus_only' => 'plusonly.png',
+        'minus' => 'minus.png',
+        'minus_bottom' => 'minusbottom.png',
+        'minus_only' => 'minusonly.png',
+        'null_only' => 'nullonly.png',
+        'folder' => 'folder.png',
+        'folderopen' => 'folderopen.png',
+        'leaf' => 'leaf.png'
+    );
+
+    /**
+     * Stores the sorting criteria temporarily.
+     *
+     * @var string
+     */
+    protected $_sortCriteria;
+
+    /**
+     * Use session to store cached Tree data?
+     *
+     * @var boolean
+     */
+    protected $_usesession = true;
+
+    /**
+     * Should the tree be rendered statically?
+     *
+     * @var boolean
+     */
+    protected $_static = false;
+
+    /**
+     * Attempts to return a reference to a concrete instance.
+     * It will only create a new instance if no instance with the same
+     * parameters currently exists.
+     *
+     * This method must be invoked as:
+     *   $var = Horde_Tree::singleton($name[, $renderer[, $params]]);
+     *
+     * @param mixed $name       @see Horde_Tree::factory.
+     * @param string $renderer  @see Horde_Tree::factory.
+     * @param array $params     @see Horde_Tree::factory.
+     *
+     * @return Horde_Tree  The concrete instance.
+     * @throws Horde_Exception
+     */
+    static public function singleton($name, $renderer, $params = array())
+    {
+        ksort($params);
+        $id = $name . ':' . $renderer . ':' . serialize($params);
+
+        if (!isset(self::$_instances[$id])) {
+            self::$_instances[$id] = Horde_Tree::factory($name, $renderer, $params);
+            if (!self::$_instances[$id]->isSupported()) {
+                $renderer = Horde_Tree::fallback($renderer);
+                return Horde_Tree::singleton($name, $renderer, $params);
+            }
+        }
+
+        return self::$_instances[$id];
+    }
+
+    /**
+     * Attempts to return a concrete instance.
+     *
+     * @param string $name     The name of this tree instance.
+     * @param mixed $renderer  The type of concrete subclass to return. This
+     *                         is based on the rendering driver. The code is
+     *                         dynamically included.
+     * @param array $params    Any additional parameters the constructor
+     *                         needs.
+     *
+     * @return Horde_Tree  The newly created concrete instance.
+     * @throws Horde_Exception
+     */
+    static public function factory($name, $renderer, $params = array())
+    {
+        $class = 'Horde_Tree_' . ucfirst($renderer);
+        if (class_exists($class)) {
+            return new $class($name, $params);
+        }
+
+        throw new Horde_Exception('Horde_Tree renderer not found: ' . $renderer);
+    }
+
+    /**
+     * Try to fall back to a simpler renderer.
+     *
+     * @paran string $renderer  The renderer that we can't handle.
+     *
+     * @return string  The next best renderer.
+     * @throws Horde_Exception
+     */
+    public function fallback($renderer)
+    {
+        switch ($renderer) {
+        case 'javascript':
+            return 'html';
+
+        case 'html':
+            throw new Horde_Exception('No fallback renderer found.');
+        }
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param string $name   The name of this tree instance.
+     * @param array $params  Additional parameters:
+     * <pre>
+     * 'nosession' - (boolean) If true, do not store tree data in session.
+     * </pre>
+     */
+    public function __construct($name, $params = array())
+    {
+        $this->_instance = $name;
+        $this->_usesession = empty($params['nosession']);
+        unset($params['nosession']);
+        $this->setOption($params);
+
+        /* Set up the session for later to save tree states. */
+        if ($this->_usesession &&
+            !isset($_SESSION['horde_tree'][$this->_instance])) {
+            $_SESSION['horde_tree'][$this->_instance] = array();
+        }
+
+        $this->_img_dir = $GLOBALS['registry']->getImageDir('horde') . '/tree';
+
+        if (!empty($GLOBALS['nls']['rtl'][$GLOBALS['language']])) {
+            $rev_imgs = array(
+                'line', 'join', 'join_bottom', 'plus', 'plus_bottom',
+                'plus_only', 'minus', 'minus_bottom', 'minus_only',
+                'null_only', 'leaf'
+            );
+            foreach ($rev_imgs as $val) {
+                $this->_images[$val] = 'rev-' . $this->_images[$val];
+            }
+        }
+    }
+
+    /**
+     * Renders the tree.
+     *
+     * @param boolean $static  If true the tree nodes can't be expanded and
+     *                         collapsed and the tree gets rendered expanded.
+     */
+    public function renderTree($static = false)
+    {
+        echo $this->getTree($static);
+    }
+
+    /**
+     * Sets an option.
+     *
+     * @param mixed $option  The option name -or- an array of option name/value
+     *                       pairs. Available options:
+     * <pre>
+     * alternate - (boolean) Alternate shading in the table?
+     * class - (string) The class to use for the table.
+     * hideHeaders - (boolean) Don't render any HTML for the header row, just
+     *               use the widths.
+     * lines - (boolean) Show tree lines?
+     * multiline - (boolean) Do the node labels contain linebreaks?
+     * </pre>
+     * @param mixed $value   The option's value.
+     */
+    public function setOption($options, $value = null)
+    {
+        if (!is_array($options)) {
+            $options = array($options => $value);
+        }
+
+        foreach ($options as $option => $value) {
+            $this->_options[$option] = $value;
+        }
+    }
+
+    /**
+     * Gets an option's value.
+     *
+     * @param string $option   The name of the option to fetch.
+     * @param boolean $html    Whether to format the return value in HTML.
+     * @param string $default  A default value to use in case none is set for
+     *                         the requested option.
+     *
+     * @return mixed  The option's value.
+     */
+    public function getOption($option, $html = false, $default = null)
+    {
+        $value = null;
+
+        if (!isset($this->_options[$option]) && !is_null($default)) {
+            /* Requested option has not been but there is a
+             * default. */
+            $value = $default;
+        } elseif (isset($this->_options[$option])) {
+            /* Requested option has been set, get its value. */
+            $value = $this->_options[$option];
+        }
+
+        if ($html && !is_null($value)) {
+            /* Format value for html output. */
+            $value = sprintf(' %s="%s"', $option, $value);
+        }
+
+        return $value;
+    }
+
+    /**
+     * Adds a node to the node tree array.
+     *
+     * @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      Deprecated, this is calculated automatically
+     *                            based on the parent node.
+     * @param boolean $expanded   Is this level expanded or not.
+     * @param array $params       Any other parameters to set (@see
+     *                            addNodeParams() for full details).
+     * @param array $extra_right  Any other columns to display to the right of
+     *                            the tree.
+     * @param array $extra_left   Any other columns to display to the left of
+     *                            the tree.
+     */
+    public function addNode($id, $parent, $label, $indent = null,
+                            $expanded = true, $params = array(),
+                            $extra_right = array(), $extra_left = array())
+    {
+        $this->_nodes[$id]['label'] = $label;
+
+        if ($this->_usesession) {
+            $session_state = $_SESSION['horde_tree'][$this->_instance];
+            $toggle_id = Horde_Util::getFormData(self::TOGGLE . $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_right)) {
+            $this->addNodeExtra($id, self::EXTRA_RIGHT, $extra_right);
+        }
+        if (!empty($extra_left)) {
+            $this->addNodeExtra($id, self::EXTRA_LEFT, $extra_left);
+        }
+
+        if (is_null($parent)) {
+            if (!in_array($id, $this->_root_nodes)) {
+                $this->_root_nodes[] = $id;
+            }
+        } else {
+            if (empty($this->_nodes[$parent]['children'])) {
+                $this->_nodes[$parent]['children'] = array();
+            }
+            if (!in_array($id, $this->_nodes[$parent]['children'])) {
+                $this->_nodes[$parent]['children'][] = $id;
+            }
+        }
+    }
+
+    /**
+     * Adds additional parameters to a node.
+     *
+     * @param string $id     The unique node id.
+     * @param array $params  Any other parameters to set.
+     * <pre>
+     * class - CSS class to use with this node
+     * icon - Icon to display next node
+     * iconalt - Alt text to use for the icon
+     * icondir - Icon directory
+     * iconopen - Icon to indicate this node as expanded
+     * onclick - Onclick event attached to this node
+     * url - URL to link the node to
+     * urlclass - CSS class for the node's URL
+     * title - Link tooltip title
+     * target - Target for the 'url' link
+     * </pre>
+     */
+    public function addNodeParams($id, $params = array())
+    {
+        if (!is_array($params)) {
+            $params = array($params);
+        }
+
+        $allowed = array(
+            'class', 'icon', 'iconalt', 'icondir', 'iconopen',
+            'onclick', 'url', 'urlclass', 'title', 'target',
+        );
+
+        foreach ($params as $param_id => $param_val) {
+            // Set only allowed and non-null params.
+            if (in_array($param_id, $allowed) && !is_null($param_val)) {
+                // Cast Horde_Url objects
+                if ($param_id == 'url') {
+                    $param_val = (string)$param_val;
+                }
+                $this->_nodes[$id][$param_id] = $param_val;
+            }
+        }
+    }
+
+    /**
+     * Adds extra columns to be displayed to the side of the node.
+     *
+     * @param mixed $id      The unique node id.
+     * @param integer $side  Which side to place the extra columns on.
+     * @param array $extra   Extra columns to display.
+     */
+    public function addNodeExtra($id, $side, $extra)
+    {
+        if (!is_array($extra)) {
+            $extra = array($extra);
+        }
+
+        $col_count = count($extra);
+
+        switch ($side) {
+        case self::EXTRA_LEFT:
+            $this->_nodes[$id]['extra'][self::EXTRA_LEFT] = $extra;
+            if ($col_count > $this->_extra_cols_left) {
+                $this->_extra_cols_left = $col_count;
+            }
+            break;
+
+        case self::EXTRA_RIGHT:
+            $this->_nodes[$id]['extra'][self::EXTRA_RIGHT] = $extra;
+            if ($col_count > $this->_extra_cols_right) {
+                $this->_extra_cols_right = $col_count;
+            }
+            break;
+        }
+    }
+
+    /**
+     * Sorts the tree by the specified node property.
+     *
+     * @param string $criteria  The node property to sort by.
+     * @param integer $id       Used internally for recursion.
+     */
+    public function sort($criteria, $id = -1)
+    {
+        if (!isset($this->_nodes[$id]['children'])) {
+            return;
+        }
+
+        if ($criteria == 'key') {
+            ksort($this->_nodes[$id]['children']);
+        } else {
+            $this->_sortCriteria = $criteria;
+            usort($this->_nodes[$id]['children'], array($this, 'sortHelper'));
+        }
+
+        foreach ($this->_nodes[$id]['children'] as $child) {
+            $this->sort($criteria, $child);
+        }
+    }
+
+    /**
+     * Helper method for sort() to compare two tree elements.
+     */
+    public function sortHelper($a, $b)
+    {
+        if (!isset($this->_nodes[$a][$this->_sortCriteria])) {
+            return 1;
+        }
+
+        if (!isset($this->_nodes[$b][$this->_sortCriteria])) {
+            return -1;
+        }
+
+        return strcoll($this->_nodes[$a][$this->_sortCriteria],
+                       $this->_nodes[$b][$this->_sortCriteria]);
+    }
+
+    /**
+     * Returns whether the specified node is currently expanded.
+     *
+     * @param mixed $id  The unique node id.
+     *
+     * @return boolean  True if the specified node is expanded.
+     */
+    public function isExpanded($id)
+    {
+        return isset($this->_nodes[$id])
+            ? $this->_nodes[$id]['expanded']
+            : false;
+    }
+
+    /**
+     * Adds column headers to the tree table.
+     *
+     * @param array $header  An array containing hashes with header
+     *                       information. The following keys are allowed:
+     * <pre>
+     * align - The alignment inside the header cell
+     * class - The CSS class of the header cell
+     * html - The HTML content of the header cell
+     * width - The width of the header cell
+     * </pre>
+     */
+    public function setHeader($header)
+    {
+        $this->_header = $header;
+    }
+
+    /**
+     * Set the indent level for each node in the tree.
+     *
+     * @param array $nodes     TODO
+     * @param integer $indent  TODO
+     */
+    protected function _buildIndents($nodes, $indent = 0)
+    {
+        foreach ($nodes as $id) {
+            $this->_nodes[$id]['indent'] = $indent;
+            if (!empty($this->_nodes[$id]['children'])) {
+                $this->_buildIndents($this->_nodes[$id]['children'], $indent + 1);
+            }
+        }
+    }
+
+    /**
+     * Check the current environment to see if we can render the tree.
+     *
+     * @return boolean  Whether or not this backend will function.
+     */
+    public function isSupported()
+    {
+        return true;
+    }
+
+    /**
+     * Returns just the JS node definitions as a string.
+     *
+     * @return string  The Javascript node array definitions.
+     */
+    public function renderNodeDefinitions()
+    {
+    }
+
+}
diff --git a/framework/Tree/lib/Horde/Tree/Html.php b/framework/Tree/lib/Horde/Tree/Html.php
new file mode 100644 (file)
index 0000000..f1e5eaf
--- /dev/null
@@ -0,0 +1,439 @@
+<?php
+/**
+ * The Horde_Tree_Html:: class extends the Horde_Tree class to provide
+ * HTML specific rendering functions.
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author   Marko Djukic <marko@oblo.com>
+ * @category Horde
+ * @package  Horde_Tree
+ */
+class Horde_Tree_Html extends Horde_Tree
+{
+    /**
+     * TODO
+     *
+     * @var array
+     */
+    protected $_nodes = array();
+
+    /**
+     * TODO
+     *
+     * @var array
+     */
+    protected $_node_pos = array();
+
+    /**
+     * TODO
+     *
+     * @var array
+     */
+    protected $_dropline = array();
+
+    /**
+     * Current value of the alt tag count.
+     *
+     * @var integer
+     */
+    protected $_alt_count = 0;
+
+    /**
+     * Returns the tree.
+     *
+     * @param boolean $static  If true the tree nodes can't be expanded and
+     *                         collapsed and the tree gets rendered expanded.
+     *
+     * @return string  The HTML code of the rendered tree.
+     */
+    public function getTree($static = false)
+    {
+        $this->_static = $static;
+        $this->_buildIndents($this->_root_nodes);
+
+        $tree = $this->_buildHeader();
+        foreach ($this->_root_nodes as $node_id) {
+            $tree .= $this->_buildTree($node_id);
+        }
+        return $tree;
+    }
+
+    /**
+     * Returns the HTML code for a header row, if necessary.
+     *
+     * @return string  The HTML code of the header row or an empty string.
+     */
+    protected function _buildHeader()
+    {
+        if (!count($this->_header)) {
+            return '';
+        }
+
+        $html = '<div';
+        /* If using alternating row shading, work out correct
+         * shade. */
+        if ($this->getOption('alternate')) {
+            $html .= ' class="item' . $this->_alt_count . '"';
+            $this->_alt_count = 1 - $this->_alt_count;
+        }
+        $html .= '>';
+
+        foreach ($this->_header as $header) {
+            $html .= '<div class="leftFloat';
+            if (!empty($header['class'])) {
+                $html .= ' ' . $header['class'];
+            }
+            $html .= '"';
+
+            $style = '';
+            if (!empty($header['width'])) {
+                $style .= 'width:' . $header['width'] . ';';
+            }
+            if (!empty($header['align'])) {
+                $style .= 'text-align:' . $header['align'] . ';';
+            }
+            if (!empty($style)) {
+                $html .= ' style="' . $style . '"';
+            }
+            $html .= '>';
+            $html .= empty($header['html']) ? '&nbsp;' : $header['html'];
+            $html .= '</div>';
+        }
+
+        return $html . '</div>';
+    }
+
+    /**
+     * Recursive function to walk through the tree array and build the output.
+     *
+     * @param string $node_id  The Node ID.
+     *
+     * @return string  The tree rendering.
+     */
+    protected function _buildTree($node_id)
+    {
+        $output = $this->_buildLine($node_id);
+
+        if (isset($this->_nodes[$node_id]['children']) &&
+            $this->_nodes[$node_id]['expanded']) {
+            $num_subnodes = count($this->_nodes[$node_id]['children']);
+            for ($c = 0; $c < $num_subnodes; $c++) {
+                $child_node_id = $this->_nodes[$node_id]['children'][$c];
+                $this->_node_pos[$child_node_id] = array();
+                $this->_node_pos[$child_node_id]['pos'] = $c + 1;
+                $this->_node_pos[$child_node_id]['count'] = $num_subnodes;
+                $output .= $this->_buildTree($child_node_id);
+            }
+        }
+
+        return $output;
+    }
+
+    /**
+     * Function to create a single line of the tree.
+     *
+     * @param string $node_id  The Node ID.
+     *
+     * @return string  The rendered line.
+     */
+    protected function _buildLine($node_id)
+    {
+        $className = 'treeRow';
+        if (!empty($this->_nodes[$node_id]['class'])) {
+            $className .= ' ' . $this->_nodes[$node_id]['class'];
+        }
+        /* If using alternating row shading, work out correct
+         * shade. */
+        if ($this->getOption('alternate')) {
+            $className .= ' item' . $this->_alt_count;
+            $this->_alt_count = 1 - $this->_alt_count;
+        }
+
+        $line = '<div class="' . $className . '">';
+
+        /* If we have headers, track which logical "column" we're in
+         * for any given cell of content. */
+        $column = 0;
+
+        if (isset($this->_nodes[$node_id]['extra'][self::EXTRA_LEFT])) {
+            $extra = $this->_nodes[$node_id]['extra'][self::EXTRA_LEFT];
+            $cMax = count($extra);
+            for ($c = 0; $c < $cMax; $c++) {
+                $style = '';
+                if (isset($this->_header[$column]['width'])) {
+                    $style .= 'width:' . $this->_header[$column]['width'] . ';';
+                }
+
+                $line .= '<div class="leftFloat"';
+                if (!empty($style)) {
+                    $line .= ' style="' . $style . '"';
+                }
+                $line .= '>' . $extra[$c] . '</div>';
+
+                $column++;
+            }
+        }
+
+        $style = '';
+        if (isset($this->_header[$column]['width'])) {
+            $style .= 'width:' . $this->_header[$column]['width'] . ';';
+        }
+        $line .= '<div class="leftFloat"';
+        if (!empty($style)) {
+            $line .= ' style="' . $style . '"';
+        }
+        $line .= '>';
+
+        if ($this->getOption('multiline')) {
+            $line .= '<table cellspacing="0"><tr><td>';
+        }
+
+        for ($i = $this->_static ? 1 : 0; $i < $this->_nodes[$node_id]['indent']; $i++) {
+            $line .= '<img src="' . $this->_img_dir . '/';
+            if ($this->_dropline[$i] && $this->getOption('lines', false, true)) {
+                $line .= $this->_images['line'] . '" '
+                    . 'alt="|&nbsp;&nbsp;&nbsp;" ';
+            } else {
+                $line .= $this->_images['blank'] . '" '
+                    . 'alt="&nbsp;&nbsp;&nbsp;" ';
+            }
+            $line .= 'height="20" width="20" style="vertical-align:middle" />';
+        }
+        $line .= $this->_setNodeToggle($node_id) . $this->_setNodeIcon($node_id);
+        if ($this->getOption('multiline')) {
+            $line .= '</td><td>';
+        }
+        $line .= $this->_setLabel($node_id);
+
+        if ($this->getOption('multiline')) {
+            $line .= '</td></tr></table>';
+        }
+
+        $line .= '</div>';
+        $column++;
+
+        if (isset($this->_nodes[$node_id]['extra'][self::EXTRA_RIGHT])) {
+            $extra = $this->_nodes[$node_id]['extra'][self::EXTRA_RIGHT];
+            $cMax = count($extra);
+            for ($c = 0; $c < $cMax; $c++) {
+                $style = '';
+                if (isset($this->_header[$column]['width'])) {
+                    $style .= 'width:' . $this->_header[$column]['width'] . ';';
+                }
+
+                $line .= '<div class="leftFloat"';
+                if (!empty($style)) {
+                    $line .= ' style="' . $style . '"';
+                }
+                $line .= '>' . $extra[$c] . '</div>';
+
+                $column++;
+            }
+        }
+
+        return $line . "</div>\n";
+    }
+
+    /**
+     * Sets the label on the tree line.
+     *
+     * @param string $node_id  The Node ID.
+     *
+     * @return string  The label for the tree line.
+     */
+    protected function _setLabel($node_id)
+    {
+        $n = $this->_nodes[$node_id];
+
+        $output = '<span';
+        if (!empty($n['onclick'])) {
+            $output .= ' onclick="' . $n['onclick'] . '"';
+        }
+        $output .= '>';
+
+        $label = $n['label'];
+        if (!empty($n['url'])) {
+            $target = '';
+            if (!empty($n['target'])) {
+                $target = ' target="' . $n['target'] . '"';
+            } elseif ($target = $this->getOption('target')) {
+                $target = ' target="' . $target . '"';
+            }
+            $output .= '<a' . (!empty($n['urlclass']) ? ' class="' . $n['urlclass'] . '"' : '') . ' href="' . $n['url'] . '"' . $target . '>' . $label . '</a>';
+        } else {
+            $output .= $label;
+        }
+
+        return $output . '</span>';
+    }
+
+    /**
+     * Sets the node toggle on the tree line.
+     *
+     * @param string $node_id  The Node ID.
+     *
+     * @return string  The node toggle for the tree line.
+     */
+    protected function _setNodeToggle($node_id)
+    {
+        $link_start = '';
+
+        if (($this->_nodes[$node_id]['indent'] == 0) &&
+            isset($this->_nodes[$node_id]['children'])) {
+            /* Top level node with children. */
+            $this->_dropline[0] = false;
+            if ($this->_static) {
+                return '';
+            } elseif (!$this->getOption('lines', false, true)) {
+                $img = $this->_images['blank'];
+                $alt = '&nbsp;&nbsp;&nbsp;';
+            } elseif ($this->_nodes[$node_id]['expanded']) {
+                $img = $this->_images['minus_only'];
+                $alt = '-';
+            } else {
+                $img = $this->_images['plus_only'];
+                $alt = '+';
+            }
+            if (!$this->_static) {
+                $url = Horde_Util::addParameter(Horde::selfUrl(), self::TOGGLE . $this->_instance, $node_id);
+                $link_start = Horde::link($url);
+            }
+        } elseif (($this->_nodes[$node_id]['indent'] != 0) &&
+            !isset($this->_nodes[$node_id]['children'])) {
+            /* Node without children. */
+            if ($this->_node_pos[$node_id]['pos'] < $this->_node_pos[$node_id]['count']) {
+                /* Not last node. */
+                if ($this->getOption('lines', false, true)) {
+                    $img = $this->_images['join'];
+                    $alt = '|-';
+                } else {
+                    $img = $this->_images['blank'];
+                    $alt = '&nbsp;&nbsp;&nbsp;';
+                }
+                $this->_dropline[$this->_nodes[$node_id]['indent']] = true;
+            } else {
+                /* Last node. */
+                if ($this->getOption('lines', false, true)) {
+                    $img = $this->_images['join_bottom'];
+                    $alt = '`-';
+                } else {
+                    $img = $this->_images['blank'];
+                    $alt = '&nbsp;&nbsp;&nbsp;';
+                }
+                $this->_dropline[$this->_nodes[$node_id]['indent']] = false;
+            }
+        } elseif (isset($this->_nodes[$node_id]['children'])) {
+            /* Node with children. */
+            if ($this->_node_pos[$node_id]['pos'] < $this->_node_pos[$node_id]['count']) {
+                /* Not last node. */
+                if (!$this->getOption('lines', false, true)) {
+                    $img = $this->_images['blank'];
+                    $alt = '&nbsp;&nbsp;&nbsp;';
+                } elseif ($this->_static) {
+                    $img = $this->_images['join'];
+                    $alt = '|-';
+                } elseif ($this->_nodes[$node_id]['expanded']) {
+                    $img = $this->_images['minus'];
+                    $alt = '-';
+                } else {
+                    $img = $this->_images['plus'];
+                    $alt = '+';
+                }
+                $this->_dropline[$this->_nodes[$node_id]['indent']] = true;
+            } else {
+                /* Last node. */
+                if (!$this->getOption('lines', false, true)) {
+                    $img = $this->_images['blank'];
+                    $alt = '&nbsp;&nbsp;&nbsp;';
+                } elseif ($this->_static) {
+                    $img = $this->_images['join_bottom'];
+                    $alt = '`-';
+                } elseif ($this->_nodes[$node_id]['expanded']) {
+                    $img = $this->_images['minus_bottom'];
+                    $alt = '-';
+                } else {
+                    $img = $this->_images['plus_bottom'];
+                    $alt = '+';
+                }
+                $this->_dropline[$this->_nodes[$node_id]['indent']] = false;
+            }
+            if (!$this->_static) {
+                $url = Horde_Util::addParameter(Horde::selfUrl(), self::TOGGLE . $this->_instance, $node_id);
+                $link_start = Horde::link($url);
+            }
+        } else {
+            /* Top level node with no children. */
+            if ($this->_static) {
+                return '';
+            }
+            if ($this->getOption('lines', false, true)) {
+                $img = $this->_images['null_only'];
+                $alt = '&nbsp;&nbsp;';
+            } else {
+                $img = $this->_images['blank'];
+                $alt = '&nbsp;&nbsp;&nbsp;';
+            }
+            $this->_dropline[0] = false;
+        }
+
+        $link_end = ($link_start) ? '</a>' : '';
+
+        $img = $link_start . '<img src="' . $this->_img_dir . '/' . $img . '"'
+            . (isset($alt) ? ' alt="' . $alt . '"' : '')
+            . ' height="20" width="20" style="vertical-align:middle" border="0" />'
+            . $link_end;
+
+        return $img;
+    }
+
+    /**
+     * Sets the icon for the node.
+     *
+     * @param string $node_id  The Node ID.
+     *
+     * @return string  The node icon for the tree line.
+     */
+    protected function _setNodeIcon($node_id)
+    {
+        $img_dir = isset($this->_nodes[$node_id]['icondir']) ? $this->_nodes[$node_id]['icondir'] : $this->_img_dir;
+        if ($img_dir) {
+            $img_dir .= '/';
+        }
+
+        if (isset($this->_nodes[$node_id]['icon'])) {
+            if (empty($this->_nodes[$node_id]['icon'])) {
+                return '';
+            }
+            /* Node has a user defined icon. */
+            if (isset($this->_nodes[$node_id]['iconopen']) &&
+                $this->_nodes[$node_id]['expanded']) {
+                $img = $this->_nodes[$node_id]['iconopen'];
+            } else {
+                $img = $this->_nodes[$node_id]['icon'];
+            }
+        } else {
+            /* Use standard icon set. */
+            if (isset($this->_nodes[$node_id]['children'])) {
+                /* Node with children. */
+                $img = ($this->_nodes[$node_id]['expanded']) ? $this->_images['folder_open'] : $this->_images['folder'];
+            } else {
+                /* Leaf node (no children). */
+                $img = $this->_images['leaf'];
+            }
+        }
+
+        $imgtxt = '<img src="' . $img_dir . $img . '"';
+
+        /* Does the node have user defined alt text? */
+        if (isset($this->_nodes[$node_id]['iconalt'])) {
+            $imgtxt .= ' alt="' . htmlspecialchars($this->_nodes[$node_id]['iconalt']) . '"';
+        }
+
+        return $imgtxt . ' />';
+    }
+
+}
diff --git a/framework/Tree/lib/Horde/Tree/Javascript.php b/framework/Tree/lib/Horde/Tree/Javascript.php
new file mode 100644 (file)
index 0000000..3fe2a72
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+/**
+ * The Horde_Tree_Javascript:: class extends the Horde_Tree class to provide
+ * javascript specific rendering functions.
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author   Marko Djukic <marko@oblo.com>
+ * @category Horde
+ * @package  Horde_Tree
+ */
+class Horde_Tree_Javascript extends Horde_Tree
+{
+    /**
+     * Constructor.
+     *
+     * @param string $name   @see Horde_Tree::__construct().
+     * @param array $params  @see Horde_Tree::__construct().
+     */
+    public function __construct($tree_name, $params = array())
+    {
+        parent::__construct($tree_name, $params);
+
+        /* Check for a javascript session state. */
+        if ($this->_usesession &&
+            isset($_COOKIE[$this->_instance . '_expanded'])) {
+            /* Remove "exp" prefix from cookie value. */
+            $nodes = explode(',', substr($_COOKIE[$this->_instance . '_expanded'], 3));
+
+            /* Make sure there are no previous nodes stored in the
+             * session. */
+            $_SESSION['horde_tree'][$this->_instance]['expanded'] = array();
+
+            /* Save nodes to the session. */
+            foreach ($nodes as $id) {
+                $_SESSION['horde_tree'][$this->_instance]['expanded'][$id] = true;
+            }
+        }
+
+        Horde::addScriptFile('prototype.js', 'horde');
+        Horde::addScriptFile('hordetree.js', 'horde');
+        if (!empty($this->_options['alternate'])) {
+            Horde::addScriptFile('stripe.js', 'horde');
+        }
+    }
+
+    /**
+     * Returns the tree.
+     *
+     * @param boolean $static  If true the tree nodes can't be expanded and
+     *                         collapsed and the tree gets rendered expanded.
+     *
+     * @return string  The HTML code of the rendered tree.
+     */
+    public function getTree($static = false)
+    {
+        $this->_static = $static;
+
+        $opts = array(
+            'extraColsLeft' => $this->_extra_cols_left,
+            'extraColsRight' => $this->_extra_cols_right,
+            'header' => $this->_header,
+            'options' => $this->_options,
+            'target' => $this->_instance,
+
+            'cookieDomain' => $GLOBALS['conf']['cookie']['domain'],
+            'cookiePath' => $GLOBALS['conf']['cookie']['path'],
+
+            'scrollbar_in_way' => $GLOBALS['browser']->hasQuirk('scrollbar_in_way'),
+
+            'imgDir' => $this->_img_dir,
+            'imgBlank' => $this->_images['blank'],
+            'imgFolder' => $this->_images['folder'],
+            'imgFolderOpen' => $this->_images['folderopen'],
+            'imgLine' => $this->_images['line'],
+            'imgJoin' => $this->_images['join'],
+            'imgJoinBottom' => $this->_images['join_bottom'],
+            'imgPlus' => $this->_images['plus'],
+            'imgPlusBottom' => $this->_images['plus_bottom'],
+            'imgPlusOnly' => $this->_images['plus_only'],
+            'imgMinus' => $this->_images['minus'],
+            'imgMinusBottom' => $this->_images['minus_bottom'],
+            'imgMinusOnly' => $this->_images['minus_only'],
+            'imgNullOnly' => $this->_images['null_only'],
+            'imgLeaf' => $this->_images['leaf'],
+
+            'floatDir' => empty($GLOBALS['nls']['rtl'][$GLOBALS['language']]) ? 'float:left;' : 'float:right'
+        );
+
+        Horde::addInlineScript(array(
+            $this->_instance . ' = new Horde_Tree(' . Horde_Serialize::serialize($opts, Horde_Serialize::JSON, Horde_Nls::getCharset()) . ')',
+            $this->renderNodeDefinitions()
+        ), 'dom');
+
+        return '<div id="' . $this->_instance . '"></div>';
+    }
+
+    /**
+     * Check the current environment to see if we can render the HTML tree.
+     * We check for DOM support in the browser.
+     *
+     * @return boolean  Whether or not this backend will function.
+     */
+    public function isSupported()
+    {
+        $browser = Horde_Browser::singleton();
+        return $browser->hasFeature('dom');
+    }
+
+    /**
+     * Returns just the JS node definitions as a string.
+     *
+     * @return string  The Javascript node array definitions.
+     */
+    public function renderNodeDefinitions()
+    {
+        $this->_buildIndents($this->_root_nodes);
+
+        return $this->_instance . '.renderTree(' . Horde_Serialize::serialize($this->_nodes, Horde_Serialize::JSON, Horde_Nls::getCharset()) . ',' . Horde_Serialize::serialize($this->_root_nodes, Horde_Serialize::JSON, Horde_Nls::getCharset()) . ',' . ($this->_static ? 'true' : 'false') . ')';
+    }
+
+}
diff --git a/framework/Tree/lib/Horde/Tree/Select.php b/framework/Tree/lib/Horde/Tree/Select.php
new file mode 100644 (file)
index 0000000..3aea840
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+/**
+ * The Horde_Tree_Select:: class extends the Horde_Tree class to provide
+ * <option> tag rendering.
+ *
+ * Copyright 2005-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author   Ben Chavet <ben@horde.org>
+ * @category Horde
+ * @package  Horde_Tree
+ */
+class Horde_Tree_Select extends Horde_Tree
+{
+    /**
+     * TODO
+     *
+     * @var array
+     */
+    protected $_nodes = array();
+
+    /**
+     * Constructor.
+     *
+     * @param string $name   @see Horde_Tree::__construct().
+     * @param array $params  @see Horde_Tree::__construct().
+     */
+    public function __construct($tree_name, $params = array())
+    {
+        parent::__construct($tree_name, $params);
+        $this->_static = true;
+    }
+
+    /**
+     * Returns the tree.
+     *
+     * @return string  The HTML code of the rendered tree.
+     */
+    public function getTree()
+    {
+        $this->_buildIndents($this->_root_nodes);
+
+        $tree = '';
+        foreach ($this->_root_nodes as $node_id) {
+            $tree .= $this->_buildTree($node_id);
+        }
+
+        return $tree;
+    }
+
+    /**
+     * Adds additional parameters to a node.
+     *
+     * @param string $id     The unique node id.
+     * @param array $params  Any other parameters to set.
+     * <pre>
+     * selected - (boolean) Whether this node is selected.
+     * </pre>
+     */
+    public function addNodeParams($id, $params = array())
+    {
+        if (!is_array($params)) {
+            $params = array($params);
+        }
+
+        $allowed = array('selected');
+
+        foreach ($params as $param_id => $param_val) {
+            /* Set only allowed and non-null params. */
+            if (in_array($param_id, $allowed) && !is_null($param_val)) {
+                $this->_nodes[$id][$param_id] = $param_val;
+            }
+        }
+    }
+
+    /**
+     * Recursive function to walk through the tree array and build the output.
+     *
+     * @param string $node_id  The Node ID.
+     *
+     * @return string  The tree rendering.
+     */
+    protected function _buildTree($node_id)
+    {
+        $selected = $this->_nodes[$node_id]['selected'] ? ' selected="selected"' : '';
+
+        $output = '<option value="' . htmlspecialchars($node_id) . '"' . $selected . '>' .
+            str_repeat('&nbsp;&nbsp;', (int)$this->_nodes[$node_id]['indent']) . htmlspecialchars($this->_nodes[$node_id]['label']) .
+            '</option>';
+
+        if (isset($this->_nodes[$node_id]['children']) &&
+            $this->_nodes[$node_id]['expanded']) {
+            $num_subnodes = count($this->_nodes[$node_id]['children']);
+            for ($c = 0; $c < $num_subnodes; $c++) {
+                $child_node_id = $this->_nodes[$node_id]['children'][$c];
+                $output .= $this->_buildTree($child_node_id);
+            }
+        }
+
+        return $output;
+    }
+
+}
diff --git a/framework/Tree/package.xml b/framework/Tree/package.xml
new file mode 100644 (file)
index 0000000..f50927e
--- /dev/null
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Tree</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde Tree API</summary>
+ <description>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 javascript code to achieve this in DHTML on supported browsers.
+ </description>
+ <lead>
+  <name>Chuck Hagenbuch</name>
+  <user>chuck</user>
+  <email>chuck@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <lead>
+  <name>Jan Schneider</name>
+  <user>jan</user>
+  <email>jan@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <date>2009-12-15</date>
+ <version>
+  <release>0.1.0</release>
+  <api>0.1.0</api>
+ </version>
+ <stability>
+  <release>beta</release>
+  <api>beta</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/gpl.html">GPL</license>
+ <notes>* Initial Horde 4 package.
+ </notes>
+ <contents>
+  <dir name="/">
+   <dir name="lib">
+    <dir name="Horde">
+     <dir name="Tree">
+      <file name="Html.php" role="php" />
+      <file name="Javascript.php" role="php" />
+      <file name="Select.php" role="php" />
+     </dir> <!-- /lib/Horde/Tree -->
+     <file name="Tree.php" role="php" />
+    </dir> <!-- /lib/Horde -->
+   </dir> <!-- /lib -->
+  </dir> <!-- / -->
+ </contents>
+ <dependencies>
+  <required>
+   <php>
+    <min>5.2.0</min>
+   </php>
+   <pearinstaller>
+    <min>1.7.0</min>
+   </pearinstaller>
+   <package>
+    <name>Util</name>
+    <channel>pear.horde.org</channel>
+   </package>
+  </required>
+  <optional>
+   <extension>
+    <name>gettext</name>
+   </extension>
+  </optional>
+ </dependencies>
+ <phprelease>
+  <filelist>
+   <install name="lib/Horde/Tree/Html.php" as="Horde/Tree/Html.php" />
+   <install name="lib/Horde/Tree/Javascript.php" as="Horde/Tree/Javascript.php" />
+   <install name="lib/Horde/Tree/Select.php" as="Horde/Tree/Select.php" />
+   <install name="lib/Horde/Tree.php" as="Horde/Tree.php" />
+  </filelist>
+ </phprelease>
+ <changelog>
+  <release>
+   <date>2006-05-08</date>
+   <time>23:51:48</time>
+   <version>
+    <release>0.0.2</release>
+    <api>0.0.2</api>
+   </version>
+   <stability>
+    <release>alpha</release>
+    <api>alpha</api>
+   </stability>
+   <license uri="http://www.gnu.org/copyleft/gpl.html">GPL</license>
+   <notes>Converted to package.xml 2.0 for pear.horde.org
+   </notes>
+  </release>
+  <release>
+   <version>
+    <release>0.0.1</release>
+    <api>0.0.1</api>
+   </version>
+   <stability>
+    <release>alpha</release>
+    <api>alpha</api>
+   </stability>
+   <date>2003-07-05</date>
+   <license uri="http://www.gnu.org/copyleft/gpl.html">GPL</license>
+   <notes>Initial release as a PEAR package
+   </notes>
+  </release>
+ </changelog>
+</package>