diff --git a/modules/doc/application/controllers/ModuleController.php b/modules/doc/application/controllers/ModuleController.php index b6b2d6375..5544dbce4 100644 --- a/modules/doc/application/controllers/ModuleController.php +++ b/modules/doc/application/controllers/ModuleController.php @@ -3,6 +3,8 @@ namespace Icinga\Module\Doc\Controllers; +use finfo; +use SplFileInfo; use Icinga\Application\Icinga; use Icinga\Module\Doc\DocController; use Icinga\Module\Doc\Exception\DocException; @@ -120,6 +122,7 @@ class ModuleController extends DocController $this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')), $chapter, 'doc/module/chapter', + 'doc/module/img', array('moduleName' => $module) ); } catch (DocException $e) { @@ -127,6 +130,60 @@ class ModuleController extends DocController } } + /** + * Deliver images + */ + public function imageAction() + { + $module = $this->params->getRequired('moduleName'); + $image = $this->params->getRequired('image'); + $docPath = $this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')); + $imagePath = realpath($docPath . '/' . $image); + if ($imagePath === false) { + $this->httpNotFound('%s does not exist', $image); + } + + $this->_helper->viewRenderer->setNoRender(true); + $this->_helper->layout()->disableLayout(); + + $imageInfo = new SplFileInfo($imagePath); + + $ETag = md5($imageInfo->getMTime() . $imagePath); + $lastModified = gmdate('D, d M Y H:i:s T', $imageInfo->getMTime()); + $match = false; + + if (isset($_SERER['HTTP_IF_NONE_MATCH'])) { + $ifNoneMatch = explode(', ', stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])); + foreach ($ifNoneMatch as $tag) { + if ($tag === $ETag) { + $match = true; + break; + } + } + } elseif (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { + $lastModifiedSince = stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']); + if ($lastModifiedSince === $lastModified) { + $match = true; + } + } + + header('ETag: "' . $ETag . '"'); + header('Cache-Control: no-transform,public,max-age=3600'); + header('Last-Modified: ' . $lastModified); + // Set additional headers for compatibility reasons (Cache-Control should have precedence) in case + // session.cache_limiter is set to no cache + header('Pragma: cache'); + header('Expires: ' . gmdate('D, d M Y H:i:s T', time() + 3600)); + + if ($match) { + header('HTTP/1.1 304 Not Modified'); + } else { + $finfo = new finfo(); + header('Content-Type: ' . $finfo->file($imagePath, FILEINFO_MIME_TYPE)); + readfile($imagePath); + } + } + /** * View a module's documentation as PDF * diff --git a/modules/doc/doc/1-module-documentation.md b/modules/doc/doc/1-module-documentation.md index edf20aac2..2334324a9 100644 --- a/modules/doc/doc/1-module-documentation.md +++ b/modules/doc/doc/1-module-documentation.md @@ -1,6 +1,6 @@ # Writing Module Documentation -![Markdown](/img/doc/doc/markdown.png) +![Markdown](img/markdown.png) Icinga Web 2 is capable of viewing your module's documentation, if the documentation is written in [Markdown](http://en.wikipedia.org/wiki/Markdown). Please refer to @@ -50,13 +50,15 @@ This syntax is also supported in Icinga Web 2. ## Including Images -Images must placed in the `img` directory beneath your module's `public` directory, e.g.: +Images must placed in the `doc` directory beneath your module's root directory, e.g.: - example-module/public/img/doc + /path/to/icingaweb2/modules/example-module/doc/img/example.png + +Note that the `img` sub directory is not mandatory but good for organizing your directory structure. Module images can be accessed using the following URL: - {baseURL}/img/{moduleName}/{file} e.g. icingaweb/img/example-module/doc/example.png + {baseURL}/doc/module/{moduleName}/image/{image} e.g. icingaweb2/doc/module/example-module/image/img/example.png Markdown's image syntax is very similar to Markdown's link syntax, but prefixed with an exclamation mark, e.g.: @@ -64,4 +66,4 @@ Markdown's image syntax is very similar to Markdown's link syntax, but prefixed URLs to images inside your Markdown documentation files must be specified without the base URL, e.g.: - ![Example](/img/example-module/doc/example.png) + ![Example](img/example.png) diff --git a/modules/doc/library/Doc/DocController.php b/modules/doc/library/Doc/DocController.php index acd085d04..5f5438ed8 100644 --- a/modules/doc/library/Doc/DocController.php +++ b/modules/doc/library/Doc/DocController.php @@ -20,6 +20,9 @@ class DocController extends Controller if ($this->hasParam('chapter')) { $this->params->set('chapter', $this->getParam('chapter')); } + if ($this->hasParam('image')) { + $this->params->set('image', $this->getParam('image')); + } if ($this->hasParam('moduleName')) { $this->params->set('moduleName', $this->getParam('moduleName')); } @@ -31,16 +34,18 @@ class DocController extends Controller * @param string $path Path to the documentation * @param string $chapter ID of the chapter * @param string $url URL to replace links with + * @param string $imageUrl URL to images * @param array $urlParams Additional URL parameters */ - protected function renderChapter($path, $chapter, $url, array $urlParams = array()) + protected function renderChapter($path, $chapter, $url, $imageUrl = null, array $urlParams = array()) { $parser = new DocParser($path); $section = new DocSectionRenderer($parser->getDocTree(), DocSectionRenderer::decodeUrlParam($chapter)); $this->view->section = $section + ->setHighlightSearch($this->params->get('highlight-search')) + ->setImageUrl($imageUrl) ->setUrl($url) - ->setUrlParams($urlParams) - ->setHighlightSearch($this->params->get('highlight-search')); + ->setUrlParams($urlParams); $this->view->title = $chapter; $this->getTabs()->add('toc', array( 'active' => true, diff --git a/modules/doc/library/Doc/Renderer/DocRenderer.php b/modules/doc/library/Doc/Renderer/DocRenderer.php index feb420e91..3662fd574 100644 --- a/modules/doc/library/Doc/Renderer/DocRenderer.php +++ b/modules/doc/library/Doc/Renderer/DocRenderer.php @@ -13,6 +13,13 @@ use Icinga\Web\View; */ abstract class DocRenderer extends RecursiveIteratorIterator { + /** + * URL to images + * + * @var string + */ + protected $imageUrl; + /** * URL to replace links with * @@ -34,6 +41,38 @@ abstract class DocRenderer extends RecursiveIteratorIterator */ protected $view; + /** + * Get the URL to images + * + * @return string + */ + public function getImageUrl() + { + return $this->imageUrl; + } + + /** + * Set the URL to images + * + * @param string $imageUrl + * + * @return $this + */ + public function setImageUrl($imageUrl) + { + $this->imageUrl = (string) $imageUrl; + return $this; + } + /** + * Get the URL to replace links with + * + * @return string + */ + public function getUrl() + { + return $this->url; + } + /** * Set the URL to replace links with * @@ -48,13 +87,13 @@ abstract class DocRenderer extends RecursiveIteratorIterator } /** - * Get the URL to replace links with + * Get additional URL parameters * - * @return string + * @return array */ - public function getUrl() + public function getUrlParams() { - return $this->url; + return $this->urlParams; } /** @@ -71,13 +110,16 @@ abstract class DocRenderer extends RecursiveIteratorIterator } /** - * Get additional URL parameters + * Get the view * - * @return array + * @return View */ - public function getUrlParams() + public function getView() { - return $this->urlParams; + if ($this->view === null) { + $this->view = Icinga::app()->getViewRenderer()->view; + } + return $this->view; } /** @@ -93,19 +135,6 @@ abstract class DocRenderer extends RecursiveIteratorIterator 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 * diff --git a/modules/doc/library/Doc/Renderer/DocSectionRenderer.php b/modules/doc/library/Doc/Renderer/DocSectionRenderer.php index a39d24a77..9bc8bfee0 100644 --- a/modules/doc/library/Doc/Renderer/DocSectionRenderer.php +++ b/modules/doc/library/Doc/Renderer/DocSectionRenderer.php @@ -190,7 +190,20 @@ class DocSectionRenderer extends DocRenderer $xpath = new DOMXPath($doc); $img = $xpath->query('//img[1]')->item(0); /** @var \DOMElement $img */ - $img->setAttribute('src', Url::fromPath($img->getAttribute('src'))->getAbsoluteUrl()); + $path = $this->getView()->getHelper('Url')->url( + array_merge( + array( + 'image' => $img->getAttribute('src') + ), + $this->urlParams + ), + $this->imageUrl, + false, + false + ); + $url = $this->getView()->url($path); + /** @var \Icinga\Web\Url $url */ + $img->setAttribute('src', $url->getAbsoluteUrl()); return substr_replace($doc->saveXML($img), '', -2, 1); // Replace '/>' with '>' } diff --git a/modules/doc/run.php b/modules/doc/run.php index 9d49b89d4..df9dd0930 100644 --- a/modules/doc/run.php +++ b/modules/doc/run.php @@ -43,7 +43,22 @@ $docModulePdf = new Zend_Controller_Router_Route( ) ); +$docModuleImg = new Zend_Controller_Router_Route_Regex( + 'doc/module/([^/]+)/image/(.+)', + array( + 'controller' => 'module', + 'action' => 'image', + 'module' => 'doc' + ), + array( + 'moduleName' => 1, + 'image' => 2 + ), + 'doc/module/%s/image/%s' +); + $this->addRoute('doc/module/chapter', $docModuleChapter); $this->addRoute('doc/icingaweb/chapter', $docIcingaWebChapter); $this->addRoute('doc/module/toc', $docModuleToc); $this->addRoute('doc/module/pdf', $docModulePdf); +$this->addRoute('doc/module/img', $docModuleImg);