1<?php
2/**
3 * XOOPS tree class
4 *
5 * You may not change or alter any portion of this comment or credits
6 * of supporting developers from this source code or any supporting source code
7 * which is considered copyrighted (c) material of the original comment or credit authors.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 *
12 * @copyright       (c) 2000-2016 XOOPS Project (www.xoops.org)
13 * @license             GNU GPL 2 (http://www.gnu.org/licenses/gpl-2.0.html)
14 * @package             kernel
15 * @since               2.0.0
16 * @author              Kazumi Ono (http://www.myweb.ne.jp/, http://jp.xoops.org/)
17 */
18
19defined('XOOPS_ROOT_PATH') || exit('Restricted access');
20
21/**
22 * A tree structures with {@link XoopsObject}s as nodes
23 *
24 * @package    kernel
25 * @subpackage core
26 * @author     Kazumi Ono <onokazu@xoops.org>
27 */
28class XoopsObjectTree
29{
30    /**
31     * @access private
32     */
33    protected $parentId;
34    protected $myId;
35    protected $rootId;
36    protected $tree = array();
37    protected $objects;
38
39    /**
40     * Constructor
41     *
42     * @param array  $objectArr Array of {@link XoopsObject}s
43     * @param string $myId      field name of object ID
44     * @param string $parentId  field name of parent object ID
45     * @param string $rootId    field name of root object ID
46     */
47    public function __construct(&$objectArr, $myId, $parentId, $rootId = null)
48    {
49        $this->objects = $objectArr;
50        $this->myId     = $myId;
51        $this->parentId = $parentId;
52        if (isset($rootId)) {
53            $this->rootId = $rootId;
54        }
55        $this->initialize();
56    }
57
58    /**
59     * Initialize the object
60     *
61     * @access private
62     */
63    protected function initialize()
64    {
65        foreach (array_keys($this->objects) as $i) {
66            $key1                          = $this->objects[$i]->getVar($this->myId);
67            $this->tree[$key1]['obj']     = $this->objects[$i];
68            $key2                          = $this->objects[$i]->getVar($this->parentId);
69            $this->tree[$key1]['parent']  = $key2;
70            $this->tree[$key2]['child'][] = $key1;
71            if (isset($this->rootId)) {
72                $this->tree[$key1]['root'] = $this->objects[$i]->getVar($this->rootId);
73            }
74        }
75    }
76
77    /**
78     * Get the tree
79     *
80     * @return array Associative array comprising the tree
81     */
82    public function &getTree()
83    {
84        return $this->tree;
85    }
86
87    /**
88     * returns an object from the tree specified by its id
89     *
90     * @param  string $key ID of the object to retrieve
91     * @return object Object within the tree
92     */
93    public function &getByKey($key)
94    {
95        return $this->tree[$key]['obj'];
96    }
97
98    /**
99     * returns an array of all the first child object of an object specified by its id
100     *
101     * @param  string $key ID of the parent object
102     * @return array  Array of children of the parent
103     */
104    public function getFirstChild($key)
105    {
106        $ret = array();
107        if (isset($this->tree[$key]['child'])) {
108            foreach ($this->tree[$key]['child'] as $childKey) {
109                $ret[$childKey] = $this->tree[$childKey]['obj'];
110            }
111        }
112
113        return $ret;
114    }
115
116    /**
117     * returns an array of all child objects of an object specified by its id
118     *
119     * @param  string $key ID of the parent
120     * @param  array  $ret (Empty when called from client) Array of children from previous recursions.
121     * @return array  Array of child nodes.
122     */
123    public function getAllChild($key, $ret = array())
124    {
125        if (isset($this->tree[$key]['child'])) {
126            foreach ($this->tree[$key]['child'] as $childKey) {
127                $ret[$childKey] = $this->tree[$childKey]['obj'];
128                $children       = $this->getAllChild($childKey, $ret);
129                foreach (array_keys($children) as $newKey) {
130                    $ret[$newKey] = $children[$newKey];
131                }
132            }
133        }
134
135        return $ret;
136    }
137
138    /**
139     * returns an array of all parent objects.
140     * the key of returned array represents how many levels up from the specified object
141     *
142     * @param  string $key     ID of the child object
143     * @param  array  $ret     (empty when called from outside) Result from previous recursions
144     * @param  int    $upLevel (empty when called from outside) level of recursion
145     * @return array  Array of parent nodes.
146     */
147    public function getAllParent($key, $ret = array(), $upLevel = 1)
148    {
149        if (isset($this->tree[$key]['parent']) && isset($this->tree[$this->tree[$key]['parent']]['obj'])) {
150            $ret[$upLevel] = $this->tree[$this->tree[$key]['parent']]['obj'];
151            $parents       = $this->getAllParent($this->tree[$key]['parent'], $ret, $upLevel + 1);
152            foreach (array_keys($parents) as $newKey) {
153                $ret[$newKey] = $parents[$newKey];
154            }
155        }
156
157        return $ret;
158    }
159
160    /**
161     * Make options for a select box from
162     *
163     * @param string $fieldName   Name of the member variable from the
164     *                            node objects that should be used as the title for the options.
165     * @param string $selected    Value to display as selected
166     * @param int    $key         ID of the object to display as the root of select options
167     * @param string $ret         (reference to a string when called from outside) Result from previous recursions
168     * @param string $prefix_orig String to indent items at deeper levels
169     * @param string $prefix_curr String to indent the current item
170     *
171     * @return void
172     * @deprecated since 2.5.9, please use makeSelectElement() functionality
173     */
174    protected function makeSelBoxOptions($fieldName, $selected, $key, &$ret, $prefix_orig, $prefix_curr = '')
175    {
176        if ($key > 0) {
177            $value = $this->tree[$key]['obj']->getVar($this->myId);
178            $ret .= '<option value="' . $value . '"';
179            if ($value == $selected) {
180                $ret .= ' selected';
181            }
182            $ret .= '>' . $prefix_curr . $this->tree[$key]['obj']->getVar($fieldName) . '</option>';
183            $prefix_curr .= $prefix_orig;
184        }
185        if (isset($this->tree[$key]['child']) && !empty($this->tree[$key]['child'])) {
186            foreach ($this->tree[$key]['child'] as $childKey) {
187                $this->makeSelBoxOptions($fieldName, $selected, $childKey, $ret, $prefix_orig, $prefix_curr);
188            }
189        }
190    }
191
192    /**
193     * Make a select box with options from the tree
194     *
195     * @param  string  $name           Name of the select box
196     * @param  string  $fieldName      Name of the member variable from the
197     *                                 node objects that should be used as the title for the options.
198     * @param  string  $prefix         String to indent deeper levels
199     * @param  string  $selected       Value to display as selected
200     * @param  bool    $addEmptyOption Set TRUE to add an empty option with value "0" at the top of the hierarchy
201     * @param  integer $key            ID of the object to display as the root of select options
202     * @param  string  $extra
203     * @return string  HTML select box
204     *
205     * @deprecated since 2.5.9, please use makeSelectElement()
206     */
207    public function makeSelBox(
208        $name,
209        $fieldName,
210        $prefix = '-',
211        $selected = '',
212        $addEmptyOption = false,
213        $key = 0,
214        $extra = ''
215    ) {
216        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
217        trigger_error("makeSelBox() is deprecated since 2.5.9, please use makeSelectElement(), accessed from {$trace[0]['file']} line {$trace[0]['line']},");
218        $ret = '<select name="' . $name . '" id="' . $name . '" ' . $extra . '>';
219        if (false !== (bool)$addEmptyOption) {
220            $ret .= '<option value="0"></option>';
221        }
222        $this->makeSelBoxOptions($fieldName, $selected, $key, $ret, $prefix);
223
224        return $ret . '</select>';
225    }
226
227    /**
228     * Make a select box with options from the tree
229     *
230     * @param  string  $name           Name of the select box
231     * @param  string  $fieldName      Name of the member variable from the
232     *                                 node objects that should be used as the title for the options.
233     * @param  string  $prefix         String to indent deeper levels
234     * @param  string  $selected       Value to display as selected
235     * @param  bool    $addEmptyOption Set TRUE to add an empty option with value "0" at the top of the hierarchy
236     * @param  integer $key            ID of the object to display as the root of select options
237     * @param  string  $extra          extra content to add to the element
238     * @param  string  $caption        optional caption for form element
239     *
240     * @return XoopsFormSelect form element
241     */
242    public function makeSelectElement(
243        $name,
244        $fieldName,
245        $prefix = '-',
246        $selected = '',
247        $addEmptyOption = false,
248        $key = 0,
249        $extra = '',
250        $caption = ''
251    ) {
252        xoops_load('xoopsformselect');
253        $element = new XoopsFormSelect($caption, $name, $selected);
254        $element->setExtra($extra);
255
256        if (false !== (bool)$addEmptyOption) {
257            $element->addOption('0', ' ');
258        }
259        $this->addSelectOptions($element, $fieldName, $key, $prefix);
260
261        return $element;
262    }
263
264    /**
265     * Make options for a select box from
266     *
267     * @param XoopsFormSelect $element     form element to receive tree values as options
268     * @param string          $fieldName   Name of the member variable from the node objects that
269     *                                     should be used as the title for the options.
270     * @param int             $key         ID of the object to display as the root of select options
271     * @param string          $prefix_orig String to indent items at deeper levels
272     * @param string          $prefix_curr String to indent the current item
273     *
274     * @return void
275     * @access private
276     */
277    protected function addSelectOptions($element, $fieldName, $key, $prefix_orig, $prefix_curr = '')
278    {
279        if ($key > 0) {
280            $value = $this->tree[$key]['obj']->getVar($this->myId);
281            $name = $prefix_curr . $this->tree[$key]['obj']->getVar($fieldName);
282            $element->addOption($value, $name);
283            $prefix_curr .= $prefix_orig;
284        }
285        if (isset($this->tree[$key]['child']) && !empty($this->tree[$key]['child'])) {
286            foreach ($this->tree[$key]['child'] as $childKey) {
287                $this->addSelectOptions($element, $fieldName, $childKey, $prefix_orig, $prefix_curr);
288            }
289        }
290    }
291
292    /**
293     * Magic __get method
294     *
295     * Some modules did not respect the leading underscore is private convention and broke
296     * when code was modernized. This will keep them running for now.
297     *
298     * @param string $name unknown variable name requested
299     *                      currently only '_tree' is supported
300     *
301     * @return mixed value
302     */
303    public function __get($name)
304    {
305        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
306        if ($name === '_tree') {
307            trigger_error("XoopsObjectTree::\$_tree is deprecated, accessed from {$trace[0]['file']} line {$trace[0]['line']},");
308            return $this->tree;
309        }
310        trigger_error(
311            'Undefined property: XoopsObjectTree::$' . $name .
312            " in {$trace[0]['file']} line {$trace[0]['line']}, ",
313            E_USER_NOTICE);
314        return null;
315    }
316}
317