diff -r 5b37998e522e -r 162c1de6545a web/lib/Zend/View/Helper/Navigation/Links.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/lib/Zend/View/Helper/Navigation/Links.php Fri Mar 11 15:05:35 2011 +0100 @@ -0,0 +1,783 @@ + elements + * + * @category Zend + * @package Zend_View + * @subpackage Helper + * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_View_Helper_Navigation_Links + extends Zend_View_Helper_Navigation_HelperAbstract +{ + /**#@+ + * Constants used for specifying which link types to find and render + * + * @var int + */ + const RENDER_ALTERNATE = 0x0001; + const RENDER_STYLESHEET = 0x0002; + const RENDER_START = 0x0004; + const RENDER_NEXT = 0x0008; + const RENDER_PREV = 0x0010; + const RENDER_CONTENTS = 0x0020; + const RENDER_INDEX = 0x0040; + const RENDER_GLOSSARY = 0x0080; + const RENDER_COPYRIGHT = 0x0100; + const RENDER_CHAPTER = 0x0200; + const RENDER_SECTION = 0x0400; + const RENDER_SUBSECTION = 0x0800; + const RENDER_APPENDIX = 0x1000; + const RENDER_HELP = 0x2000; + const RENDER_BOOKMARK = 0x4000; + const RENDER_CUSTOM = 0x8000; + const RENDER_ALL = 0xffff; + /**#@+**/ + + /** + * Maps render constants to W3C link types + * + * @var array + */ + protected static $_RELATIONS = array( + self::RENDER_ALTERNATE => 'alternate', + self::RENDER_STYLESHEET => 'stylesheet', + self::RENDER_START => 'start', + self::RENDER_NEXT => 'next', + self::RENDER_PREV => 'prev', + self::RENDER_CONTENTS => 'contents', + self::RENDER_INDEX => 'index', + self::RENDER_GLOSSARY => 'glossary', + self::RENDER_COPYRIGHT => 'copyright', + self::RENDER_CHAPTER => 'chapter', + self::RENDER_SECTION => 'section', + self::RENDER_SUBSECTION => 'subsection', + self::RENDER_APPENDIX => 'appendix', + self::RENDER_HELP => 'help', + self::RENDER_BOOKMARK => 'bookmark' + ); + + /** + * The helper's render flag + * + * @see render() + * @see setRenderFlag() + * @var int + */ + protected $_renderFlag = self::RENDER_ALL; + + /** + * Root container + * + * Used for preventing methods to traverse above the container given to + * the {@link render()} method. + * + * @see _findRoot() + * + * @var Zend_Navigation_Container + */ + protected $_root; + + /** + * View helper entry point: + * Retrieves helper and optionally sets container to operate on + * + * @param Zend_Navigation_Container $container [optional] container to + * operate on + * @return Zend_View_Helper_Navigation_Links fluent interface, returns + * self + */ + public function links(Zend_Navigation_Container $container = null) + { + if (null !== $container) { + $this->setContainer($container); + } + + return $this; + } + + /** + * Magic overload: Proxy calls to {@link findRelation()} or container + * + * Examples of finder calls: + * + * // METHOD // SAME AS + * $h->findRelNext($page); // $h->findRelation($page, 'rel', 'next') + * $h->findRevSection($page); // $h->findRelation($page, 'rev', 'section'); + * $h->findRelFoo($page); // $h->findRelation($page, 'rel', 'foo'); + * + * + * @param string $method method name + * @param array $arguments method arguments + * @throws Zend_Navigation_Exception if method does not exist in container + */ + public function __call($method, array $arguments = array()) + { + if (@preg_match('/find(Rel|Rev)(.+)/', $method, $match)) { + return $this->findRelation($arguments[0], + strtolower($match[1]), + strtolower($match[2])); + } + + return parent::__call($method, $arguments); + } + + // Accessors: + + /** + * Sets the helper's render flag + * + * The helper uses the bitwise '&' operator against the hex values of the + * render constants. This means that the flag can is "bitwised" value of + * the render constants. Examples: + * + * // render all links except glossary + * $flag = Zend_View_Helper_Navigation_Links:RENDER_ALL ^ + * Zend_View_Helper_Navigation_Links:RENDER_GLOSSARY; + * $helper->setRenderFlag($flag); + * + * // render only chapters and sections + * $flag = Zend_View_Helper_Navigation_Links:RENDER_CHAPTER | + * Zend_View_Helper_Navigation_Links:RENDER_SECTION; + * $helper->setRenderFlag($flag); + * + * // render only relations that are not native W3C relations + * $helper->setRenderFlag(Zend_View_Helper_Navigation_Links:RENDER_CUSTOM); + * + * // render all relations (default) + * $helper->setRenderFlag(Zend_View_Helper_Navigation_Links:RENDER_ALL); + * + * + * Note that custom relations can also be rendered directly using the + * {@link renderLink()} method. + * + * @param int $renderFlag render flag + * @return Zend_View_Helper_Navigation_Links fluent interface, returns self + */ + public function setRenderFlag($renderFlag) + { + $this->_renderFlag = (int) $renderFlag; + return $this; + } + + /** + * Returns the helper's render flag + * + * @return int render flag + */ + public function getRenderFlag() + { + return $this->_renderFlag; + } + + // Finder methods: + + /** + * Finds all relations (forward and reverse) for the given $page + * + * The form of the returned array: + * + * // $page denotes an instance of Zend_Navigation_Page + * $returned = array( + * 'rel' => array( + * 'alternate' => array($page, $page, $page), + * 'start' => array($page), + * 'next' => array($page), + * 'prev' => array($page), + * 'canonical' => array($page) + * ), + * 'rev' => array( + * 'section' => array($page) + * ) + * ); + * + * + * @param Zend_Navigation_Page $page page to find links for + * @return array related pages + */ + public function findAllRelations(Zend_Navigation_Page $page, + $flag = null) + { + if (!is_int($flag)) { + $flag = self::RENDER_ALL; + } + + $result = array('rel' => array(), 'rev' => array()); + $native = array_values(self::$_RELATIONS); + + foreach (array_keys($result) as $rel) { + $meth = 'getDefined' . ucfirst($rel); + $types = array_merge($native, array_diff($page->$meth(), $native)); + + foreach ($types as $type) { + if (!$relFlag = array_search($type, self::$_RELATIONS)) { + $relFlag = self::RENDER_CUSTOM; + } + if (!($flag & $relFlag)) { + continue; + } + if ($found = $this->findRelation($page, $rel, $type)) { + if (!is_array($found)) { + $found = array($found); + } + $result[$rel][$type] = $found; + } + } + } + + return $result; + } + + /** + * Finds relations of the given $rel=$type from $page + * + * This method will first look for relations in the page instance, then + * by searching the root container if nothing was found in the page. + * + * @param Zend_Navigation_Page $page page to find relations for + * @param string $rel relation, "rel" or "rev" + * @param string $type link type, e.g. 'start', 'next' + * @return Zend_Navigaiton_Page|array|null page(s), or null if not found + * @throws Zend_View_Exception if $rel is not "rel" or "rev" + */ + public function findRelation(Zend_Navigation_Page $page, $rel, $type) + { + if (!in_array($rel, array('rel', 'rev'))) { + require_once 'Zend/View/Exception.php'; + $e = new Zend_View_Exception(sprintf( + 'Invalid argument: $rel must be "rel" or "rev"; "%s" given', + $rel)); + $e->setView($this->view); + throw $e; + } + + if (!$result = $this->_findFromProperty($page, $rel, $type)) { + $result = $this->_findFromSearch($page, $rel, $type); + } + + return $result; + } + + /** + * Finds relations of given $type for $page by checking if the + * relation is specified as a property of $page + * + * @param Zend_Navigation_Page $page page to find relations for + * @param string $rel relation, 'rel' or 'rev' + * @param string $type link type, e.g. 'start', 'next' + * @return Zend_Navigation_Page|array|null page(s), or null if not found + */ + protected function _findFromProperty(Zend_Navigation_Page $page, $rel, $type) + { + $method = 'get' . ucfirst($rel); + if ($result = $page->$method($type)) { + if ($result = $this->_convertToPages($result)) { + if (!is_array($result)) { + $result = array($result); + } + + foreach ($result as $key => $page) { + if (!$this->accept($page)) { + unset($result[$key]); + } + } + + return count($result) == 1 ? $result[0] : $result; + } + } + + return null; + } + + /** + * Finds relations of given $rel=$type for $page by using the helper to + * search for the relation in the root container + * + * @param Zend_Navigation_Page $page page to find relations for + * @param string $rel relation, 'rel' or 'rev' + * @param string $type link type, e.g. 'start', 'next', etc + * @return array|null array of pages, or null if not found + */ + protected function _findFromSearch(Zend_Navigation_Page $page, $rel, $type) + { + $found = null; + + $method = 'search' . ucfirst($rel) . ucfirst($type); + if (method_exists($this, $method)) { + $found = $this->$method($page); + } + + return $found; + } + + // Search methods: + + /** + * Searches the root container for the forward 'start' relation of the given + * $page + * + * From {@link http://www.w3.org/TR/html4/types.html#type-links}: + * Refers to the first document in a collection of documents. This link type + * tells search engines which document is considered by the author to be the + * starting point of the collection. + * + * @param Zend_Navigation_Page $page page to find relation for + * @return Zend_Navigation_Page|null page or null + */ + public function searchRelStart(Zend_Navigation_Page $page) + { + $found = $this->_findRoot($page); + if (!$found instanceof Zend_Navigation_Page) { + $found->rewind(); + $found = $found->current(); + } + + if ($found === $page || !$this->accept($found)) { + $found = null; + } + + return $found; + } + + /** + * Searches the root container for the forward 'next' relation of the given + * $page + * + * From {@link http://www.w3.org/TR/html4/types.html#type-links}: + * Refers to the next document in a linear sequence of documents. User + * agents may choose to preload the "next" document, to reduce the perceived + * load time. + * + * @param Zend_Navigation_Page $page page to find relation for + * @return Zend_Navigation_Page|null page(s) or null + */ + public function searchRelNext(Zend_Navigation_Page $page) + { + $found = null; + $break = false; + $iterator = new RecursiveIteratorIterator($this->_findRoot($page), + RecursiveIteratorIterator::SELF_FIRST); + foreach ($iterator as $intermediate) { + if ($intermediate === $page) { + // current page; break at next accepted page + $break = true; + continue; + } + + if ($break && $this->accept($intermediate)) { + $found = $intermediate; + break; + } + } + + return $found; + } + + /** + * Searches the root container for the forward 'prev' relation of the given + * $page + * + * From {@link http://www.w3.org/TR/html4/types.html#type-links}: + * Refers to the previous document in an ordered series of documents. Some + * user agents also support the synonym "Previous". + * + * @param Zend_Navigation_Page $page page to find relation for + * @return Zend_Navigation_Page|null page or null + */ + public function searchRelPrev(Zend_Navigation_Page $page) + { + $found = null; + $prev = null; + $iterator = new RecursiveIteratorIterator( + $this->_findRoot($page), + RecursiveIteratorIterator::SELF_FIRST); + foreach ($iterator as $intermediate) { + if (!$this->accept($intermediate)) { + continue; + } + if ($intermediate === $page) { + $found = $prev; + break; + } + + $prev = $intermediate; + } + + return $found; + } + + /** + * Searches the root container for forward 'chapter' relations of the given + * $page + * + * From {@link http://www.w3.org/TR/html4/types.html#type-links}: + * Refers to a document serving as a chapter in a collection of documents. + * + * @param Zend_Navigation_Page $page page to find relation for + * @return Zend_Navigation_Page|array|null page(s) or null + */ + public function searchRelChapter(Zend_Navigation_Page $page) + { + $found = array(); + + // find first level of pages + $root = $this->_findRoot($page); + + // find start page(s) + $start = $this->findRelation($page, 'rel', 'start'); + if (!is_array($start)) { + $start = array($start); + } + + foreach ($root as $chapter) { + // exclude self and start page from chapters + if ($chapter !== $page && + !in_array($chapter, $start) && + $this->accept($chapter)) { + $found[] = $chapter; + } + } + + switch (count($found)) { + case 0: + return null; + case 1: + return $found[0]; + default: + return $found; + } + } + + /** + * Searches the root container for forward 'section' relations of the given + * $page + * + * From {@link http://www.w3.org/TR/html4/types.html#type-links}: + * Refers to a document serving as a section in a collection of documents. + * + * @param Zend_Navigation_Page $page page to find relation for + * @return Zend_Navigation_Page|array|null page(s) or null + */ + public function searchRelSection(Zend_Navigation_Page $page) + { + $found = array(); + + // check if given page has pages and is a chapter page + if ($page->hasPages() && $this->_findRoot($page)->hasPage($page)) { + foreach ($page as $section) { + if ($this->accept($section)) { + $found[] = $section; + } + } + } + + switch (count($found)) { + case 0: + return null; + case 1: + return $found[0]; + default: + return $found; + } + } + + /** + * Searches the root container for forward 'subsection' relations of the + * given $page + * + * From {@link http://www.w3.org/TR/html4/types.html#type-links}: + * Refers to a document serving as a subsection in a collection of + * documents. + * + * @param Zend_Navigation_Page $page page to find relation for + * @return Zend_Navigation_Page|array|null page(s) or null + */ + public function searchRelSubsection(Zend_Navigation_Page $page) + { + $found = array(); + + if ($page->hasPages()) { + // given page has child pages, loop chapters + foreach ($this->_findRoot($page) as $chapter) { + // is page a section? + if ($chapter->hasPage($page)) { + foreach ($page as $subsection) { + if ($this->accept($subsection)) { + $found[] = $subsection; + } + } + } + } + } + + switch (count($found)) { + case 0: + return null; + case 1: + return $found[0]; + default: + return $found; + } + } + + /** + * Searches the root container for the reverse 'section' relation of the + * given $page + * + * From {@link http://www.w3.org/TR/html4/types.html#type-links}: + * Refers to a document serving as a section in a collection of documents. + * + * @param Zend_Navigation_Page $page page to find relation for + * @return Zend_Navigation_Page|null page(s) or null + */ + public function searchRevSection(Zend_Navigation_Page $page) + { + $found = null; + + if ($parent = $page->getParent()) { + if ($parent instanceof Zend_Navigation_Page && + $this->_findRoot($page)->hasPage($parent)) { + $found = $parent; + } + } + + return $found; + } + + /** + * Searches the root container for the reverse 'section' relation of the + * given $page + * + * From {@link http://www.w3.org/TR/html4/types.html#type-links}: + * Refers to a document serving as a subsection in a collection of + * documents. + * + * @param Zend_Navigation_Page $page page to find relation for + * @return Zend_Navigation_Page|null page(s) or null + */ + public function searchRevSubsection(Zend_Navigation_Page $page) + { + $found = null; + + if ($parent = $page->getParent()) { + if ($parent instanceof Zend_Navigation_Page) { + $root = $this->_findRoot($page); + foreach ($root as $chapter) { + if ($chapter->hasPage($parent)) { + $found = $parent; + break; + } + } + } + } + + return $found; + } + + // Util methods: + + /** + * Returns the root container of the given page + * + * When rendering a container, the render method still store the given + * container as the root container, and unset it when done rendering. This + * makes sure finder methods will not traverse above the container given + * to the render method. + * + * @param Zend_Navigaiton_Page $page page to find root for + * @return Zend_Navigation_Container the root container of the given page + */ + protected function _findRoot(Zend_Navigation_Page $page) + { + if ($this->_root) { + return $this->_root; + } + + $root = $page; + + while ($parent = $page->getParent()) { + $root = $parent; + if ($parent instanceof Zend_Navigation_Page) { + $page = $parent; + } else { + break; + } + } + + return $root; + } + + /** + * Converts a $mixed value to an array of pages + * + * @param mixed $mixed mixed value to get page(s) from + * @param bool $recursive whether $value should be looped + * if it is an array or a config + * @return Zend_Navigation_Page|array|null empty if unable to convert + */ + protected function _convertToPages($mixed, $recursive = true) + { + if (is_object($mixed)) { + if ($mixed instanceof Zend_Navigation_Page) { + // value is a page instance; return directly + return $mixed; + } elseif ($mixed instanceof Zend_Navigation_Container) { + // value is a container; return pages in it + $pages = array(); + foreach ($mixed as $page) { + $pages[] = $page; + } + return $pages; + } elseif ($mixed instanceof Zend_Config) { + // convert config object to array and extract + return $this->_convertToPages($mixed->toArray(), $recursive); + } + } elseif (is_string($mixed)) { + // value is a string; make an URI page + return Zend_Navigation_Page::factory(array( + 'type' => 'uri', + 'uri' => $mixed + )); + } elseif (is_array($mixed) && !empty($mixed)) { + if ($recursive && is_numeric(key($mixed))) { + // first key is numeric; assume several pages + $pages = array(); + foreach ($mixed as $value) { + if ($value = $this->_convertToPages($value, false)) { + $pages[] = $value; + } + } + return $pages; + } else { + // pass array to factory directly + try { + $page = Zend_Navigation_Page::factory($mixed); + return $page; + } catch (Exception $e) { + } + } + } + + // nothing found + return null; + } + + // Render methods: + + /** + * Renders the given $page as a link element, with $attrib = $relation + * + * @param Zend_Navigation_Page $page the page to render the link for + * @param string $attrib the attribute to use for $type, + * either 'rel' or 'rev' + * @param string $relation relation type, muse be one of; + * alternate, appendix, bookmark, + * chapter, contents, copyright, + * glossary, help, home, index, next, + * prev, section, start, stylesheet, + * subsection + * @return string rendered link element + * @throws Zend_View_Exception if $attrib is invalid + */ + public function renderLink(Zend_Navigation_Page $page, $attrib, $relation) + { + if (!in_array($attrib, array('rel', 'rev'))) { + require_once 'Zend/View/Exception.php'; + $e = new Zend_View_Exception(sprintf( + 'Invalid relation attribute "%s", must be "rel" or "rev"', + $attrib)); + $e->setView($this->view); + throw $e; + } + + if (!$href = $page->getHref()) { + return ''; + } + + // TODO: add more attribs + // http://www.w3.org/TR/html401/struct/links.html#h-12.2 + $attribs = array( + $attrib => $relation, + 'href' => $href, + 'title' => $page->getLabel() + ); + + return '_htmlAttribs($attribs) . + $this->getClosingBracket(); + } + + // Zend_View_Helper_Navigation_Helper: + + /** + * Renders helper + * + * Implements {@link Zend_View_Helper_Navigation_Helper::render()}. + * + * @param Zend_Navigation_Container $container [optional] container to + * render. Default is to + * render the container + * registered in the helper. + * @return string helper output + */ + public function render(Zend_Navigation_Container $container = null) + { + if (null === $container) { + $container = $this->getContainer(); + } + + if ($active = $this->findActive($container)) { + $active = $active['page']; + } else { + // no active page + return ''; + } + + $output = ''; + $indent = $this->getIndent(); + $this->_root = $container; + + $result = $this->findAllRelations($active, $this->getRenderFlag()); + foreach ($result as $attrib => $types) { + foreach ($types as $relation => $pages) { + foreach ($pages as $page) { + if ($r = $this->renderLink($page, $attrib, $relation)) { + $output .= $indent . $r . self::EOL; + } + } + } + } + + $this->_root = null; + + // return output (trim last newline by spec) + return strlen($output) ? rtrim($output, self::EOL) : ''; + } +}