diff --git a/modules/doc/library/Doc/SectionRenderer.php b/modules/doc/library/Doc/SectionRenderer.php
new file mode 100644
index 000000000..012d50678
--- /dev/null
+++ b/modules/doc/library/Doc/SectionRenderer.php
@@ -0,0 +1,192 @@
+docTree = $docTree;
+ $this->view = $view;
+ $this->zendUrlHelper = $zendUrlHelper;
+ $this->url = $url;
+ $this->urlParams = $urlParams;
+ }
+
+ public function render($match)
+ {
+ $node = $this->docTree->getNode(Renderer::decodeAnchor($match['fragment']));
+ /* @var $node \Icinga\Data\Tree\Node */
+ if ($node === null) {
+ return $match[0];
+ }
+ $section = $node->getValue();
+ /* @var $section \Icinga\Module\Doc\Section */
+ $path = $this->zendUrlHelper->url(
+ array_merge(
+ $this->urlParams,
+ array(
+ 'chapterName' => SectionRenderer::encodeUrlParam($section->getChapterTitle())
+ )
+ ),
+ $this->url,
+ false,
+ false
+ );
+ $url = $this->view->url($path);
+ $url->setAnchor(SectionRenderer::encodeAnchor($section->getId()));
+ return sprintf(
+ 'isNofollow() ? 'rel="nofollow" ' : '',
+ $url->getAbsoluteUrl()
+ );
+ }
+}
+
+/**
+ * Section renderer
+ */
+class SectionRenderer extends Renderer
+{
+ /**
+ * The documentation tree
+ *
+ * @var DocTree
+ */
+ protected $docTree;
+
+ /**
+ * The URL to replace links with
+ *
+ * @var string
+ */
+ protected $url;
+
+ /**
+ * Additional URL parameters
+ *
+ * @var array
+ */
+ protected $urlParams;
+
+ /**
+ * Parsedown instance
+ *
+ * @var Parsedown
+ */
+ protected $parsedown;
+
+ /**
+ * Content
+ *
+ * @var array
+ */
+ protected $content = array();
+
+ /**
+ * Create a new section renderer
+ *
+ * @param DocTree $docTree The documentation tree
+ * @param string|null $chapterTitle If not null, the chapter title to filter for
+ * @param string $url The URL to replace links with
+ * @param array $urlParams Additional URL parameters
+ *
+ * @throws ChapterNotFoundException If the chapter to filter for was not found
+ */
+ public function __construct(DocTree $docTree, $chapterTitle, $url, array $urlParams)
+ {
+ if ($chapterTitle !== null) {
+ $filter = new SectionFilterIterator($docTree, $chapterTitle);
+ if ($filter->count() === 0) {
+ throw new ChapterNotFoundException(
+ mt('doc', 'Chapter') . ' \'' . $chapterTitle . '\' ' . mt('doc', 'not found')
+ );
+ }
+ parent::__construct(
+ $filter,
+ RecursiveIteratorIterator::SELF_FIRST
+ );
+ } else {
+ parent::__construct($docTree, RecursiveIteratorIterator::SELF_FIRST);
+ }
+ $this->docTree = $docTree;
+ $this->url = $url;
+ $this->urlParams = array_map(array($this, 'encodeUrlParam'), $urlParams);
+ $this->parsedown = Parsedown::instance();
+ }
+
+ /**
+ * Syntax highlighting for PHP code
+ *
+ * @param $match
+ *
+ * @return string
+ */
+ protected function highlightPhp($match)
+ {
+ return '' . highlight_string(htmlspecialchars_decode($match[1]), true) . '
';
+ }
+
+ /**
+ * Render the section
+ *
+ * @param View $view
+ * @param Zend_View_Helper_Url $zendUrlHelper
+ * @return string
+ */
+ public function render(View $view, Zend_View_Helper_Url $zendUrlHelper)
+ {
+ $callback = new Callback($this->docTree, $view, $zendUrlHelper, $this->url, $this->urlParams);
+ $content = array();
+ foreach ($this as $node) {
+ $section = $node->getValue();
+ /* @var $section \Icinga\Module\Doc\Section */
+ $content[] = sprintf(
+ '
(.*?)
#s',
+ array($this, 'highlightPhp'),
+ $this->parsedown->text(implode('', $section->getContent()))
+ );
+ $content[] = preg_replace_callback(
+ '/[^>]*?\s+)?href="#(?P