1: <?php
2: /**
3: * This file contains the tree item storage class.
4: *
5: * @package Core
6: * @subpackage Backend
7: * @author Timo Hummel
8: * @copyright four for business AG <www.4fb.de>
9: * @license http://www.contenido.org/license/LIZENZ.txt
10: * @link http://www.4fb.de
11: * @link http://www.contenido.org
12: */
13:
14: defined('CON_FRAMEWORK') || die('Illegal call: Missing framework initialization - request aborted.');
15:
16: /**
17: * Class TreeItem
18: * Class to create tree-based items
19: *
20: * The treeitem class allows you to logically store
21: * tree-based structures.
22: *
23: * Example:
24: *
25: * Let's have a tree with 3 nodes. It's important that
26: * we always have a "root" key.
27: *
28: * $root = new TreeItem("root", 1);
29: * $item1 = new TreeItem("node1",2);
30: * $item2 = new TreeItem("node2",3);
31: * $item3 = new TreeItem("node3",4);
32: *
33: * $root->addItem($item1);
34: * $root->addItem($item2);
35: * $root->addItem($item3);
36: *
37: * This represents the tree we described above.
38: *
39: * If you know the ID of the item you want to add
40: * to, there's no need to have a specific item handy,
41: * but rather you can use the "addItemToID" function.
42: *
43: * @package Core
44: * @subpackage Backend
45: */
46: class TreeItem {
47:
48: /**
49: * Sub Items for this tree item
50: *
51: * @var array
52: */
53: protected $_subitems;
54:
55: /**
56: * Determinates if this tree item is collapsed
57: *
58: * @var bool
59: */
60: protected $_collapsed;
61:
62: /**
63: * ID for this item
64: *
65: * @var string
66: */
67: protected $_id;
68:
69: /**
70: * Name for this item
71: *
72: * @var string
73: */
74: protected $_name;
75:
76: /**
77: * Icon for the collapsed item
78: *
79: * @var string
80: */
81: protected $_collapsed_icon;
82:
83: /**
84: * Icon for the expanded item
85: *
86: * @var string
87: */
88: protected $_expanded_icon;
89:
90: /**
91: * Icon for last node in a branch
92: *
93: * @var string
94: */
95: protected $_lastnode_icon;
96:
97: /**
98: * Contains the level of this item
99: *
100: * @var int
101: */
102: protected $_level;
103:
104: /**
105: * Contains custom entries
106: *
107: * @var array
108: */
109: protected $_custom;
110:
111: /**
112: * Contains the parent of this item
113: *
114: * @var array
115: */
116: protected $_parent;
117:
118: /**
119: * Constructor to create an instance of this class.
120: *
121: * Creates a new, independant tree item.
122: *
123: * @param string $name [optional]
124: * The name of that item
125: * @param string $id [optional]
126: * The unique ID of that item
127: * @param bool $collapsed [optional]
128: * Is this item collapsed by default
129: */
130: public function __construct($name = "", $id = "", $collapsed = false) {
131: $this->_name = $name;
132: $this->_id = $id;
133: $this->_collapsed = $collapsed;
134: $this->_subitems = array();
135: $this->setCollapsedIcon('images/but_plus.gif');
136: $this->setExpandedIcon('images/but_minus.gif');
137: $this->setLastnodeIcon('images/but_lastnode.gif');
138: $this->_parent = -1;
139: }
140:
141: /**
142: * Get method for _collapsed_icon variable
143: *
144: * @return string
145: */
146: public function getCollapsedIcon() {
147: return $this->_collapsed_icon;
148: }
149:
150: /**
151: * Get method for _costum variable
152: *
153: * @param string $key
154: * @return string mixed
155: */
156: public function getCustom($key) {
157: return $this->_custom[$key];
158: }
159:
160: /**
161: * Get method for _expanded_icon variable
162: *
163: * @return string
164: */
165: public function getExpandedIcon() {
166: return $this->_expanded_icon;
167: }
168:
169: /**
170: * Get method for _id variable
171: *
172: * @return string
173: */
174: public function getId() {
175: return $this->_id;
176: }
177:
178: /**
179: * Get method for _name variable
180: *
181: * @return string
182: */
183: public function getName() {
184: return $this->_name;
185: }
186:
187: /**
188: * Get method for _subitems array
189: *
190: * @return array
191: */
192: public function getSubItems() {
193: return $this->_subitems;
194: }
195:
196: /**
197: * Set method for custom array
198: *
199: * @param string $key
200: * @param string|int $content
201: */
202: public function setCustom($key, $content) {
203: $this->_custom[$key] = $content;
204: }
205:
206: /**
207: * Set method for _collapsed_icon variable
208: *
209: * @param string $iconPath
210: */
211: public function setCollapsedIcon($iconPath) {
212: if (cSecurity::isString($iconPath)) {
213: $this->_collapsed_icon = $iconPath;
214: }
215: }
216:
217: /**
218: * Set method for _expanded_icon variable
219: *
220: * @param string $iconPath
221: */
222: public function setExpandedIcon($iconPath) {
223: if (cSecurity::isString($iconPath)) {
224: $this->_expanded_icon = $iconPath;
225: }
226: }
227:
228: /**
229: * Set method for _lastnode_icon variable
230: *
231: * @param string $iconPath
232: */
233: public function setLastnodeIcon($iconPath) {
234: if (cSecurity::isString($iconPath)) {
235: $this->_lastnode_icon = $iconPath;
236: }
237: }
238:
239: /**
240: * Set method for name variable
241: *
242: * @param string $name
243: */
244: public function setName($name) {
245: $this->_name = $name;
246: }
247:
248: /**
249: * Get status of collapsed (_collapsed variable)
250: *
251: * @return bool
252: */
253: public function isCollapsed() {
254: return $this->_collapsed;
255: }
256:
257: /**
258: * Adds a new subitem to this item.
259: *
260: * @param object $item
261: * the item to add
262: */
263: public function addItem(&$item) {
264: $this->_subitems[count($this->_subitems)] = &$item;
265: $item->parent = $this->_id;
266: }
267:
268: /**
269: * Adds a new subitem to a specific item with an ID.
270: * Traverses all subitems to find the correct item.
271: *
272: * @param object $item
273: * the item to add
274: * @param string $id
275: * the ID to add the item to
276: */
277: protected function _addItemToID($item, $id) {
278: if ($this->_id == $id) {
279: $this->_subitems[count($this->_subitems)] = &$item;
280: $item->parent = $this->_id;
281: } else {
282: foreach (array_keys($this->_subitems) as $key) {
283: $this->_subitems[$key]->_addItemToID($item, $id);
284: }
285: }
286: }
287:
288: /**
289: * Retrieves a specific item by its ID.
290: * Note that this
291: * function traverses all subitems to find the correct item.
292: *
293: * @param string $id
294: * the ID to find
295: * @return object
296: * The item, or false if nothing was found
297: */
298: public function &getItemByID($id) {
299: if ($this->_id == $id) {
300: return $this;
301: } else {
302: foreach (array_keys($this->_subitems) as $key) {
303: $retObj = &$this->_subitems[$key]->getItemByID($id);
304: if ($retObj->id == $id) {
305: return $retObj;
306: }
307: }
308: }
309:
310: return false;
311: }
312:
313: /**
314: * Removes an item with a specific ID.
315: *
316: * @param string $id
317: * the ID to find
318: */
319: public function removeItem($id) {
320: foreach (array_keys($this->_subitems) as $key) {
321: if ($this->_subitems[$key]->id == $id) {
322: unset($this->_subitems[$key]);
323: }
324: }
325: }
326:
327: /**
328: * Checks if a specific custom attribute is set
329: *
330: * @param string $item
331: * the attribute name to find
332: * @return bool
333: */
334: protected function _isCustomAttributeSet($item) {
335: if (array_key_exists($item, $this->_custom)) {
336: return true;
337: } else {
338: foreach (array_keys($this->_subitems) as $key) {
339: if ($this->_subitems[$key]->_isCustomAttributeSet($item)) {
340: return true;
341: }
342: }
343: }
344:
345: return false;
346: }
347:
348: /**
349: * Marks an item as expanded.
350: * Traverses all subitems to find the ID. Note that only the item with $id
351: * is expanded, but not its childs.
352: *
353: * @param string $id
354: * the ID to expand, or an array with all id's
355: * @return bool
356: */
357: public function markExpanded($id) {
358: if (is_array($id)) {
359: if (in_array($this->_id, $id)) {
360: $this->_collapsed = false;
361: }
362:
363: foreach (array_keys($this->_subitems) as $key) {
364: $this->_subitems[$key]->markExpanded($id);
365: }
366: } else {
367: if ($this->_id == $id) {
368: $this->_collapsed = false;
369: return true;
370: } else {
371: foreach (array_keys($this->_subitems) as $key) {
372: $this->_subitems[$key]->markExpanded($id);
373: }
374: }
375: }
376: }
377:
378: /**
379: * Expands all items, starting from the $start item.
380: *
381: * @param string $start [optional]
382: * the ID to start expanding from
383: */
384: public function expandAll($start = -2) {
385: if ($start != $this->_id) {
386: $this->_collapsed = false;
387: }
388:
389: foreach (array_keys($this->_subitems) as $key) {
390: $this->_subitems[$key]->expandAll();
391: }
392: }
393:
394: /**
395: * Collapses all items, starting from the $start item.
396: *
397: * @param string $start [optional]
398: * the ID to start collapsing from
399: */
400: public function collapseAll($start = -2) {
401: if ($start != $this->_id) {
402: $this->_collapsed = true;
403: }
404:
405: foreach (array_keys($this->_subitems) as $key) {
406: $this->_subitems[$key]->collapseAll();
407: }
408: }
409:
410: /**
411: * Marks an item as collpased.
412: * Traverses all subitems
413: * to find the ID. Note that only the item with $id is
414: * collapsed, but not its childs.
415: *
416: * @param string $id
417: * the ID to collapse
418: */
419: public function markCollapsed($id) {
420: if ($this->_id == $id) {
421: $this->_collapsed = true;
422: } else {
423: foreach (array_keys($this->_subitems) as $key) {
424: $this->_subitems[$key]->markCollapsed($id);
425: }
426: }
427: }
428:
429: /**
430: * Traverses the tree starting from this item, and returning
431: * all objects as $objects.
432: *
433: * @param object $objects
434: * all found objects
435: * @param int $level [optional]
436: * Level to start on
437: */
438: public function traverse(&$objects, $level = 0) {
439: $objects[count($objects)] = &$this;
440: $this->_level = $level;
441:
442: if ($this->_collapsed == false) {
443: foreach (array_keys($this->_subitems) as $key) {
444: $this->_subitems[$key]->traverse($objects, $level + 1);
445: }
446: }
447: }
448:
449: /**
450: * Starts iterating at root node and flattens the tree into an array
451: *
452: * @param TreeItem $item
453: * @param array $flat_tree
454: */
455: public function getFlatTree($item, &$flat_tree) {
456: foreach ($item->getSubItems() as $curItem) {
457: $curItem->custom['vertline'] = array();
458: $flat_tree[] = $curItem;
459: $this->getFlatTree($curItem, $flat_tree);
460: }
461: }
462:
463: /**
464: *
465: * @param unknown_type $item_id
466: * @return bool
467: */
468: public function hasCollapsedNode($item_id) {
469: $parentNodeList = array();
470: $this->getTreeParentNodes($parentNodeList, $item_id);
471: $collapsedList = array();
472: $this->getRealCollapsedList($collapsedList);
473:
474: if (sizeof(array_intersect($parentNodeList, $collapsedList)) > 0) {
475: return TRUE;
476: } else {
477: return FALSE;
478: }
479: }
480:
481: /**
482: * Returns a list of the id of all parent nodes of the given node
483: *
484: * @param unknown_type $parentNodes
485: * @param unknown_type $id
486: */
487: public function getTreeParentNodes(&$parentNodes, $id) {
488: $curItem = $this->_getItemByID($id);
489: $parentId = $curItem->parent;
490:
491: if ($parentId && $parentId != -1) {
492: $parentNodes[] = $parentId;
493: $this->getTreeParentNodes($parentNodes, $parentId);
494: }
495: }
496:
497: /**
498: * Returns a list of the id of all parent nodes of the given node
499: * Not using the nodes of hierarchical tree, but flat tree !!
500: *
501: * @param unknown_type $parentNodes
502: * @param unknown_type $stop_id
503: */
504: protected function _getParentNodes(&$parentNodes, $stop_id) {
505: $flat_tree = array();
506: $this->getFlatTree($this, $flat_tree);
507:
508: foreach ($flat_tree as $key => $value) {
509: if ($value->id != $stop_id) {
510: $parentNodes[] = $value->id;
511: } else {
512: break;
513: }
514: }
515: }
516:
517: /**
518: * getCollapsedList thinks if a node has no subnodes it is collapsed
519: * I don't think so
520: *
521: * @param array $list
522: */
523: public function getRealCollapsedList(&$list) {
524: $this->getCollapsedList($list);
525: $cleared_list = array();
526:
527: // remove all nodes that have no subnodes
528: foreach ($list as $key) {
529: $item = $this->getItemByID($key);
530: if (sizeof($item->subitems) > 0) {
531: $cleared_list[] = $key;
532: }
533: }
534: }
535:
536: /**
537: * Returns all items (as ID array) which are collapsed.
538: *
539: * @param array $list
540: * Contains the list with all collapsed items
541: */
542: public function getCollapsedList(&$list) {
543: if ($this->_collapsed == true) {
544: $list[] = $this->_id;
545: }
546:
547: foreach (array_keys($this->_subitems) as $key) {
548: $this->_subitems[$key]->getCollapsedList($list);
549: }
550: }
551:
552: /**
553: * Returns all items (as ID array) which are expanded.
554: *
555: * @param array $list
556: * Contains the list with all expanded items
557: */
558: public function getExpandedList(&$list) {
559: if ($this->_collapsed == false && !in_array($this->_id, $list)) {
560: $list[] = $this->_id;
561: }
562:
563: foreach (array_keys($this->_subitems) as $key) {
564: $this->_subitems[$key]->getExpandedList($list);
565: }
566: }
567: }
568: