Merge branch 'master' into feature/skip-content-7933
This commit is contained in:
commit
09349288e1
|
@ -10,29 +10,22 @@
|
|||
#
|
||||
# icinga2::feature { 'example-feature'; }
|
||||
#
|
||||
define icinga2::feature ($source = undef) {
|
||||
define icinga2::feature ($ensure = 'present') {
|
||||
include icinga2
|
||||
|
||||
$target = "features-available/${name}"
|
||||
$cfgpath = '/etc/icinga2'
|
||||
$path = "${cfgpath}/features-enabled/${name}.conf"
|
||||
|
||||
if $source != undef {
|
||||
icinga2::config { $target:
|
||||
source => $source,
|
||||
}
|
||||
$action = $ensure ? {
|
||||
/(present)/ => 'enable',
|
||||
/(absent)/ => 'disable',
|
||||
}
|
||||
$test = $ensure ? {
|
||||
/(present)/ => '-e',
|
||||
/(absent)/ => '! -e',
|
||||
}
|
||||
|
||||
parent_dirs { $path:
|
||||
user => 'icinga',
|
||||
require => [
|
||||
User['icinga'],
|
||||
File['icinga2cfgDir']
|
||||
],
|
||||
}
|
||||
-> file { $path:
|
||||
ensure => link,
|
||||
target => "${cfgpath}/${target}.conf",
|
||||
exec { "icinga2-feature-${action}-${name}":
|
||||
unless => "/usr/bin/test ${test} /etc/icinga2/features-enabled/${name}.conf",
|
||||
command => "/usr/sbin/icinga2 feature ${action} ${name}",
|
||||
require => Package['icinga2'],
|
||||
notify => Service['icinga2'],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,8 @@ class icinga2_mysql {
|
|||
privileges => 'SELECT,INSERT,UPDATE,DELETE',
|
||||
schemafile => '/usr/share/icinga2-ido-mysql/schema/mysql.sql',
|
||||
}
|
||||
-> icinga2::feature { 'ido-mysql':
|
||||
-> icinga2::config { 'features-available/ido-mysql':
|
||||
source => 'puppet:///modules/icinga2_mysql',
|
||||
}
|
||||
-> icinga2::feature { 'ido-mysql': }
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#
|
||||
# icinga2_mysql
|
||||
# icinga2::config
|
||||
# icinga2::feature
|
||||
#
|
||||
# Sample Usage:
|
||||
#
|
||||
|
@ -21,4 +22,9 @@ class icinga2_dev {
|
|||
'conf.d/test-config', 'conf.d/commands', 'constants' ]:
|
||||
source => 'puppet:///modules/icinga2_dev',
|
||||
}
|
||||
|
||||
icinga2::feature { 'ido-pgsql':
|
||||
ensure => absent,
|
||||
require => Class['icinga2_pgsql'],
|
||||
}
|
||||
}
|
||||
|
|
2
AUTHORS
2
AUTHORS
|
@ -1,9 +1,11 @@
|
|||
Alexander Fuhr <alexander.fuhr@netways.de>
|
||||
Alexander Klimov <alexander.klimov@netways.de>
|
||||
baufrecht <baufrecht@users.noreply.github.com>
|
||||
Bernd Erk <bernd.erk@icinga.org>
|
||||
Boden Garman <boden.garman@spintel.net.au>
|
||||
Carlos Cesario <carloscesario@gmail.com>
|
||||
Chris Rüll <christopher.ruell@netways.de>
|
||||
Davide Demuru <davide.demuru@buongiorno.com>
|
||||
Eric Lippmann <eric.lippmann@netways.de>
|
||||
Goran Rakic <grakic@devbase.net>
|
||||
Gunnar Beutner <gunnar.beutner@netways.de>
|
||||
|
|
|
@ -141,7 +141,7 @@ class AuthenticationController extends ActionController
|
|||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->view->errorInfo = $e->getMessage();
|
||||
$this->view->form->addError($e->getMessage());
|
||||
}
|
||||
|
||||
$this->view->requiresExternalAuth = $triedOnlyExternalAuth && !$auth->isAuthenticated();
|
||||
|
|
|
@ -40,13 +40,22 @@ class ExternalBackendForm extends Form
|
|||
array(
|
||||
'pattern' => '/^[^\\[\\]:]+$/',
|
||||
'messages' => array(
|
||||
'regexNotMatch' => 'The backend name cannot contain \'[\', \']\' or \':\'.'
|
||||
'regexNotMatch' => $this->translate(
|
||||
'The backend name cannot contain \'[\', \']\' or \':\'.'
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
$callbackValidator = new Zend_Validate_Callback(function ($value) {
|
||||
return @preg_match($value, '') !== false;
|
||||
});
|
||||
$callbackValidator->setMessage(
|
||||
$this->translate('"%value%" is not a valid regular expression'),
|
||||
Zend_Validate_Callback::INVALID_VALUE
|
||||
);
|
||||
$this->addElement(
|
||||
'text',
|
||||
'strip_username_regexp',
|
||||
|
@ -56,11 +65,7 @@ class ExternalBackendForm extends Form
|
|||
'The regular expression to use to strip specific parts off from usernames.'
|
||||
. ' Leave empty if you do not want to strip off anything'
|
||||
),
|
||||
'validators' => array(
|
||||
new Zend_Validate_Callback(function ($value) {
|
||||
return @preg_match($value, '') !== false;
|
||||
})
|
||||
)
|
||||
'validators' => array($callbackValidator)
|
||||
)
|
||||
);
|
||||
$this->addElement(
|
||||
|
|
|
@ -5,7 +5,6 @@ namespace Icinga\Forms\Config\General;
|
|||
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Web\Form;
|
||||
use Icinga\Web\Form\Validator\WritablePathValidator;
|
||||
|
||||
class LoggingConfigForm extends Form
|
||||
{
|
||||
|
@ -75,7 +74,9 @@ class LoggingConfigForm extends Form
|
|||
array(
|
||||
'pattern' => '/^[^\W]+$/',
|
||||
'messages' => array(
|
||||
'regexNotMatch' => 'The application prefix cannot contain any whitespaces.'
|
||||
'regexNotMatch' => $this->translate(
|
||||
'The application prefix must not contain whitespace.'
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -107,7 +108,7 @@ class LoggingConfigForm extends Form
|
|||
'label' => $this->translate('File path'),
|
||||
'description' => $this->translate('The full path to the log file to write messages to.'),
|
||||
'value' => '/var/log/icingaweb2/icingaweb2.log',
|
||||
'validators' => array(new WritablePathValidator())
|
||||
'validators' => array('WritablePathValidator')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
namespace Icinga\Forms\Config\Resource;
|
||||
|
||||
use Icinga\Web\Form;
|
||||
use Icinga\Web\Form\Validator\ReadablePathValidator;
|
||||
|
||||
/**
|
||||
* Form class for adding/modifying file resources
|
||||
|
@ -40,7 +39,7 @@ class FileResourceForm extends Form
|
|||
'required' => true,
|
||||
'label' => $this->translate('Filepath'),
|
||||
'description' => $this->translate('The filename to fetch information from'),
|
||||
'validators' => array(new ReadablePathValidator())
|
||||
'validators' => array('ReadablePathValidator')
|
||||
)
|
||||
);
|
||||
$this->addElement(
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -28,8 +28,8 @@
|
|||
Enter some text
|
||||
<span class="required-indicator" aria-hidden="true"></span>
|
||||
<span class="sr-only"> (required)</span>
|
||||
<input type="text" name="some_text" value="" aria-required="true" required>
|
||||
</label>
|
||||
<input type="text" name="some_text" value="" aria-required="true" required>
|
||||
<input type="submit" name="btn_submit" value="Submit">
|
||||
</form>
|
||||
</body>
|
||||
|
|
|
@ -40,7 +40,7 @@ or LDAP configuration method.
|
|||
Directive | Description
|
||||
------------------------|------------
|
||||
**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_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
|
||||
------------------------|------------
|
||||
**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:**
|
||||
|
||||
|
@ -82,7 +82,7 @@ authentication method.
|
|||
Directive | Description
|
||||
------------------------|------------
|
||||
**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:**
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ usermod -A icingaweb2 wwwrun
|
|||
|
||||
Debian and Ubuntu:
|
||||
````
|
||||
usermod -a -G icingaweb2 wwwrun
|
||||
usermod -a -G icingaweb2 www-data
|
||||
````
|
||||
|
||||
Use `icingacli` to create the configuration directory which defaults to **/etc/icingaweb2**:
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
$current = $this->current();
|
||||
/* @var $current \SplFileInfo */
|
||||
/** @type $current \SplFileInfo */
|
||||
if (! $current->isFile()) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -37,19 +37,22 @@ class Pdf extends DOMPDF
|
|||
public function renderControllerAction($controller)
|
||||
{
|
||||
$this->assertNoHeadersSent();
|
||||
|
||||
ini_set('memory_limit', '384M');
|
||||
ini_set('max_execution_time', 300);
|
||||
|
||||
$request = $controller->getRequest();
|
||||
$viewRenderer = $controller->getHelper('viewRenderer');
|
||||
$controller->render(
|
||||
$viewRenderer->getScriptAction(),
|
||||
$viewRenderer->getResponseSegment(),
|
||||
$viewRenderer->getNoController()
|
||||
);
|
||||
$layout = $controller->getHelper('layout')->setLayout('pdf');
|
||||
$controller->render();
|
||||
$layout->content = $controller->getResponse();
|
||||
$html = $layout->render();
|
||||
$imgDir = Url::fromPath('img');
|
||||
$html = preg_replace('~src="' . $imgDir . '/~', 'src="' . Icinga::app()->getBootstrapDirectory() . '/img/', $html);
|
||||
$this->load_html($html);
|
||||
$this->render();
|
||||
$request = $controller->getRequest();
|
||||
$this->stream(
|
||||
sprintf(
|
||||
'%s-%s-%d.pdf',
|
||||
|
|
|
@ -53,4 +53,30 @@ class String
|
|||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find and return all similar strings in $possibilites matching $string with the given minimum $similarity
|
||||
*
|
||||
* @param string $string
|
||||
* @param array $possibilities
|
||||
* @param float $similarity
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function findSimilar($string, array $possibilities, $similarity = 0.33)
|
||||
{
|
||||
if (empty($string)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$matches = array();
|
||||
foreach ($possibilities as $possibility) {
|
||||
$distance = levenshtein($string, $possibility);
|
||||
if ($distance / strlen($string) <= $similarity) {
|
||||
$matches[] = $possibility;
|
||||
}
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -402,7 +402,6 @@ class ActionController extends Zend_Controller_Action
|
|||
}
|
||||
|
||||
if ($req->getParam('format') === 'pdf') {
|
||||
$layout->setLayout('pdf');
|
||||
$this->shutdownSession();
|
||||
$this->sendAsPdf();
|
||||
exit;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ use Icinga\Application\Icinga;
|
|||
use Icinga\Authentication\Manager;
|
||||
use Icinga\Security\SecurityException;
|
||||
use Icinga\Util\Translator;
|
||||
use Icinga\Web\Form\ErrorLabeller;
|
||||
use Icinga\Web\Form\Decorator\NoScriptApply;
|
||||
use Icinga\Web\Form\Element\CsrfCounterMeasure;
|
||||
|
||||
|
@ -520,6 +521,15 @@ class Form extends Zend_Form
|
|||
}
|
||||
|
||||
$el = parent::createElement($type, $name, $options);
|
||||
$el->setTranslator(new ErrorLabeller(array('element' => $el)));
|
||||
|
||||
$el->addPrefixPaths(array(
|
||||
array(
|
||||
'prefix' => 'Icinga\\Web\\Form\\Validator\\',
|
||||
'path' => Icinga::app()->getLibraryDir('Icinga/Web/Form/Validator'),
|
||||
'type' => $el::VALIDATE
|
||||
)
|
||||
));
|
||||
|
||||
if (($description = $el->getDescription()) !== null && ($label = $el->getDecorator('label')) !== false) {
|
||||
$label->setOptions(array(
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Web\Form;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Zend_Translate_Adapter;
|
||||
use Zend_Validate_NotEmpty;
|
||||
use Icinga\Web\Form\Validator\DateTimeValidator;
|
||||
use Icinga\Web\Form\Validator\ReadablePathValidator;
|
||||
use Icinga\Web\Form\Validator\WritablePathValidator;
|
||||
|
||||
class ErrorLabeller extends Zend_Translate_Adapter
|
||||
{
|
||||
protected $messages;
|
||||
|
||||
public function __construct($options = array())
|
||||
{
|
||||
if (! isset($options['element'])) {
|
||||
throw new BadMethodCallException('Option "element" is missing');
|
||||
}
|
||||
|
||||
$this->messages = $this->createMessages($options['element']);
|
||||
}
|
||||
|
||||
public function isTranslated($messageId, $original = false, $locale = null)
|
||||
{
|
||||
return array_key_exists($messageId, $this->messages);
|
||||
}
|
||||
|
||||
public function translate($messageId, $locale = null)
|
||||
{
|
||||
if (array_key_exists($messageId, $this->messages)) {
|
||||
return $this->messages[$messageId];
|
||||
}
|
||||
|
||||
return $messageId;
|
||||
}
|
||||
|
||||
protected function createMessages($element)
|
||||
{
|
||||
$label = $element->getLabel();
|
||||
|
||||
return array(
|
||||
Zend_Validate_NotEmpty::IS_EMPTY => sprintf(t('%s is required and must not be empty'), $label),
|
||||
WritablePathValidator::NOT_WRITABLE => sprintf(t('%s is not writable', 'config.path'), $label),
|
||||
WritablePathValidator::DOES_NOT_EXIST => sprintf(t('%s does not exist', 'config.path'), $label),
|
||||
ReadablePathValidator::NOT_READABLE => sprintf(t('%s is not readable', 'config.path'), $label),
|
||||
DateTimeValidator::INVALID_DATETIME_FORMAT => sprintf(
|
||||
t('%s not in the expected format: %%value%%'),
|
||||
$label
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected function _loadTranslationData($data, $locale, array $options = array())
|
||||
{
|
||||
// nonsense, required as being abstract otherwise...
|
||||
}
|
||||
|
||||
public function toString()
|
||||
{
|
||||
return 'ErrorLabeller'; // nonsense, required as being abstract otherwise...
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ namespace Icinga\Web\Form\Validator;
|
|||
|
||||
use DateTime;
|
||||
use Zend_Validate_Abstract;
|
||||
use Icinga\Util\DateTimeFactory;
|
||||
|
||||
/**
|
||||
* Validator for date-and-time input controls
|
||||
|
@ -13,6 +14,21 @@ use Zend_Validate_Abstract;
|
|||
*/
|
||||
class DateTimeValidator extends Zend_Validate_Abstract
|
||||
{
|
||||
const INVALID_DATETIME_TYPE = 'invalidDateTimeType';
|
||||
const INVALID_DATETIME_FORMAT = 'invalidDateTimeFormat';
|
||||
|
||||
/**
|
||||
* The messages to write on differen error states
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @see Zend_Validate_Abstract::$_messageTemplates‚
|
||||
*/
|
||||
protected $_messageTemplates = array(
|
||||
self::INVALID_DATETIME_TYPE => 'Invalid type given. Instance of DateTime or date/time string expected',
|
||||
self::INVALID_DATETIME_FORMAT => 'Date/time string not in the expected format: %value%'
|
||||
);
|
||||
|
||||
protected $local;
|
||||
|
||||
/**
|
||||
|
@ -38,14 +54,14 @@ class DateTimeValidator extends Zend_Validate_Abstract
|
|||
public function isValid($value, $context = null)
|
||||
{
|
||||
if (! $value instanceof DateTime && ! is_string($value)) {
|
||||
$this->_error(t('Invalid type given. Instance of DateTime or date/time string expected'));
|
||||
$this->_error(self::INVALID_DATETIME_TYPE);
|
||||
return false;
|
||||
}
|
||||
if (is_string($value)) {
|
||||
$format = $this->local === true ? 'Y-m-d\TH:i:s' : DateTime::RFC3339;
|
||||
$dateTime = DateTime::createFromFormat($format, $value);
|
||||
if ($dateTime === false || $dateTime->format($format) !== $value) {
|
||||
$this->_error(sprintf(t('Date/time string not in the expected format %s'), $format));
|
||||
$this->_error(self::INVALID_DATETIME_FORMAT, DateTimeFactory::create()->format($format));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Web\Form\Validator;
|
||||
|
||||
use Zend_Validate_InArray;
|
||||
use Icinga\Util\String;
|
||||
|
||||
class InArray extends Zend_Validate_InArray
|
||||
{
|
||||
protected function _error($messageKey, $value = null)
|
||||
{
|
||||
if ($messageKey === static::NOT_IN_ARRAY) {
|
||||
$matches = String::findSimilar($this->_value, $this->_haystack);
|
||||
if (empty($matches)) {
|
||||
$this->_messages[$messageKey] = sprintf(t('"%s" is not in the list of allowed values.'), $this->_value);
|
||||
} else {
|
||||
$this->_messages[$messageKey] = sprintf(
|
||||
t('"%s" is not in the list of allowed values. Did you mean one of the following?: %s'),
|
||||
$this->_value,
|
||||
implode(', ', $matches)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
parent::_error($messageKey, $value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,9 @@ use Zend_Validate_Abstract;
|
|||
*/
|
||||
class ReadablePathValidator extends Zend_Validate_Abstract
|
||||
{
|
||||
const NOT_READABLE = 'notReadable';
|
||||
const DOES_NOT_EXIST = 'doesNotExist';
|
||||
|
||||
/**
|
||||
* The messages to write on different error states
|
||||
*
|
||||
|
@ -20,18 +23,10 @@ class ReadablePathValidator extends Zend_Validate_Abstract
|
|||
*
|
||||
* @see Zend_Validate_Abstract::$_messageTemplates‚
|
||||
*/
|
||||
protected $_messageTemplates;
|
||||
|
||||
/**
|
||||
* Initialize this validator
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->_messageTemplates = array(
|
||||
'NOT_READABLE' => t('Path is not readable'),
|
||||
'DOES_NOT_EXIST' => t('Path does not exist')
|
||||
);
|
||||
}
|
||||
protected $_messageTemplates = array(
|
||||
self::NOT_READABLE => 'Path is not readable',
|
||||
self::DOES_NOT_EXIST => 'Path does not exist'
|
||||
);
|
||||
|
||||
/**
|
||||
* Check whether the given value is a readable filepath
|
||||
|
@ -44,12 +39,13 @@ class ReadablePathValidator extends Zend_Validate_Abstract
|
|||
public function isValid($value, $context = null)
|
||||
{
|
||||
if (false === file_exists($value)) {
|
||||
$this->_error('DOES_NOT_EXIST');
|
||||
$this->_error(self::DOES_NOT_EXIST);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false === is_readable($value)) {
|
||||
$this->_error('NOT_READABLE');
|
||||
$this->_error(self::NOT_READABLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -10,6 +10,9 @@ use Zend_Validate_Abstract;
|
|||
*/
|
||||
class WritablePathValidator extends Zend_Validate_Abstract
|
||||
{
|
||||
const NOT_WRITABLE = 'notWritable';
|
||||
const DOES_NOT_EXIST = 'doesNotExist';
|
||||
|
||||
/**
|
||||
* The messages to write on differen error states
|
||||
*
|
||||
|
@ -18,8 +21,8 @@ class WritablePathValidator extends Zend_Validate_Abstract
|
|||
* @see Zend_Validate_Abstract::$_messageTemplates‚
|
||||
*/
|
||||
protected $_messageTemplates = array(
|
||||
'NOT_WRITABLE' => 'Path is not writable',
|
||||
'DOES_NOT_EXIST' => 'Path does not exist'
|
||||
self::NOT_WRITABLE => 'Path is not writable',
|
||||
self::DOES_NOT_EXIST => 'Path does not exist'
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -53,7 +56,7 @@ class WritablePathValidator extends Zend_Validate_Abstract
|
|||
|
||||
$this->_setValue($value);
|
||||
if ($this->requireExistence && !file_exists($value)) {
|
||||
$this->_error('DOES_NOT_EXIST');
|
||||
$this->_error(self::DOES_NOT_EXIST);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -62,7 +65,8 @@ class WritablePathValidator extends Zend_Validate_Abstract
|
|||
) {
|
||||
return true;
|
||||
}
|
||||
$this->_error('NOT_WRITABLE');
|
||||
|
||||
$this->_error(self::NOT_WRITABLE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,17 +46,16 @@ class Doc_IcingawebController extends DocController
|
|||
*/
|
||||
public function chapterAction()
|
||||
{
|
||||
$chapterId = $this->getParam('chapterId');
|
||||
if ($chapterId === null) {
|
||||
$chapter = $this->getParam('chapter');
|
||||
if ($chapter === null) {
|
||||
throw new Zend_Controller_Action_Exception(
|
||||
sprintf($this->translate('Missing parameter \'%s\''), 'chapterId'),
|
||||
sprintf($this->translate('Missing parameter %s'), 'chapter'),
|
||||
404
|
||||
);
|
||||
}
|
||||
$this->renderChapter(
|
||||
$this->getPath(),
|
||||
$chapterId,
|
||||
'doc/icingaweb/toc',
|
||||
$chapter,
|
||||
'doc/icingaweb/chapter'
|
||||
);
|
||||
}
|
||||
|
|
|
@ -122,10 +122,10 @@ class Doc_ModuleController extends DocController
|
|||
{
|
||||
$module = $this->getParam('moduleName');
|
||||
$this->assertModuleEnabled($module);
|
||||
$chapterId = $this->getParam('chapterId');
|
||||
if ($chapterId === null) {
|
||||
$chapter = $this->getParam('chapter');
|
||||
if ($chapter === null) {
|
||||
throw new Zend_Controller_Action_Exception(
|
||||
sprintf($this->translate('Missing parameter \'%s\''), 'chapterId'),
|
||||
sprintf($this->translate('Missing parameter %s'), 'chapter'),
|
||||
404
|
||||
);
|
||||
}
|
||||
|
@ -133,8 +133,7 @@ class Doc_ModuleController extends DocController
|
|||
try {
|
||||
$this->renderChapter(
|
||||
$this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')),
|
||||
$chapterId,
|
||||
$this->_helper->url->url(array('moduleName' => $module), 'doc/module/toc'),
|
||||
$chapter,
|
||||
'doc/module/chapter',
|
||||
array('moduleName' => $module)
|
||||
);
|
||||
|
|
|
@ -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">
|
||||
<?= $sectionRenderer->render($this, $this->getHelper('Url')); ?>
|
||||
<div class="controls">
|
||||
<?= /** @type \Icinga\Web\Widget\Tabs $tabs */ $tabs->showOnlyCloseButton() ?>
|
||||
</div>
|
||||
<div class="content">
|
||||
<?= /** @type \Icinga\Module\Doc\Renderer\DocSectionRenderer $section */ $section ?>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<div class="controls"></div>
|
||||
<h1><?= $this->translate('Available documentations'); ?></h1>
|
||||
<ul>
|
||||
<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>
|
||||
</ul>
|
||||
<?= /** @type \Icinga\Web\Widget\Tabs $tabs */ $tabs->showOnlyCloseButton() ?>
|
||||
<h1><?= $this->translate('Available documentations') ?></h1>
|
||||
<div class="content">
|
||||
<ul>
|
||||
<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>
|
||||
</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>
|
||||
<ul>
|
||||
<?php foreach ($modules as $module): ?>
|
||||
<li>
|
||||
<a href="<?= $this->getHelper('Url')->url(array('moduleName' => $module), 'doc/module/toc', false, false); ?>">
|
||||
<?= $module ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
<div class="controls">
|
||||
<?= /** @type \Icinga\Web\Widget\Tabs $tabs */ $tabs->showOnlyCloseButton() ?>
|
||||
<h1><?= $this->translate('Module documentations') ?></h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<ul>
|
||||
<?php foreach ($modules as $module): ?>
|
||||
<li>
|
||||
<a href="<?= $this->getHelper('Url')->url(array('moduleName' => $module), 'doc/module/toc', false, false) ?>">
|
||||
<?= $module ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</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="toc">
|
||||
<?= $tocRenderer->render($this, $this->getHelper('Url')); ?>
|
||||
</div>
|
||||
<div class="chapter">
|
||||
<?= $sectionRenderer->render($this, $this->getHelper('Url')); ?>
|
||||
<div class="content">
|
||||
<h1><?= /** @type string $title */ $title ?></h1>
|
||||
<?= /** @type \Icinga\Module\Doc\Renderer\DocTocRenderer $toc */ $toc ?>
|
||||
<?= /** @type \Icinga\Module\Doc\Renderer\DocSectionRenderer $section */ $section ?>
|
||||
</div>
|
||||
|
|
|
@ -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">
|
||||
<h1><?= $title ?></h1>
|
||||
<?= /** @type \Icinga\Web\Widget\Tabs $tabs */ $tabs->showOnlyCloseButton() ?>
|
||||
<h1><?= /** @type string $title */ $title ?></h1>
|
||||
</div>
|
||||
<div class="content toc">
|
||||
<?= $tocRenderer->render($this, $this->getHelper('Url')); ?>
|
||||
<div class="content">
|
||||
<?= /** @type \Icinga\Module\Doc\Renderer\DocTocRenderer $toc */ $toc ?>
|
||||
</div>
|
||||
|
|
|
@ -20,3 +20,5 @@ $section->add($this->translate('Developer - Style'), array(
|
|||
'url' => 'doc/style/guide',
|
||||
'priority' => 200,
|
||||
));
|
||||
|
||||
$this->provideSearchUrl($this->translate('Doc'), 'doc/search');
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
namespace Icinga\Module\Doc;
|
||||
|
||||
use Icinga\Module\Doc\Renderer\DocSectionRenderer;
|
||||
use Icinga\Module\Doc\Renderer\DocTocRenderer;
|
||||
use Icinga\Web\Controller\ModuleActionController;
|
||||
|
||||
class DocController extends ModuleActionController
|
||||
|
@ -10,40 +12,39 @@ class DocController extends ModuleActionController
|
|||
/**
|
||||
* Render a chapter
|
||||
*
|
||||
* @param string $path Path to the documentation
|
||||
* @param string $chapterId ID of the chapter
|
||||
* @param string $tocUrl
|
||||
* @param string $url
|
||||
* @param array $urlParams
|
||||
* @param string $path Path to the documentation
|
||||
* @param string $chapter ID of the chapter
|
||||
* @param string $url URL to replace links with
|
||||
* @param array $urlParams Additional URL parameters
|
||||
*/
|
||||
protected function renderChapter($path, $chapterId, $tocUrl, $url, array $urlParams = array())
|
||||
protected function renderChapter($path, $chapter, $url, array $urlParams = array())
|
||||
{
|
||||
$parser = new DocParser($path);
|
||||
$this->view->sectionRenderer = new SectionRenderer(
|
||||
$parser->getDocTree(),
|
||||
SectionRenderer::decodeUrlParam($chapterId),
|
||||
$tocUrl,
|
||||
$url,
|
||||
$urlParams
|
||||
);
|
||||
$this->view->title = $chapterId;
|
||||
$section = new DocSectionRenderer($parser->getDocTree(), DocSectionRenderer::decodeUrlParam($chapter));
|
||||
$this->view->section = $section
|
||||
->setUrl($url)
|
||||
->setUrlParams($urlParams)
|
||||
->setHighlightSearch($this->params->get('highlight-search'));
|
||||
$this->view->title = $chapter;
|
||||
$this->render('chapter', null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a toc
|
||||
*
|
||||
* @param string $path Path to the documentation
|
||||
* @param string $name Name of the documentation
|
||||
* @param string $url
|
||||
* @param array $urlParams
|
||||
* @param string $path Path to the documentation
|
||||
* @param string $name Name of the documentation
|
||||
* @param string $url URL to replace links with
|
||||
* @param array $urlParams Additional URL parameters
|
||||
*/
|
||||
protected function renderToc($path, $name, $url, array $urlParams = array())
|
||||
{
|
||||
$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);
|
||||
$this->view->docName = $name;
|
||||
$this->view->title = sprintf($this->translate('%s Documentation'), $name);
|
||||
$this->render('toc', null, true);
|
||||
}
|
||||
|
@ -59,17 +60,16 @@ class DocController extends ModuleActionController
|
|||
protected function renderPdf($path, $name, $url, array $urlParams = array())
|
||||
{
|
||||
$parser = new DocParser($path);
|
||||
$docTree = $parser->getDocTree();
|
||||
$this->view->tocRenderer = new TocRenderer($docTree, $url, $urlParams);
|
||||
$this->view->sectionRenderer = new SectionRenderer(
|
||||
$docTree,
|
||||
null,
|
||||
null,
|
||||
$url,
|
||||
$urlParams
|
||||
);
|
||||
$this->view->docName = $name;
|
||||
$toc = new DocTocRenderer($parser->getDocTree()->getIterator());
|
||||
$this->view->toc = $toc
|
||||
->setUrl($url)
|
||||
->setUrlParams($urlParams);
|
||||
$section = new DocSectionRenderer($parser->getDocTree());
|
||||
$this->view->section = $section
|
||||
->setUrl($url)
|
||||
->setUrlParams($urlParams);
|
||||
$this->view->title = sprintf($this->translate('%s Documentation'), $name);
|
||||
$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 Countable;
|
||||
use IteratorAggregate;
|
||||
use RecursiveIteratorIterator;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use Icinga\File\NonEmptyFileIterator;
|
||||
use Icinga\File\FileExtensionFilterIterator;
|
||||
|
||||
|
@ -19,7 +19,7 @@ class DocIterator implements Countable, IteratorAggregate
|
|||
/**
|
||||
* Ordered files
|
||||
*
|
||||
* @var array
|
||||
* @type array
|
||||
*/
|
||||
protected $fileInfo;
|
||||
|
||||
|
@ -46,8 +46,7 @@ class DocIterator implements Countable, IteratorAggregate
|
|||
}
|
||||
|
||||
/**
|
||||
* (non-PHPDoc)
|
||||
* @see Countable::count()
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
|
@ -55,8 +54,7 @@ class DocIterator implements Countable, IteratorAggregate
|
|||
}
|
||||
|
||||
/**
|
||||
* (non-PHPDoc)
|
||||
* @see IteratorAggregate::getIterator()
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
namespace Icinga\Module\Doc;
|
||||
|
||||
use SplDoublyLinkedList;
|
||||
use LogicException;
|
||||
use SplStack;
|
||||
use Icinga\Data\Tree\SimpleTree;
|
||||
use Icinga\Exception\NotReadableError;
|
||||
use Icinga\Module\Doc\Exception\DocEmptyException;
|
||||
use Icinga\Module\Doc\Exception\DocException;
|
||||
|
@ -16,14 +18,14 @@ class DocParser
|
|||
/**
|
||||
* Path to the documentation
|
||||
*
|
||||
* @var string
|
||||
* @type string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* Iterator over documentation files
|
||||
*
|
||||
* @var DocIterator
|
||||
* @type DocIterator
|
||||
*/
|
||||
protected $docIterator;
|
||||
|
||||
|
@ -121,16 +123,15 @@ class DocParser
|
|||
/**
|
||||
* Get the documentation tree
|
||||
*
|
||||
* @return DocTree
|
||||
* @return SimpleTree
|
||||
*/
|
||||
public function getDocTree()
|
||||
{
|
||||
$tree = new DocTree();
|
||||
$stack = new SplDoublyLinkedList();
|
||||
$tree = new SimpleTree();
|
||||
$stack = new SplStack();
|
||||
foreach ($this->docIterator as $fileInfo) {
|
||||
/* @var $file \SplFileInfo */
|
||||
/** @type $fileInfo \SplFileInfo */
|
||||
$file = $fileInfo->openFile();
|
||||
/* @var $file \SplFileObject */
|
||||
$lastLine = null;
|
||||
foreach ($file as $line) {
|
||||
$header = $this->extractHeader($line, $lastLine);
|
||||
|
@ -142,7 +143,7 @@ class DocParser
|
|||
if ($id === null) {
|
||||
$path = array();
|
||||
foreach ($stack as $section) {
|
||||
/* @var $section Section */
|
||||
/** @type $section DocSection */
|
||||
$path[] = $section->getTitle();
|
||||
}
|
||||
$path[] = $title;
|
||||
|
@ -151,17 +152,27 @@ class DocParser
|
|||
} else {
|
||||
$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()) {
|
||||
$chapterId = $id;
|
||||
$section = new Section($id, $title, $level, $noFollow, $chapterId);
|
||||
$tree->addRoot($section);
|
||||
$section->setChapter($section);
|
||||
$tree->addChild($section);
|
||||
} else {
|
||||
$chapterId = $stack->bottom()->getId();
|
||||
$section = new Section($id, $title, $level, $noFollow, $chapterId);
|
||||
$section->setChapter($stack->bottom());
|
||||
$tree->addChild($section, $stack->top());
|
||||
}
|
||||
$stack->push($section);
|
||||
} else {
|
||||
if ($stack->isEmpty()) {
|
||||
throw new LogicException('Heading required');
|
||||
}
|
||||
$stack->top()->appendContent($line);
|
||||
}
|
||||
// Save last line for setext-style headers
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
class DocEmptyException extends DocException {}
|
||||
class DocEmptyException extends DocException
|
||||
{
|
||||
}
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
|
||||
namespace Icinga\Module\Doc\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
use Icinga\Exception\IcingaException;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
<?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();
|
||||
@$doc->loadHTML($html);
|
||||
$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);
|
||||
}
|
||||
// Remove <!DOCTYPE
|
||||
$doc->removeChild($doc->doctype);
|
||||
// Remove <html><body> and </body></html>
|
||||
return substr($doc->saveHTML(), 12, -15);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
$html = $this->parsedown->text(implode('', $section->getContent()));
|
||||
if (empty($html)) {
|
||||
continue;
|
||||
}
|
||||
$html = preg_replace_callback(
|
||||
'#<pre><code class="language-php">(.*?)</code></pre>#s',
|
||||
array($this, 'highlightPhp'),
|
||||
$html
|
||||
);
|
||||
$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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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+ */
|
||||
|
||||
div.chapter {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.uppercase {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
@ -22,18 +18,6 @@ div.chapter {
|
|||
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 {
|
||||
// Reset
|
||||
border-collapse: collapse;
|
||||
|
@ -77,7 +61,6 @@ thead {
|
|||
.uppercase;
|
||||
.bold;
|
||||
}
|
||||
position: sticky;
|
||||
border-bottom: 0.25rem solid @icinga;
|
||||
}
|
||||
|
||||
|
@ -105,31 +88,12 @@ pre > code {
|
|||
.box-shadow;
|
||||
}
|
||||
|
||||
div.chapter > ul.navigation {
|
||||
ul.toc {
|
||||
margin: 0;
|
||||
padding: 0.4em;
|
||||
text-align: center;
|
||||
background-color: #888;
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
display: inline;
|
||||
margin: 0.2em;
|
||||
padding: 0;
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.prev {
|
||||
padding-right: 0.6em;
|
||||
border-right: 2px solid #fff;
|
||||
}
|
||||
|
||||
&.next {
|
||||
padding-left: 0.6em;
|
||||
border-left: 2px solid #fff;
|
||||
}
|
||||
}
|
||||
padding: 0 0 0 1em;
|
||||
}
|
||||
|
||||
.search-highlight {
|
||||
color: #FBE012;
|
||||
background: @icinga;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ if (Icinga::app()->isCli()) {
|
|||
}
|
||||
|
||||
$docModuleChapter = new Zend_Controller_Router_Route(
|
||||
'doc/module/:moduleName/chapter/:chapterId',
|
||||
'doc/module/:moduleName/chapter/:chapter',
|
||||
array(
|
||||
'controller' => 'module',
|
||||
'action' => 'chapter',
|
||||
|
@ -18,7 +18,7 @@ $docModuleChapter = new Zend_Controller_Router_Route(
|
|||
);
|
||||
|
||||
$docIcingaWebChapter = new Zend_Controller_Router_Route(
|
||||
'doc/icingaweb/chapter/:chapterId',
|
||||
'doc/icingaweb/chapter/:chapter',
|
||||
array(
|
||||
'controller' => 'icingaweb',
|
||||
'action' => 'chapter',
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue