<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@zend.com so we can send you a copy immediately.
*
* @category Zend
* @package Zend_View
* @subpackage Helper
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @version $Id: Sitemap.php 24594 2012-01-05 21:27:01Z matthew $
*/
/**
* @see Zend_View_Helper_Navigation_HelperAbstract
*/
require_once 'Zend/View/Helper/Navigation/HelperAbstract.php';
/**
* Helper for printing sitemaps
*
* @link http://www.sitemaps.org/protocol.php
*
* @category Zend
* @package Zend_View
* @subpackage Helper
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_View_Helper_Navigation_Sitemap
extends Zend_View_Helper_Navigation_HelperAbstract
{
/**
* Namespace for the <urlset> 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);
}
}