File indexing completed on 2025-01-26 05:29:42
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 }