PageRenderTime 25ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

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

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