Xataface  2.0alpha2
Xataface Application Framework
 All Data Structures Namespaces Files Functions Variables Groups Pages
Menu.php
Go to the documentation of this file.
1 <?php
2 class Dataface_Menu {
3  private $nextID=0;
4  private $items;
5  private $urlMap;
6  private $root;
7  private $triggers = array();
8  public $dependencies = array();
9  public function __construct($rootPath='/'){
10  $this->items = array();
11  $this->urlMap = array();
12  $this->root = new Dataface_Menu_Item('__root__', $rootPath, null, $this);
13  $this->addItem($this->root);
14 
15  }
16 
17  public function __destruct(){
18  unset($this->root);
19  unset($this->items);
20  unset($this->urlMap);
21  }
22 
23  public function newMenuItem($label, $url, $parent=null){
24 
25  // We set $reorganize variable to indicate whether we should
26  // attempt to reorganize existing menu items according to
27  // their URL path. This is advantageous with systems where
28  // the menu corresponds to the URL paths.
29  $reorganize = false;
30  if ( !isset($parent) ){
31  // If no parent was specified we must be using the URL to find the parent.
32  // so we should reorganize.
33  $reorganize=true;
34  $p = $this->getItemByURL($url);
36  $parent = $p['menuItem'];
37  } else if ( $p['type'] & Dataface_Menu_URLMap::$SELF ){
38  $parent = $p['menuItem']->getParent();
39  } else {
40  $parent = $this->getRoot();
41  }
42  }
43  $item = new Dataface_Menu_Item($label, $url, $parent, $this);
44  $parent->addChild($item, $reorganize);
45  return $item;
46  }
47 
48  public function getItems(){
49  return $this->items;
50  }
51 
52  public function getRoot(){
53  return $this->root;
54  }
55 
56 
57 
58  public function nextID(){
59  while ( isset($this->items[$this->nextID]) ) $this->nextID++;
60  return $this->nextID;
61  }
62 
63 
64  public function addItem(Dataface_Menu_Item $item){
65  $menuID = $item->getId();
66  if ( !isset($menuID) ) $item->setId($this->nextID());
67  $this->items[$item->getId()] = $item;
68  $url = $item->getURL();
69  if ( isset($url) ){
70 
71  $this->registerURL($url, $item);
72  }
73  }
74 
75  public function registerURL($url, $menuItem, $type=null){
76  $mapper = new Dataface_Menu_URLMap($url, $menuItem, $type);
77  $this->urlMap[$url] = $mapper;
78  }
79 
80  public function getItemById($id){
81  if ( !isset($this->items[$id]) ) throw new Exception("Attempt to access item that doesn't exists: ".$id);
82  return $this->items[$id];
83  }
84 
85  public function getItemByURL($url){
86  $parts = explode('/', $url);
87 
88  $type = null;
89  while ( count($parts) > 0 ){
90  $curr = implode('/', $parts);
91 
92  if ( isset($this->urlMap[$curr]) ){
93 
94  $map = $this->urlMap[$curr];
95  $menuID = $map->getMenuID();
96  if ( isset($this->items[$menuID]) ){
97 
98  if ( !isset($type) ) $type = $map->getType();
99  else if ( $type == Dataface_Menu_URLMap::$SELF ){
101  } else {
103  }
104  return array(
105  'menuItem'=>$this->items[$menuID],
106  'type'=>$type
107  );
108  }
109  else throw new Exception("Menu with id $menuID could not be found.");
110  } else {
111  array_pop($parts);
112  if ( !isset($type) ) $type = Dataface_Menu_URLMap::$SELF;
113  else if ( $type == Dataface_Menu_URLMap::$SELF ){
115  } else {
117  }
118  }
119  }
120 
121  return null;
122 
123  }
124 
125 
126 
127  public function getItemPath($item, $type=null){
128  if ( !isset($item) ) return array();
129  if ( !isset($type) ) $type = Dataface_Menu_URLMap::$SELF;
130  $path = array($item, $type);
131  while (($curr = $item->getParent() ) !== null ){
132  array_unshift($path, $curr);
133  unset($item);
134  $item = $curr;
135  unset($curr);
136  }
137  return $path;
138 
139  }
140 
141 
142  public function buildMenu($url){
143 
144  $menu = array();
145  $item = $this->getItemByURL($url);
146  $pageTitle = (
147  $item['type'] == Dataface_Menu_URLMap::$SELF
148  ) ? $item['menuItem']->getLabel() : ucwords(str_replace(array('-','_','+'), array(' ',' ',' '), basename($url)));
149 
150  $path = $this->getItemPath($item['menuItem'], $item['type']);
151 
152  $this->getRoot()->buildMenu($path, 0, $pageTitle, $menu);
153  return $menu;
154  }
155 
156  public function toJSON(){
157 
158  $out = array(
159  'items' => array(),
160  'nextID' => $this->nextID,
161  'urlMap'=>array(),
162  'root' => $this->getRoot()->getId()
163  );
164 
165  foreach ($this->items as $key=>$val){
166  $out['items'][$key] = $val->toJSON(false);
167  }
168 
169  foreach ($this->urlMap as $key=>$val ){
170  $out['urlMap'][$key] = $val->toJSON(false);
171  }
172  return json_encode($out);
173  }
174 
175  public function registerTrigger($name, $callback){
176  $this->triggers[$name][] = $callback;
177  }
178 
179  public function fireTrigger($name){
180  while ( isset($this->triggers[$name]) and count($this->triggers[$name])>0 ){
181  $callback = array_shift($this->triggers[$name]);
182  if ( is_array($callback) ){
183  $obj =& $callback[0];
184  $method = $callback[1];
185  $obj->$method();
186 
187  } else {
188  $callback();
189  }
190  }
191  }
192 
193  public function loadJSON($in){
194  $in = json_decode($in);
195  $this->items = array();
196  $this->urlMap = array();
197  $this->nextID = $in->nextID;
198 
199  foreach ( $in->items as $key=>$val ){
200  $this->items[$val->menuID] = Dataface_Menu_Item::fromJSON($val, $this, false);
201  }
202 
203 
204  foreach ($in->urlMap as $key=>$val ){
205  $this->urlMap[$key] = Dataface_Menu_URLMap::fromJSON($val, $this, false);
206  }
207 
208  $this->root = $this->getItemById($in->root);
209 
210  $this->fireTrigger('afterLoadJSON');
211 
212 
213  }
214 
215 
216  public function toHtml($url, $id='main_nav'){
217  $m = $this->buildMenu($url);
218  array_shift($m); //get rid of root node.
219  $out = array();
220  $level = 0;
221  foreach ($m as $i){
222  if ( $i['level'] > $level ){
223  while ( $level < $i['level'] ){
224  if ( $level>0 ) $out[] = '<li>';
225  $out[] = '<ul '.($level==0?'id="'.htmlspecialchars($id).'"':'').'>';
226  $level++;
227  }
228  }
229 
230 
231 
232  else if ( $i['level'] < $level ){
233  while ( $level > $i['level'] ){
234  $out[] = '</ul>';
235  $level--;
236  }
237  }
238  $class = array('menu-level-'.$i['level']);
239  if ( $i['selected'] ) $class[] = 'menu-selected';
240  if ( $i['breadCrumb'] ) $class[] = 'menu-breadCrumb current';
241  if ( $i['parent'] ) $class[] = 'menu-parent';
242  $out[] = '<li class="'.implode(' ',$class).'"><a href="'.htmlspecialchars($i['url']).'">'.htmlspecialchars($i['label']).'</a></li>';
243 
244  }
245  while ( $level > 0 ){
246  $out[] = '</ul>';
247  $level--;
248  if ( $level > 0 ) $out[] = '</li>';
249  }
250 
251  return implode("\n", $out);
252  }
253 
254 
255 }
256 
257 
259  private $url;
260  private $menuID;
261  public static $CHILD=1;
262  public static $DESCENDENT=2;
263  public static $SELF=4;
264  private $type;
265 
266  public function __construct($url, $menu, $type=null){
267  if ( is_int($menu) ) $this->menuID = $menu;
268  else if ( is_a($menu, 'Dataface_Menu_Item') ){
269  $this->menuID = $menu->getId();
270  } else {
271  throw Exception("2nd parameter of URLMap constructor takes either an integer ID or a Dataface_Menu_Item class but received a ".get_class($menu)." object instead.");
272 
273  }
274  $this->url = $url;
275  if ( !isset($type) ) $type= self::$SELF;
276  $this->type = $type;
277  }
278 
279  public function toJSON($serialize=true){
280  $out = array(
281  'url'=>$this->url,
282  'menuID'=>$this->menuID,
283  'type'=>$this->type
284  );
285  if ( $serialize ) $out = json_encode($out);
286  return $out;
287  }
288 
289  public static function fromJSON($in, $menu, $serialized=true){
290  if ( $serialized ) $in = json_decode($data);
291  $map = new Dataface_Menu_URLMap($in->url, $in->menuID, $in->type);
292  return $map;
293  }
294 
295  public function toArray(){
296  $out = array(
297  'url'=>$this->url,
298  'menuID'=>$this->menuID,
299  'type'=>$this->type
300  );
301  return $out;
302  }
303 
304  public function getMenuID(){
305  return $this->menuID;
306  }
307 
308  public function getURL(){
309  return $this->url;
310  }
311 
312  public function getType(){
313  return $this->type;
314  }
315 
316 
317 
318 
319 }
320 
322  private $menu;
323  private $url;
324  private $label;
325  private $menuID;
326  private $parent;
327  private $children;
328  private $order=0;
329  private $sorted = false;
330 
332  public static $SHOW_CHILDREN_WHEN_PARENT=2;
334  public static $SHOW_CHILDREN_ALWAYS=8;
335 
336  private $showChildrenSetting;
337 
338  private $_loadChildIDs = array();
339  private $_loadParent = null;
340 
341  public function __construct($label, $url, Dataface_Menu_Item $parent=null, Dataface_Menu $menu=null){
342  $this->children = array();
343  $this->label = $label;
344  $this->parent = $parent;
345  $this->url = $url;
346  $this->menu = $menu;
347  if ( !isset($this->menu) ) $this->menu = $this->parent->menu;
348  $this->showChildrenSetting = 1;
349  }
350 
351  public function __destruct(){
352  unset($this->parent);
353  unset($this->children);
354  unset($this->menu);
355  }
356 
357  public function toJSON($serialize=true){
358  $out = array(
359  'url'=>$this->url,
360  'label'=>$this->label,
361  'menuID'=>$this->menuID,
362  'parent'=>null,
363  'children'=>array(),
364  'order'=>$this->order,
365  'showChildrenSetting'=>$this->showChildrenSetting
366  );
367  if ( isset($this->parent) ) $out['parent'] = $this->parent->getId();
368  foreach ($this->getChildren() as $key=>$val){
369  $out['children'][$key] = $val->getId();
370  }
371  if ( $serialize ) $out = json_encode($out);
372  return $out;
373  }
374 
375  public static function fromJSON($in, $menu, $serialized=true){
376  if ( $serialized ) $in = json_decode($in);
377 
378  $item = new Dataface_Menu_Item($in->label, $in->url, null, $menu);
379  $item->menuID = $in->menuID;
380  $item->setOrder($in->order);
381  $item->setShowChildrenSetting($in->showChildrenSetting);
382  $item->setLoadData(array(
383  'childIDs' => $in->children,
384  'parent'=>$in->parent
385  ));
386  $menu->registerTrigger('afterLoadJSON', array(&$item, 'afterFromJSON'));
387  return $item;
388  }
389 
390 
391  public function setLoadData($params=array()){
392  if ( isset($params['childIDs']) ) $this->_loadChildIDs = $params['childIDs'];
393  if ( isset($params['parent']) ) $this->_loadParent = $params['parent'];
394  return $this;
395  }
396  public function afterFromJSON(){
397  if ( isset($this->_loadParent) ){
398  $this->parent = $this->menu->getItemById($this->_loadParent);
399  }
400  foreach ( $this->_loadChildIDs as $childID ){
401  $this->children[] = $this->menu->getItemById($childID);
402  }
403  return $this;
404 
405  }
406 
407  public function addChild(Dataface_Menu_Item $menuItem, $reorganize=false){
408  // Now we should check our siblings to see if any of them should
409  // be our children. This may happen because when we add
410  // menu items if the immediate parent isn't part of the menu, then
411  // the menu item will be added as a child to the nearest ancestor.
412  if ( $reorganize ){
413  foreach ( $this->children as $key=>$sibling ){
414  if ( stristr( $sibling->getURL(), $menuItem->getURL() ) == $sibling->getURL() ){
415  unset($this->children[$key]);
416  $menuItem->addChild($sibling);
417  }
418  }
419  }
420 
421 
422  $menuItem->parent = $this;
423  $this->children[] = $menuItem;
424  $this->sorted = false;
425 
426  // Now we add to the main menu and assign menuID if not set already.
427  if ( !isset($menuItem->menuID) ){
428  $menuItem->menuID = $this->menu->nextID();
429  $this->menu->addItem($menuItem);
430  }
431 
432 
433 
434  }
435 
436 
437 
438  public function setOrder($order){
439  $oldOrder = $this->order;
440  $this->order = $order;
441  if ( $order != $oldOrder and isset($this->parent) ){
442  $this->parent->sorted = false;
443  }
444  return $this;
445  }
446 
447 
448  public function buildMenu($path, $level, $pageTitle, &$menu){
449  if ( !$this->sorted ){
450  $this->_sort();
451  }
452  //echo "[$level :".count($path)." Building menu for {$this->getLabel()} : {$this->showChildrenSetting}]";
453  /*if ( $level == count($path)-1 and !is_object($path[$level])){
454  echo "bitch";
455  // We are simply at the point where we decide
456  // whether we are an ancestor, a decendent, or something else
457  //This next bit basically checks to see if we should display
458  // the children. We display the children if:
459  // 1. The url points directly to the menu item, and
460  // a. The menu item is set to display children when selected.
461  // or
462  // b. The menu item is set to display children always
463  // or
464  // 2. The url points to the last menu item as its parent and
465  // a. The menu item is set to display children when it is the parent of the selected page.
466  // b. The menu item is set to display children when it is the ancestor of the selected page.
467  // c. The menu item is set to display children always.
468  //
469  // or
470  // 3. The url points to the last menu item as its ancestor and
471  // a. The menu item is set to display children when it is an ancestor of the selected page.
472  // b. The menu item is set to display children always.
473  //
474 
475  if (
476  (
477  $path[$level] == Dataface_Menu_URLMap::$SELF and (
478  $this->showChildrenSetting & (
479  self::$SHOW_CHILDREN_WHEN_SELECTED |
480  self::$SHOW_CHILDREN_ALWAYS
481  )
482  )
483  ) or (
484  $path[$level] == Dataface_Menu_URLMap::$CHILD and (
485  $this->showChildrenSetting & (
486  self::$SHOW_CHILDREN_WHEN_PARENT |
487  self::$SHOW_CHILDREN_WHEN_ANCESTOR |
488  self::$SHOW_CHILDREN_ALWAYS
489  )
490  )
491  ) or (
492  $path[$level] == Dataface_Menu_URLMap::$DESCENDENT and (
493  $this->showChildrenSetting & (
494  self::$SHOW_CHILDREN_WHEN_ANCESTOR |
495  self::$SHOW_CHILDREN_ALWAYS
496  )
497  )
498  )
499  ){
500 
501 
502  foreach ($this->getChildren() as $child){
503  $menu[] = $child->selfToMenuStruct(array('level'=>$level));
504  if ( $child->showChildrenSetting & self::$SHOW_CHILDREN_ALWAYS ){
505  $child->buildMenu($path, $level+1, $pageTitle, $menu);
506  }
507  }
508 
509 
510  // We don't do anything here
511 
512  }
513 
514 
515 
516  } else */if ( $level < count($path)-1 ){
517  //echo "there";
518  // Find out if we are showing children.
519  $breadCrumb = ( $path[$level]->menuID == $this->menuID );
520  $parent = (
521  $breadCrumb and (
522  (
523  $level == count($path)-2 and (
525  )
526  ) or (
527  $level == count($path)-3 and (
529  )
530  )
531  )
532  );
533  $selected = ( $breadCrumb and ($level == count($path)-2) and $path[$level+1] == Dataface_Menu_URLMap::$SELF );
534  $showChildren = (
535  (
536  $selected and (
537  $this->showChildrenSetting & (
538  self::$SHOW_CHILDREN_ALWAYS |
539  self::$SHOW_CHILDREN_WHEN_SELECTED
540  )
541  )
542  ) or (
543  $parent and (
544  $this->showChildrenSetting & (
545  self::$SHOW_CHILDREN_ALWAYS |
546  self::$SHOW_CHILDREN_WHEN_PARENT |
547  self::$SHOW_CHILDREN_WHEN_ANCESTOR
548  )
549  )
550  ) or (
551  ($breadCrumb and !$parent and !$selected) and (
552  $this->showChildrenSetting & (
553  self::$SHOW_CHILDREN_ALWAYS |
554  self::$SHOW_CHILDREN_WHEN_ANCESTOR
555  )
556  )
557  )
558  );
559  //echo "[Show children $showChildren]";
560 
561 
562  $menu[] = $this->selfToMenuStruct(array(
563  'selected'=>$selected,
564  'parent'=>$parent,
565  'breadCrumb'=>$breadCrumb,
566  'level'=>$level
567  ));
568 
569  if ( $level == count($path) - 2 and $path[$level+1] != Dataface_Menu_URLMap::$SELF and $path[$level]->getId()==$this->getId() ){
570  $item = new Dataface_Menu_Item($pageTitle, '#', $this);
571  $menu[] = $item->selfToMenuStruct(array(
572  'selected'=>true,
573  'level'=>$level+1
574  ));
575  }
576  foreach ($this->getChildren() as $child){
577  if ( $showChildren or $path[$level+1]->menuID == $child->menuID ){
578 
579  $child->buildMenu($path, $level+1, $pageTitle, $menu);
580  } else {
581 
582  }
583  unset($child);
584  }
585 
586 
587  } else {
588  // We are beyond the throws of the path... we're just finishing up.
589  //echo "Here";
590  $menu[] = $this->selfToMenuStruct(array('level'=>$level));
591  if ( $this->showChildrenSetting & self::$SHOW_CHILDREN_ALWAYS ){
592  foreach ($this->children as $child ){
593  $child->buildMenu($path, $level+1, $pageTitle, $menu);
594  }
595  }
596 
597 
598  }
599 
600  return $this;
601 
602  }
603 
604 
605 
606 
607  public function selfToMenuStruct($params=array()){
608  $defaults = array(
609  'menuID'=>$this->menuID,
610  'url'=>$this->url,
611  'label'=>$this->label,
612  'selected'=>false,
613  'parent'=>false,
614  'breadCrumb'=>false,
615  'level'=>0
616  );
617 
618  return array_merge($defaults, $params);
619  }
620 
621  private function _sort(){
622  uasort($this->children, array(&$this, '_cmp'));
623  $this->sorted = true;
624  return $this;
625  }
626 
627  private function _cmp($a, $b){
628  if ( $a->order == $b->order ) return 0;
629  else return ($a->order<$b->order)?-1:1;
630  }
631 
632 
633 
634  public function getId(){ return $this->menuID;}
635  public function getLabel(){ return $this->label;}
636  public function getURL(){ return $this->url;}
637  public function getParent(){ return $this->parent;}
638  public function setId($id){ $this->menuID = $id;}
639  public function setShowChildrenSetting($setting){
640  $this->showChildrenSetting = $setting;
641  return $this;
642  }
643  public function getShowChildrenSetting(){ return $this->showChildrenSetting;}
644 
645  public function getChildren(){
646  if ( !$this->sorted ) $this->_sort();
647  return $this->children;
648  }
649 
650 
651 }
652 
653 
654