<?php

// SPDX-FileCopyrightText: 2018 Icinga GmbH <https://icinga.com>
// SPDX-License-Identifier: GPL-3.0-or-later

namespace Icinga\Module\Doc\Controllers;

use finfo;
use SplFileInfo;
use Icinga\Application\Icinga;
use Icinga\Module\Doc\DocController;
use Icinga\Module\Doc\Exception\DocException;
use Icinga\Web\Url;

class ModuleController extends DocController
{
    /**
     * Get the path to a module documentation
     *
     * @param   string  $module         The name of the module
     * @param   string  $default        The default path
     * @param   bool    $suppressErrors Whether to not throw an exception if the module documentation is not available
     *
     * @return  string|null             Path to the documentation or null if the module documentation is not available
     *                                  and errors are suppressed
     *
     * @throws  \Icinga\Exception\Http\HttpNotFoundException    If the module documentation is not available and errors
     *                                                          are not suppressed
     */
    protected function getPath($module, $default, $suppressErrors = false)
    {
        if (is_dir($default)) {
            return $default;
        }
        if (($path = $this->Config()->get('documentation', 'modules')) !== null) {
            $path = str_replace('{module}', $module, $path);
            if (is_dir($path)) {
                return $path;
            }
        }
        if ($suppressErrors) {
            return null;
        }
        $this->httpNotFound($this->translate('Documentation for module \'%s\' is not available'), $module);
    }

    /**
     * List modules which are enabled and having the 'doc' directory
     */
    public function indexAction()
    {
        $moduleManager = Icinga::app()->getModuleManager();
        $modules = array();
        foreach ($moduleManager->listInstalledModules() as $module) {
            $path = $this->getPath($module, $moduleManager->getModuleDir($module, '/doc'), true);
            if ($path !== null) {
                $modules[] = $moduleManager->getModule($module, false);
            }
        }
        $this->view->modules = $modules;
        $this->getTabs()->add('module-documentation', array(
            'active'    => true,
            'title'     => $this->translate('Module Documentation', 'Tab title'),
            'url'       => Url::fromRequest()
        ));
    }

    /**
     * Assert that the given module is installed
     *
     * @param   string $moduleName
     *
     * @throws  \Icinga\Exception\Http\HttpNotFoundException If the given module is not installed
     */
    protected function assertModuleInstalled($moduleName)
    {
        $moduleManager = Icinga::app()->getModuleManager();
        if (! $moduleManager->hasInstalled($moduleName)) {
            $this->httpNotFound($this->translate('Module \'%s\' is not installed'), $moduleName);
        }
    }

    /**
     * View the toc of a module's documentation
     *
     * @throws  \Icinga\Exception\MissingParameterException     If the required parameter 'moduleName' is empty
     * @throws  \Icinga\Exception\Http\HttpNotFoundException    If the given module is not installed
     * @see     assertModuleInstalled()
     */
    public function tocAction()
    {
        $module = $this->params->getRequired('moduleName');
        $this->assertModuleInstalled($module);
        $moduleManager = Icinga::app()->getModuleManager();
        $name = $moduleManager->getModule($module, false)->getTitle();
        try {
            $this->renderToc(
                $this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')),
                $name,
                'doc/module/chapter',
                array('moduleName' => $module)
            );
        } catch (DocException $e) {
            $this->httpNotFound($e->getMessage());
        }
    }

    /**
     * View a chapter of a module's documentation
     *
     * @throws  \Icinga\Exception\MissingParameterException     If one of the required parameters 'moduleName' and
     *                                                          'chapter' is empty
     * @throws  \Icinga\Exception\Http\HttpNotFoundException    If the given module is not installed
     * @see     assertModuleInstalled()
     */
    public function chapterAction()
    {
        $module = $this->params->getRequired('moduleName');
        $this->assertModuleInstalled($module);
        $chapter = $this->params->getRequired('chapter');
        $this->view->moduleName = $module;
        try {
            $this->renderChapter(
                $this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')),
                $chapter,
                'doc/module/chapter',
                'doc/module/img',
                array('moduleName' => $module)
            );
        } catch (DocException $e) {
            $this->httpNotFound($e->getMessage());
        }
    }

    /**
     * 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 || substr($imagePath, 0, strlen($docPath)) !== $docPath) {
            $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($_SERVER['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;
            }
        }

        $this->getResponse()
            ->setHeader('ETag', $etag)
            ->setHeader('Cache-Control', 'no-transform,public,max-age=3600,must-revalidate', true);

        if ($match) {
            $this->getResponse()->setHttpResponseCode(304);
        } else {
            $finfo = new finfo();
            $this->getResponse()
                ->setHeader('Content-Type', $finfo->file($imagePath, FILEINFO_MIME_TYPE))
                ->setHeader('Content-Length', $imageInfo->getSize())
                ->sendHeaders();

            ob_end_clean();
            readfile($imagePath);
        }
    }

    /**
     * View a module's documentation as PDF
     *
     * @throws  \Icinga\Exception\MissingParameterException     If the required parameter 'moduleName' is empty
     * @throws  \Icinga\Exception\Http\HttpNotFoundException    If the given module is not installed
     * @see     assertModuleInstalled()
     */
    public function pdfAction()
    {
        $module = $this->params->getRequired('moduleName');
        $this->assertModuleInstalled($module);
        $this->renderPdf(
            $this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')),
            $module,
            'doc/module/chapter',
            array('moduleName' => $module)
        );
    }
}
