mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-28 16:24:04 +02:00
commit
4f6a8629d1
@ -40,7 +40,7 @@ or LDAP configuration method.
|
|||||||
Directive | Description
|
Directive | Description
|
||||||
------------------------|------------
|
------------------------|------------
|
||||||
**backend** | `ldap`
|
**backend** | `ldap`
|
||||||
**resource** | The name of the LDAP resource defined in [resources.ini](resources).
|
**resource** | The name of the LDAP resource defined in [resources.ini](#resources).
|
||||||
**user_class** | LDAP user class.
|
**user_class** | LDAP user class.
|
||||||
**user_name_attribute** | LDAP attribute which contains the username.
|
**user_name_attribute** | LDAP attribute which contains the username.
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ with Icinga Web 2 (e.g. an alias) no matter what the primary user id might actua
|
|||||||
Directive | Description
|
Directive | Description
|
||||||
------------------------|------------
|
------------------------|------------
|
||||||
**backend** | `ad`
|
**backend** | `ad`
|
||||||
**resource** | The name of the LDAP resource defined in [resources.ini](resources).
|
**resource** | The name of the LDAP resource defined in [resources.ini](#resources).
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ authentication method.
|
|||||||
Directive | Description
|
Directive | Description
|
||||||
------------------------|------------
|
------------------------|------------
|
||||||
**backend** | `db`
|
**backend** | `db`
|
||||||
**resource** | The name of the database resource defined in [resources.ini](resources).
|
**resource** | The name of the database resource defined in [resources.ini](#resources).
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
|
@ -1,78 +0,0 @@
|
|||||||
<?php
|
|
||||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
|
||||||
|
|
||||||
namespace Icinga\Data\Tree;
|
|
||||||
|
|
||||||
use SplDoublyLinkedList;
|
|
||||||
|
|
||||||
class Node extends SplDoublyLinkedList implements NodeInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The node's value
|
|
||||||
*
|
|
||||||
* @var mixed
|
|
||||||
*/
|
|
||||||
protected $value;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new node
|
|
||||||
*
|
|
||||||
* @param mixed $value The node's value
|
|
||||||
*/
|
|
||||||
public function __construct($value = null)
|
|
||||||
{
|
|
||||||
$this->value = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the node's value
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function getValue()
|
|
||||||
{
|
|
||||||
return $this->value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new node from the given value and insert the node as the last child of this node
|
|
||||||
*
|
|
||||||
* @param mixed $value The node's value
|
|
||||||
*
|
|
||||||
* @return NodeInterface The appended node
|
|
||||||
*/
|
|
||||||
public function appendChild($value)
|
|
||||||
{
|
|
||||||
$child = new static($value);
|
|
||||||
$this->push($child);
|
|
||||||
return $child;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this node has child nodes
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function hasChildren()
|
|
||||||
{
|
|
||||||
$current = $this->current();
|
|
||||||
if ($current === null) {
|
|
||||||
$current = $this;
|
|
||||||
}
|
|
||||||
return ! $current->isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the node's child nodes
|
|
||||||
*
|
|
||||||
* @return NodeInterface
|
|
||||||
*/
|
|
||||||
public function getChildren()
|
|
||||||
{
|
|
||||||
$current = $this->current();
|
|
||||||
if ($current === null) {
|
|
||||||
$current = $this;
|
|
||||||
}
|
|
||||||
return $current;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
|
||||||
|
|
||||||
namespace Icinga\Data\Tree;
|
|
||||||
|
|
||||||
use RecursiveIterator;
|
|
||||||
|
|
||||||
interface NodeInterface extends RecursiveIterator
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Create a new node from the given value and insert the node as the last child of this node
|
|
||||||
*
|
|
||||||
* @param mixed $value The node's value
|
|
||||||
*
|
|
||||||
* @return NodeInterface The appended node
|
|
||||||
*/
|
|
||||||
public function appendChild($value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the node's value
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function getValue();
|
|
||||||
}
|
|
90
library/Icinga/Data/Tree/SimpleTree.php
Normal file
90
library/Icinga/Data/Tree/SimpleTree.php
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Icinga\Data\Tree;
|
||||||
|
|
||||||
|
use IteratorAggregate;
|
||||||
|
use LogicException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple tree
|
||||||
|
*/
|
||||||
|
class SimpleTree implements IteratorAggregate
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Root node
|
||||||
|
*
|
||||||
|
* @type TreeNode
|
||||||
|
*/
|
||||||
|
protected $sentinel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nodes
|
||||||
|
*
|
||||||
|
* @type array
|
||||||
|
*/
|
||||||
|
protected $nodes = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new simple tree
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->sentinel = new TreeNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a child node
|
||||||
|
*
|
||||||
|
* @param TreeNode $child
|
||||||
|
* @param TreeNode $parent
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addChild(TreeNode $child, TreeNode $parent = null)
|
||||||
|
{
|
||||||
|
if ($parent === null) {
|
||||||
|
$parent = $this->sentinel;
|
||||||
|
} elseif (! isset($this->nodes[$parent->getId()])) {
|
||||||
|
throw new LogicException(sprintf(
|
||||||
|
'Can\'t append child node %s to parent node %s: Parent node does not exist',
|
||||||
|
$child->getId(),
|
||||||
|
$parent->getId()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (isset($this->nodes[$child->getId()])) {
|
||||||
|
throw new LogicException(sprintf(
|
||||||
|
'Can\'t append child node %s to parent node %s: Child node does already exist',
|
||||||
|
$child->getId(),
|
||||||
|
$parent->getId()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
$this->nodes[$child->getId()] = $child;
|
||||||
|
$parent->appendChild($child);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a node by its ID
|
||||||
|
*
|
||||||
|
* @param mixed $id
|
||||||
|
*
|
||||||
|
* @return TreeNode|null
|
||||||
|
*/
|
||||||
|
public function getNode($id)
|
||||||
|
{
|
||||||
|
if (! isset($this->nodes[$id])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $this->nodes[$id];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
* @return TreeNodeIterator
|
||||||
|
*/
|
||||||
|
public function getIterator()
|
||||||
|
{
|
||||||
|
return new TreeNodeIterator($this->sentinel);
|
||||||
|
}
|
||||||
|
}
|
109
library/Icinga/Data/Tree/TreeNode.php
Normal file
109
library/Icinga/Data/Tree/TreeNode.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Icinga\Data\Tree;
|
||||||
|
|
||||||
|
use Icinga\Data\Identifiable;
|
||||||
|
|
||||||
|
class TreeNode implements Identifiable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The node's ID
|
||||||
|
*
|
||||||
|
* @type mixed
|
||||||
|
*/
|
||||||
|
protected $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The node's value
|
||||||
|
*
|
||||||
|
* @var mixed
|
||||||
|
*/
|
||||||
|
protected $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The node's children
|
||||||
|
*
|
||||||
|
* @type array
|
||||||
|
*/
|
||||||
|
protected $children = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the node's ID
|
||||||
|
*
|
||||||
|
* @param mixed $id ID of the node
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setId($id)
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (non-PHPDoc)
|
||||||
|
* @see Identifiable::getId() For the method documentation.
|
||||||
|
*/
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the node's value
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setValue($value)
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the node's value
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getValue()
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a child node as the last child of this node
|
||||||
|
*
|
||||||
|
* @param TreeNode $child The child to append
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function appendChild(TreeNode $child)
|
||||||
|
{
|
||||||
|
$this->children[] = $child;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether the node has children
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasChildren()
|
||||||
|
{
|
||||||
|
return ! empty($this->children);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the node's children
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getChildren()
|
||||||
|
{
|
||||||
|
return $this->children;
|
||||||
|
}
|
||||||
|
}
|
87
library/Icinga/Data/Tree/TreeNodeIterator.php
Normal file
87
library/Icinga/Data/Tree/TreeNodeIterator.php
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Icinga\Data\Tree;
|
||||||
|
|
||||||
|
use ArrayIterator;
|
||||||
|
use RecursiveIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator over a tree node's children
|
||||||
|
*/
|
||||||
|
class TreeNodeIterator implements RecursiveIterator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The node's children
|
||||||
|
*
|
||||||
|
* @type array
|
||||||
|
*/
|
||||||
|
protected $children;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new iterator over a tree node's children
|
||||||
|
*
|
||||||
|
* @param TreeNode $node
|
||||||
|
*/
|
||||||
|
public function __construct(TreeNode $node)
|
||||||
|
{
|
||||||
|
$this->children = new ArrayIterator($node->getChildren());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function current()
|
||||||
|
{
|
||||||
|
return $this->children->current();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function key()
|
||||||
|
{
|
||||||
|
return $this->children->key();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function next()
|
||||||
|
{
|
||||||
|
$this->children->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function rewind()
|
||||||
|
{
|
||||||
|
$this->children->rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function valid()
|
||||||
|
{
|
||||||
|
return $this->children->valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function hasChildren()
|
||||||
|
{
|
||||||
|
return $this->current()->hasChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
* @return TreeNodeIterator
|
||||||
|
*/
|
||||||
|
public function getChildren()
|
||||||
|
{
|
||||||
|
return new static($this->current());
|
||||||
|
}
|
||||||
|
}
|
@ -58,7 +58,7 @@ class FileExtensionFilterIterator extends FilterIterator
|
|||||||
public function accept()
|
public function accept()
|
||||||
{
|
{
|
||||||
$current = $this->current();
|
$current = $this->current();
|
||||||
/* @var $current \SplFileInfo */
|
/** @type $current \SplFileInfo */
|
||||||
if (! $current->isFile()) {
|
if (! $current->isFile()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -37,19 +37,22 @@ class Pdf extends DOMPDF
|
|||||||
public function renderControllerAction($controller)
|
public function renderControllerAction($controller)
|
||||||
{
|
{
|
||||||
$this->assertNoHeadersSent();
|
$this->assertNoHeadersSent();
|
||||||
|
|
||||||
ini_set('memory_limit', '384M');
|
ini_set('memory_limit', '384M');
|
||||||
ini_set('max_execution_time', 300);
|
ini_set('max_execution_time', 300);
|
||||||
|
$viewRenderer = $controller->getHelper('viewRenderer');
|
||||||
$request = $controller->getRequest();
|
$controller->render(
|
||||||
|
$viewRenderer->getScriptAction(),
|
||||||
|
$viewRenderer->getResponseSegment(),
|
||||||
|
$viewRenderer->getNoController()
|
||||||
|
);
|
||||||
$layout = $controller->getHelper('layout')->setLayout('pdf');
|
$layout = $controller->getHelper('layout')->setLayout('pdf');
|
||||||
$controller->render();
|
|
||||||
$layout->content = $controller->getResponse();
|
$layout->content = $controller->getResponse();
|
||||||
$html = $layout->render();
|
$html = $layout->render();
|
||||||
$imgDir = Url::fromPath('img');
|
$imgDir = Url::fromPath('img');
|
||||||
$html = preg_replace('~src="' . $imgDir . '/~', 'src="' . Icinga::app()->getBootstrapDirectory() . '/img/', $html);
|
$html = preg_replace('~src="' . $imgDir . '/~', 'src="' . Icinga::app()->getBootstrapDirectory() . '/img/', $html);
|
||||||
$this->load_html($html);
|
$this->load_html($html);
|
||||||
$this->render();
|
$this->render();
|
||||||
|
$request = $controller->getRequest();
|
||||||
$this->stream(
|
$this->stream(
|
||||||
sprintf(
|
sprintf(
|
||||||
'%s-%s-%d.pdf',
|
'%s-%s-%d.pdf',
|
||||||
|
@ -402,7 +402,6 @@ class ActionController extends Zend_Controller_Action
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($req->getParam('format') === 'pdf') {
|
if ($req->getParam('format') === 'pdf') {
|
||||||
$layout->setLayout('pdf');
|
|
||||||
$this->shutdownSession();
|
$this->shutdownSession();
|
||||||
$this->sendAsPdf();
|
$this->sendAsPdf();
|
||||||
exit;
|
exit;
|
||||||
|
106
library/Icinga/Web/Dom/DomNodeIterator.php
Normal file
106
library/Icinga/Web/Dom/DomNodeIterator.php
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Icinga\Web\Dom;
|
||||||
|
|
||||||
|
use DOMNode;
|
||||||
|
use IteratorIterator;
|
||||||
|
use RecursiveIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive iterator over a DOMNode
|
||||||
|
*
|
||||||
|
* Usage example:
|
||||||
|
* <code>
|
||||||
|
* <?php
|
||||||
|
*
|
||||||
|
* namespace Icinga\Example;
|
||||||
|
*
|
||||||
|
* use DOMDocument;
|
||||||
|
* use RecursiveIteratorIterator;
|
||||||
|
* use Icinga\Web\Dom\DomIterator;
|
||||||
|
*
|
||||||
|
* $doc = new DOMDocument();
|
||||||
|
* $doc->loadHTML(...);
|
||||||
|
* $dom = new RecursiveIteratorIterator(new DomNodeIterator($doc), RecursiveIteratorIterator::SELF_FIRST);
|
||||||
|
* foreach ($dom as $node) {
|
||||||
|
* ....
|
||||||
|
* }
|
||||||
|
* </code>
|
||||||
|
*/
|
||||||
|
class DomNodeIterator implements RecursiveIterator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The node's children
|
||||||
|
*
|
||||||
|
* @type IteratorIterator
|
||||||
|
*/
|
||||||
|
protected $children;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new iterator over a DOMNode's children
|
||||||
|
*
|
||||||
|
* @param DOMNode $node
|
||||||
|
*/
|
||||||
|
public function __construct(DOMNode $node)
|
||||||
|
{
|
||||||
|
$this->children = new IteratorIterator($node->childNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function current()
|
||||||
|
{
|
||||||
|
return $this->children->current();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function key()
|
||||||
|
{
|
||||||
|
return $this->children->key();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function next()
|
||||||
|
{
|
||||||
|
$this->children->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function rewind()
|
||||||
|
{
|
||||||
|
$this->children->rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function valid()
|
||||||
|
{
|
||||||
|
return $this->children->valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function hasChildren()
|
||||||
|
{
|
||||||
|
return $this->current()->hasChildNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
* @return DomNodeIterator
|
||||||
|
*/
|
||||||
|
public function getChildren()
|
||||||
|
{
|
||||||
|
return new static($this->current());
|
||||||
|
}
|
||||||
|
}
|
@ -46,17 +46,16 @@ class Doc_IcingawebController extends DocController
|
|||||||
*/
|
*/
|
||||||
public function chapterAction()
|
public function chapterAction()
|
||||||
{
|
{
|
||||||
$chapterId = $this->getParam('chapterId');
|
$chapter = $this->getParam('chapter');
|
||||||
if ($chapterId === null) {
|
if ($chapter === null) {
|
||||||
throw new Zend_Controller_Action_Exception(
|
throw new Zend_Controller_Action_Exception(
|
||||||
sprintf($this->translate('Missing parameter \'%s\''), 'chapterId'),
|
sprintf($this->translate('Missing parameter %s'), 'chapter'),
|
||||||
404
|
404
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$this->renderChapter(
|
$this->renderChapter(
|
||||||
$this->getPath(),
|
$this->getPath(),
|
||||||
$chapterId,
|
$chapter,
|
||||||
'doc/icingaweb/toc',
|
|
||||||
'doc/icingaweb/chapter'
|
'doc/icingaweb/chapter'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -122,10 +122,10 @@ class Doc_ModuleController extends DocController
|
|||||||
{
|
{
|
||||||
$module = $this->getParam('moduleName');
|
$module = $this->getParam('moduleName');
|
||||||
$this->assertModuleEnabled($module);
|
$this->assertModuleEnabled($module);
|
||||||
$chapterId = $this->getParam('chapterId');
|
$chapter = $this->getParam('chapter');
|
||||||
if ($chapterId === null) {
|
if ($chapter === null) {
|
||||||
throw new Zend_Controller_Action_Exception(
|
throw new Zend_Controller_Action_Exception(
|
||||||
sprintf($this->translate('Missing parameter \'%s\''), 'chapterId'),
|
sprintf($this->translate('Missing parameter %s'), 'chapter'),
|
||||||
404
|
404
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -133,8 +133,7 @@ class Doc_ModuleController extends DocController
|
|||||||
try {
|
try {
|
||||||
$this->renderChapter(
|
$this->renderChapter(
|
||||||
$this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')),
|
$this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')),
|
||||||
$chapterId,
|
$chapter,
|
||||||
$this->_helper->url->url(array('moduleName' => $module), 'doc/module/toc'),
|
|
||||||
'doc/module/chapter',
|
'doc/module/chapter',
|
||||||
array('moduleName' => $module)
|
array('moduleName' => $module)
|
||||||
);
|
);
|
||||||
|
97
modules/doc/application/controllers/SearchController.php
Normal file
97
modules/doc/application/controllers/SearchController.php
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
use \Zend_Controller_Action_Exception;
|
||||||
|
use Icinga\Application\Icinga;
|
||||||
|
use Icinga\Module\Doc\DocController;
|
||||||
|
use Icinga\Module\Doc\DocParser;
|
||||||
|
use Icinga\Module\Doc\Exception\DocException;
|
||||||
|
use Icinga\Module\Doc\Search\DocSearch;
|
||||||
|
use Icinga\Module\Doc\Search\DocSearchIterator;
|
||||||
|
use Icinga\Module\Doc\Renderer\DocSearchRenderer;
|
||||||
|
|
||||||
|
class Doc_SearchController extends DocController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Render search
|
||||||
|
*/
|
||||||
|
public function indexAction()
|
||||||
|
{
|
||||||
|
$parser = new DocParser($this->getWebPath());
|
||||||
|
$search = new DocSearchRenderer(
|
||||||
|
new DocSearchIterator(
|
||||||
|
$parser->getDocTree()->getIterator(),
|
||||||
|
new DocSearch($this->params->get('q'))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$search->setUrl('doc/icingaweb/chapter');
|
||||||
|
$searches = array(
|
||||||
|
'Icinga Web 2' => $search
|
||||||
|
);
|
||||||
|
foreach (Icinga::app()->getModuleManager()->listEnabledModules() as $module) {
|
||||||
|
if (($path = $this->getModulePath($module)) !== null) {
|
||||||
|
try {
|
||||||
|
$parser = new DocParser($path);
|
||||||
|
} catch (DocException $e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$search = new DocSearchRenderer(
|
||||||
|
new DocSearchIterator(
|
||||||
|
$parser->getDocTree()->getIterator(),
|
||||||
|
new DocSearch($this->params->get('q'))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$search
|
||||||
|
->setUrl('doc/module/chapter')
|
||||||
|
->setUrlParams(array('moduleName' => $module));
|
||||||
|
$searches[$module] = $search;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->view->searches = $searches;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path to a module's documentation
|
||||||
|
*
|
||||||
|
* @param string $module
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
protected function getModulePath($module)
|
||||||
|
{
|
||||||
|
if (is_dir(($path = Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')))) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
if (($path = $this->Config()->get('documentation', 'modules')) !== null) {
|
||||||
|
$path = str_replace('{module}', $module, $path);
|
||||||
|
if (is_dir($path)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path to Icinga Web 2's documentation
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
* @throws Zend_Controller_Action_Exception If Icinga Web 2's documentation is not available
|
||||||
|
*/
|
||||||
|
protected function getWebPath()
|
||||||
|
{
|
||||||
|
$path = Icinga::app()->getBaseDir('doc');
|
||||||
|
if (is_dir($path)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
if (($path = $this->Config()->get('documentation', 'icingaweb2')) !== null) {
|
||||||
|
if (is_dir($path)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Zend_Controller_Action_Exception(
|
||||||
|
$this->translate('Documentation for Icinga Web 2 is not available'),
|
||||||
|
404
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,6 @@
|
|||||||
<div class="chapter">
|
<div class="controls">
|
||||||
<?= $sectionRenderer->render($this, $this->getHelper('Url')); ?>
|
<?= /** @type \Icinga\Web\Widget\Tabs $tabs */ $tabs->showOnlyCloseButton() ?>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<?= /** @type \Icinga\Module\Doc\Renderer\DocSectionRenderer $section */ $section ?>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
<div class="controls"></div>
|
<div class="controls"></div>
|
||||||
<h1><?= $this->translate('Available documentations'); ?></h1>
|
<?= /** @type \Icinga\Web\Widget\Tabs $tabs */ $tabs->showOnlyCloseButton() ?>
|
||||||
|
<h1><?= $this->translate('Available documentations') ?></h1>
|
||||||
|
<div class="content">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="<?= $this->href('doc/icingaweb/toc'); ?>">Icinga Web 2</a></li>
|
<li><a href="<?= $this->href('doc/icingaweb/toc') ?>">Icinga Web 2</a></li>
|
||||||
<li><a href="<?= $this->href('doc/module/'); ?>"><?= $this->translate('Module documentations'); ?></a></li>
|
<li><a href="<?= $this->href('doc/module/') ?>"><?= $this->translate('Module documentations') ?></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
<div class="chapter">
|
|
||||||
<?= $sectionRenderer->render($this, $this->getHelper('Url')); ?>
|
|
||||||
</div>
|
|
@ -1,10 +1,15 @@
|
|||||||
<h1><?= $this->translate('Module documentations'); ?></h1>
|
<div class="controls">
|
||||||
|
<?= /** @type \Icinga\Web\Widget\Tabs $tabs */ $tabs->showOnlyCloseButton() ?>
|
||||||
|
<h1><?= $this->translate('Module documentations') ?></h1>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
<ul>
|
<ul>
|
||||||
<?php foreach ($modules as $module): ?>
|
<?php foreach ($modules as $module): ?>
|
||||||
<li>
|
<li>
|
||||||
<a href="<?= $this->getHelper('Url')->url(array('moduleName' => $module), 'doc/module/toc', false, false); ?>">
|
<a href="<?= $this->getHelper('Url')->url(array('moduleName' => $module), 'doc/module/toc', false, false) ?>">
|
||||||
<?= $module ?>
|
<?= $module ?>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
<div class="controls">
|
|
||||||
<h1><?= $title ?></h1>
|
|
||||||
</div>
|
|
||||||
<div class="content toc">
|
|
||||||
<?= $tocRenderer->render($this, $this->getHelper('Url')); ?>
|
|
||||||
</div>
|
|
@ -1,7 +1,5 @@
|
|||||||
<h1><?= $docName ?> <?= $this->translate('Documentation'); ?></h1>
|
<div class="content">
|
||||||
<div class="toc">
|
<h1><?= /** @type string $title */ $title ?></h1>
|
||||||
<?= $tocRenderer->render($this, $this->getHelper('Url')); ?>
|
<?= /** @type \Icinga\Module\Doc\Renderer\DocTocRenderer $toc */ $toc ?>
|
||||||
</div>
|
<?= /** @type \Icinga\Module\Doc\Renderer\DocSectionRenderer $section */ $section ?>
|
||||||
<div class="chapter">
|
|
||||||
<?= $sectionRenderer->render($this, $this->getHelper('Url')); ?>
|
|
||||||
</div>
|
</div>
|
||||||
|
8
modules/doc/application/views/scripts/search/index.phtml
Normal file
8
modules/doc/application/views/scripts/search/index.phtml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<div class="content">
|
||||||
|
<?php foreach (/** @type \Icinga\Module\Doc\Renderer\DocSearchRenderer[] $searches */ $searches as $title => $search): ?>
|
||||||
|
<?php if (! $search->isEmpty()): ?>
|
||||||
|
<h1><?= $this->escape($title) ?></h1>
|
||||||
|
<?= $search ?>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</div>
|
@ -1,6 +1,7 @@
|
|||||||
<div class="controls">
|
<div class="controls">
|
||||||
<h1><?= $title ?></h1>
|
<?= /** @type \Icinga\Web\Widget\Tabs $tabs */ $tabs->showOnlyCloseButton() ?>
|
||||||
|
<h1><?= /** @type string $title */ $title ?></h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="content toc">
|
<div class="content">
|
||||||
<?= $tocRenderer->render($this, $this->getHelper('Url')); ?>
|
<?= /** @type \Icinga\Module\Doc\Renderer\DocTocRenderer $toc */ $toc ?>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,3 +20,5 @@ $section->add($this->translate('Developer - Style'), array(
|
|||||||
'url' => 'doc/style/guide',
|
'url' => 'doc/style/guide',
|
||||||
'priority' => 200,
|
'priority' => 200,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
$this->provideSearchUrl($this->translate('Doc'), 'doc/search');
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
namespace Icinga\Module\Doc;
|
namespace Icinga\Module\Doc;
|
||||||
|
|
||||||
|
use Icinga\Module\Doc\Renderer\DocSectionRenderer;
|
||||||
|
use Icinga\Module\Doc\Renderer\DocTocRenderer;
|
||||||
use Icinga\Web\Controller\ModuleActionController;
|
use Icinga\Web\Controller\ModuleActionController;
|
||||||
|
|
||||||
class DocController extends ModuleActionController
|
class DocController extends ModuleActionController
|
||||||
@ -11,22 +13,19 @@ class DocController extends ModuleActionController
|
|||||||
* Render a chapter
|
* Render a chapter
|
||||||
*
|
*
|
||||||
* @param string $path Path to the documentation
|
* @param string $path Path to the documentation
|
||||||
* @param string $chapterId ID of the chapter
|
* @param string $chapter ID of the chapter
|
||||||
* @param string $tocUrl
|
* @param string $url URL to replace links with
|
||||||
* @param string $url
|
* @param array $urlParams Additional URL parameters
|
||||||
* @param array $urlParams
|
|
||||||
*/
|
*/
|
||||||
protected function renderChapter($path, $chapterId, $tocUrl, $url, array $urlParams = array())
|
protected function renderChapter($path, $chapter, $url, array $urlParams = array())
|
||||||
{
|
{
|
||||||
$parser = new DocParser($path);
|
$parser = new DocParser($path);
|
||||||
$this->view->sectionRenderer = new SectionRenderer(
|
$section = new DocSectionRenderer($parser->getDocTree(), DocSectionRenderer::decodeUrlParam($chapter));
|
||||||
$parser->getDocTree(),
|
$this->view->section = $section
|
||||||
SectionRenderer::decodeUrlParam($chapterId),
|
->setUrl($url)
|
||||||
$tocUrl,
|
->setUrlParams($urlParams)
|
||||||
$url,
|
->setHighlightSearch($this->params->get('highlight-search'));
|
||||||
$urlParams
|
$this->view->title = $chapter;
|
||||||
);
|
|
||||||
$this->view->title = $chapterId;
|
|
||||||
$this->render('chapter', null, true);
|
$this->render('chapter', null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,15 +34,17 @@ class DocController extends ModuleActionController
|
|||||||
*
|
*
|
||||||
* @param string $path Path to the documentation
|
* @param string $path Path to the documentation
|
||||||
* @param string $name Name of the documentation
|
* @param string $name Name of the documentation
|
||||||
* @param string $url
|
* @param string $url URL to replace links with
|
||||||
* @param array $urlParams
|
* @param array $urlParams Additional URL parameters
|
||||||
*/
|
*/
|
||||||
protected function renderToc($path, $name, $url, array $urlParams = array())
|
protected function renderToc($path, $name, $url, array $urlParams = array())
|
||||||
{
|
{
|
||||||
$parser = new DocParser($path);
|
$parser = new DocParser($path);
|
||||||
$this->view->tocRenderer = new TocRenderer($parser->getDocTree(), $url, $urlParams);
|
$toc = new DocTocRenderer($parser->getDocTree()->getIterator());
|
||||||
|
$this->view->toc = $toc
|
||||||
|
->setUrl($url)
|
||||||
|
->setUrlParams($urlParams);
|
||||||
$name = ucfirst($name);
|
$name = ucfirst($name);
|
||||||
$this->view->docName = $name;
|
|
||||||
$this->view->title = sprintf($this->translate('%s Documentation'), $name);
|
$this->view->title = sprintf($this->translate('%s Documentation'), $name);
|
||||||
$this->render('toc', null, true);
|
$this->render('toc', null, true);
|
||||||
}
|
}
|
||||||
@ -59,17 +60,16 @@ class DocController extends ModuleActionController
|
|||||||
protected function renderPdf($path, $name, $url, array $urlParams = array())
|
protected function renderPdf($path, $name, $url, array $urlParams = array())
|
||||||
{
|
{
|
||||||
$parser = new DocParser($path);
|
$parser = new DocParser($path);
|
||||||
$docTree = $parser->getDocTree();
|
$toc = new DocTocRenderer($parser->getDocTree()->getIterator());
|
||||||
$this->view->tocRenderer = new TocRenderer($docTree, $url, $urlParams);
|
$this->view->toc = $toc
|
||||||
$this->view->sectionRenderer = new SectionRenderer(
|
->setUrl($url)
|
||||||
$docTree,
|
->setUrlParams($urlParams);
|
||||||
null,
|
$section = new DocSectionRenderer($parser->getDocTree());
|
||||||
null,
|
$this->view->section = $section
|
||||||
$url,
|
->setUrl($url)
|
||||||
$urlParams
|
->setUrlParams($urlParams);
|
||||||
);
|
$this->view->title = sprintf($this->translate('%s Documentation'), $name);
|
||||||
$this->view->docName = $name;
|
|
||||||
$this->_request->setParam('format', 'pdf');
|
$this->_request->setParam('format', 'pdf');
|
||||||
$this->render('pdf', null, true);
|
$this->_helper->viewRenderer->setRender('pdf', null, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@ namespace Icinga\Module\Doc;
|
|||||||
use ArrayIterator;
|
use ArrayIterator;
|
||||||
use Countable;
|
use Countable;
|
||||||
use IteratorAggregate;
|
use IteratorAggregate;
|
||||||
use RecursiveIteratorIterator;
|
|
||||||
use RecursiveDirectoryIterator;
|
use RecursiveDirectoryIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
use Icinga\File\NonEmptyFileIterator;
|
use Icinga\File\NonEmptyFileIterator;
|
||||||
use Icinga\File\FileExtensionFilterIterator;
|
use Icinga\File\FileExtensionFilterIterator;
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ class DocIterator implements Countable, IteratorAggregate
|
|||||||
/**
|
/**
|
||||||
* Ordered files
|
* Ordered files
|
||||||
*
|
*
|
||||||
* @var array
|
* @type array
|
||||||
*/
|
*/
|
||||||
protected $fileInfo;
|
protected $fileInfo;
|
||||||
|
|
||||||
@ -46,8 +46,7 @@ class DocIterator implements Countable, IteratorAggregate
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (non-PHPDoc)
|
* {@inheritdoc}
|
||||||
* @see Countable::count()
|
|
||||||
*/
|
*/
|
||||||
public function count()
|
public function count()
|
||||||
{
|
{
|
||||||
@ -55,8 +54,7 @@ class DocIterator implements Countable, IteratorAggregate
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (non-PHPDoc)
|
* {@inheritdoc}
|
||||||
* @see IteratorAggregate::getIterator()
|
|
||||||
*/
|
*/
|
||||||
public function getIterator()
|
public function getIterator()
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
|
|
||||||
namespace Icinga\Module\Doc;
|
namespace Icinga\Module\Doc;
|
||||||
|
|
||||||
use SplDoublyLinkedList;
|
use LogicException;
|
||||||
|
use SplStack;
|
||||||
|
use Icinga\Data\Tree\SimpleTree;
|
||||||
use Icinga\Exception\NotReadableError;
|
use Icinga\Exception\NotReadableError;
|
||||||
use Icinga\Module\Doc\Exception\DocEmptyException;
|
use Icinga\Module\Doc\Exception\DocEmptyException;
|
||||||
use Icinga\Module\Doc\Exception\DocException;
|
use Icinga\Module\Doc\Exception\DocException;
|
||||||
@ -16,14 +18,14 @@ class DocParser
|
|||||||
/**
|
/**
|
||||||
* Path to the documentation
|
* Path to the documentation
|
||||||
*
|
*
|
||||||
* @var string
|
* @type string
|
||||||
*/
|
*/
|
||||||
protected $path;
|
protected $path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterator over documentation files
|
* Iterator over documentation files
|
||||||
*
|
*
|
||||||
* @var DocIterator
|
* @type DocIterator
|
||||||
*/
|
*/
|
||||||
protected $docIterator;
|
protected $docIterator;
|
||||||
|
|
||||||
@ -121,16 +123,15 @@ class DocParser
|
|||||||
/**
|
/**
|
||||||
* Get the documentation tree
|
* Get the documentation tree
|
||||||
*
|
*
|
||||||
* @return DocTree
|
* @return SimpleTree
|
||||||
*/
|
*/
|
||||||
public function getDocTree()
|
public function getDocTree()
|
||||||
{
|
{
|
||||||
$tree = new DocTree();
|
$tree = new SimpleTree();
|
||||||
$stack = new SplDoublyLinkedList();
|
$stack = new SplStack();
|
||||||
foreach ($this->docIterator as $fileInfo) {
|
foreach ($this->docIterator as $fileInfo) {
|
||||||
/* @var $file \SplFileInfo */
|
/** @type $fileInfo \SplFileInfo */
|
||||||
$file = $fileInfo->openFile();
|
$file = $fileInfo->openFile();
|
||||||
/* @var $file \SplFileObject */
|
|
||||||
$lastLine = null;
|
$lastLine = null;
|
||||||
foreach ($file as $line) {
|
foreach ($file as $line) {
|
||||||
$header = $this->extractHeader($line, $lastLine);
|
$header = $this->extractHeader($line, $lastLine);
|
||||||
@ -142,7 +143,7 @@ class DocParser
|
|||||||
if ($id === null) {
|
if ($id === null) {
|
||||||
$path = array();
|
$path = array();
|
||||||
foreach ($stack as $section) {
|
foreach ($stack as $section) {
|
||||||
/* @var $section Section */
|
/** @type $section DocSection */
|
||||||
$path[] = $section->getTitle();
|
$path[] = $section->getTitle();
|
||||||
}
|
}
|
||||||
$path[] = $title;
|
$path[] = $title;
|
||||||
@ -151,17 +152,27 @@ class DocParser
|
|||||||
} else {
|
} else {
|
||||||
$noFollow = false;
|
$noFollow = false;
|
||||||
}
|
}
|
||||||
|
if ($tree->getNode($id) !== null) {
|
||||||
|
$id = uniqid($id);
|
||||||
|
}
|
||||||
|
$section = new DocSection();
|
||||||
|
$section
|
||||||
|
->setId($id)
|
||||||
|
->setTitle($title)
|
||||||
|
->setLevel($level)
|
||||||
|
->setNoFollow($noFollow);
|
||||||
if ($stack->isEmpty()) {
|
if ($stack->isEmpty()) {
|
||||||
$chapterId = $id;
|
$section->setChapter($section);
|
||||||
$section = new Section($id, $title, $level, $noFollow, $chapterId);
|
$tree->addChild($section);
|
||||||
$tree->addRoot($section);
|
|
||||||
} else {
|
} else {
|
||||||
$chapterId = $stack->bottom()->getId();
|
$section->setChapter($stack->bottom());
|
||||||
$section = new Section($id, $title, $level, $noFollow, $chapterId);
|
|
||||||
$tree->addChild($section, $stack->top());
|
$tree->addChild($section, $stack->top());
|
||||||
}
|
}
|
||||||
$stack->push($section);
|
$stack->push($section);
|
||||||
} else {
|
} else {
|
||||||
|
if ($stack->isEmpty()) {
|
||||||
|
throw new LogicException('Heading required');
|
||||||
|
}
|
||||||
$stack->top()->appendContent($line);
|
$stack->top()->appendContent($line);
|
||||||
}
|
}
|
||||||
// Save last line for setext-style headers
|
// Save last line for setext-style headers
|
||||||
|
159
modules/doc/library/Doc/DocSection.php
Normal file
159
modules/doc/library/Doc/DocSection.php
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Icinga\Module\Doc;
|
||||||
|
|
||||||
|
use Icinga\Data\Tree\TreeNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A section of a documentation
|
||||||
|
*/
|
||||||
|
class DocSection extends TreeNode
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Chapter the section belongs to
|
||||||
|
*
|
||||||
|
* @type DocSection
|
||||||
|
*/
|
||||||
|
protected $chapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content of the section
|
||||||
|
*
|
||||||
|
* @type array
|
||||||
|
*/
|
||||||
|
protected $content = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Header level
|
||||||
|
*
|
||||||
|
* @type int
|
||||||
|
*/
|
||||||
|
protected $level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to instruct search engines to not index the link to the section
|
||||||
|
*
|
||||||
|
* @type bool
|
||||||
|
*/
|
||||||
|
protected $noFollow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Title of the section
|
||||||
|
*
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
protected $title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the chapter the section belongs to
|
||||||
|
*
|
||||||
|
* @param DocSection $section
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setChapter(DocSection $section)
|
||||||
|
{
|
||||||
|
$this->chapter = $section;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the chapter the section belongs to
|
||||||
|
*
|
||||||
|
* @return DocSection
|
||||||
|
*/
|
||||||
|
public function getChapter()
|
||||||
|
{
|
||||||
|
return $this->chapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append content
|
||||||
|
*
|
||||||
|
* @param string $content
|
||||||
|
*/
|
||||||
|
public function appendContent($content)
|
||||||
|
{
|
||||||
|
$this->content[] = $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the content of the section
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getContent()
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the header level
|
||||||
|
*
|
||||||
|
* @param int $level Header level
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setLevel($level)
|
||||||
|
{
|
||||||
|
$this->level = (int) $level;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the header level
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getLevel()
|
||||||
|
{
|
||||||
|
return $this->level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether to instruct search engines to not index the link to the section
|
||||||
|
*
|
||||||
|
* @param bool $noFollow Whether to instruct search engines to not index the link to the section
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setNoFollow($noFollow = true)
|
||||||
|
{
|
||||||
|
$this->noFollow = (bool) $noFollow;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether to instruct search engines to not index the link to the section
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getNoFollow()
|
||||||
|
{
|
||||||
|
return $this->noFollow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the title of the section
|
||||||
|
*
|
||||||
|
* @param string $title Title of the section
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setTitle($title)
|
||||||
|
{
|
||||||
|
$this->title = (string) $title;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of the section
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTitle()
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
}
|
79
modules/doc/library/Doc/DocSectionFilterIterator.php
Normal file
79
modules/doc/library/Doc/DocSectionFilterIterator.php
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Icinga\Module\Doc;
|
||||||
|
|
||||||
|
use Countable;
|
||||||
|
use RecursiveFilterIterator;
|
||||||
|
use Icinga\Data\Tree\TreeNodeIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive filter iterator over sections that are part of a particular chapter
|
||||||
|
*
|
||||||
|
* @method TreeNodeIterator getInnerIterator() {
|
||||||
|
* {@inheritdoc}
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
class DocSectionFilterIterator extends RecursiveFilterIterator implements Countable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Chapter to filter for
|
||||||
|
*
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
protected $chapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new recursive filter iterator over sections that are part of a particular chapter
|
||||||
|
*
|
||||||
|
* @param TreeNodeIterator $iterator
|
||||||
|
* @param string $chapter The chapter to filter for
|
||||||
|
*/
|
||||||
|
public function __construct(TreeNodeIterator $iterator, $chapter)
|
||||||
|
{
|
||||||
|
parent::__construct($iterator);
|
||||||
|
$this->chapter = $chapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept sections that are part of the given chapter
|
||||||
|
*
|
||||||
|
* @return bool Whether the current element of the iterator is acceptable
|
||||||
|
* through this filter
|
||||||
|
*/
|
||||||
|
public function accept()
|
||||||
|
{
|
||||||
|
$section = $this->current();
|
||||||
|
/** @type \Icinga\Module\Doc\DocSection $section */
|
||||||
|
if ($section->getChapter()->getId() === $this->chapter) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getChildren()
|
||||||
|
{
|
||||||
|
return new static($this->getInnerIterator()->getChildren(), $this->chapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function count()
|
||||||
|
{
|
||||||
|
return iterator_count($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the filter swallowed every section
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isEmpty()
|
||||||
|
{
|
||||||
|
return $this->count() === 0;
|
||||||
|
}
|
||||||
|
}
|
@ -1,79 +0,0 @@
|
|||||||
<?php
|
|
||||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
|
||||||
|
|
||||||
namespace Icinga\Module\Doc;
|
|
||||||
|
|
||||||
use LogicException;
|
|
||||||
use Icinga\Data\Identifiable;
|
|
||||||
use Icinga\Data\Tree\Node;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Documentation tree
|
|
||||||
*/
|
|
||||||
class DocTree extends Node
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* All nodes of the tree
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $nodes = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a root node to the tree
|
|
||||||
*
|
|
||||||
* @param Identifiable $root
|
|
||||||
*/
|
|
||||||
public function addRoot(Identifiable $root)
|
|
||||||
{
|
|
||||||
$rootId = $root->getId();
|
|
||||||
if (isset($this->nodes[$rootId])) {
|
|
||||||
$rootId = uniqid($rootId);
|
|
||||||
// throw new LogicException(
|
|
||||||
// sprintf('Can\'t add root node: a root node with the id \'%s\' already exists', $rootId)
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
$this->nodes[$rootId] = $this->appendChild($root);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a child node to a parent node
|
|
||||||
*
|
|
||||||
* @param Identifiable $child
|
|
||||||
* @param Identifiable $parent
|
|
||||||
*
|
|
||||||
* @throws LogicException If the the tree does not contain the parent node
|
|
||||||
*/
|
|
||||||
public function addChild(Identifiable $child, Identifiable $parent)
|
|
||||||
{
|
|
||||||
$childId = $child->getId();
|
|
||||||
$parentId = $parent->getId();
|
|
||||||
if (isset($this->nodes[$childId])) {
|
|
||||||
$childId = uniqid($childId);
|
|
||||||
// throw new LogicException(
|
|
||||||
// sprintf('Can\'t add child node: a child node with the id \'%s\' already exists', $childId)
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
if (! isset($this->nodes[$parentId])) {
|
|
||||||
throw new LogicException(
|
|
||||||
sprintf(mt('doc', 'Can\'t add child node: there\'s no parent node having the id \'%s\''), $parentId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$this->nodes[$childId] = $this->nodes[$parentId]->appendChild($child);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a node
|
|
||||||
*
|
|
||||||
* @param mixed $id
|
|
||||||
*
|
|
||||||
* @return Node|null
|
|
||||||
*/
|
|
||||||
public function getNode($id)
|
|
||||||
{
|
|
||||||
if (! isset($this->nodes[$id])) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return $this->nodes[$id];
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,4 +6,6 @@ namespace Icinga\Module\Doc\Exception;
|
|||||||
/**
|
/**
|
||||||
* Exception thrown if a chapter was not found
|
* Exception thrown if a chapter was not found
|
||||||
*/
|
*/
|
||||||
class ChapterNotFoundException extends DocException {}
|
class ChapterNotFoundException extends DocException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
@ -6,4 +6,6 @@ namespace Icinga\Module\Doc\Exception;
|
|||||||
/**
|
/**
|
||||||
* Exception thrown if a documentation directory is empty
|
* Exception thrown if a documentation directory is empty
|
||||||
*/
|
*/
|
||||||
class DocEmptyException extends DocException {}
|
class DocEmptyException extends DocException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
@ -3,9 +3,11 @@
|
|||||||
|
|
||||||
namespace Icinga\Module\Doc\Exception;
|
namespace Icinga\Module\Doc\Exception;
|
||||||
|
|
||||||
use RuntimeException;
|
use Icinga\Exception\IcingaException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception thrown if an error in the documentation module's library occurs
|
* Exception thrown if an error in the documentation module's library occurs
|
||||||
*/
|
*/
|
||||||
class DocException extends RuntimeException {}
|
class DocException extends IcingaException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
<?php
|
|
||||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
|
||||||
|
|
||||||
namespace Icinga\Module\Doc;
|
|
||||||
|
|
||||||
use RecursiveIteratorIterator;
|
|
||||||
use Zend_View_Helper_Url;
|
|
||||||
use Icinga\Web\View;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for toc and section renderer
|
|
||||||
*/
|
|
||||||
abstract class Renderer extends RecursiveIteratorIterator
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Encode an anchor identifier
|
|
||||||
*
|
|
||||||
* @param string $anchor
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public static function encodeAnchor($anchor)
|
|
||||||
{
|
|
||||||
return rawurlencode($anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode an anchor identifier
|
|
||||||
*
|
|
||||||
* @param string $anchor
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public static function decodeAnchor($anchor)
|
|
||||||
{
|
|
||||||
return rawurldecode($anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode a URL parameter
|
|
||||||
*
|
|
||||||
* @param string $param
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public static function encodeUrlParam($param)
|
|
||||||
{
|
|
||||||
return str_replace(array('%2F','%5C'), array('%252F','%255C'), rawurlencode($param));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode a URL parameter
|
|
||||||
*
|
|
||||||
* @param string $param
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public static function decodeUrlParam($param)
|
|
||||||
{
|
|
||||||
return str_replace(array('%2F', '%5C'), array('/', '\\'), $param);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render to HTML
|
|
||||||
*
|
|
||||||
* Meant to be overwritten by concrete classes.
|
|
||||||
*
|
|
||||||
* @param View $view
|
|
||||||
* @param Zend_View_Helper_Url $zendUrlHelper
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
abstract public function render(View $view, Zend_View_Helper_Url $zendUrlHelper);
|
|
||||||
}
|
|
178
modules/doc/library/Doc/Renderer/DocRenderer.php
Normal file
178
modules/doc/library/Doc/Renderer/DocRenderer.php
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Icinga\Module\Doc\Renderer;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
use Icinga\Application\Icinga;
|
||||||
|
use Icinga\Web\View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for toc and section renderer
|
||||||
|
*/
|
||||||
|
abstract class DocRenderer extends RecursiveIteratorIterator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* URL to replace links with
|
||||||
|
*
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
protected $url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional URL parameters
|
||||||
|
*
|
||||||
|
* @type array
|
||||||
|
*/
|
||||||
|
protected $urlParams = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View
|
||||||
|
*
|
||||||
|
* @type View|null
|
||||||
|
*/
|
||||||
|
protected $view;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the URL to replace links with
|
||||||
|
*
|
||||||
|
* @param string $url
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setUrl($url)
|
||||||
|
{
|
||||||
|
$this->url = (string) $url;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the URL to replace links with
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getUrl()
|
||||||
|
{
|
||||||
|
return $this->url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set additional URL parameters
|
||||||
|
*
|
||||||
|
* @param array $urlParams
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setUrlParams(array $urlParams)
|
||||||
|
{
|
||||||
|
$this->urlParams = array_map(array($this, 'encodeUrlParam'), $urlParams);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get additional URL parameters
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getUrlParams()
|
||||||
|
{
|
||||||
|
return $this->urlParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the view
|
||||||
|
*
|
||||||
|
* @param View $view
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setView(View $view)
|
||||||
|
{
|
||||||
|
$this->view = $view;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the view
|
||||||
|
*
|
||||||
|
* @return View
|
||||||
|
*/
|
||||||
|
public function getView()
|
||||||
|
{
|
||||||
|
if ($this->view === null) {
|
||||||
|
$this->view = Icinga::app()->getViewRenderer()->view;
|
||||||
|
}
|
||||||
|
return $this->view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode an anchor identifier
|
||||||
|
*
|
||||||
|
* @param string $anchor
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function encodeAnchor($anchor)
|
||||||
|
{
|
||||||
|
return rawurlencode($anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode an anchor identifier
|
||||||
|
*
|
||||||
|
* @param string $anchor
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function decodeAnchor($anchor)
|
||||||
|
{
|
||||||
|
return rawurldecode($anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a URL parameter
|
||||||
|
*
|
||||||
|
* @param string $param
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function encodeUrlParam($param)
|
||||||
|
{
|
||||||
|
return str_replace(array('%2F','%5C'), array('%252F','%255C'), rawurlencode($param));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a URL parameter
|
||||||
|
*
|
||||||
|
* @param string $param
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function decodeUrlParam($param)
|
||||||
|
{
|
||||||
|
return str_replace(array('%2F', '%5C'), array('/', '\\'), $param);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render to HTML
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
abstract public function render();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render to HTML
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @see \Icinga\Module\Doc\Renderer::render() For the render method.
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return $this->render();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $e->getMessage() . ': ' . $e->getTraceAsString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
125
modules/doc/library/Doc/Renderer/DocSearchRenderer.php
Normal file
125
modules/doc/library/Doc/Renderer/DocSearchRenderer.php
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Icinga\Module\Doc\Renderer;
|
||||||
|
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
use Icinga\Module\Doc\Search\DocSearchIterator;
|
||||||
|
use Icinga\Module\Doc\Search\DocSearchMatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renderer for doc searches
|
||||||
|
*
|
||||||
|
* @method DocSearchIterator getInnerIterator() {
|
||||||
|
* @{inheritdoc}
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
class DocSearchRenderer extends DocRenderer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The content to render
|
||||||
|
*
|
||||||
|
* @type array
|
||||||
|
*/
|
||||||
|
protected $content = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new renderer for doc searches
|
||||||
|
*
|
||||||
|
* @param DocSearchIterator $iterator
|
||||||
|
*/
|
||||||
|
public function __construct (DocSearchIterator $iterator)
|
||||||
|
{
|
||||||
|
parent::__construct($iterator, RecursiveIteratorIterator::SELF_FIRST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function beginIteration()
|
||||||
|
{
|
||||||
|
$this->content[] = '<nav role="navigation"><ul class="toc">';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function endIteration()
|
||||||
|
{
|
||||||
|
$this->content[] = '</ul></nav>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function beginChildren()
|
||||||
|
{
|
||||||
|
if ($this->getInnerIterator()->getMatches()) {
|
||||||
|
$this->content[] = '<ul class="toc">';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function endChildren()
|
||||||
|
{
|
||||||
|
if ($this->getInnerIterator()->getMatches()) {
|
||||||
|
$this->content[] = '</ul>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
foreach ($this as $section) {
|
||||||
|
if (($matches = $this->getInnerIterator()->getMatches()) === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$title = $this->getView()->escape($section->getTitle());
|
||||||
|
$contentMatches = array();
|
||||||
|
foreach ($matches as $match) {
|
||||||
|
if ($match->getMatchType() === DocSearchMatch::MATCH_HEADER) {
|
||||||
|
$title = $match->highlight();
|
||||||
|
} else {
|
||||||
|
$contentMatches[] = sprintf(
|
||||||
|
'<p>%s</p>',
|
||||||
|
$match->highlight()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$path = $this->getView()->getHelper('Url')->url(
|
||||||
|
array_merge(
|
||||||
|
$this->getUrlParams(),
|
||||||
|
array(
|
||||||
|
'chapter' => $this->encodeUrlParam($section->getChapter()->getId())
|
||||||
|
)
|
||||||
|
),
|
||||||
|
$this->url,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
$url = $this->getView()->url(
|
||||||
|
$path,
|
||||||
|
array('highlight-search' => $this->getInnerIterator()->getSearch()->getInput())
|
||||||
|
);
|
||||||
|
/** @type \Icinga\Web\Url $url */
|
||||||
|
$url->setAnchor($this->encodeAnchor($section->getId()));
|
||||||
|
$this->content[] = sprintf(
|
||||||
|
'<li><a data-base-target="_next" %shref="%s">%s</a>',
|
||||||
|
$section->getNoFollow() ? 'rel="nofollow" ' : '',
|
||||||
|
$url->getAbsoluteUrl(),
|
||||||
|
$title
|
||||||
|
);
|
||||||
|
if (! empty($contentMatches)) {
|
||||||
|
$this->content = array_merge($this->content, $contentMatches);
|
||||||
|
}
|
||||||
|
if (! $section->hasChildren()) {
|
||||||
|
$this->content[] = '</li>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return implode("\n", $this->content);
|
||||||
|
}
|
||||||
|
}
|
284
modules/doc/library/Doc/Renderer/DocSectionRenderer.php
Normal file
284
modules/doc/library/Doc/Renderer/DocSectionRenderer.php
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Icinga\Module\Doc\Renderer;
|
||||||
|
|
||||||
|
require_once 'Parsedown/Parsedown.php';
|
||||||
|
|
||||||
|
use DOMDocument;
|
||||||
|
use DOMXPath;
|
||||||
|
use Parsedown;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
use Icinga\Data\Tree\SimpleTree;
|
||||||
|
use Icinga\Module\Doc\Exception\ChapterNotFoundException;
|
||||||
|
use Icinga\Module\Doc\DocSectionFilterIterator;
|
||||||
|
use Icinga\Module\Doc\Search\DocSearch;
|
||||||
|
use Icinga\Module\Doc\Search\DocSearchMatch;
|
||||||
|
use Icinga\Web\Dom\DomNodeIterator;
|
||||||
|
use Icinga\Web\Url;
|
||||||
|
use Icinga\Web\View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Section renderer
|
||||||
|
*/
|
||||||
|
class DocSectionRenderer extends DocRenderer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Content to render
|
||||||
|
*
|
||||||
|
* @type array
|
||||||
|
*/
|
||||||
|
protected $content = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search criteria to highlight
|
||||||
|
*
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
protected $highlightSearch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsedown instance
|
||||||
|
*
|
||||||
|
* @type Parsedown
|
||||||
|
*/
|
||||||
|
protected $parsedown;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Documentation tree
|
||||||
|
*
|
||||||
|
* @type SimpleTree
|
||||||
|
*/
|
||||||
|
protected $tree;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new section renderer
|
||||||
|
*
|
||||||
|
* @param SimpleTree $tree The documentation tree
|
||||||
|
* @param string|null $chapter If not null, the chapter to filter for
|
||||||
|
*
|
||||||
|
* @throws ChapterNotFoundException If the chapter to filter for was not found
|
||||||
|
*/
|
||||||
|
public function __construct(SimpleTree $tree, $chapter = null)
|
||||||
|
{
|
||||||
|
if ($chapter !== null) {
|
||||||
|
$filter = new DocSectionFilterIterator($tree->getIterator(), $chapter);
|
||||||
|
if ($filter->isEmpty()) {
|
||||||
|
throw new ChapterNotFoundException(
|
||||||
|
mt('doc', 'Chapter %s not found'), $chapter
|
||||||
|
);
|
||||||
|
}
|
||||||
|
parent::__construct(
|
||||||
|
$filter,
|
||||||
|
RecursiveIteratorIterator::SELF_FIRST
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
parent::__construct($tree->getIterator(), RecursiveIteratorIterator::SELF_FIRST);
|
||||||
|
}
|
||||||
|
$this->tree = $tree;
|
||||||
|
$this->parsedown = Parsedown::instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the search criteria to highlight
|
||||||
|
*
|
||||||
|
* @param string $highlightSearch
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setHighlightSearch($highlightSearch)
|
||||||
|
{
|
||||||
|
$this->highlightSearch = $highlightSearch;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the search criteria to highlight
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getHighlightSearch()
|
||||||
|
{
|
||||||
|
return $this->highlightSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syntax highlighting for PHP code
|
||||||
|
*
|
||||||
|
* @param array $match
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function highlightPhp($match)
|
||||||
|
{
|
||||||
|
return '<pre>' . highlight_string(htmlspecialchars_decode($match[1]), true) . '</pre>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight search criteria
|
||||||
|
*
|
||||||
|
* @param string $html
|
||||||
|
* @param DocSearch $search Search criteria
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function highlightSearch($html, DocSearch $search)
|
||||||
|
{
|
||||||
|
$doc = new DOMDocument();
|
||||||
|
$fragment = $doc->createDocumentFragment();
|
||||||
|
$fragment->appendXML($html);
|
||||||
|
$doc->appendChild($fragment);
|
||||||
|
$iter = new RecursiveIteratorIterator(new DomNodeIterator($doc), RecursiveIteratorIterator::SELF_FIRST);
|
||||||
|
foreach ($iter as $node) {
|
||||||
|
if ($node->nodeType !== XML_TEXT_NODE
|
||||||
|
|| ($node->parentNode->nodeType === XML_ELEMENT_NODE && $node->parentNode->tagName === 'code')
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$text = $node->nodeValue;
|
||||||
|
if (($match = $search->search($text)) === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$matches = $match->getMatches();
|
||||||
|
ksort($matches);
|
||||||
|
$offset = 0;
|
||||||
|
$fragment = $doc->createDocumentFragment();
|
||||||
|
foreach ($matches as $position => $match) {
|
||||||
|
$fragment->appendChild($doc->createTextNode(substr($text, $offset, $position - $offset)));
|
||||||
|
$fragment->appendChild($doc->createElement('span', $match))
|
||||||
|
->setAttribute('class', DocSearchMatch::HIGHLIGHT_CSS_CLASS);
|
||||||
|
$offset = $position + strlen($match);
|
||||||
|
}
|
||||||
|
$fragment->appendChild($doc->createTextNode(substr($text, $offset)));
|
||||||
|
$node->parentNode->replaceChild($fragment, $node);
|
||||||
|
}
|
||||||
|
return $doc->saveHTML();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Markup notes
|
||||||
|
*
|
||||||
|
* @param array $match
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function markupNotes($match)
|
||||||
|
{
|
||||||
|
$doc = new DOMDocument();
|
||||||
|
$doc->loadHTML($match[0]);
|
||||||
|
$xpath = new DOMXPath($doc);
|
||||||
|
$blockquote = $xpath->query('//blockquote[1]')->item(0);
|
||||||
|
/** @type \DOMElement $blockquote */
|
||||||
|
if (strtolower(substr(trim($blockquote->nodeValue), 0, 5)) === 'note:') {
|
||||||
|
$blockquote->setAttribute('class', 'note');
|
||||||
|
}
|
||||||
|
return $doc->saveXML($blockquote);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace img src tags
|
||||||
|
*
|
||||||
|
* @param $match
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function replaceImg($match)
|
||||||
|
{
|
||||||
|
$doc = new DOMDocument();
|
||||||
|
$doc->loadHTML($match[0]);
|
||||||
|
$xpath = new DOMXPath($doc);
|
||||||
|
$img = $xpath->query('//img[1]')->item(0);
|
||||||
|
/** @type \DOMElement $img */
|
||||||
|
$img->setAttribute('src', Url::fromPath($img->getAttribute('src'))->getAbsoluteUrl());
|
||||||
|
return substr_replace($doc->saveXML($img), '', -2, 1); // Replace '/>' with '>'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace link
|
||||||
|
*
|
||||||
|
* @param array $match
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function replaceLink($match)
|
||||||
|
{
|
||||||
|
if (($section = $this->tree->getNode($this->decodeAnchor($match['fragment']))) === null) {
|
||||||
|
return $match[0];
|
||||||
|
}
|
||||||
|
/** @type \Icinga\Module\Doc\DocSection $section */
|
||||||
|
$path = $this->getView()->getHelper('Url')->url(
|
||||||
|
array_merge(
|
||||||
|
$this->urlParams,
|
||||||
|
array(
|
||||||
|
'chapter' => $this->encodeUrlParam($section->getChapter()->getId())
|
||||||
|
)
|
||||||
|
),
|
||||||
|
$this->url,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
$url = $this->getView()->url($path);
|
||||||
|
/** @type \Icinga\Web\Url $url */
|
||||||
|
$url->setAnchor($this->encodeAnchor($section->getId()));
|
||||||
|
return sprintf(
|
||||||
|
'<a %s%shref="%s"',
|
||||||
|
strlen($match['attribs']) ? trim($match['attribs']) . ' ' : '',
|
||||||
|
$section->getNoFollow() ? 'rel="nofollow" ' : '',
|
||||||
|
$url->getAbsoluteUrl()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$search = null;
|
||||||
|
if (($highlightSearch = $this->getHighlightSearch()) !== null) {
|
||||||
|
$search = new DocSearch($highlightSearch);
|
||||||
|
}
|
||||||
|
foreach ($this as $section) {
|
||||||
|
$title = $section->getTitle();
|
||||||
|
if ($search !== null && ($match = $search->search($title)) !== null) {
|
||||||
|
$title = $match->highlight();
|
||||||
|
} else {
|
||||||
|
$title = $this->getView()->escape($title);
|
||||||
|
}
|
||||||
|
$this->content[] = sprintf(
|
||||||
|
'<a name="%1$s"></a><h%2$d>%3$s</h%2$d>',
|
||||||
|
static::encodeAnchor($section->getId()),
|
||||||
|
$section->getLevel(),
|
||||||
|
$title
|
||||||
|
);
|
||||||
|
$content = $this->parsedown->text(implode('', $section->getContent()));
|
||||||
|
if (empty($content)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$html = preg_replace_callback(
|
||||||
|
'#<pre><code class="language-php">(.*?)</code></pre>#s',
|
||||||
|
array($this, 'highlightPhp'),
|
||||||
|
$content
|
||||||
|
);
|
||||||
|
$html = preg_replace_callback(
|
||||||
|
'/<img[^>]+>/',
|
||||||
|
array($this, 'replaceImg'),
|
||||||
|
$html
|
||||||
|
);
|
||||||
|
$html = preg_replace_callback(
|
||||||
|
'#<blockquote>.+</blockquote>#ms',
|
||||||
|
array($this, 'markupNotes'),
|
||||||
|
$html
|
||||||
|
);
|
||||||
|
$html = preg_replace_callback(
|
||||||
|
'/<a\s+(?P<attribs>[^>]*?\s+)?href="(?:(?!http:\/\/)[^#]*)#(?P<fragment>[^"]+)"/',
|
||||||
|
array($this, 'replaceLink'),
|
||||||
|
$html
|
||||||
|
);
|
||||||
|
if ($search !== null) {
|
||||||
|
$html = $this->highlightSearch($html, $search);
|
||||||
|
}
|
||||||
|
$this->content[] = $html;
|
||||||
|
}
|
||||||
|
return implode("\n", $this->content);
|
||||||
|
}
|
||||||
|
}
|
102
modules/doc/library/Doc/Renderer/DocTocRenderer.php
Normal file
102
modules/doc/library/Doc/Renderer/DocTocRenderer.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Icinga\Module\Doc\Renderer;
|
||||||
|
|
||||||
|
use Icinga\Web\View;
|
||||||
|
use Icinga\Data\Tree\TreeNodeIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TOC renderer
|
||||||
|
*
|
||||||
|
* @method TreeNodeIterator getInnerIterator() {
|
||||||
|
* {@inheritdoc}
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
class DocTocRenderer extends DocRenderer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Content to render
|
||||||
|
*
|
||||||
|
* @type array
|
||||||
|
*/
|
||||||
|
protected $content = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new toc renderer
|
||||||
|
*
|
||||||
|
* @param TreeNodeIterator $iterator
|
||||||
|
*/
|
||||||
|
public function __construct(TreeNodeIterator $iterator)
|
||||||
|
{
|
||||||
|
parent::__construct($iterator, RecursiveIteratorIterator::SELF_FIRST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function beginIteration()
|
||||||
|
{
|
||||||
|
$this->content[] = '<nav role="navigation"><ul class="toc">';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function endIteration()
|
||||||
|
{
|
||||||
|
$this->content[] = '</ul></nav>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function beginChildren()
|
||||||
|
{
|
||||||
|
$this->content[] = '<ul class="toc">';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function endChildren()
|
||||||
|
{
|
||||||
|
$this->content[] = '</ul>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$view = $this->getView();
|
||||||
|
$zendUrlHelper = $view->getHelper('Url');
|
||||||
|
foreach ($this as $section) {
|
||||||
|
$path = $zendUrlHelper->url(
|
||||||
|
array_merge(
|
||||||
|
$this->urlParams,
|
||||||
|
array(
|
||||||
|
'chapter' => $this->encodeUrlParam($section->getChapter()->getId())
|
||||||
|
)
|
||||||
|
),
|
||||||
|
$this->url,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
$url = $view->url($path);
|
||||||
|
/** @type \Icinga\Web\Url $url */
|
||||||
|
$url->setAnchor($this->encodeAnchor($section->getId()));
|
||||||
|
$this->content[] = sprintf(
|
||||||
|
'<li><a data-base-target="_next" %shref="%s">%s</a>',
|
||||||
|
$section->getNoFollow() ? 'rel="nofollow" ' : '',
|
||||||
|
$url->getAbsoluteUrl(),
|
||||||
|
$view->escape($section->getTitle())
|
||||||
|
);
|
||||||
|
if (! $section->hasChildren()) {
|
||||||
|
$this->content[] = '</li>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return implode("\n", $this->content);
|
||||||
|
}
|
||||||
|
}
|
95
modules/doc/library/Doc/Search/DocSearch.php
Normal file
95
modules/doc/library/Doc/Search/DocSearch.php
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Icinga\Module\Doc\Search;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search documentation for a given search string
|
||||||
|
*/
|
||||||
|
class DocSearch
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Search string
|
||||||
|
*
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
protected $input;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search criteria
|
||||||
|
*
|
||||||
|
* @type array
|
||||||
|
*/
|
||||||
|
protected $search;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new doc search from the given search string
|
||||||
|
*
|
||||||
|
* @param string $search
|
||||||
|
*/
|
||||||
|
public function __construct($search)
|
||||||
|
{
|
||||||
|
$this->input = $search = (string) $search;
|
||||||
|
$criteria = array();
|
||||||
|
if (preg_match_all('/"(?P<search>[^"]*)"/', $search, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
|
||||||
|
$unquoted = array();
|
||||||
|
$offset = 0;
|
||||||
|
foreach ($matches as $match) {
|
||||||
|
$fullMatch = $match[0];
|
||||||
|
$searchMatch = $match['search'];
|
||||||
|
$unquoted[] = substr($search, $offset, $fullMatch[1] - $offset);
|
||||||
|
$offset = $fullMatch[1] + strlen($fullMatch[0]);
|
||||||
|
if (strlen($searchMatch[0]) > 0) {
|
||||||
|
$criteria[] = $searchMatch[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$unquoted[] = substr($search, $offset);
|
||||||
|
$search = implode(' ', $unquoted);
|
||||||
|
}
|
||||||
|
$this->search = array_map(
|
||||||
|
'strtolower',
|
||||||
|
array_unique(array_merge($criteria, array_filter(explode(' ', trim($search)))))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the search criteria
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getCriteria()
|
||||||
|
{
|
||||||
|
return $this->search;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the search string
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getInput()
|
||||||
|
{
|
||||||
|
return $this->input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search in the given line
|
||||||
|
*
|
||||||
|
* @param string $line
|
||||||
|
*
|
||||||
|
* @return DocSearchMatch|null
|
||||||
|
*/
|
||||||
|
public function search($line)
|
||||||
|
{
|
||||||
|
$match = new DocSearchMatch();
|
||||||
|
$match->setLine($line);
|
||||||
|
foreach ($this->search as $criteria) {
|
||||||
|
$offset = 0;
|
||||||
|
while (($position = stripos($line, $criteria, $offset)) !== false) {
|
||||||
|
$match->appendMatch(substr($line, $position, strlen($criteria)), $position);
|
||||||
|
$offset = $position + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $match->isEmpty() ? null : $match;
|
||||||
|
}
|
||||||
|
}
|
120
modules/doc/library/Doc/Search/DocSearchIterator.php
Normal file
120
modules/doc/library/Doc/Search/DocSearchIterator.php
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Icinga\Module\Doc\Search;
|
||||||
|
|
||||||
|
use RecursiveFilterIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
use Icinga\Data\Tree\TreeNodeIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator over doc sections that match a given search criteria
|
||||||
|
*
|
||||||
|
* @method TreeNodeIterator getInnerIterator() {
|
||||||
|
* {@inheritdoc}
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
class DocSearchIterator extends RecursiveFilterIterator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Search criteria
|
||||||
|
*
|
||||||
|
* @type DocSearch
|
||||||
|
*/
|
||||||
|
protected $search;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current search matches
|
||||||
|
*
|
||||||
|
* @type DocSearchMatch[]|null
|
||||||
|
*/
|
||||||
|
protected $matches;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new iterator over doc sections that match the given search criteria
|
||||||
|
*
|
||||||
|
* @param TreeNodeIterator $iterator
|
||||||
|
* @param DocSearch $search
|
||||||
|
*/
|
||||||
|
public function __construct(TreeNodeIterator $iterator, DocSearch $search)
|
||||||
|
{
|
||||||
|
$this->search = $search;
|
||||||
|
parent::__construct($iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept sections that match the search
|
||||||
|
*
|
||||||
|
* @return bool Whether the current element of the iterator is acceptable
|
||||||
|
* through this filter
|
||||||
|
*/
|
||||||
|
public function accept()
|
||||||
|
{
|
||||||
|
$section = $this->current();
|
||||||
|
/** @type $section \Icinga\Module\Doc\DocSection */
|
||||||
|
$matches = array();
|
||||||
|
if (($match = $this->search->search($section->getTitle())) !== null) {
|
||||||
|
$matches[] = $match->setMatchType(DocSearchMatch::MATCH_HEADER);
|
||||||
|
}
|
||||||
|
foreach ($section->getContent() as $lineno => $line) {
|
||||||
|
if (($match = $this->search->search($line)) !== null) {
|
||||||
|
$matches[] = $match
|
||||||
|
->setMatchType(DocSearchMatch::MATCH_CONTENT)
|
||||||
|
->setLineno($lineno);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! empty($matches)) {
|
||||||
|
$this->matches = $matches;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
if ($section->hasChildren()) {
|
||||||
|
$this->matches = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the search criteria
|
||||||
|
*
|
||||||
|
* @return DocSearch
|
||||||
|
*/
|
||||||
|
public function getSearch()
|
||||||
|
{
|
||||||
|
return $this->search;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getChildren()
|
||||||
|
{
|
||||||
|
return new static($this->getInnerIterator()->getChildren(), $this->search);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the search did not yield any match
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isEmpty()
|
||||||
|
{
|
||||||
|
$iter = new RecursiveIteratorIterator($this, RecursiveIteratorIterator::SELF_FIRST);
|
||||||
|
foreach ($iter as $section) {
|
||||||
|
if ($iter->getInnerIterator()->getMatches() !== null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current matches
|
||||||
|
*
|
||||||
|
* @return DocSearchMatch[]|null
|
||||||
|
*/
|
||||||
|
public function getMatches()
|
||||||
|
{
|
||||||
|
return $this->matches;
|
||||||
|
}
|
||||||
|
}
|
215
modules/doc/library/Doc/Search/DocSearchMatch.php
Normal file
215
modules/doc/library/Doc/Search/DocSearchMatch.php
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
<?php
|
||||||
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
namespace Icinga\Module\Doc\Search;
|
||||||
|
|
||||||
|
use UnexpectedValueException;
|
||||||
|
use Icinga\Application\Icinga;
|
||||||
|
use Icinga\Web\View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A doc search match
|
||||||
|
*/
|
||||||
|
class DocSearchMatch
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* CSS class for highlighting matches
|
||||||
|
*
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
const HIGHLIGHT_CSS_CLASS = 'search-highlight';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Header match
|
||||||
|
*
|
||||||
|
* @type int
|
||||||
|
*/
|
||||||
|
const MATCH_HEADER = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content match
|
||||||
|
*
|
||||||
|
* @type int
|
||||||
|
*/
|
||||||
|
const MATCH_CONTENT = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Line
|
||||||
|
*
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
protected $line;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Line number
|
||||||
|
*
|
||||||
|
* @type int
|
||||||
|
*/
|
||||||
|
protected $lineno;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of the match
|
||||||
|
*
|
||||||
|
* @type int
|
||||||
|
*/
|
||||||
|
protected $matchType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches
|
||||||
|
*
|
||||||
|
* @type array
|
||||||
|
*/
|
||||||
|
protected $matches = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View
|
||||||
|
*
|
||||||
|
* @type View|null
|
||||||
|
*/
|
||||||
|
protected $view;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the line
|
||||||
|
*
|
||||||
|
* @param string $line
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setLine($line)
|
||||||
|
{
|
||||||
|
$this->line = (string) $line;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the line
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLine()
|
||||||
|
{
|
||||||
|
return $this->line;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the line number
|
||||||
|
*
|
||||||
|
* @param int $lineno
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setLineno($lineno)
|
||||||
|
{
|
||||||
|
$this->lineno = (int) $lineno;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the match type
|
||||||
|
*
|
||||||
|
* @param int $matchType
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setMatchType($matchType)
|
||||||
|
{
|
||||||
|
$matchType = (int) $matchType;
|
||||||
|
if ($matchType !== static::MATCH_HEADER && $matchType !== static::MATCH_CONTENT) {
|
||||||
|
throw new UnexpectedValueException();
|
||||||
|
}
|
||||||
|
$this->matchType = $matchType;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the match type
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getMatchType()
|
||||||
|
{
|
||||||
|
return $this->matchType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a match
|
||||||
|
*
|
||||||
|
* @param string $match
|
||||||
|
* @param int $position
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function appendMatch($match, $position)
|
||||||
|
{
|
||||||
|
$this->matches[(int) $position] = (string) $match;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the matches
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getMatches()
|
||||||
|
{
|
||||||
|
return $this->matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the view
|
||||||
|
*
|
||||||
|
* @param View $view
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setView(View $view)
|
||||||
|
{
|
||||||
|
$this->view = $view;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the view
|
||||||
|
*
|
||||||
|
* @return View
|
||||||
|
*/
|
||||||
|
public function getView()
|
||||||
|
{
|
||||||
|
if ($this->view === null) {
|
||||||
|
$this->view = Icinga::app()->getViewRenderer()->view;
|
||||||
|
}
|
||||||
|
return $this->view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the line having matches highlighted
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function highlight()
|
||||||
|
{
|
||||||
|
$highlighted = '';
|
||||||
|
$offset = 0;
|
||||||
|
$matches = $this->getMatches();
|
||||||
|
ksort($matches);
|
||||||
|
foreach ($matches as $position => $match) {
|
||||||
|
$highlighted .= $this->getView()->escape(substr($this->line, $offset, $position - $offset))
|
||||||
|
. '<span class="' . static::HIGHLIGHT_CSS_CLASS .'">'
|
||||||
|
. $this->getView()->escape($match)
|
||||||
|
. '</span>';
|
||||||
|
$offset = $position + strlen($match);
|
||||||
|
}
|
||||||
|
$highlighted .= $this->getView()->escape(substr($this->line, $offset));
|
||||||
|
return $highlighted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the match is empty
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isEmpty()
|
||||||
|
{
|
||||||
|
return empty($this->matches);
|
||||||
|
}
|
||||||
|
}
|
@ -1,142 +0,0 @@
|
|||||||
<?php
|
|
||||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
|
||||||
|
|
||||||
namespace Icinga\Module\Doc;
|
|
||||||
|
|
||||||
use Icinga\Data\Identifiable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A section of a documentation
|
|
||||||
*/
|
|
||||||
class Section implements Identifiable
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The ID of the section
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The title of the section
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $title;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The header level
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
protected $level;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to instruct search engines to not index the link to the section
|
|
||||||
*
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
protected $noFollow;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ID of the chapter the section is part of
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $chapterId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The content of the section
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $content = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new section
|
|
||||||
*
|
|
||||||
* @param string $id The ID of the section
|
|
||||||
* @param string $title The title of the section
|
|
||||||
* @param int $level The header level
|
|
||||||
* @param bool $noFollow Whether to instruct search engines to not index the link to the section
|
|
||||||
* @param string $chapterId The ID of the chapter the section is part of
|
|
||||||
*/
|
|
||||||
public function __construct($id, $title, $level, $noFollow, $chapterId)
|
|
||||||
{
|
|
||||||
$this->id = $id;
|
|
||||||
$this->title = $title;
|
|
||||||
$this->level = $level;
|
|
||||||
$this->noFollow = $noFollow;
|
|
||||||
$this->chapterId= $chapterId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the ID of the section
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the title of the section
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getTitle()
|
|
||||||
{
|
|
||||||
return $this->title;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the header level
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function getLevel()
|
|
||||||
{
|
|
||||||
return $this->level;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to instruct search engines to not index the link to the section
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isNoFollow()
|
|
||||||
{
|
|
||||||
return $this->noFollow;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ID of the chapter the section is part of
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getChapterId()
|
|
||||||
{
|
|
||||||
return $this->chapterId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append content
|
|
||||||
*
|
|
||||||
* @param string $content
|
|
||||||
*/
|
|
||||||
public function appendContent($content)
|
|
||||||
{
|
|
||||||
$this->content[] = $content;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the content of the section
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getContent()
|
|
||||||
{
|
|
||||||
return $this->content;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
<?php
|
|
||||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
|
||||||
|
|
||||||
namespace Icinga\Module\Doc;
|
|
||||||
|
|
||||||
use Countable;
|
|
||||||
use RecursiveFilterIterator;
|
|
||||||
use Icinga\Data\Tree\NodeInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursive iterator over sections that are part of a particular chapter
|
|
||||||
*/
|
|
||||||
class SectionFilterIterator extends RecursiveFilterIterator implements Countable
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The chapter ID to filter for
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $chapterId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new SectionFilterIterator
|
|
||||||
*
|
|
||||||
* @param NodeInterface $node Node
|
|
||||||
* @param string $chapterId The chapter ID to filter for
|
|
||||||
*/
|
|
||||||
public function __construct(NodeInterface $node, $chapterId)
|
|
||||||
{
|
|
||||||
parent::__construct($node);
|
|
||||||
$this->chapterId = $chapterId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accept sections that are part of the given chapter
|
|
||||||
*
|
|
||||||
* @return bool Whether the current element of the iterator is acceptable
|
|
||||||
* through this filter
|
|
||||||
*/
|
|
||||||
public function accept()
|
|
||||||
{
|
|
||||||
$section = $this->getInnerIterator()->current()->getValue();
|
|
||||||
/* @var $section \Icinga\Module\Doc\Section */
|
|
||||||
if ($section->getChapterId() === $this->chapterId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (non-PHPDoc)
|
|
||||||
* @see RecursiveFilterIterator::getChildren()
|
|
||||||
*/
|
|
||||||
public function getChildren()
|
|
||||||
{
|
|
||||||
return new static($this->getInnerIterator()->getChildren(), $this->chapterId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (non-PHPDoc)
|
|
||||||
* @see Countable::count()
|
|
||||||
*/
|
|
||||||
public function count()
|
|
||||||
{
|
|
||||||
return iterator_count($this);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,309 +0,0 @@
|
|||||||
<?php
|
|
||||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
|
||||||
|
|
||||||
namespace Icinga\Module\Doc;
|
|
||||||
|
|
||||||
require_once 'Parsedown/Parsedown.php';
|
|
||||||
|
|
||||||
use DOMDocument;
|
|
||||||
use DOMXPath;
|
|
||||||
use RecursiveIteratorIterator;
|
|
||||||
use Parsedown;
|
|
||||||
use Zend_View_Helper_Url;
|
|
||||||
use Icinga\Module\Doc\Exception\ChapterNotFoundException;
|
|
||||||
use Icinga\Web\Url;
|
|
||||||
use Icinga\Web\View;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* preg_replace_callback helper to replace links
|
|
||||||
*/
|
|
||||||
class Callback
|
|
||||||
{
|
|
||||||
protected $docTree;
|
|
||||||
|
|
||||||
protected $view;
|
|
||||||
|
|
||||||
protected $zendUrlHelper;
|
|
||||||
|
|
||||||
protected $url;
|
|
||||||
|
|
||||||
protected $urlParams;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
DocTree $docTree,
|
|
||||||
View $view,
|
|
||||||
Zend_View_Helper_Url $zendUrlHelper,
|
|
||||||
$url,
|
|
||||||
array $urlParams)
|
|
||||||
{
|
|
||||||
$this->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(
|
|
||||||
'chapterId' => SectionRenderer::encodeUrlParam($section->getChapterId())
|
|
||||||
)
|
|
||||||
),
|
|
||||||
$this->url,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
$url = $this->view->url($path);
|
|
||||||
$url->setAnchor(SectionRenderer::encodeAnchor($section->getId()));
|
|
||||||
return sprintf(
|
|
||||||
'<a %s%shref="%s"',
|
|
||||||
strlen($match['attribs']) ? trim($match['attribs']) . ' ' : '',
|
|
||||||
$section->isNoFollow() ? 'rel="nofollow" ' : '',
|
|
||||||
$url->getAbsoluteUrl()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Section renderer
|
|
||||||
*/
|
|
||||||
class SectionRenderer extends Renderer
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The documentation tree
|
|
||||||
*
|
|
||||||
* @var DocTree
|
|
||||||
*/
|
|
||||||
protected $docTree;
|
|
||||||
|
|
||||||
protected $tocUrl;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 $chapterId If not null, the chapter ID to filter for
|
|
||||||
* @param string $tocUrl
|
|
||||||
* @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, $chapterId, $tocUrl, $url, array $urlParams)
|
|
||||||
{
|
|
||||||
if ($chapterId !== null) {
|
|
||||||
$filter = new SectionFilterIterator($docTree, $chapterId);
|
|
||||||
if ($filter->count() === 0) {
|
|
||||||
throw new ChapterNotFoundException(
|
|
||||||
sprintf(mt('doc', 'Chapter \'%s\' not found'), $chapterId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
parent::__construct(
|
|
||||||
$filter,
|
|
||||||
RecursiveIteratorIterator::SELF_FIRST
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
parent::__construct($docTree, RecursiveIteratorIterator::SELF_FIRST);
|
|
||||||
}
|
|
||||||
$this->docTree = $docTree;
|
|
||||||
$this->tocUrl = $tocUrl;
|
|
||||||
$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 '<pre>' . highlight_string(htmlspecialchars_decode($match[1]), true) . '</pre>';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace img src tags
|
|
||||||
*
|
|
||||||
* @param $match
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function replaceImg($match)
|
|
||||||
{
|
|
||||||
$doc = new DOMDocument();
|
|
||||||
$doc->loadHTML($match[0]);
|
|
||||||
$xpath = new DOMXPath($doc);
|
|
||||||
$img = $xpath->query('//img[1]')->item(0);
|
|
||||||
/* @var $img \DOMElement */
|
|
||||||
$img->setAttribute('src', Url::fromPath($img->getAttribute('src'))->getAbsoluteUrl());
|
|
||||||
return substr_replace($doc->saveXML($img), '', -2, 1); // Replace '/>' with '>'
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function blubb($match)
|
|
||||||
{
|
|
||||||
$doc = new DOMDocument();
|
|
||||||
$doc->loadHTML($match[0]);
|
|
||||||
$xpath = new DOMXPath($doc);
|
|
||||||
$blockquote = $xpath->query('//blockquote[1]')->item(0);
|
|
||||||
/* @var $blockquote \DOMElement */
|
|
||||||
if (strtolower(substr(trim($blockquote->nodeValue), 0, 5)) === 'note:') {
|
|
||||||
$blockquote->setAttribute('class', 'note');
|
|
||||||
}
|
|
||||||
return $doc->saveXML($blockquote);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the section
|
|
||||||
*
|
|
||||||
* @param View $view
|
|
||||||
* @param Zend_View_Helper_Url $zendUrlHelper
|
|
||||||
* @param bool $renderNavigation
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function render(View $view, Zend_View_Helper_Url $zendUrlHelper, $renderNavigation = true)
|
|
||||||
{
|
|
||||||
$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(
|
|
||||||
'<a name="%1$s"></a><h%2$d>%3$s</h%2$d>',
|
|
||||||
Renderer::encodeAnchor($section->getId()),
|
|
||||||
$section->getLevel(),
|
|
||||||
$view->escape($section->getTitle())
|
|
||||||
);
|
|
||||||
$html = preg_replace_callback(
|
|
||||||
'#<pre><code class="language-php">(.*?)</code></pre>#s',
|
|
||||||
array($this, 'highlightPhp'),
|
|
||||||
$this->parsedown->text(implode('', $section->getContent()))
|
|
||||||
);
|
|
||||||
$html = preg_replace_callback(
|
|
||||||
'/<img[^>]+>/',
|
|
||||||
array($this, 'replaceImg'),
|
|
||||||
$html
|
|
||||||
);
|
|
||||||
$html = preg_replace_callback(
|
|
||||||
'#<blockquote>.+</blockquote>#ms',
|
|
||||||
array($this, 'blubb'),
|
|
||||||
$html
|
|
||||||
);
|
|
||||||
$content[] = preg_replace_callback(
|
|
||||||
'/<a\s+(?P<attribs>[^>]*?\s+)?href="(?:(?!http:\/\/)[^#]*)#(?P<fragment>[^"]+)"/',
|
|
||||||
array($callback, 'render'),
|
|
||||||
$html
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if ($renderNavigation) {
|
|
||||||
foreach ($this->docTree as $chapter) {
|
|
||||||
if ($chapter->getValue()->getId() === $section->getChapterId()) {
|
|
||||||
$navigation = array('<ul class="navigation">');
|
|
||||||
$this->docTree->prev();
|
|
||||||
$prev = $this->docTree->current();
|
|
||||||
if ($prev !== null) {
|
|
||||||
$prev = $prev->getValue();
|
|
||||||
$path = $zendUrlHelper->url(
|
|
||||||
array_merge(
|
|
||||||
$this->urlParams,
|
|
||||||
array(
|
|
||||||
'chapterId' => $this->encodeUrlParam($prev->getChapterId())
|
|
||||||
)
|
|
||||||
),
|
|
||||||
$this->url,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
$url = $view->url($path);
|
|
||||||
$url->setAnchor($this->encodeAnchor($prev->getId()));
|
|
||||||
$navigation[] = sprintf(
|
|
||||||
'<li class="prev"><a %shref="%s">%s</a></li>',
|
|
||||||
$prev->isNoFollow() ? 'rel="nofollow" ' : '',
|
|
||||||
$url->getAbsoluteUrl(),
|
|
||||||
$view->escape($prev->getTitle())
|
|
||||||
);
|
|
||||||
$this->docTree->next();
|
|
||||||
$this->docTree->next();
|
|
||||||
} else {
|
|
||||||
$this->docTree->rewind();
|
|
||||||
$this->docTree->next();
|
|
||||||
}
|
|
||||||
$url = $view->url($this->tocUrl);
|
|
||||||
$navigation[] = sprintf(
|
|
||||||
'<li><a href="%s">%s</a></li>',
|
|
||||||
$url->getAbsoluteUrl(),
|
|
||||||
mt('doc', 'Index')
|
|
||||||
);
|
|
||||||
$next = $this->docTree->current();
|
|
||||||
if ($next !== null) {
|
|
||||||
$next = $next->getValue();
|
|
||||||
$path = $zendUrlHelper->url(
|
|
||||||
array_merge(
|
|
||||||
$this->urlParams,
|
|
||||||
array(
|
|
||||||
'chapterId' => $this->encodeUrlParam($next->getChapterId())
|
|
||||||
)
|
|
||||||
),
|
|
||||||
$this->url,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
$url = $view->url($path);
|
|
||||||
$url->setAnchor($this->encodeAnchor($next->getId()));
|
|
||||||
$navigation[] = sprintf(
|
|
||||||
'<li class="next"><a %shref="%s">%s</a></li>',
|
|
||||||
$next->isNoFollow() ? 'rel="nofollow" ' : '',
|
|
||||||
$url->getAbsoluteUrl(),
|
|
||||||
$view->escape($next->getTitle())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$navigation[] = '</ul>';
|
|
||||||
$content = array_merge($navigation, $content, $navigation);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return implode("\n", $content);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,108 +0,0 @@
|
|||||||
<?php
|
|
||||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
|
||||||
|
|
||||||
namespace Icinga\Module\Doc;
|
|
||||||
|
|
||||||
use RecursiveIteratorIterator;
|
|
||||||
use Zend_View_Helper_Url;
|
|
||||||
use Icinga\Web\View;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TOC renderer
|
|
||||||
*/
|
|
||||||
class TocRenderer extends Renderer
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The URL to replace links with
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $url;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Additional URL parameters
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $urlParams;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Content
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $content = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new toc renderer
|
|
||||||
*
|
|
||||||
* @param DocTree $docTree The documentation tree
|
|
||||||
* @param string $url The URL to replace links with
|
|
||||||
* @param array $urlParams Additional URL parameters
|
|
||||||
*/
|
|
||||||
public function __construct(DocTree $docTree, $url, array $urlParams)
|
|
||||||
{
|
|
||||||
parent::__construct($docTree, RecursiveIteratorIterator::SELF_FIRST);
|
|
||||||
$this->url = $url;
|
|
||||||
$this->urlParams = array_map(array($this, 'encodeUrlParam'), $urlParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function beginIteration()
|
|
||||||
{
|
|
||||||
$this->content[] = '<nav><ul>';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function endIteration()
|
|
||||||
{
|
|
||||||
$this->content[] = '</ul></nav>';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function beginChildren()
|
|
||||||
{
|
|
||||||
$this->content[] = '<ul>';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function endChildren()
|
|
||||||
{
|
|
||||||
$this->content[] = '</ul></li>';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the toc
|
|
||||||
*
|
|
||||||
* @param View $view
|
|
||||||
* @param Zend_View_Helper_Url $zendUrlHelper
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function render(View $view, Zend_View_Helper_Url $zendUrlHelper)
|
|
||||||
{
|
|
||||||
foreach ($this as $node) {
|
|
||||||
$section = $node->getValue();
|
|
||||||
/* @var $section \Icinga\Module\Doc\Section */
|
|
||||||
$path = $zendUrlHelper->url(
|
|
||||||
array_merge(
|
|
||||||
$this->urlParams,
|
|
||||||
array(
|
|
||||||
'chapterId' => $this->encodeUrlParam($section->getChapterId())
|
|
||||||
)
|
|
||||||
),
|
|
||||||
$this->url,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
$url = $view->url($path);
|
|
||||||
$url->setAnchor($this->encodeAnchor($section->getId()));
|
|
||||||
$this->content[] = sprintf(
|
|
||||||
'<li><a %shref="%s">%s</a>',
|
|
||||||
$section->isNoFollow() ? 'rel="nofollow" ' : '',
|
|
||||||
$url->getAbsoluteUrl(),
|
|
||||||
$view->escape($section->getTitle())
|
|
||||||
);
|
|
||||||
if (! $this->getInnerIterator()->current()->hasChildren()) {
|
|
||||||
$this->content[] = '</li>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return implode("\n", $this->content);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,5 @@
|
|||||||
/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
div.chapter {
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uppercase {
|
.uppercase {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
@ -22,18 +18,6 @@ div.chapter {
|
|||||||
background: linear-gradient(to bottom, @a 0%, @b 100%);
|
background: linear-gradient(to bottom, @a 0%, @b 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.box-shadow(@x: 3px; @y: 3px; @blur: 2px; @spread: 0px; @color: rgba(45, 45, 45, 0.75)) {
|
|
||||||
-webkit-box-shadow: @arguments;
|
|
||||||
-moz-box-shadow: @arguments;
|
|
||||||
box-shadow: @arguments;
|
|
||||||
}
|
|
||||||
|
|
||||||
.round-corners {
|
|
||||||
-moz-border-radius: 0.2em;
|
|
||||||
-webkit-border-radius: 0.2em;
|
|
||||||
border-radius: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
table {
|
||||||
// Reset
|
// Reset
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
@ -77,7 +61,6 @@ thead {
|
|||||||
.uppercase;
|
.uppercase;
|
||||||
.bold;
|
.bold;
|
||||||
}
|
}
|
||||||
position: sticky;
|
|
||||||
border-bottom: 0.25rem solid @icinga;
|
border-bottom: 0.25rem solid @icinga;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,31 +88,12 @@ pre > code {
|
|||||||
.box-shadow;
|
.box-shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.chapter > ul.navigation {
|
ul.toc {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.4em;
|
padding: 0 0 0 1em;
|
||||||
text-align: center;
|
|
||||||
background-color: #888;
|
|
||||||
|
|
||||||
li {
|
|
||||||
list-style: none;
|
|
||||||
display: inline;
|
|
||||||
margin: 0.2em;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #fff;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.prev {
|
.search-highlight {
|
||||||
padding-right: 0.6em;
|
color: #FBE012;
|
||||||
border-right: 2px solid #fff;
|
background: @icinga;
|
||||||
}
|
|
||||||
|
|
||||||
&.next {
|
|
||||||
padding-left: 0.6em;
|
|
||||||
border-left: 2px solid #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ if (Icinga::app()->isCli()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$docModuleChapter = new Zend_Controller_Router_Route(
|
$docModuleChapter = new Zend_Controller_Router_Route(
|
||||||
'doc/module/:moduleName/chapter/:chapterId',
|
'doc/module/:moduleName/chapter/:chapter',
|
||||||
array(
|
array(
|
||||||
'controller' => 'module',
|
'controller' => 'module',
|
||||||
'action' => 'chapter',
|
'action' => 'chapter',
|
||||||
@ -18,7 +18,7 @@ $docModuleChapter = new Zend_Controller_Router_Route(
|
|||||||
);
|
);
|
||||||
|
|
||||||
$docIcingaWebChapter = new Zend_Controller_Router_Route(
|
$docIcingaWebChapter = new Zend_Controller_Router_Route(
|
||||||
'doc/icingaweb/chapter/:chapterId',
|
'doc/icingaweb/chapter/:chapter',
|
||||||
array(
|
array(
|
||||||
'controller' => 'icingaweb',
|
'controller' => 'icingaweb',
|
||||||
'action' => 'chapter',
|
'action' => 'chapter',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user