|
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-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
19 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
20 * @version $Id: Sitemap.php 20104 2010-01-06 21:26:01Z matthew $ |
|
21 */ |
|
22 |
|
23 /** |
|
24 * @see Zend_View_Helper_Navigation_HelperAbstract |
|
25 */ |
|
26 require_once 'Zend/View/Helper/Navigation/HelperAbstract.php'; |
|
27 |
|
28 /** |
|
29 * Helper for printing sitemaps |
|
30 * |
|
31 * @link http://www.sitemaps.org/protocol.php |
|
32 * |
|
33 * @category Zend |
|
34 * @package Zend_View |
|
35 * @subpackage Helper |
|
36 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
37 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
38 */ |
|
39 class Zend_View_Helper_Navigation_Sitemap |
|
40 extends Zend_View_Helper_Navigation_HelperAbstract |
|
41 { |
|
42 /** |
|
43 * Namespace for the <urlset> tag |
|
44 * |
|
45 * @var string |
|
46 */ |
|
47 const SITEMAP_NS = 'http://www.sitemaps.org/schemas/sitemap/0.9'; |
|
48 |
|
49 /** |
|
50 * Schema URL |
|
51 * |
|
52 * @var string |
|
53 */ |
|
54 const SITEMAP_XSD = 'http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd'; |
|
55 |
|
56 /** |
|
57 * Whether XML output should be formatted |
|
58 * |
|
59 * @var bool |
|
60 */ |
|
61 protected $_formatOutput = false; |
|
62 |
|
63 /** |
|
64 * Whether the XML declaration should be included in XML output |
|
65 * |
|
66 * @var bool |
|
67 */ |
|
68 protected $_useXmlDeclaration = true; |
|
69 |
|
70 /** |
|
71 * Whether sitemap should be validated using Zend_Validate_Sitemap_* |
|
72 * |
|
73 * @var bool |
|
74 */ |
|
75 protected $_useSitemapValidators = true; |
|
76 |
|
77 /** |
|
78 * Whether sitemap should be schema validated when generated |
|
79 * |
|
80 * @var bool |
|
81 */ |
|
82 protected $_useSchemaValidation = false; |
|
83 |
|
84 /** |
|
85 * Server url |
|
86 * |
|
87 * @var string |
|
88 */ |
|
89 protected $_serverUrl; |
|
90 |
|
91 /** |
|
92 * View helper entry point: |
|
93 * Retrieves helper and optionally sets container to operate on |
|
94 * |
|
95 * @param Zend_Navigation_Container $container [optional] container to |
|
96 * operate on |
|
97 * @return Zend_View_Helper_Navigation_Sitemap fluent interface, returns |
|
98 * self |
|
99 */ |
|
100 public function sitemap(Zend_Navigation_Container $container = null) |
|
101 { |
|
102 if (null !== $container) { |
|
103 $this->setContainer($container); |
|
104 } |
|
105 |
|
106 return $this; |
|
107 } |
|
108 |
|
109 // Accessors: |
|
110 |
|
111 /** |
|
112 * Sets whether XML output should be formatted |
|
113 * |
|
114 * @param bool $formatOutput [optional] whether output |
|
115 * should be formatted. Default |
|
116 * is true. |
|
117 * @return Zend_View_Helper_Navigation_Sitemap fluent interface, returns |
|
118 * self |
|
119 */ |
|
120 public function setFormatOutput($formatOutput = true) |
|
121 { |
|
122 $this->_formatOutput = (bool) $formatOutput; |
|
123 return $this; |
|
124 } |
|
125 |
|
126 /** |
|
127 * Returns whether XML output should be formatted |
|
128 * |
|
129 * @return bool whether XML output should be formatted |
|
130 */ |
|
131 public function getFormatOutput() |
|
132 { |
|
133 return $this->_formatOutput; |
|
134 } |
|
135 |
|
136 /** |
|
137 * Sets whether the XML declaration should be used in output |
|
138 * |
|
139 * @param bool $useXmlDecl whether XML delcaration |
|
140 * should be rendered |
|
141 * @return Zend_View_Helper_Navigation_Sitemap fluent interface, returns |
|
142 * self |
|
143 */ |
|
144 public function setUseXmlDeclaration($useXmlDecl) |
|
145 { |
|
146 $this->_useXmlDeclaration = (bool) $useXmlDecl; |
|
147 return $this; |
|
148 } |
|
149 |
|
150 /** |
|
151 * Returns whether the XML declaration should be used in output |
|
152 * |
|
153 * @return bool whether the XML declaration should be used in output |
|
154 */ |
|
155 public function getUseXmlDeclaration() |
|
156 { |
|
157 return $this->_useXmlDeclaration; |
|
158 } |
|
159 |
|
160 /** |
|
161 * Sets whether sitemap should be validated using Zend_Validate_Sitemap_* |
|
162 * |
|
163 * @param bool $useSitemapValidators whether sitemap validators |
|
164 * should be used |
|
165 * @return Zend_View_Helper_Navigation_Sitemap fluent interface, returns |
|
166 * self |
|
167 */ |
|
168 public function setUseSitemapValidators($useSitemapValidators) |
|
169 { |
|
170 $this->_useSitemapValidators = (bool) $useSitemapValidators; |
|
171 return $this; |
|
172 } |
|
173 |
|
174 /** |
|
175 * Returns whether sitemap should be validated using Zend_Validate_Sitemap_* |
|
176 * |
|
177 * @return bool whether sitemap should be validated using validators |
|
178 */ |
|
179 public function getUseSitemapValidators() |
|
180 { |
|
181 return $this->_useSitemapValidators; |
|
182 } |
|
183 |
|
184 /** |
|
185 * Sets whether sitemap should be schema validated when generated |
|
186 * |
|
187 * @param bool $schemaValidation whether sitemap should |
|
188 * validated using XSD Schema |
|
189 * @return Zend_View_Helper_Navigation_Sitemap fluent interface, returns |
|
190 * self |
|
191 */ |
|
192 public function setUseSchemaValidation($schemaValidation) |
|
193 { |
|
194 $this->_useSchemaValidation = (bool) $schemaValidation; |
|
195 return $this; |
|
196 } |
|
197 |
|
198 /** |
|
199 * Returns true if sitemap should be schema validated when generated |
|
200 * |
|
201 * @return bool |
|
202 */ |
|
203 public function getUseSchemaValidation() |
|
204 { |
|
205 return $this->_useSchemaValidation; |
|
206 } |
|
207 |
|
208 /** |
|
209 * Sets server url (scheme and host-related stuff without request URI) |
|
210 * |
|
211 * E.g. http://www.example.com |
|
212 * |
|
213 * @param string $serverUrl server URL to set (only |
|
214 * scheme and host) |
|
215 * @throws Zend_Uri_Exception if invalid server URL |
|
216 * @return Zend_View_Helper_Navigation_Sitemap fluent interface, returns |
|
217 * self |
|
218 */ |
|
219 public function setServerUrl($serverUrl) |
|
220 { |
|
221 require_once 'Zend/Uri.php'; |
|
222 $uri = Zend_Uri::factory($serverUrl); |
|
223 $uri->setFragment(''); |
|
224 $uri->setPath(''); |
|
225 $uri->setQuery(''); |
|
226 |
|
227 if ($uri->valid()) { |
|
228 $this->_serverUrl = $uri->getUri(); |
|
229 } else { |
|
230 require_once 'Zend/Uri/Exception.php'; |
|
231 $e = new Zend_Uri_Exception(sprintf( |
|
232 'Invalid server URL: "%s"', |
|
233 $serverUrl)); |
|
234 $e->setView($this->view); |
|
235 throw $e; |
|
236 } |
|
237 |
|
238 return $this; |
|
239 } |
|
240 |
|
241 /** |
|
242 * Returns server URL |
|
243 * |
|
244 * @return string server URL |
|
245 */ |
|
246 public function getServerUrl() |
|
247 { |
|
248 if (!isset($this->_serverUrl)) { |
|
249 $this->_serverUrl = $this->view->serverUrl(); |
|
250 } |
|
251 |
|
252 return $this->_serverUrl; |
|
253 } |
|
254 |
|
255 // Helper methods: |
|
256 |
|
257 /** |
|
258 * Escapes string for XML usage |
|
259 * |
|
260 * @param string $string string to escape |
|
261 * @return string escaped string |
|
262 */ |
|
263 protected function _xmlEscape($string) |
|
264 { |
|
265 $enc = 'UTF-8'; |
|
266 if ($this->view instanceof Zend_View_Interface |
|
267 && method_exists($this->view, 'getEncoding') |
|
268 ) { |
|
269 $enc = $this->view->getEncoding(); |
|
270 } |
|
271 |
|
272 // TODO: remove check when minimum PHP version is >= 5.2.3 |
|
273 if (version_compare(PHP_VERSION, '5.2.3', '>=')) { |
|
274 // do not encode existing HTML entities |
|
275 return htmlspecialchars($string, ENT_QUOTES, $enc, false); |
|
276 } else { |
|
277 $string = preg_replace('/&(?!(?:#\d++|[a-z]++);)/ui', '&', $string); |
|
278 $string = str_replace(array('<', '>', '\'', '"'), array('<', '>', ''', '"'), $string); |
|
279 return $string; |
|
280 } |
|
281 } |
|
282 |
|
283 // Public methods: |
|
284 |
|
285 /** |
|
286 * Returns an escaped absolute URL for the given page |
|
287 * |
|
288 * @param Zend_Navigation_Page $page page to get URL from |
|
289 * @return string |
|
290 */ |
|
291 public function url(Zend_Navigation_Page $page) |
|
292 { |
|
293 $href = $page->getHref(); |
|
294 |
|
295 if (!isset($href{0})) { |
|
296 // no href |
|
297 return ''; |
|
298 } elseif ($href{0} == '/') { |
|
299 // href is relative to root; use serverUrl helper |
|
300 $url = $this->getServerUrl() . $href; |
|
301 } elseif (preg_match('/^[a-z]+:/im', (string) $href)) { |
|
302 // scheme is given in href; assume absolute URL already |
|
303 $url = (string) $href; |
|
304 } else { |
|
305 // href is relative to current document; use url helpers |
|
306 $url = $this->getServerUrl() |
|
307 . rtrim($this->view->url(), '/') . '/' |
|
308 . $href; |
|
309 } |
|
310 |
|
311 return $this->_xmlEscape($url); |
|
312 } |
|
313 |
|
314 /** |
|
315 * Returns a DOMDocument containing the Sitemap XML for the given container |
|
316 * |
|
317 * @param Zend_Navigation_Container $container [optional] container to get |
|
318 * breadcrumbs from, defaults |
|
319 * to what is registered in the |
|
320 * helper |
|
321 * @return DOMDocument DOM representation of the |
|
322 * container |
|
323 * @throws Zend_View_Exception if schema validation is on |
|
324 * and the sitemap is invalid |
|
325 * according to the sitemap |
|
326 * schema, or if sitemap |
|
327 * validators are used and the |
|
328 * loc element fails validation |
|
329 */ |
|
330 public function getDomSitemap(Zend_Navigation_Container $container = null) |
|
331 { |
|
332 if (null === $container) { |
|
333 $container = $this->getContainer(); |
|
334 } |
|
335 |
|
336 // check if we should validate using our own validators |
|
337 if ($this->getUseSitemapValidators()) { |
|
338 require_once 'Zend/Validate/Sitemap/Changefreq.php'; |
|
339 require_once 'Zend/Validate/Sitemap/Lastmod.php'; |
|
340 require_once 'Zend/Validate/Sitemap/Loc.php'; |
|
341 require_once 'Zend/Validate/Sitemap/Priority.php'; |
|
342 |
|
343 // create validators |
|
344 $locValidator = new Zend_Validate_Sitemap_Loc(); |
|
345 $lastmodValidator = new Zend_Validate_Sitemap_Lastmod(); |
|
346 $changefreqValidator = new Zend_Validate_Sitemap_Changefreq(); |
|
347 $priorityValidator = new Zend_Validate_Sitemap_Priority(); |
|
348 } |
|
349 |
|
350 // create document |
|
351 $dom = new DOMDocument('1.0', 'UTF-8'); |
|
352 $dom->formatOutput = $this->getFormatOutput(); |
|
353 |
|
354 // ...and urlset (root) element |
|
355 $urlSet = $dom->createElementNS(self::SITEMAP_NS, 'urlset'); |
|
356 $dom->appendChild($urlSet); |
|
357 |
|
358 // create iterator |
|
359 $iterator = new RecursiveIteratorIterator($container, |
|
360 RecursiveIteratorIterator::SELF_FIRST); |
|
361 |
|
362 $maxDepth = $this->getMaxDepth(); |
|
363 if (is_int($maxDepth)) { |
|
364 $iterator->setMaxDepth($maxDepth); |
|
365 } |
|
366 $minDepth = $this->getMinDepth(); |
|
367 if (!is_int($minDepth) || $minDepth < 0) { |
|
368 $minDepth = 0; |
|
369 } |
|
370 |
|
371 // iterate container |
|
372 foreach ($iterator as $page) { |
|
373 if ($iterator->getDepth() < $minDepth || !$this->accept($page)) { |
|
374 // page should not be included |
|
375 continue; |
|
376 } |
|
377 |
|
378 // get absolute url from page |
|
379 if (!$url = $this->url($page)) { |
|
380 // skip page if it has no url (rare case) |
|
381 continue; |
|
382 } |
|
383 |
|
384 // create url node for this page |
|
385 $urlNode = $dom->createElementNS(self::SITEMAP_NS, 'url'); |
|
386 $urlSet->appendChild($urlNode); |
|
387 |
|
388 if ($this->getUseSitemapValidators() && |
|
389 !$locValidator->isValid($url)) { |
|
390 require_once 'Zend/View/Exception.php'; |
|
391 $e = new Zend_View_Exception(sprintf( |
|
392 'Encountered an invalid URL for Sitemap XML: "%s"', |
|
393 $url)); |
|
394 $e->setView($this->view); |
|
395 throw $e; |
|
396 } |
|
397 |
|
398 // put url in 'loc' element |
|
399 $urlNode->appendChild($dom->createElementNS(self::SITEMAP_NS, |
|
400 'loc', $url)); |
|
401 |
|
402 // add 'lastmod' element if a valid lastmod is set in page |
|
403 if (isset($page->lastmod)) { |
|
404 $lastmod = strtotime((string) $page->lastmod); |
|
405 |
|
406 // prevent 1970-01-01... |
|
407 if ($lastmod !== false) { |
|
408 $lastmod = date('c', $lastmod); |
|
409 } |
|
410 |
|
411 if (!$this->getUseSitemapValidators() || |
|
412 $lastmodValidator->isValid($lastmod)) { |
|
413 $urlNode->appendChild( |
|
414 $dom->createElementNS(self::SITEMAP_NS, 'lastmod', |
|
415 $lastmod) |
|
416 ); |
|
417 } |
|
418 } |
|
419 |
|
420 // add 'changefreq' element if a valid changefreq is set in page |
|
421 if (isset($page->changefreq)) { |
|
422 $changefreq = $page->changefreq; |
|
423 if (!$this->getUseSitemapValidators() || |
|
424 $changefreqValidator->isValid($changefreq)) { |
|
425 $urlNode->appendChild( |
|
426 $dom->createElementNS(self::SITEMAP_NS, 'changefreq', |
|
427 $changefreq) |
|
428 ); |
|
429 } |
|
430 } |
|
431 |
|
432 // add 'priority' element if a valid priority is set in page |
|
433 if (isset($page->priority)) { |
|
434 $priority = $page->priority; |
|
435 if (!$this->getUseSitemapValidators() || |
|
436 $priorityValidator->isValid($priority)) { |
|
437 $urlNode->appendChild( |
|
438 $dom->createElementNS(self::SITEMAP_NS, 'priority', |
|
439 $priority) |
|
440 ); |
|
441 } |
|
442 } |
|
443 } |
|
444 |
|
445 // validate using schema if specified |
|
446 if ($this->getUseSchemaValidation()) { |
|
447 if (!@$dom->schemaValidate(self::SITEMAP_XSD)) { |
|
448 require_once 'Zend/View/Exception.php'; |
|
449 $e = new Zend_View_Exception(sprintf( |
|
450 'Sitemap is invalid according to XML Schema at "%s"', |
|
451 self::SITEMAP_XSD)); |
|
452 $e->setView($this->view); |
|
453 throw $e; |
|
454 } |
|
455 } |
|
456 |
|
457 return $dom; |
|
458 } |
|
459 |
|
460 // Zend_View_Helper_Navigation_Helper: |
|
461 |
|
462 /** |
|
463 * Renders helper |
|
464 * |
|
465 * Implements {@link Zend_View_Helper_Navigation_Helper::render()}. |
|
466 * |
|
467 * @param Zend_Navigation_Container $container [optional] container to |
|
468 * render. Default is to |
|
469 * render the container |
|
470 * registered in the helper. |
|
471 * @return string helper output |
|
472 */ |
|
473 public function render(Zend_Navigation_Container $container = null) |
|
474 { |
|
475 $dom = $this->getDomSitemap($container); |
|
476 |
|
477 $xml = $this->getUseXmlDeclaration() ? |
|
478 $dom->saveXML() : |
|
479 $dom->saveXML($dom->documentElement); |
|
480 |
|
481 return rtrim($xml, PHP_EOL); |
|
482 } |
|
483 } |