![]() |
Xataface 2.0
Xataface Application Framework
|
00001 <?php 00002 class Dataface_Menu { 00003 private $nextID=0; 00004 private $items; 00005 private $urlMap; 00006 private $root; 00007 private $triggers = array(); 00008 public $dependencies = array(); 00009 public function __construct($rootPath='/'){ 00010 $this->items = array(); 00011 $this->urlMap = array(); 00012 $this->root = new Dataface_Menu_Item('__root__', $rootPath, null, $this); 00013 $this->addItem($this->root); 00014 00015 } 00016 00017 public function __destruct(){ 00018 unset($this->root); 00019 unset($this->items); 00020 unset($this->urlMap); 00021 } 00022 00023 public function newMenuItem($label, $url, $parent=null){ 00024 00025 // We set $reorganize variable to indicate whether we should 00026 // attempt to reorganize existing menu items according to 00027 // their URL path. This is advantageous with systems where 00028 // the menu corresponds to the URL paths. 00029 $reorganize = false; 00030 if ( !isset($parent) ){ 00031 // If no parent was specified we must be using the URL to find the parent. 00032 // so we should reorganize. 00033 $reorganize=true; 00034 $p = $this->getItemByURL($url); 00035 if ( $p['type'] & (Dataface_Menu_URLMap::$CHILD | Dataface_Menu_URLMap::$DESCENDENT) ){ 00036 $parent = $p['menuItem']; 00037 } else if ( $p['type'] & Dataface_Menu_URLMap::$SELF ){ 00038 $parent = $p['menuItem']->getParent(); 00039 } else { 00040 $parent = $this->getRoot(); 00041 } 00042 } 00043 $item = new Dataface_Menu_Item($label, $url, $parent, $this); 00044 $parent->addChild($item, $reorganize); 00045 return $item; 00046 } 00047 00048 public function getItems(){ 00049 return $this->items; 00050 } 00051 00052 public function getRoot(){ 00053 return $this->root; 00054 } 00055 00056 00057 00058 public function nextID(){ 00059 while ( isset($this->items[$this->nextID]) ) $this->nextID++; 00060 return $this->nextID; 00061 } 00062 00063 00064 public function addItem(Dataface_Menu_Item $item){ 00065 $menuID = $item->getId(); 00066 if ( !isset($menuID) ) $item->setId($this->nextID()); 00067 $this->items[$item->getId()] = $item; 00068 $url = $item->getURL(); 00069 if ( isset($url) ){ 00070 00071 $this->registerURL($url, $item); 00072 } 00073 } 00074 00075 public function registerURL($url, $menuItem, $type=null){ 00076 $mapper = new Dataface_Menu_URLMap($url, $menuItem, $type); 00077 $this->urlMap[$url] = $mapper; 00078 } 00079 00080 public function getItemById($id){ 00081 if ( !isset($this->items[$id]) ) throw new Exception("Attempt to access item that doesn't exists: ".$id); 00082 return $this->items[$id]; 00083 } 00084 00085 public function getItemByURL($url){ 00086 $parts = explode('/', $url); 00087 00088 $type = null; 00089 while ( count($parts) > 0 ){ 00090 $curr = implode('/', $parts); 00091 00092 if ( isset($this->urlMap[$curr]) ){ 00093 00094 $map = $this->urlMap[$curr]; 00095 $menuID = $map->getMenuID(); 00096 if ( isset($this->items[$menuID]) ){ 00097 00098 if ( !isset($type) ) $type = $map->getType(); 00099 else if ( $type == Dataface_Menu_URLMap::$SELF ){ 00100 $type = Dataface_Menu_URLMap::$CHILD; 00101 } else { 00102 $type = Dataface_Menu_URLMap::$DESCENDENT; 00103 } 00104 return array( 00105 'menuItem'=>$this->items[$menuID], 00106 'type'=>$type 00107 ); 00108 } 00109 else throw new Exception("Menu with id $menuID could not be found."); 00110 } else { 00111 array_pop($parts); 00112 if ( !isset($type) ) $type = Dataface_Menu_URLMap::$SELF; 00113 else if ( $type == Dataface_Menu_URLMap::$SELF ){ 00114 $type = Dataface_Menu_URLMap::$CHILD; 00115 } else { 00116 $type = Dataface_Menu_URLMap::$DESCENDENT; 00117 } 00118 } 00119 } 00120 00121 return null; 00122 00123 } 00124 00125 00126 00127 public function getItemPath($item, $type=null){ 00128 if ( !isset($item) ) return array(); 00129 if ( !isset($type) ) $type = Dataface_Menu_URLMap::$SELF; 00130 $path = array($item, $type); 00131 while (($curr = $item->getParent() ) !== null ){ 00132 array_unshift($path, $curr); 00133 unset($item); 00134 $item = $curr; 00135 unset($curr); 00136 } 00137 return $path; 00138 00139 } 00140 00141 00142 public function buildMenu($url){ 00143 00144 $menu = array(); 00145 $item = $this->getItemByURL($url); 00146 $pageTitle = ( 00147 $item['type'] == Dataface_Menu_URLMap::$SELF 00148 ) ? $item['menuItem']->getLabel() : ucwords(str_replace(array('-','_','+'), array(' ',' ',' '), basename($url))); 00149 00150 $path = $this->getItemPath($item['menuItem'], $item['type']); 00151 00152 $this->getRoot()->buildMenu($path, 0, $pageTitle, $menu); 00153 return $menu; 00154 } 00155 00156 public function toJSON(){ 00157 00158 $out = array( 00159 'items' => array(), 00160 'nextID' => $this->nextID, 00161 'urlMap'=>array(), 00162 'root' => $this->getRoot()->getId() 00163 ); 00164 00165 foreach ($this->items as $key=>$val){ 00166 $out['items'][$key] = $val->toJSON(false); 00167 } 00168 00169 foreach ($this->urlMap as $key=>$val ){ 00170 $out['urlMap'][$key] = $val->toJSON(false); 00171 } 00172 return json_encode($out); 00173 } 00174 00175 public function registerTrigger($name, $callback){ 00176 $this->triggers[$name][] = $callback; 00177 } 00178 00179 public function fireTrigger($name){ 00180 while ( isset($this->triggers[$name]) and count($this->triggers[$name])>0 ){ 00181 $callback = array_shift($this->triggers[$name]); 00182 if ( is_array($callback) ){ 00183 $obj =& $callback[0]; 00184 $method = $callback[1]; 00185 $obj->$method(); 00186 00187 } else { 00188 $callback(); 00189 } 00190 } 00191 } 00192 00193 public function loadJSON($in){ 00194 $in = json_decode($in); 00195 $this->items = array(); 00196 $this->urlMap = array(); 00197 $this->nextID = $in->nextID; 00198 00199 foreach ( $in->items as $key=>$val ){ 00200 $this->items[$val->menuID] = Dataface_Menu_Item::fromJSON($val, $this, false); 00201 } 00202 00203 00204 foreach ($in->urlMap as $key=>$val ){ 00205 $this->urlMap[$key] = Dataface_Menu_URLMap::fromJSON($val, $this, false); 00206 } 00207 00208 $this->root = $this->getItemById($in->root); 00209 00210 $this->fireTrigger('afterLoadJSON'); 00211 00212 00213 } 00214 00215 00216 public function toHtml($url, $id='main_nav'){ 00217 $m = $this->buildMenu($url); 00218 array_shift($m); //get rid of root node. 00219 $out = array(); 00220 $level = 0; 00221 foreach ($m as $i){ 00222 if ( $i['level'] > $level ){ 00223 while ( $level < $i['level'] ){ 00224 if ( $level>0 ) $out[] = '<li>'; 00225 $out[] = '<ul '.($level==0?'id="'.htmlspecialchars($id).'"':'').'>'; 00226 $level++; 00227 } 00228 } 00229 00230 00231 00232 else if ( $i['level'] < $level ){ 00233 while ( $level > $i['level'] ){ 00234 $out[] = '</ul>'; 00235 $level--; 00236 } 00237 } 00238 $class = array('menu-level-'.$i['level']); 00239 if ( $i['selected'] ) $class[] = 'menu-selected'; 00240 if ( $i['breadCrumb'] ) $class[] = 'menu-breadCrumb current'; 00241 if ( $i['parent'] ) $class[] = 'menu-parent'; 00242 $out[] = '<li class="'.implode(' ',$class).'"><a href="'.htmlspecialchars($i['url']).'">'.htmlspecialchars($i['label']).'</a></li>'; 00243 00244 } 00245 while ( $level > 0 ){ 00246 $out[] = '</ul>'; 00247 $level--; 00248 if ( $level > 0 ) $out[] = '</li>'; 00249 } 00250 00251 return implode("\n", $out); 00252 } 00253 00254 00255 } 00256 00257 00258 class Dataface_Menu_URLMap { 00259 private $url; 00260 private $menuID; 00261 public static $CHILD=1; 00262 public static $DESCENDENT=2; 00263 public static $SELF=4; 00264 private $type; 00265 00266 public function __construct($url, $menu, $type=null){ 00267 if ( is_int($menu) ) $this->menuID = $menu; 00268 else if ( is_a($menu, 'Dataface_Menu_Item') ){ 00269 $this->menuID = $menu->getId(); 00270 } else { 00271 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."); 00272 00273 } 00274 $this->url = $url; 00275 if ( !isset($type) ) $type= self::$SELF; 00276 $this->type = $type; 00277 } 00278 00279 public function toJSON($serialize=true){ 00280 $out = array( 00281 'url'=>$this->url, 00282 'menuID'=>$this->menuID, 00283 'type'=>$this->type 00284 ); 00285 if ( $serialize ) $out = json_encode($out); 00286 return $out; 00287 } 00288 00289 public static function fromJSON($in, $menu, $serialized=true){ 00290 if ( $serialized ) $in = json_decode($data); 00291 $map = new Dataface_Menu_URLMap($in->url, $in->menuID, $in->type); 00292 return $map; 00293 } 00294 00295 public function toArray(){ 00296 $out = array( 00297 'url'=>$this->url, 00298 'menuID'=>$this->menuID, 00299 'type'=>$this->type 00300 ); 00301 return $out; 00302 } 00303 00304 public function getMenuID(){ 00305 return $this->menuID; 00306 } 00307 00308 public function getURL(){ 00309 return $this->url; 00310 } 00311 00312 public function getType(){ 00313 return $this->type; 00314 } 00315 00316 00317 00318 00319 } 00320 00321 class Dataface_Menu_Item { 00322 private $menu; 00323 private $url; 00324 private $label; 00325 private $menuID; 00326 private $parent; 00327 private $children; 00328 private $order=0; 00329 private $sorted = false; 00330 00331 public static $SHOW_CHILDREN_WHEN_SELECTED=1; 00332 public static $SHOW_CHILDREN_WHEN_PARENT=2; 00333 public static $SHOW_CHILDREN_WHEN_ANCESTOR=4; 00334 public static $SHOW_CHILDREN_ALWAYS=8; 00335 00336 private $showChildrenSetting; 00337 00338 private $_loadChildIDs = array(); 00339 private $_loadParent = null; 00340 00341 public function __construct($label, $url, Dataface_Menu_Item $parent=null, Dataface_Menu $menu=null){ 00342 $this->children = array(); 00343 $this->label = $label; 00344 $this->parent = $parent; 00345 $this->url = $url; 00346 $this->menu = $menu; 00347 if ( !isset($this->menu) ) $this->menu = $this->parent->menu; 00348 $this->showChildrenSetting = 1; 00349 } 00350 00351 public function __destruct(){ 00352 unset($this->parent); 00353 unset($this->children); 00354 unset($this->menu); 00355 } 00356 00357 public function toJSON($serialize=true){ 00358 $out = array( 00359 'url'=>$this->url, 00360 'label'=>$this->label, 00361 'menuID'=>$this->menuID, 00362 'parent'=>null, 00363 'children'=>array(), 00364 'order'=>$this->order, 00365 'showChildrenSetting'=>$this->showChildrenSetting 00366 ); 00367 if ( isset($this->parent) ) $out['parent'] = $this->parent->getId(); 00368 foreach ($this->getChildren() as $key=>$val){ 00369 $out['children'][$key] = $val->getId(); 00370 } 00371 if ( $serialize ) $out = json_encode($out); 00372 return $out; 00373 } 00374 00375 public static function fromJSON($in, $menu, $serialized=true){ 00376 if ( $serialized ) $in = json_decode($in); 00377 00378 $item = new Dataface_Menu_Item($in->label, $in->url, null, $menu); 00379 $item->menuID = $in->menuID; 00380 $item->setOrder($in->order); 00381 $item->setShowChildrenSetting($in->showChildrenSetting); 00382 $item->setLoadData(array( 00383 'childIDs' => $in->children, 00384 'parent'=>$in->parent 00385 )); 00386 $menu->registerTrigger('afterLoadJSON', array(&$item, 'afterFromJSON')); 00387 return $item; 00388 } 00389 00390 00391 public function setLoadData($params=array()){ 00392 if ( isset($params['childIDs']) ) $this->_loadChildIDs = $params['childIDs']; 00393 if ( isset($params['parent']) ) $this->_loadParent = $params['parent']; 00394 return $this; 00395 } 00396 public function afterFromJSON(){ 00397 if ( isset($this->_loadParent) ){ 00398 $this->parent = $this->menu->getItemById($this->_loadParent); 00399 } 00400 foreach ( $this->_loadChildIDs as $childID ){ 00401 $this->children[] = $this->menu->getItemById($childID); 00402 } 00403 return $this; 00404 00405 } 00406 00407 public function addChild(Dataface_Menu_Item $menuItem, $reorganize=false){ 00408 // Now we should check our siblings to see if any of them should 00409 // be our children. This may happen because when we add 00410 // menu items if the immediate parent isn't part of the menu, then 00411 // the menu item will be added as a child to the nearest ancestor. 00412 if ( $reorganize ){ 00413 foreach ( $this->children as $key=>$sibling ){ 00414 if ( stristr( $sibling->getURL(), $menuItem->getURL() ) == $sibling->getURL() ){ 00415 unset($this->children[$key]); 00416 $menuItem->addChild($sibling); 00417 } 00418 } 00419 } 00420 00421 00422 $menuItem->parent = $this; 00423 $this->children[] = $menuItem; 00424 $this->sorted = false; 00425 00426 // Now we add to the main menu and assign menuID if not set already. 00427 if ( !isset($menuItem->menuID) ){ 00428 $menuItem->menuID = $this->menu->nextID(); 00429 $this->menu->addItem($menuItem); 00430 } 00431 00432 00433 00434 } 00435 00436 00437 00438 public function setOrder($order){ 00439 $oldOrder = $this->order; 00440 $this->order = $order; 00441 if ( $order != $oldOrder and isset($this->parent) ){ 00442 $this->parent->sorted = false; 00443 } 00444 return $this; 00445 } 00446 00447 00448 public function buildMenu($path, $level, $pageTitle, &$menu){ 00449 if ( !$this->sorted ){ 00450 $this->_sort(); 00451 } 00452 //echo "[$level :".count($path)." Building menu for {$this->getLabel()} : {$this->showChildrenSetting}]"; 00453 /*if ( $level == count($path)-1 and !is_object($path[$level])){ 00454 echo "bitch"; 00455 // We are simply at the point where we decide 00456 // whether we are an ancestor, a decendent, or something else 00457 //This next bit basically checks to see if we should display 00458 // the children. We display the children if: 00459 // 1. The url points directly to the menu item, and 00460 // a. The menu item is set to display children when selected. 00461 // or 00462 // b. The menu item is set to display children always 00463 // or 00464 // 2. The url points to the last menu item as its parent and 00465 // a. The menu item is set to display children when it is the parent of the selected page. 00466 // b. The menu item is set to display children when it is the ancestor of the selected page. 00467 // c. The menu item is set to display children always. 00468 // 00469 // or 00470 // 3. The url points to the last menu item as its ancestor and 00471 // a. The menu item is set to display children when it is an ancestor of the selected page. 00472 // b. The menu item is set to display children always. 00473 // 00474 00475 if ( 00476 ( 00477 $path[$level] == Dataface_Menu_URLMap::$SELF and ( 00478 $this->showChildrenSetting & ( 00479 self::$SHOW_CHILDREN_WHEN_SELECTED | 00480 self::$SHOW_CHILDREN_ALWAYS 00481 ) 00482 ) 00483 ) or ( 00484 $path[$level] == Dataface_Menu_URLMap::$CHILD and ( 00485 $this->showChildrenSetting & ( 00486 self::$SHOW_CHILDREN_WHEN_PARENT | 00487 self::$SHOW_CHILDREN_WHEN_ANCESTOR | 00488 self::$SHOW_CHILDREN_ALWAYS 00489 ) 00490 ) 00491 ) or ( 00492 $path[$level] == Dataface_Menu_URLMap::$DESCENDENT and ( 00493 $this->showChildrenSetting & ( 00494 self::$SHOW_CHILDREN_WHEN_ANCESTOR | 00495 self::$SHOW_CHILDREN_ALWAYS 00496 ) 00497 ) 00498 ) 00499 ){ 00500 00501 00502 foreach ($this->getChildren() as $child){ 00503 $menu[] = $child->selfToMenuStruct(array('level'=>$level)); 00504 if ( $child->showChildrenSetting & self::$SHOW_CHILDREN_ALWAYS ){ 00505 $child->buildMenu($path, $level+1, $pageTitle, $menu); 00506 } 00507 } 00508 00509 00510 // We don't do anything here 00511 00512 } 00513 00514 00515 00516 } else */if ( $level < count($path)-1 ){ 00517 //echo "there"; 00518 // Find out if we are showing children. 00519 $breadCrumb = ( $path[$level]->menuID == $this->menuID ); 00520 $parent = ( 00521 $breadCrumb and ( 00522 ( 00523 $level == count($path)-2 and ( 00524 $path[$level+1] == Dataface_Menu_URLMap::$CHILD 00525 ) 00526 ) or ( 00527 $level == count($path)-3 and ( 00528 $path[$level+2] == Dataface_Menu_URLMap::$SELF 00529 ) 00530 ) 00531 ) 00532 ); 00533 $selected = ( $breadCrumb and ($level == count($path)-2) and $path[$level+1] == Dataface_Menu_URLMap::$SELF ); 00534 $showChildren = ( 00535 ( 00536 $selected and ( 00537 $this->showChildrenSetting & ( 00538 self::$SHOW_CHILDREN_ALWAYS | 00539 self::$SHOW_CHILDREN_WHEN_SELECTED 00540 ) 00541 ) 00542 ) or ( 00543 $parent and ( 00544 $this->showChildrenSetting & ( 00545 self::$SHOW_CHILDREN_ALWAYS | 00546 self::$SHOW_CHILDREN_WHEN_PARENT | 00547 self::$SHOW_CHILDREN_WHEN_ANCESTOR 00548 ) 00549 ) 00550 ) or ( 00551 ($breadCrumb and !$parent and !$selected) and ( 00552 $this->showChildrenSetting & ( 00553 self::$SHOW_CHILDREN_ALWAYS | 00554 self::$SHOW_CHILDREN_WHEN_ANCESTOR 00555 ) 00556 ) 00557 ) 00558 ); 00559 //echo "[Show children $showChildren]"; 00560 00561 00562 $menu[] = $this->selfToMenuStruct(array( 00563 'selected'=>$selected, 00564 'parent'=>$parent, 00565 'breadCrumb'=>$breadCrumb, 00566 'level'=>$level 00567 )); 00568 00569 if ( $level == count($path) - 2 and $path[$level+1] != Dataface_Menu_URLMap::$SELF and $path[$level]->getId()==$this->getId() ){ 00570 $item = new Dataface_Menu_Item($pageTitle, '#', $this); 00571 $menu[] = $item->selfToMenuStruct(array( 00572 'selected'=>true, 00573 'level'=>$level+1 00574 )); 00575 } 00576 foreach ($this->getChildren() as $child){ 00577 if ( $showChildren or $path[$level+1]->menuID == $child->menuID ){ 00578 00579 $child->buildMenu($path, $level+1, $pageTitle, $menu); 00580 } else { 00581 00582 } 00583 unset($child); 00584 } 00585 00586 00587 } else { 00588 // We are beyond the throws of the path... we're just finishing up. 00589 //echo "Here"; 00590 $menu[] = $this->selfToMenuStruct(array('level'=>$level)); 00591 if ( $this->showChildrenSetting & self::$SHOW_CHILDREN_ALWAYS ){ 00592 foreach ($this->children as $child ){ 00593 $child->buildMenu($path, $level+1, $pageTitle, $menu); 00594 } 00595 } 00596 00597 00598 } 00599 00600 return $this; 00601 00602 } 00603 00604 00605 00606 00607 public function selfToMenuStruct($params=array()){ 00608 $defaults = array( 00609 'menuID'=>$this->menuID, 00610 'url'=>$this->url, 00611 'label'=>$this->label, 00612 'selected'=>false, 00613 'parent'=>false, 00614 'breadCrumb'=>false, 00615 'level'=>0 00616 ); 00617 00618 return array_merge($defaults, $params); 00619 } 00620 00621 private function _sort(){ 00622 uasort($this->children, array(&$this, '_cmp')); 00623 $this->sorted = true; 00624 return $this; 00625 } 00626 00627 private function _cmp($a, $b){ 00628 if ( $a->order == $b->order ) return 0; 00629 else return ($a->order<$b->order)?-1:1; 00630 } 00631 00632 00633 00634 public function getId(){ return $this->menuID;} 00635 public function getLabel(){ return $this->label;} 00636 public function getURL(){ return $this->url;} 00637 public function getParent(){ return $this->parent;} 00638 public function setId($id){ $this->menuID = $id;} 00639 public function setShowChildrenSetting($setting){ 00640 $this->showChildrenSetting = $setting; 00641 return $this; 00642 } 00643 public function getShowChildrenSetting(){ return $this->showChildrenSetting;} 00644 00645 public function getChildren(){ 00646 if ( !$this->sorted ) $this->_sort(); 00647 return $this->children; 00648 } 00649 00650 00651 } 00652 00653 00654