File indexing completed on 2024-05-12 06:02:48

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category  Zend
0016  * @package   Zend_Navigation
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license   http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version    $Id$
0020  */
0021 
0022 /**
0023  * Zend_Navigation_Container
0024  *
0025  * Container class for Zend_Navigation_Page classes.
0026  *
0027  * @category  Zend
0028  * @package   Zend_Navigation
0029  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0030  * @license   http://framework.zend.com/license/new-bsd     New BSD License
0031  */
0032 abstract class Zend_Navigation_Container implements RecursiveIterator, Countable
0033 {
0034     /**
0035      * Contains sub pages
0036      *
0037      * @var Zend_Navigation_Page[]
0038      */
0039     protected $_pages = array();
0040 
0041     /**
0042      * An index that contains the order in which to iterate pages
0043      *
0044      * @var array
0045      */
0046     protected $_index = array();
0047 
0048     /**
0049      * Whether index is dirty and needs to be re-arranged
0050      *
0051      * @var bool
0052      */
0053     protected $_dirtyIndex = false;
0054 
0055     // Internal methods:
0056 
0057     /**
0058      * Sorts the page index according to page order
0059      *
0060      * @return void
0061      */
0062     protected function _sort()
0063     {
0064         if ($this->_dirtyIndex) {
0065             $newIndex = array();
0066             $index = 0;
0067 
0068             foreach ($this->_pages as $hash => $page) {
0069                 $order = $page->getOrder();
0070                 if ($order === null) {
0071                     $newIndex[$hash] = $index;
0072                     $index++;
0073                 } else {
0074                     $newIndex[$hash] = $order;
0075                 }
0076             }
0077 
0078             asort($newIndex);
0079             $this->_index = $newIndex;
0080             $this->_dirtyIndex = false;
0081         }
0082     }
0083 
0084     // Public methods:
0085 
0086     /**
0087      * Notifies container that the order of pages are updated
0088      *
0089      * @return void
0090      */
0091     public function notifyOrderUpdated()
0092     {
0093         $this->_dirtyIndex = true;
0094     }
0095 
0096     /**
0097      * Adds a page to the container
0098      *
0099      * This method will inject the container as the given page's parent by
0100      * calling {@link Zend_Navigation_Page::setParent()}.
0101      *
0102      * @param  Zend_Navigation_Page|array|Zend_Config $page  page to add
0103      * @return Zend_Navigation_Container                     fluent interface,
0104      *                                                       returns self
0105      * @throws Zend_Navigation_Exception                     if page is invalid
0106      */
0107     public function addPage($page)
0108     {
0109         if ($page === $this) {
0110             // require_once 'Zend/Navigation/Exception.php';
0111             throw new Zend_Navigation_Exception(
0112                 'A page cannot have itself as a parent');
0113         }
0114 
0115         if (is_array($page) || $page instanceof Zend_Config) {
0116             // require_once 'Zend/Navigation/Page.php';
0117             $page = Zend_Navigation_Page::factory($page);
0118         } elseif (!$page instanceof Zend_Navigation_Page) {
0119             // require_once 'Zend/Navigation/Exception.php';
0120             throw new Zend_Navigation_Exception(
0121                     'Invalid argument: $page must be an instance of ' .
0122                     'Zend_Navigation_Page or Zend_Config, or an array');
0123         }
0124 
0125         $hash = $page->hashCode();
0126 
0127         if (array_key_exists($hash, $this->_index)) {
0128             // page is already in container
0129             return $this;
0130         }
0131 
0132         // adds page to container and sets dirty flag
0133         $this->_pages[$hash] = $page;
0134         $this->_index[$hash] = $page->getOrder();
0135         $this->_dirtyIndex = true;
0136 
0137         // inject self as page parent
0138         $page->setParent($this);
0139 
0140         return $this;
0141     }
0142 
0143     /**
0144      * Adds several pages at once
0145      *
0146      * @param  Zend_Navigation_Page[]|Zend_Config|Zend_Navigation_Container  $pages  pages to add
0147      * @return Zend_Navigation_Container                    fluent interface,
0148      *                                                      returns self
0149      * @throws Zend_Navigation_Exception                    if $pages is not 
0150      *                                                      array, Zend_Config or
0151      *                                                      Zend_Navigation_Container
0152      */
0153     public function addPages($pages)
0154     {
0155         if ($pages instanceof Zend_Config) {
0156             $pages = $pages->toArray();
0157         }
0158 
0159         if ($pages instanceof Zend_Navigation_Container) {
0160             $pages = iterator_to_array($pages);
0161         }
0162 
0163         if (!is_array($pages)) {
0164             // require_once 'Zend/Navigation/Exception.php';
0165             throw new Zend_Navigation_Exception(
0166                     'Invalid argument: $pages must be an array, an ' .
0167                     'instance of Zend_Config or an instance of ' .
0168                     'Zend_Navigation_Container');
0169         }
0170 
0171         foreach ($pages as $page) {
0172             $this->addPage($page);
0173         }
0174 
0175         return $this;
0176     }
0177 
0178     /**
0179      * Sets pages this container should have, removing existing pages
0180      *
0181      * @param  Zend_Navigation_Page[] $pages               pages to set
0182      * @return Zend_Navigation_Container  fluent interface, returns self
0183      */
0184     public function setPages(array $pages)
0185     {
0186         $this->removePages();
0187         return $this->addPages($pages);
0188     }
0189 
0190     /**
0191      * Returns pages in the container
0192      *
0193      * @return Zend_Navigation_Page[]  array of Zend_Navigation_Page instances
0194      */
0195     public function getPages()
0196     {
0197         return $this->_pages;
0198     }
0199 
0200     /**
0201      * Removes the given page from the container
0202      *
0203      * @param  Zend_Navigation_Page|int $page      page to remove, either a page
0204      *                                             instance or a specific page order
0205      * @param  bool                     $recursive [optional] whether to remove recursively
0206      * @return bool whether the removal was successful
0207      */
0208     public function removePage($page, $recursive = false)
0209     {
0210         if ($page instanceof Zend_Navigation_Page) {
0211             $hash = $page->hashCode();
0212         } elseif (is_int($page)) {
0213             $this->_sort();
0214             if (!$hash = array_search($page, $this->_index)) {
0215                 return false;
0216             }
0217         } else {
0218             return false;
0219         }
0220 
0221         if (isset($this->_pages[$hash])) {
0222             unset($this->_pages[$hash]);
0223             unset($this->_index[$hash]);
0224             $this->_dirtyIndex = true;
0225             return true;
0226         }
0227 
0228         if ($recursive) {
0229             /** @var Zend_Navigation_Page $childPage */
0230             foreach ($this->_pages as $childPage) {
0231                 if ($childPage->hasPage($page, true)) {
0232                     $childPage->removePage($page, true);
0233                     return true;
0234                 }
0235             }
0236         }
0237 
0238         return false;
0239     }
0240 
0241     /**
0242      * Removes all pages in container
0243      *
0244      * @return Zend_Navigation_Container  fluent interface, returns self
0245      */
0246     public function removePages()
0247     {
0248         $this->_pages = array();
0249         $this->_index = array();
0250         return $this;
0251     }
0252 
0253     /**
0254      * Checks if the container has the given page
0255      *
0256      * @param  Zend_Navigation_Page $page       page to look for
0257      * @param  bool                 $recursive  [optional] whether to search
0258      *                                          recursively. Default is false.
0259      * @return bool                             whether page is in container
0260      */
0261     public function hasPage(Zend_Navigation_Page $page, $recursive = false)
0262     {
0263         if (array_key_exists($page->hashCode(), $this->_index)) {
0264             return true;
0265         } elseif ($recursive) {
0266             foreach ($this->_pages as $childPage) {
0267                 if ($childPage->hasPage($page, true)) {
0268                     return true;
0269                 }
0270             }
0271         }
0272 
0273         return false;
0274     }
0275 
0276     /**
0277      * Returns true if container contains any pages
0278      *
0279      * @return bool  whether container has any pages
0280      */
0281     public function hasPages()
0282     {
0283         return count($this->_index) > 0;
0284     }
0285 
0286     /**
0287      * Returns a child page matching $property == $value or 
0288      * preg_match($value, $property), or null if not found
0289      *
0290      * @param  string  $property          name of property to match against
0291      * @param  mixed   $value             value to match property against
0292      * @param  bool    $useRegex          [optional] if true PHP's preg_match
0293      *                                    is used. Default is false.
0294      * @return Zend_Navigation_Page|null  matching page or null
0295      */
0296     public function findOneBy($property, $value, $useRegex = false)
0297     {        
0298         $iterator = new RecursiveIteratorIterator(
0299             $this,
0300             RecursiveIteratorIterator::SELF_FIRST
0301         );
0302 
0303         foreach ($iterator as $page) {
0304             $pageProperty = $page->get($property);
0305             
0306             // Rel and rev
0307             if (is_array($pageProperty)) {
0308                 foreach ($pageProperty as $item) {
0309                     if (is_array($item)) {
0310                         // Use regex?
0311                         if (true === $useRegex) {
0312                             foreach ($item as $item2) {
0313                                 if (0 !== preg_match($value, $item2)) {
0314                                     return $page;
0315                                 }
0316                             }
0317                         } else {
0318                             if (in_array($value, $item)) {
0319                                 return $page;
0320                             }
0321                         }
0322                     } else {
0323                         // Use regex?
0324                         if (true === $useRegex) {
0325                             if (0 !== preg_match($value, $item)) {
0326                                 return $page;
0327                             }
0328                         } else {
0329                             if ($item == $value) {
0330                                 return $page;
0331                             }
0332                         }
0333                     }
0334                 }
0335                 
0336                 continue;
0337             }
0338             
0339             // Use regex?
0340             if (true === $useRegex) {
0341                 if (preg_match($value, $pageProperty)) {
0342                     return $page;
0343                 }
0344             } else {
0345                 if ($pageProperty == $value) {
0346                     return $page;
0347                 }
0348             }
0349         }
0350         
0351         return null;
0352     }
0353 
0354     /**
0355      * Returns all child pages matching $property == $value or
0356      * preg_match($value, $property), or an empty array if no pages are found
0357      *
0358      * @param  string $property  name of property to match against
0359      * @param  mixed  $value     value to match property against
0360      * @param  bool   $useRegex  [optional] if true PHP's preg_match is used.
0361      *                           Default is false.
0362      * @return Zend_Navigation_Page[] array containing only Zend_Navigation_Page
0363      *                           instances
0364      */
0365     public function findAllBy($property, $value, $useRegex = false)
0366     {        
0367         $found = array();
0368 
0369         $iterator = new RecursiveIteratorIterator(
0370             $this,
0371             RecursiveIteratorIterator::SELF_FIRST
0372         );
0373         
0374         foreach ($iterator as $page) {
0375             $pageProperty = $page->get($property);
0376             
0377             // Rel and rev
0378             if (is_array($pageProperty)) {
0379                 foreach ($pageProperty as $item) {
0380                     if (is_array($item)) {
0381                         // Use regex?
0382                         if (true === $useRegex) {
0383                             foreach ($item as $item2) {
0384                                 if (0 !== preg_match($value, $item2)) {
0385                                     $found[] = $page;
0386                                 }
0387                             }
0388                         } else {
0389                             if (in_array($value, $item)) {
0390                                 $found[] = $page;
0391                             }
0392                         }
0393                     } else {
0394                         // Use regex?
0395                         if (true === $useRegex) {
0396                             if (0 !== preg_match($value, $item)) {
0397                                 $found[] = $page;
0398                             }
0399                         } else {
0400                             if ($item == $value) {
0401                                 $found[] = $page;
0402                             }
0403                         }
0404                     }
0405                 }
0406                 
0407                 continue;
0408             }
0409             
0410             // Use regex?
0411             if (true === $useRegex) {
0412                 if (0 !== preg_match($value, $pageProperty)) {
0413                     $found[] = $page;
0414                 }
0415             } else {
0416                 if ($pageProperty == $value) {
0417                     $found[] = $page;
0418                 }
0419             }
0420         }
0421 
0422         return $found;
0423     }
0424 
0425     /**
0426      * Returns page(s) matching $property == $value or
0427      * preg_match($value, $property)
0428      *
0429      * @param  string $property  name of property to match against
0430      * @param  mixed  $value     value to match property against
0431      * @param  bool   $all       [optional] whether an array of all matching
0432      *                           pages should be returned, or only the first.
0433      *                           If true, an array will be returned, even if not
0434      *                           matching pages are found. If false, null will
0435      *                           be returned if no matching page is found.
0436      *                           Default is false.
0437      * @param  bool   $useRegex  [optional] if true PHP's preg_match is used.
0438      *                           Default is false.
0439      * @return Zend_Navigation_Page|null  matching page or null
0440      */
0441     public function findBy($property, $value, $all = false, $useRegex = false)
0442     {
0443         if ($all) {
0444             return $this->findAllBy($property, $value, $useRegex);
0445         } else {
0446             return $this->findOneBy($property, $value, $useRegex);
0447         }
0448     }
0449 
0450     /**
0451      * Magic overload: Proxy calls to finder methods
0452      *
0453      * Examples of finder calls:
0454      * <code>
0455      * // METHOD                         // SAME AS
0456      * $nav->findByLabel('foo');         // $nav->findOneBy('label', 'foo');
0457      * $nav->findByLabel('/foo/', true); // $nav->findBy('label', '/foo/', true);
0458      * $nav->findOneByLabel('foo');      // $nav->findOneBy('label', 'foo');
0459      * $nav->findAllByClass('foo');      // $nav->findAllBy('class', 'foo');
0460      * </code>
0461      *
0462      * @param  string $method                       method name
0463      * @param  array  $arguments                    method arguments
0464      * @return mixed  Zend_Navigation|array|null    matching page, array of pages
0465      *                                              or null
0466      * @throws Zend_Navigation_Exception            if method does not exist
0467      */
0468     public function __call($method, $arguments)
0469     {
0470         if (@preg_match('/(find(?:One|All)?By)(.+)/', $method, $match)) {
0471             return $this->{$match[1]}($match[2], $arguments[0], !empty($arguments[1]));
0472         }
0473 
0474         // require_once 'Zend/Navigation/Exception.php';
0475         throw new Zend_Navigation_Exception(
0476             sprintf(
0477                 'Bad method call: Unknown method %s::%s',
0478                 get_class($this),
0479                 $method
0480             )
0481         );
0482     }
0483 
0484     /**
0485      * Returns an array representation of all pages in container
0486      *
0487      * @return Zend_Navigation_Page[]
0488      */
0489     public function toArray()
0490     {
0491         $pages = array();
0492 
0493         $this->_dirtyIndex = true;
0494         $this->_sort();
0495         $indexes = array_keys($this->_index);
0496         foreach ($indexes as $hash) {
0497             $pages[] = $this->_pages[$hash]->toArray();
0498         }
0499         return $pages;
0500     }
0501 
0502     // RecursiveIterator interface:
0503 
0504     /**
0505      * Returns current page
0506      *
0507      * Implements RecursiveIterator interface.
0508      *
0509      * @return Zend_Navigation_Page       current page or null
0510      * @throws Zend_Navigation_Exception  if the index is invalid
0511      */
0512     public function current()
0513     {
0514         $this->_sort();
0515         current($this->_index);
0516         $hash = key($this->_index);
0517 
0518         if (isset($this->_pages[$hash])) {
0519             return $this->_pages[$hash];
0520         } else {
0521             // require_once 'Zend/Navigation/Exception.php';
0522             throw new Zend_Navigation_Exception(
0523                     'Corruption detected in container; ' .
0524                     'invalid key found in internal iterator');
0525         }
0526     }
0527 
0528     /**
0529      * Returns hash code of current page
0530      *
0531      * Implements RecursiveIterator interface.
0532      *
0533      * @return string  hash code of current page
0534      */
0535     public function key()
0536     {
0537         $this->_sort();
0538         return key($this->_index);
0539     }
0540 
0541     /**
0542      * Moves index pointer to next page in the container
0543      *
0544      * Implements RecursiveIterator interface.
0545      *
0546      * @return void
0547      */
0548     public function next()
0549     {
0550         $this->_sort();
0551         next($this->_index);
0552     }
0553 
0554     /**
0555      * Sets index pointer to first page in the container
0556      *
0557      * Implements RecursiveIterator interface.
0558      *
0559      * @return void
0560      */
0561     public function rewind()
0562     {
0563         $this->_sort();
0564         reset($this->_index);
0565     }
0566 
0567     /**
0568      * Checks if container index is valid
0569      *
0570      * Implements RecursiveIterator interface.
0571      *
0572      * @return bool
0573      */
0574     public function valid()
0575     {
0576         $this->_sort();
0577         return current($this->_index) !== false;
0578     }
0579 
0580     /**
0581      * Proxy to hasPages()
0582      *
0583      * Implements RecursiveIterator interface.
0584      *
0585      * @return bool  whether container has any pages
0586      */
0587     public function hasChildren()
0588     {
0589         return $this->hasPages();
0590     }
0591 
0592     /**
0593      * Returns the child container.
0594      *
0595      * Implements RecursiveIterator interface.
0596      *
0597      * @return Zend_Navigation_Page|null
0598      */
0599     public function getChildren()
0600     {
0601         $hash = key($this->_index);
0602 
0603         if (isset($this->_pages[$hash])) {
0604             return $this->_pages[$hash];
0605         }
0606 
0607         return null;
0608     }
0609 
0610     // Countable interface:
0611 
0612     /**
0613      * Returns number of pages in container
0614      *
0615      * Implements Countable interface.
0616      *
0617      * @return int  number of pages in the container
0618      */
0619     public function count()
0620     {
0621         return count($this->_index);
0622     }
0623 }