Sitemap Renderer

Why not add a XML sitemap as a small excercise of what a renderer may do? A sitemap basically advertises a search engine direct paths to pages in your website along with a last-modified timestamp at a well known location on your website like this: https://pwflex.team-tofahrn.de/sitemap.xml. The pages contained in this sitemap are managed manually, similar to entries contained in a menu. The sitemap for this site looks like this:

As you can see, there only is a single item type available for this matrix, named "Sitemap Entry". This item plugin is somewhat trivial and basically traverses a page tree, if the checkbox is set or simply outputs that single node only:

plugins/SitemapRenderer/SitemapEntry.inc

<?php namespace ProcessWire;
/*
 * Module generating Sitemap entries
 */
class SitemapEntry extends RepeaterFlexItem {
	public static function getModuleInfo() {
		return array(
			'title' => __('Sitemap Entry', __FILE__), // Module Title
			'version' => 1,
			'renderer' => 'SitemapRenderer',
			'head' => '{flex_label} [• {PageSelect}]',
			);
	}
	public static function getFields() {
		return([
			'@PageSelect' => RepeaterFlexItem::PageSelect([
					'columnWidth' => 50,
				]),
			'@Checkbox' => RepeaterFlexItem::Checkbox([
					'columnWidth' => 25,
					'label' => __('Enumerate children'),
				]),
		]);
		}
	public function renderTree(Page $pg, $depth)
		{
		$out = $this->ctx->getPageEntry($pg, $depth);
		foreach($pg->children as $child)
			{
			$out .= $this->renderTree($child, $depth+1);
			}
		return($out);
		}
	public function render(Page $pg) {
		$pgId = $pg->PageSelect;
		$pgId = $pg->PageSelect;
		if($pg->Checkbox)
			$out = $this->renderTree($pgId, 1);
		else
			$out = $this->ctx->getPageEntry($pgId, 1);
/*
			{
			foreach($pg->PageSelect->children as $child)
				{
				$out .= $this->ctx->getPageEntry($child);
				}
			}
*/
		return($out);
	}
}

The essential magic happens inside the renderer which not only generates the XML output but may output a human readable undecorated list as well, if requested. So let's take a look at the renderer module:

render/SitemapRenderer.inc

<?php namespace ProcessWire;
/*
 * Simple Sitemap Renderer for RepeaterFlex
 */
class SitemapRenderer extends RepeaterFlexRenderer {
	public static function getModuleInfo() { return [
		'title' => __('Sitemap Renderer', __FILE__), // Module Title
		'summary' => __('Renders RepeaterFlex as a Sitemap.', __FILE__), // Module Summary
		'version' => 80,
		];
	}

	protected $depth = 0;
	protected $isHuman = false;	// Output undecorated list instead of XML
	public function setHuman($bHuman) { $this->isHuman = $bHuman; }

	// Define prefix and postfix depending on requested output format
	public function renderPrefix()
		{
		$this->depth = 1;	// Reset depth
		return( $this->isHuman	? '<ul>'
								: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">" );
		}
	public function renderSuffix()
		{
		return( $this->isHuman	? str_repeat('</ul>', $this->depth)
								: "\n</urlset>" );
		}

	public function getPageEntry(Page $pg, $depth) {
		if($pg->isHidden() || $pg->isUnpublished())
			return('');
	if($this->isHuman)
		{
		$prefix = '';
		$dDepth = $depth - $this->depth;
		if($dDepth > 0)	$prefix = str_repeat('<ul>', $dDepth);
		if($dDepth < 0)	$prefix = str_repeat('</ul>', -$dDepth);
		$this->depth = $depth;
		return($prefix."<li><a href='{$pg->httpUrl}'>{$pg->title}</a></li>");
		}
	
	return("
<url>
\t<loc>{$pg->httpUrl}</loc>
\t<lastmod>" . date("Y-m-d", $pg->modified) . "</lastmod>
</url>");
	}
}

This renderer does not implement a render method but relies on the default renderer provided by the RepeaterFlex, which invokes renderPrefix first, iterates over all repeater elements and finalizes the output with a call to renderSuffix.

The remarkable point is the render variable $isHuman which controls the output format and may be controlled from the template code, so it's worth to take a look at that as well:

/site/templates/sitemap.php

<?php namespace ProcessWire;
$sm = $page->flex_sitemap;	// Reference the sitemap definition
if(isset($input->human))	// Human output requested?
	{	// Output content markup, this site uses delayed output from $content variable
	$content = "<div class='uk-container uk-padding'><h1>{$page->title}</h1>";
	$sm->getContext()->setHuman(true);	// Request regular HTML markup
	$content .= $sm->render();			// Get contents
	$content .= '</div>';
	}
else
	{	// Regular XML output
	header("Content-Type: text/xml");	// Switch reply header declare correct format
	echo $sm->render();					// Get contents as XML
	die();								// Stop any processing NOW
	}

The template checks for a specified url parameter "human" and then renders the sitemap as a simple, undecorated list: https://pwflex.team-tofahrn.de/sitemap.xml?human

prepared in 61ms (content 28ms, header 0ms, Menu 21ms, Footer 11ms)