web/lib/Zend/Navigation/Container.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category  Zend
       
    16  * @package   Zend_Navigation
       
    17  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    18  * @license   http://framework.zend.com/license/new-bsd     New BSD License
       
    19  * @version    $Id: Container.php 20096 2010-01-06 02:05:09Z bkarwin $
       
    20  */
       
    21 
       
    22 /**
       
    23  * Zend_Navigation_Container
       
    24  *
       
    25  * Container class for Zend_Navigation_Page classes.
       
    26  *
       
    27  * @category  Zend
       
    28  * @package   Zend_Navigation
       
    29  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    30  * @license   http://framework.zend.com/license/new-bsd     New BSD License
       
    31  */
       
    32 abstract class Zend_Navigation_Container implements RecursiveIterator, Countable
       
    33 {
       
    34     /**
       
    35      * Contains sub pages
       
    36      *
       
    37      * @var array
       
    38      */
       
    39     protected $_pages = array();
       
    40 
       
    41     /**
       
    42      * An index that contains the order in which to iterate pages
       
    43      *
       
    44      * @var array
       
    45      */
       
    46     protected $_index = array();
       
    47 
       
    48     /**
       
    49      * Whether index is dirty and needs to be re-arranged
       
    50      *
       
    51      * @var bool
       
    52      */
       
    53     protected $_dirtyIndex = false;
       
    54 
       
    55     // Internal methods:
       
    56 
       
    57     /**
       
    58      * Sorts the page index according to page order
       
    59      *
       
    60      * @return void
       
    61      */
       
    62     protected function _sort()
       
    63     {
       
    64         if ($this->_dirtyIndex) {
       
    65             $newIndex = array();
       
    66             $index = 0;
       
    67 
       
    68             foreach ($this->_pages as $hash => $page) {
       
    69                 $order = $page->getOrder();
       
    70                 if ($order === null) {
       
    71                     $newIndex[$hash] = $index;
       
    72                     $index++;
       
    73                 } else {
       
    74                     $newIndex[$hash] = $order;
       
    75                 }
       
    76             }
       
    77 
       
    78             asort($newIndex);
       
    79             $this->_index = $newIndex;
       
    80             $this->_dirtyIndex = false;
       
    81         }
       
    82     }
       
    83 
       
    84     // Public methods:
       
    85 
       
    86     /**
       
    87      * Notifies container that the order of pages are updated
       
    88      *
       
    89      * @return void
       
    90      */
       
    91     public function notifyOrderUpdated()
       
    92     {
       
    93         $this->_dirtyIndex = true;
       
    94     }
       
    95 
       
    96     /**
       
    97      * Adds a page to the container
       
    98      *
       
    99      * This method will inject the container as the given page's parent by
       
   100      * calling {@link Zend_Navigation_Page::setParent()}.
       
   101      *
       
   102      * @param  Zend_Navigation_Page|array|Zend_Config $page  page to add
       
   103      * @return Zend_Navigation_Container                     fluent interface,
       
   104      *                                                       returns self
       
   105      * @throws Zend_Navigation_Exception                     if page is invalid
       
   106      */
       
   107     public function addPage($page)
       
   108     {
       
   109         if ($page === $this) {
       
   110             require_once 'Zend/Navigation/Exception.php';
       
   111             throw new Zend_Navigation_Exception(
       
   112                 'A page cannot have itself as a parent');
       
   113         }
       
   114 
       
   115         if (is_array($page) || $page instanceof Zend_Config) {
       
   116             require_once 'Zend/Navigation/Page.php';
       
   117             $page = Zend_Navigation_Page::factory($page);
       
   118         } elseif (!$page instanceof Zend_Navigation_Page) {
       
   119             require_once 'Zend/Navigation/Exception.php';
       
   120             throw new Zend_Navigation_Exception(
       
   121                     'Invalid argument: $page must be an instance of ' .
       
   122                     'Zend_Navigation_Page or Zend_Config, or an array');
       
   123         }
       
   124 
       
   125         $hash = $page->hashCode();
       
   126 
       
   127         if (array_key_exists($hash, $this->_index)) {
       
   128             // page is already in container
       
   129             return $this;
       
   130         }
       
   131 
       
   132         // adds page to container and sets dirty flag
       
   133         $this->_pages[$hash] = $page;
       
   134         $this->_index[$hash] = $page->getOrder();
       
   135         $this->_dirtyIndex = true;
       
   136 
       
   137         // inject self as page parent
       
   138         $page->setParent($this);
       
   139 
       
   140         return $this;
       
   141     }
       
   142 
       
   143     /**
       
   144      * Adds several pages at once
       
   145      *
       
   146      * @param  array|Zend_Config $pages   pages to add
       
   147      * @return Zend_Navigation_Container  fluent interface, returns self
       
   148      * @throws Zend_Navigation_Exception  if $pages is not array or Zend_Config
       
   149      */
       
   150     public function addPages($pages)
       
   151     {
       
   152         if ($pages instanceof Zend_Config) {
       
   153             $pages = $pages->toArray();
       
   154         }
       
   155 
       
   156         if (!is_array($pages)) {
       
   157             require_once 'Zend/Navigation/Exception.php';
       
   158             throw new Zend_Navigation_Exception(
       
   159                     'Invalid argument: $pages must be an array or an ' .
       
   160                     'instance of Zend_Config');
       
   161         }
       
   162 
       
   163         foreach ($pages as $page) {
       
   164             $this->addPage($page);
       
   165         }
       
   166 
       
   167         return $this;
       
   168     }
       
   169 
       
   170     /**
       
   171      * Sets pages this container should have, removing existing pages
       
   172      *
       
   173      * @param  array $pages               pages to set
       
   174      * @return Zend_Navigation_Container  fluent interface, returns self
       
   175      */
       
   176     public function setPages(array $pages)
       
   177     {
       
   178         $this->removePages();
       
   179         return $this->addPages($pages);
       
   180     }
       
   181 
       
   182     /**
       
   183      * Returns pages in the container
       
   184      *
       
   185      * @return array  array of Zend_Navigation_Page instances
       
   186      */
       
   187     public function getPages()
       
   188     {
       
   189         return $this->_pages;
       
   190     }
       
   191 
       
   192     /**
       
   193      * Removes the given page from the container
       
   194      *
       
   195      * @param  Zend_Navigation_Page|int $page  page to remove, either a page
       
   196      *                                         instance or a specific page order
       
   197      * @return bool                            whether the removal was
       
   198      *                                         successful
       
   199      */
       
   200     public function removePage($page)
       
   201     {
       
   202         if ($page instanceof Zend_Navigation_Page) {
       
   203             $hash = $page->hashCode();
       
   204         } elseif (is_int($page)) {
       
   205             $this->_sort();
       
   206             if (!$hash = array_search($page, $this->_index)) {
       
   207                 return false;
       
   208             }
       
   209         } else {
       
   210             return false;
       
   211         }
       
   212 
       
   213         if (isset($this->_pages[$hash])) {
       
   214             unset($this->_pages[$hash]);
       
   215             unset($this->_index[$hash]);
       
   216             $this->_dirtyIndex = true;
       
   217             return true;
       
   218         }
       
   219 
       
   220         return false;
       
   221     }
       
   222 
       
   223     /**
       
   224      * Removes all pages in container
       
   225      *
       
   226      * @return Zend_Navigation_Container  fluent interface, returns self
       
   227      */
       
   228     public function removePages()
       
   229     {
       
   230         $this->_pages = array();
       
   231         $this->_index = array();
       
   232         return $this;
       
   233     }
       
   234 
       
   235     /**
       
   236      * Checks if the container has the given page
       
   237      *
       
   238      * @param  Zend_Navigation_Page $page       page to look for
       
   239      * @param  bool                 $recursive  [optional] whether to search
       
   240      *                                          recursively. Default is false.
       
   241      * @return bool                             whether page is in container
       
   242      */
       
   243     public function hasPage(Zend_Navigation_Page $page, $recursive = false)
       
   244     {
       
   245         if (array_key_exists($page->hashCode(), $this->_index)) {
       
   246             return true;
       
   247         } elseif ($recursive) {
       
   248             foreach ($this->_pages as $childPage) {
       
   249                 if ($childPage->hasPage($page, true)) {
       
   250                     return true;
       
   251                 }
       
   252             }
       
   253         }
       
   254 
       
   255         return false;
       
   256     }
       
   257 
       
   258     /**
       
   259      * Returns true if container contains any pages
       
   260      *
       
   261      * @return bool  whether container has any pages
       
   262      */
       
   263     public function hasPages()
       
   264     {
       
   265         return count($this->_index) > 0;
       
   266     }
       
   267 
       
   268     /**
       
   269      * Returns a child page matching $property == $value, or null if not found
       
   270      *
       
   271      * @param  string $property           name of property to match against
       
   272      * @param  mixed  $value              value to match property against
       
   273      * @return Zend_Navigation_Page|null  matching page or null
       
   274      */
       
   275     public function findOneBy($property, $value)
       
   276     {
       
   277         $iterator = new RecursiveIteratorIterator($this,
       
   278                             RecursiveIteratorIterator::SELF_FIRST);
       
   279 
       
   280         foreach ($iterator as $page) {
       
   281             if ($page->get($property) == $value) {
       
   282                 return $page;
       
   283             }
       
   284         }
       
   285 
       
   286         return null;
       
   287     }
       
   288 
       
   289     /**
       
   290      * Returns all child pages matching $property == $value, or an empty array
       
   291      * if no pages are found
       
   292      *
       
   293      * @param  string $property  name of property to match against
       
   294      * @param  mixed  $value     value to match property against
       
   295      * @return array             array containing only Zend_Navigation_Page
       
   296      *                           instances
       
   297      */
       
   298     public function findAllBy($property, $value)
       
   299     {
       
   300         $found = array();
       
   301 
       
   302         $iterator = new RecursiveIteratorIterator($this,
       
   303                             RecursiveIteratorIterator::SELF_FIRST);
       
   304 
       
   305         foreach ($iterator as $page) {
       
   306             if ($page->get($property) == $value) {
       
   307                 $found[] = $page;
       
   308             }
       
   309         }
       
   310 
       
   311         return $found;
       
   312     }
       
   313 
       
   314     /**
       
   315      * Returns page(s) matching $property == $value
       
   316      *
       
   317      * @param  string $property  name of property to match against
       
   318      * @param  mixed  $value     value to match property against
       
   319      * @param  bool   $all       [optional] whether an array of all matching
       
   320      *                           pages should be returned, or only the first.
       
   321      *                           If true, an array will be returned, even if not
       
   322      *                           matching pages are found. If false, null will
       
   323      *                           be returned if no matching page is found.
       
   324      *                           Default is false.
       
   325      * @return Zend_Navigation_Page|null  matching page or null
       
   326      */
       
   327     public function findBy($property, $value, $all = false)
       
   328     {
       
   329         if ($all) {
       
   330             return $this->findAllBy($property, $value);
       
   331         } else {
       
   332             return $this->findOneBy($property, $value);
       
   333         }
       
   334     }
       
   335 
       
   336     /**
       
   337      * Magic overload: Proxy calls to finder methods
       
   338      *
       
   339      * Examples of finder calls:
       
   340      * <code>
       
   341      * // METHOD                    // SAME AS
       
   342      * $nav->findByLabel('foo');    // $nav->findOneBy('label', 'foo');
       
   343      * $nav->findOneByLabel('foo'); // $nav->findOneBy('label', 'foo');
       
   344      * $nav->findAllByClass('foo'); // $nav->findAllBy('class', 'foo');
       
   345      * </code>
       
   346      *
       
   347      * @param  string $method             method name
       
   348      * @param  array  $arguments          method arguments
       
   349      * @throws Zend_Navigation_Exception  if method does not exist
       
   350      */
       
   351     public function __call($method, $arguments)
       
   352     {
       
   353         if (@preg_match('/(find(?:One|All)?By)(.+)/', $method, $match)) {
       
   354             return $this->{$match[1]}($match[2], $arguments[0]);
       
   355         }
       
   356 
       
   357         require_once 'Zend/Navigation/Exception.php';
       
   358         throw new Zend_Navigation_Exception(sprintf(
       
   359                 'Bad method call: Unknown method %s::%s',
       
   360                 get_class($this),
       
   361                 $method));
       
   362     }
       
   363 
       
   364     /**
       
   365      * Returns an array representation of all pages in container
       
   366      *
       
   367      * @return array
       
   368      */
       
   369     public function toArray()
       
   370     {
       
   371         $pages = array();
       
   372         
       
   373         $this->_dirtyIndex = true;
       
   374         $this->_sort();
       
   375         $indexes = array_keys($this->_index);
       
   376         foreach ($indexes as $hash) {
       
   377             $pages[] = $this->_pages[$hash]->toArray();
       
   378         }
       
   379         return $pages;
       
   380     }
       
   381 
       
   382     // RecursiveIterator interface:
       
   383 
       
   384     /**
       
   385      * Returns current page
       
   386      *
       
   387      * Implements RecursiveIterator interface.
       
   388      *
       
   389      * @return Zend_Navigation_Page       current page or null
       
   390      * @throws Zend_Navigation_Exception  if the index is invalid
       
   391      */
       
   392     public function current()
       
   393     {
       
   394         $this->_sort();
       
   395         current($this->_index);
       
   396         $hash = key($this->_index);
       
   397 
       
   398         if (isset($this->_pages[$hash])) {
       
   399             return $this->_pages[$hash];
       
   400         } else {
       
   401             require_once 'Zend/Navigation/Exception.php';
       
   402             throw new Zend_Navigation_Exception(
       
   403                     'Corruption detected in container; ' .
       
   404                     'invalid key found in internal iterator');
       
   405         }
       
   406     }
       
   407 
       
   408     /**
       
   409      * Returns hash code of current page
       
   410      *
       
   411      * Implements RecursiveIterator interface.
       
   412      *
       
   413      * @return string  hash code of current page
       
   414      */
       
   415     public function key()
       
   416     {
       
   417         $this->_sort();
       
   418         return key($this->_index);
       
   419     }
       
   420 
       
   421     /**
       
   422      * Moves index pointer to next page in the container
       
   423      *
       
   424      * Implements RecursiveIterator interface.
       
   425      *
       
   426      * @return void
       
   427      */
       
   428     public function next()
       
   429     {
       
   430         $this->_sort();
       
   431         next($this->_index);
       
   432     }
       
   433 
       
   434     /**
       
   435      * Sets index pointer to first page in the container
       
   436      *
       
   437      * Implements RecursiveIterator interface.
       
   438      *
       
   439      * @return void
       
   440      */
       
   441     public function rewind()
       
   442     {
       
   443         $this->_sort();
       
   444         reset($this->_index);
       
   445     }
       
   446 
       
   447     /**
       
   448      * Checks if container index is valid
       
   449      *
       
   450      * Implements RecursiveIterator interface.
       
   451      *
       
   452      * @return bool
       
   453      */
       
   454     public function valid()
       
   455     {
       
   456         $this->_sort();
       
   457         return current($this->_index) !== false;
       
   458     }
       
   459 
       
   460     /**
       
   461      * Proxy to hasPages()
       
   462      *
       
   463      * Implements RecursiveIterator interface.
       
   464      *
       
   465      * @return bool  whether container has any pages
       
   466      */
       
   467     public function hasChildren()
       
   468     {
       
   469         return $this->hasPages();
       
   470     }
       
   471 
       
   472     /**
       
   473      * Returns the child container.
       
   474      *
       
   475      * Implements RecursiveIterator interface.
       
   476      *
       
   477      * @return Zend_Navigation_Page|null
       
   478      */
       
   479     public function getChildren()
       
   480     {
       
   481         $hash = key($this->_index);
       
   482 
       
   483         if (isset($this->_pages[$hash])) {
       
   484             return $this->_pages[$hash];
       
   485         }
       
   486 
       
   487         return null;
       
   488     }
       
   489 
       
   490     // Countable interface:
       
   491 
       
   492     /**
       
   493      * Returns number of pages in container
       
   494      *
       
   495      * Implements Countable interface.
       
   496      *
       
   497      * @return int  number of pages in the container
       
   498      */
       
   499     public function count()
       
   500     {
       
   501         return count($this->_index);
       
   502     }
       
   503 }