PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/View/Helper/Navigation/Sitemap.php

https://github.com/bate/zf2
PHP | 461 lines | 221 code | 56 blank | 184 comment | 30 complexity | e284fc5ed6ac27e04262878df391ac48 MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework (http://framework.zend.com/)
  4. *
  5. * @link http://github.com/zendframework/zf2 for the canonical source repository
  6. * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. * @package Zend_View
  9. */
  10. namespace Zend\View\Helper\Navigation;
  11. use DOMDocument;
  12. use RecursiveIteratorIterator;
  13. use Zend\Navigation\AbstractContainer;
  14. use Zend\Navigation\Page\AbstractPage;
  15. use Zend\Stdlib\ErrorHandler;
  16. use Zend\Uri;
  17. use Zend\View;
  18. use Zend\View\Exception;
  19. /**
  20. * Helper for printing sitemaps
  21. *
  22. * @link http://www.sitemaps.org/protocol.php
  23. *
  24. * @category Zend
  25. * @package Zend_View
  26. * @subpackage Helper
  27. */
  28. class Sitemap extends AbstractHelper
  29. {
  30. /**
  31. * Namespace for the <urlset> tag
  32. *
  33. * @var string
  34. */
  35. const SITEMAP_NS = 'http://www.sitemaps.org/schemas/sitemap/0.9';
  36. /**
  37. * Schema URL
  38. *
  39. * @var string
  40. */
  41. const SITEMAP_XSD = 'http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd';
  42. /**
  43. * Whether XML output should be formatted
  44. *
  45. * @var bool
  46. */
  47. protected $formatOutput = false;
  48. /**
  49. * Whether the XML declaration should be included in XML output
  50. *
  51. * @var bool
  52. */
  53. protected $useXmlDeclaration = true;
  54. /**
  55. * Whether sitemap should be validated using Zend\Validate\Sitemap\*
  56. *
  57. * @var bool
  58. */
  59. protected $useSitemapValidators = true;
  60. /**
  61. * Whether sitemap should be schema validated when generated
  62. *
  63. * @var bool
  64. */
  65. protected $useSchemaValidation = false;
  66. /**
  67. * Server url
  68. *
  69. * @var string
  70. */
  71. protected $serverUrl;
  72. /**
  73. * List of urls in the sitemap
  74. *
  75. * @var array
  76. */
  77. protected $urls = array();
  78. /**
  79. * Helper entry point
  80. *
  81. * @param string|AbstractContainer $container container to operate on
  82. * @return Navigation
  83. */
  84. public function __invoke($container = null)
  85. {
  86. if (null !== $container) {
  87. $this->setContainer($container);
  88. }
  89. return $this;
  90. }
  91. /**
  92. * Sets whether XML output should be formatted
  93. *
  94. * @param bool $formatOutput [optional] whether output should be formatted. Default is true.
  95. * @return Sitemap fluent interface, returns self
  96. */
  97. public function setFormatOutput($formatOutput = true)
  98. {
  99. $this->formatOutput = (bool) $formatOutput;
  100. return $this;
  101. }
  102. /**
  103. * Returns whether XML output should be formatted
  104. *
  105. * @return bool whether XML output should be formatted
  106. */
  107. public function getFormatOutput()
  108. {
  109. return $this->formatOutput;
  110. }
  111. /**
  112. * Sets whether the XML declaration should be used in output
  113. *
  114. * @param bool $useXmlDecl whether XML declaration should be rendered
  115. * @return Sitemap fluent interface, returns self
  116. */
  117. public function setUseXmlDeclaration($useXmlDecl)
  118. {
  119. $this->useXmlDeclaration = (bool) $useXmlDecl;
  120. return $this;
  121. }
  122. /**
  123. * Returns whether the XML declaration should be used in output
  124. *
  125. * @return bool whether the XML declaration should be used in output
  126. */
  127. public function getUseXmlDeclaration()
  128. {
  129. return $this->useXmlDeclaration;
  130. }
  131. /**
  132. * Sets whether sitemap should be validated using Zend\Validate\Sitemap_*
  133. *
  134. * @param bool $useSitemapValidators whether sitemap validators should be used
  135. * @return Sitemap fluent interface, returns self
  136. */
  137. public function setUseSitemapValidators($useSitemapValidators)
  138. {
  139. $this->useSitemapValidators = (bool) $useSitemapValidators;
  140. return $this;
  141. }
  142. /**
  143. * Returns whether sitemap should be validated using Zend\Validate\Sitemap_*
  144. *
  145. * @return bool whether sitemap should be validated using validators
  146. */
  147. public function getUseSitemapValidators()
  148. {
  149. return $this->useSitemapValidators;
  150. }
  151. /**
  152. * Sets whether sitemap should be schema validated when generated
  153. *
  154. * @param bool $schemaValidation whether sitemap should validated using XSD Schema
  155. * @return Sitemap
  156. */
  157. public function setUseSchemaValidation($schemaValidation)
  158. {
  159. $this->useSchemaValidation = (bool) $schemaValidation;
  160. return $this;
  161. }
  162. /**
  163. * Returns true if sitemap should be schema validated when generated
  164. *
  165. * @return bool
  166. */
  167. public function getUseSchemaValidation()
  168. {
  169. return $this->useSchemaValidation;
  170. }
  171. /**
  172. * Sets server url (scheme and host-related stuff without request URI)
  173. *
  174. * E.g. http://www.example.com
  175. *
  176. * @param string $serverUrl server URL to set (only scheme and host)
  177. * @return Sitemap fluent interface, returns self
  178. * @throws Exception\InvalidArgumentException if invalid server URL
  179. */
  180. public function setServerUrl($serverUrl)
  181. {
  182. $uri = Uri\UriFactory::factory($serverUrl);
  183. $uri->setFragment('');
  184. $uri->setPath('');
  185. $uri->setQuery('');
  186. if ($uri->isValid()) {
  187. $this->serverUrl = $uri->toString();
  188. } else {
  189. throw new Exception\InvalidArgumentException(sprintf(
  190. 'Invalid server URL: "%s"',
  191. $serverUrl
  192. ));
  193. }
  194. return $this;
  195. }
  196. /**
  197. * Returns server URL
  198. *
  199. * @return string server URL
  200. */
  201. public function getServerUrl()
  202. {
  203. if (!isset($this->serverUrl)) {
  204. $serverUrlHelper = $this->getView()->plugin('serverUrl');
  205. $this->serverUrl = $serverUrlHelper();
  206. }
  207. return $this->serverUrl;
  208. }
  209. // Helper methods:
  210. /**
  211. * Escapes string for XML usage
  212. *
  213. * @param string $string string to escape
  214. * @return string escaped string
  215. */
  216. protected function xmlEscape($string)
  217. {
  218. $enc = 'UTF-8';
  219. if ($this->view instanceof View\Renderer\RendererInterface
  220. && method_exists($this->view, 'getEncoding')
  221. ) {
  222. $enc = $this->view->getEncoding();
  223. }
  224. return htmlspecialchars($string, ENT_QUOTES, $enc, false);
  225. }
  226. // Public methods:
  227. /**
  228. * Returns an escaped absolute URL for the given page
  229. *
  230. * @param AbstractPage $page page to get URL from
  231. * @return string
  232. */
  233. public function url(AbstractPage $page)
  234. {
  235. $href = $page->getHref();
  236. if (!isset($href{0})) {
  237. // no href
  238. return '';
  239. } elseif ($href{0} == '/') {
  240. // href is relative to root; use serverUrl helper
  241. $url = $this->getServerUrl() . $href;
  242. } elseif (preg_match('/^[a-z]+:/im', (string) $href)) {
  243. // scheme is given in href; assume absolute URL already
  244. $url = (string) $href;
  245. } else {
  246. // href is relative to current document; use url helpers
  247. $basePathHelper = $this->getView()->plugin('basepath');
  248. $curDoc = $basePathHelper();
  249. $curDoc = ('/' == $curDoc) ? '' : trim($curDoc, '/');
  250. $url = rtrim($this->getServerUrl(), '/') . '/'
  251. . $curDoc
  252. . (empty($curDoc) ? '' : '/') . $href;
  253. }
  254. if (! in_array($url, $this->urls)) {
  255. $this->urls[] = $url;
  256. return $this->xmlEscape($url);
  257. }
  258. return null;
  259. }
  260. /**
  261. * Returns a DOMDocument containing the Sitemap XML for the given container
  262. *
  263. * @param AbstractContainer $container [optional] container to get
  264. * breadcrumbs from, defaults
  265. * to what is registered in the
  266. * helper
  267. * @return DOMDocument DOM representation of the
  268. * container
  269. * @throws Exception\RuntimeException if schema validation is on
  270. * and the sitemap is invalid
  271. * according to the sitemap
  272. * schema, or if sitemap
  273. * validators are used and the
  274. * loc element fails validation
  275. */
  276. public function getDomSitemap(AbstractContainer $container = null)
  277. {
  278. // Reset the urls
  279. $this->urls = array();
  280. if (null === $container) {
  281. $container = $this->getContainer();
  282. }
  283. // check if we should validate using our own validators
  284. if ($this->getUseSitemapValidators()) {
  285. // create validators
  286. $locValidator = new \Zend\Validator\Sitemap\Loc();
  287. $lastmodValidator = new \Zend\Validator\Sitemap\Lastmod();
  288. $changefreqValidator = new \Zend\Validator\Sitemap\Changefreq();
  289. $priorityValidator = new \Zend\Validator\Sitemap\Priority();
  290. }
  291. // create document
  292. $dom = new DOMDocument('1.0', 'UTF-8');
  293. $dom->formatOutput = $this->getFormatOutput();
  294. // ...and urlset (root) element
  295. $urlSet = $dom->createElementNS(self::SITEMAP_NS, 'urlset');
  296. $dom->appendChild($urlSet);
  297. // create iterator
  298. $iterator = new RecursiveIteratorIterator($container,
  299. RecursiveIteratorIterator::SELF_FIRST);
  300. $maxDepth = $this->getMaxDepth();
  301. if (is_int($maxDepth)) {
  302. $iterator->setMaxDepth($maxDepth);
  303. }
  304. $minDepth = $this->getMinDepth();
  305. if (!is_int($minDepth) || $minDepth < 0) {
  306. $minDepth = 0;
  307. }
  308. // iterate container
  309. foreach ($iterator as $page) {
  310. if ($iterator->getDepth() < $minDepth || !$this->accept($page)) {
  311. // page should not be included
  312. continue;
  313. }
  314. // get absolute url from page
  315. if (!$url = $this->url($page)) {
  316. // skip page if it has no url (rare case)
  317. // or already is in the sitemap
  318. continue;
  319. }
  320. // create url node for this page
  321. $urlNode = $dom->createElementNS(self::SITEMAP_NS, 'url');
  322. $urlSet->appendChild($urlNode);
  323. if ($this->getUseSitemapValidators()
  324. && !$locValidator->isValid($url)
  325. ) {
  326. throw new Exception\RuntimeException(sprintf(
  327. 'Encountered an invalid URL for Sitemap XML: "%s"',
  328. $url
  329. ));
  330. }
  331. // put url in 'loc' element
  332. $urlNode->appendChild($dom->createElementNS(self::SITEMAP_NS,
  333. 'loc', $url));
  334. // add 'lastmod' element if a valid lastmod is set in page
  335. if (isset($page->lastmod)) {
  336. $lastmod = strtotime((string) $page->lastmod);
  337. // prevent 1970-01-01...
  338. if ($lastmod !== false) {
  339. $lastmod = date('c', $lastmod);
  340. }
  341. if (!$this->getUseSitemapValidators() ||
  342. $lastmodValidator->isValid($lastmod)) {
  343. $urlNode->appendChild(
  344. $dom->createElementNS(self::SITEMAP_NS, 'lastmod',
  345. $lastmod)
  346. );
  347. }
  348. }
  349. // add 'changefreq' element if a valid changefreq is set in page
  350. if (isset($page->changefreq)) {
  351. $changefreq = $page->changefreq;
  352. if (!$this->getUseSitemapValidators() ||
  353. $changefreqValidator->isValid($changefreq)) {
  354. $urlNode->appendChild(
  355. $dom->createElementNS(self::SITEMAP_NS, 'changefreq',
  356. $changefreq)
  357. );
  358. }
  359. }
  360. // add 'priority' element if a valid priority is set in page
  361. if (isset($page->priority)) {
  362. $priority = $page->priority;
  363. if (!$this->getUseSitemapValidators() ||
  364. $priorityValidator->isValid($priority)) {
  365. $urlNode->appendChild(
  366. $dom->createElementNS(self::SITEMAP_NS, 'priority',
  367. $priority)
  368. );
  369. }
  370. }
  371. }
  372. // validate using schema if specified
  373. if ($this->getUseSchemaValidation()) {
  374. ErrorHandler::start();
  375. $test = $dom->schemaValidate(self::SITEMAP_XSD);
  376. $error = ErrorHandler::stop();
  377. if (!$test) {
  378. throw new Exception\RuntimeException(sprintf(
  379. 'Sitemap is invalid according to XML Schema at "%s"',
  380. self::SITEMAP_XSD
  381. ), 0, $error);
  382. }
  383. }
  384. return $dom;
  385. }
  386. // Zend_View_Helper_Navigation_Helper:
  387. /**
  388. * Renders helper
  389. *
  390. * Implements {@link HelperInterface::render()}.
  391. *
  392. * @param link|AbstractContainer $container [optional] container to render. Default is
  393. * to render the container registered in the
  394. * helper.
  395. * @return string helper output
  396. */
  397. public function render($container = null)
  398. {
  399. $dom = $this->getDomSitemap($container);
  400. $xml = $this->getUseXmlDeclaration() ?
  401. $dom->saveXML() :
  402. $dom->saveXML($dom->documentElement);
  403. return rtrim($xml, PHP_EOL);
  404. }
  405. }