Xataface 2.0
Xataface Application Framework
Dataface/Menu.php
Go to the documentation of this file.
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 
 All Data Structures Namespaces Files Functions Variables Enumerations