--- /dev/null
+<?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()
+ {
+ }
+
+}
--- /dev/null
+<?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']) ? ' ' : $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="| " ';
+ } else {
+ $line .= $this->_images['blank'] . '" '
+ . 'alt=" " ';
+ }
+ $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 = ' ';
+ } 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 = ' ';
+ }
+ $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 = ' ';
+ }
+ $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 = ' ';
+ } 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 = ' ';
+ } 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 = ' ';
+ } else {
+ $img = $this->_images['blank'];
+ $alt = ' ';
+ }
+ $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 . ' />';
+ }
+
+}
--- /dev/null
+<?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') . ')';
+ }
+
+}
--- /dev/null
+<?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(' ', (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;
+ }
+
+}
--- /dev/null
+<?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>