diff -r 000000000000 -r 4eba9c11703f web/Zend/View/Helper/Navigation/Sitemap.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/Zend/View/Helper/Navigation/Sitemap.php Mon Dec 13 18:29:26 2010 +0100 @@ -0,0 +1,483 @@ + tag + * + * @var string + */ + const SITEMAP_NS = 'http://www.sitemaps.org/schemas/sitemap/0.9'; + + /** + * Schema URL + * + * @var string + */ + const SITEMAP_XSD = 'http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd'; + + /** + * Whether XML output should be formatted + * + * @var bool + */ + protected $_formatOutput = false; + + /** + * Whether the XML declaration should be included in XML output + * + * @var bool + */ + protected $_useXmlDeclaration = true; + + /** + * Whether sitemap should be validated using Zend_Validate_Sitemap_* + * + * @var bool + */ + protected $_useSitemapValidators = true; + + /** + * Whether sitemap should be schema validated when generated + * + * @var bool + */ + protected $_useSchemaValidation = false; + + /** + * Server url + * + * @var string + */ + protected $_serverUrl; + + /** + * 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_Sitemap fluent interface, returns + * self + */ + public function sitemap(Zend_Navigation_Container $container = null) + { + if (null !== $container) { + $this->setContainer($container); + } + + return $this; + } + + // Accessors: + + /** + * Sets whether XML output should be formatted + * + * @param bool $formatOutput [optional] whether output + * should be formatted. Default + * is true. + * @return Zend_View_Helper_Navigation_Sitemap fluent interface, returns + * self + */ + public function setFormatOutput($formatOutput = true) + { + $this->_formatOutput = (bool) $formatOutput; + return $this; + } + + /** + * Returns whether XML output should be formatted + * + * @return bool whether XML output should be formatted + */ + public function getFormatOutput() + { + return $this->_formatOutput; + } + + /** + * Sets whether the XML declaration should be used in output + * + * @param bool $useXmlDecl whether XML delcaration + * should be rendered + * @return Zend_View_Helper_Navigation_Sitemap fluent interface, returns + * self + */ + public function setUseXmlDeclaration($useXmlDecl) + { + $this->_useXmlDeclaration = (bool) $useXmlDecl; + return $this; + } + + /** + * Returns whether the XML declaration should be used in output + * + * @return bool whether the XML declaration should be used in output + */ + public function getUseXmlDeclaration() + { + return $this->_useXmlDeclaration; + } + + /** + * Sets whether sitemap should be validated using Zend_Validate_Sitemap_* + * + * @param bool $useSitemapValidators whether sitemap validators + * should be used + * @return Zend_View_Helper_Navigation_Sitemap fluent interface, returns + * self + */ + public function setUseSitemapValidators($useSitemapValidators) + { + $this->_useSitemapValidators = (bool) $useSitemapValidators; + return $this; + } + + /** + * Returns whether sitemap should be validated using Zend_Validate_Sitemap_* + * + * @return bool whether sitemap should be validated using validators + */ + public function getUseSitemapValidators() + { + return $this->_useSitemapValidators; + } + + /** + * Sets whether sitemap should be schema validated when generated + * + * @param bool $schemaValidation whether sitemap should + * validated using XSD Schema + * @return Zend_View_Helper_Navigation_Sitemap fluent interface, returns + * self + */ + public function setUseSchemaValidation($schemaValidation) + { + $this->_useSchemaValidation = (bool) $schemaValidation; + return $this; + } + + /** + * Returns true if sitemap should be schema validated when generated + * + * @return bool + */ + public function getUseSchemaValidation() + { + return $this->_useSchemaValidation; + } + + /** + * Sets server url (scheme and host-related stuff without request URI) + * + * E.g. http://www.example.com + * + * @param string $serverUrl server URL to set (only + * scheme and host) + * @throws Zend_Uri_Exception if invalid server URL + * @return Zend_View_Helper_Navigation_Sitemap fluent interface, returns + * self + */ + public function setServerUrl($serverUrl) + { + require_once 'Zend/Uri.php'; + $uri = Zend_Uri::factory($serverUrl); + $uri->setFragment(''); + $uri->setPath(''); + $uri->setQuery(''); + + if ($uri->valid()) { + $this->_serverUrl = $uri->getUri(); + } else { + require_once 'Zend/Uri/Exception.php'; + $e = new Zend_Uri_Exception(sprintf( + 'Invalid server URL: "%s"', + $serverUrl)); + $e->setView($this->view); + throw $e; + } + + return $this; + } + + /** + * Returns server URL + * + * @return string server URL + */ + public function getServerUrl() + { + if (!isset($this->_serverUrl)) { + $this->_serverUrl = $this->view->serverUrl(); + } + + return $this->_serverUrl; + } + + // Helper methods: + + /** + * Escapes string for XML usage + * + * @param string $string string to escape + * @return string escaped string + */ + protected function _xmlEscape($string) + { + $enc = 'UTF-8'; + if ($this->view instanceof Zend_View_Interface + && method_exists($this->view, 'getEncoding') + ) { + $enc = $this->view->getEncoding(); + } + + // TODO: remove check when minimum PHP version is >= 5.2.3 + if (version_compare(PHP_VERSION, '5.2.3', '>=')) { + // do not encode existing HTML entities + return htmlspecialchars($string, ENT_QUOTES, $enc, false); + } else { + $string = preg_replace('/&(?!(?:#\d++|[a-z]++);)/ui', '&', $string); + $string = str_replace(array('<', '>', '\'', '"'), array('<', '>', ''', '"'), $string); + return $string; + } + } + + // Public methods: + + /** + * Returns an escaped absolute URL for the given page + * + * @param Zend_Navigation_Page $page page to get URL from + * @return string + */ + public function url(Zend_Navigation_Page $page) + { + $href = $page->getHref(); + + if (!isset($href{0})) { + // no href + return ''; + } elseif ($href{0} == '/') { + // href is relative to root; use serverUrl helper + $url = $this->getServerUrl() . $href; + } elseif (preg_match('/^[a-z]+:/im', (string) $href)) { + // scheme is given in href; assume absolute URL already + $url = (string) $href; + } else { + // href is relative to current document; use url helpers + $url = $this->getServerUrl() + . rtrim($this->view->url(), '/') . '/' + . $href; + } + + return $this->_xmlEscape($url); + } + + /** + * Returns a DOMDocument containing the Sitemap XML for the given container + * + * @param Zend_Navigation_Container $container [optional] container to get + * breadcrumbs from, defaults + * to what is registered in the + * helper + * @return DOMDocument DOM representation of the + * container + * @throws Zend_View_Exception if schema validation is on + * and the sitemap is invalid + * according to the sitemap + * schema, or if sitemap + * validators are used and the + * loc element fails validation + */ + public function getDomSitemap(Zend_Navigation_Container $container = null) + { + if (null === $container) { + $container = $this->getContainer(); + } + + // check if we should validate using our own validators + if ($this->getUseSitemapValidators()) { + require_once 'Zend/Validate/Sitemap/Changefreq.php'; + require_once 'Zend/Validate/Sitemap/Lastmod.php'; + require_once 'Zend/Validate/Sitemap/Loc.php'; + require_once 'Zend/Validate/Sitemap/Priority.php'; + + // create validators + $locValidator = new Zend_Validate_Sitemap_Loc(); + $lastmodValidator = new Zend_Validate_Sitemap_Lastmod(); + $changefreqValidator = new Zend_Validate_Sitemap_Changefreq(); + $priorityValidator = new Zend_Validate_Sitemap_Priority(); + } + + // create document + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = $this->getFormatOutput(); + + // ...and urlset (root) element + $urlSet = $dom->createElementNS(self::SITEMAP_NS, 'urlset'); + $dom->appendChild($urlSet); + + // create iterator + $iterator = new RecursiveIteratorIterator($container, + RecursiveIteratorIterator::SELF_FIRST); + + $maxDepth = $this->getMaxDepth(); + if (is_int($maxDepth)) { + $iterator->setMaxDepth($maxDepth); + } + $minDepth = $this->getMinDepth(); + if (!is_int($minDepth) || $minDepth < 0) { + $minDepth = 0; + } + + // iterate container + foreach ($iterator as $page) { + if ($iterator->getDepth() < $minDepth || !$this->accept($page)) { + // page should not be included + continue; + } + + // get absolute url from page + if (!$url = $this->url($page)) { + // skip page if it has no url (rare case) + continue; + } + + // create url node for this page + $urlNode = $dom->createElementNS(self::SITEMAP_NS, 'url'); + $urlSet->appendChild($urlNode); + + if ($this->getUseSitemapValidators() && + !$locValidator->isValid($url)) { + require_once 'Zend/View/Exception.php'; + $e = new Zend_View_Exception(sprintf( + 'Encountered an invalid URL for Sitemap XML: "%s"', + $url)); + $e->setView($this->view); + throw $e; + } + + // put url in 'loc' element + $urlNode->appendChild($dom->createElementNS(self::SITEMAP_NS, + 'loc', $url)); + + // add 'lastmod' element if a valid lastmod is set in page + if (isset($page->lastmod)) { + $lastmod = strtotime((string) $page->lastmod); + + // prevent 1970-01-01... + if ($lastmod !== false) { + $lastmod = date('c', $lastmod); + } + + if (!$this->getUseSitemapValidators() || + $lastmodValidator->isValid($lastmod)) { + $urlNode->appendChild( + $dom->createElementNS(self::SITEMAP_NS, 'lastmod', + $lastmod) + ); + } + } + + // add 'changefreq' element if a valid changefreq is set in page + if (isset($page->changefreq)) { + $changefreq = $page->changefreq; + if (!$this->getUseSitemapValidators() || + $changefreqValidator->isValid($changefreq)) { + $urlNode->appendChild( + $dom->createElementNS(self::SITEMAP_NS, 'changefreq', + $changefreq) + ); + } + } + + // add 'priority' element if a valid priority is set in page + if (isset($page->priority)) { + $priority = $page->priority; + if (!$this->getUseSitemapValidators() || + $priorityValidator->isValid($priority)) { + $urlNode->appendChild( + $dom->createElementNS(self::SITEMAP_NS, 'priority', + $priority) + ); + } + } + } + + // validate using schema if specified + if ($this->getUseSchemaValidation()) { + if (!@$dom->schemaValidate(self::SITEMAP_XSD)) { + require_once 'Zend/View/Exception.php'; + $e = new Zend_View_Exception(sprintf( + 'Sitemap is invalid according to XML Schema at "%s"', + self::SITEMAP_XSD)); + $e->setView($this->view); + throw $e; + } + } + + return $dom; + } + + // 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) + { + $dom = $this->getDomSitemap($container); + + $xml = $this->getUseXmlDeclaration() ? + $dom->saveXML() : + $dom->saveXML($dom->documentElement); + + return rtrim($xml, PHP_EOL); + } +}