NatLibFi/Skosmos

View on GitHub
src/controller/WebController.php

Summary

Maintainability
D
2 days
Test Coverage
F
19%
<?php

/**
 * Importing the dependencies.
 */
use Punic\Language;
use Symfony\Bridge\Twig\Extension\TranslationExtension;

/**
 * WebController is an extension of the Controller that handles all
 * the requests originating from the view of the website.
 */
class WebController extends Controller
{
    /**
     * Provides access to the templating engine.
     * @property object $twig the twig templating engine.
     */
    public $twig;
    public $honeypot;
    public $translator;

    /**
     * Constructor for the WebController.
     * @param Model $model
     */
    public function __construct($model)
    {
        parent::__construct($model);

        // initialize Twig templates
        $tmpDir = $model->getConfig()->getTemplateCache();

        // check if the cache pointed by config.ttl exists, if not we create it.
        if (!file_exists($tmpDir)) {
            mkdir($tmpDir);
        }

        // specify where to look for templates and cache
        $loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../view');
        // initialize Twig environment
        $this->twig = new \Twig\Environment($loader, array('cache' => $tmpDir,'auto_reload' => true));
        // used for setting the base href for the relative urls
        $this->twig->addGlobal("BaseHref", $this->getBaseHref());

        // pass the GlobalConfig object to templates so they can access configuration
        $this->twig->addGlobal("GlobalConfig", $this->model->getConfig());

        // setting the list of properties to be displayed in the search results
        $this->twig->addGlobal("PreferredProperties", array('skos:prefLabel', 'skos:narrower', 'skos:broader', 'skosmos:memberOf', 'skos:altLabel', 'skos:related'));

        // register a Twig filter for generating URLs for vocabulary resources (concepts and groups)
        $this->twig->addExtension(new LinkUrlExtension($model));

        // register a Twig filter for generating strings from language codes with CLDR
        $langFilter = new \Twig\TwigFilter('lang_name', function ($langcode, $lang) {
            return Language::getName($langcode, $lang);
        });
        $this->twig->addFilter($langFilter);

        $this->translator = $model->getTranslator();
        $this->twig->addExtension(new TranslationExtension($this->translator));

        // create the honeypot
        $this->honeypot = new \Honeypot();
        if (!$this->model->getConfig()->getHoneypotEnabled()) {
            $this->honeypot->disable();
        }
        $this->twig->addGlobal('honeypot', $this->honeypot);
    }

    /**
     * Guess the language of the user. Return a language string that is one
     * of the supported languages defined in the $LANGUAGES setting, e.g. "fi".
     * @param Request $request HTTP request
     * @param string $vocid identifier for the vocabulary eg. 'yso'.
     * @return string returns the language choice as a numeric string value
     */
    public function guessLanguage($request, $vocid = null)
    {
        // 1. select language based on SKOSMOS_LANGUAGE cookie
        $languageCookie = $request->getCookie('SKOSMOS_LANGUAGE');
        if ($languageCookie) {
            return $languageCookie;
        }

        // 2. if vocabulary given, select based on the default language of the vocabulary
        if ($vocid !== null && $vocid !== '') {
            try {
                $vocab = $this->model->getVocabulary($vocid);
                return $vocab->getConfig()->getDefaultLanguage();
            } catch (Exception $e) {
                // vocabulary id not found, move on to the next selection method
            }
        }

        // 3. select language based on Accept-Language header
        header('Vary: Accept-Language'); // inform caches that a decision was made based on Accept header
        $this->negotiator = new \Negotiation\LanguageNegotiator();
        $langcodes = array_keys($this->languages);
        // using a random language from the configured UI languages when there is no accept language header set
        $acceptLanguage = $request->getServerConstant('HTTP_ACCEPT_LANGUAGE') ? $request->getServerConstant('HTTP_ACCEPT_LANGUAGE') : $langcodes[0];

        $bestLang = $this->negotiator->getBest($acceptLanguage, $langcodes);
        if (isset($bestLang) && in_array($bestLang->getValue(), $langcodes)) {
            return $bestLang->getValue();
        }

        // show default site or prompt for language
        return $langcodes[0];
    }

    /**
     * Determines a css class that controls width and positioning of the vocabulary list element.
     * The layout is wider if the left/right box templates have not been provided.
     * @return string css class for the container eg. 'voclist-wide' or 'voclist-right'
     */
    private function listStyle()
    {
        $left = file_exists('view/left.inc');
        $right = file_exists('view/right.inc');
        $ret = 'voclist';
        if (!$left && !$right) {
            $ret .= '-wide';
        } elseif (!($left && $right) && ($right || $left)) {
            $ret .= ($right) ? '-left' : '-right';
        }
        return $ret;
    }

    /**
     * Loads and renders the landing page view.
     * @param Request $request
     */
    public function invokeLandingPage($request)
    {
        $this->model->setLocale($request->getLang());
        // load template
        $template = $this->twig->load('landing.twig');
        // set template variables
        $categoryLabel = $this->model->getClassificationLabel($request->getLang());
        $sortedVocabs = $this->model->getVocabularyList(false, true);
        $langList = $this->model->getLanguages($request->getLang());
        $listStyle = $this->listStyle();

        // render template
        echo $template->render(
            array(
                'sorted_vocabs' => $sortedVocabs,
                'category_label' => $categoryLabel,
                'languages' => $this->languages,
                'lang_list' => $langList,
                'request' => $request,
                'list_style' => $listStyle
            )
        );
    }

    /**
     * Invokes the concept page of a single concept in a specific vocabulary.
     *
     * @param Request $request
     */
    public function invokeVocabularyConcept(Request $request)
    {
        $lang = $request->getLang();
        $this->model->setLocale($request->getLang());
        $vocab = $request->getVocab();

        $langcodes = $vocab->getConfig()->getShowLangCodes();
        $uri = $vocab->getConceptURI($request->getUri()); // make sure it's a full URI

        $concept = $vocab->getConceptInfo($uri, $request->getContentLang());
        if (empty($concept)) {
            $this->invokeGenericErrorPage($request);
            return;
        }
        if ($this->notModified($concept)) {
            return;
        }
        $customLabels = $vocab->getConfig()->getPropertyLabelOverrides();

        $pluginParameters = json_encode($vocab->getConfig()->getPluginParameters());
        $template = $this->twig->load('concept.twig');

        $crumbs = $vocab->getBreadCrumbs($request->getContentLang(), $uri);
        echo $template->render(
            array(
            'concept' => $concept,
            'vocab' => $vocab,
            'concept_uri' => $uri,
            'languages' => $this->languages,
            'explicit_langcodes' => $langcodes,
            'visible_breadcrumbs' => $crumbs['breadcrumbs'],
            'hidden_breadcrumbs' => $crumbs['combined'],
            'request' => $request,
            'plugin_params' => $pluginParameters,
            'custom_labels' => $customLabels)
        );
    }

    /**
     * Invokes the feedback page with information of the users current vocabulary.
     */
    public function invokeFeedbackForm($request)
    {
        $template = $this->twig->load('feedback.twig');
        $this->model->setLocale($request->getLang());
        $vocabList = $this->model->getVocabularyList(false);
        $vocab = $request->getVocab();

        $feedbackSent = false;
        if ($request->getQueryParamPOST('message')) {
            $feedbackSent = true;
            $feedbackMsg = $request->getQueryParamPOST('message');
            $feedbackName = $request->getQueryParamPOST('name');
            $feedbackEmail = $request->getQueryParamPOST('email');
            $msgSubject = $request->getQueryParamPOST('msgsubject');
            $feedbackVocab = $request->getQueryParamPOST('vocab');
            $feedbackVocabEmail = ($feedbackVocab !== null && $feedbackVocab !== '') ?
                $this->model->getVocabulary($feedbackVocab)->getConfig()->getFeedbackRecipient() : null;
            // if the hidden field has been set a value we have found a spam bot
            // and we do not actually send the message.
            if ($this->honeypot->validateHoneypot($request->getQueryParamPOST('item-description')) &&
                $this->honeypot->validateHoneytime($request->getQueryParamPOST('user-captcha'), $this->model->getConfig()->getHoneypotTime())) {
                $this->sendFeedback($request, $feedbackMsg, $msgSubject, $feedbackName, $feedbackEmail, $feedbackVocab, $feedbackVocabEmail);
            }
        }
        echo $template->render(
            array(
                'languages' => $this->languages,
                'vocab' => $vocab,
                'vocabList' => $vocabList,
                'feedback_sent' => $feedbackSent,
                'request' => $request,
            )
        );
    }

    private function createFeedbackHeaders($fromName, $fromEmail, $toMail, $sender)
    {
        $headers = "MIME-Version: 1.0" . "\r\n";
        $headers .= "Content-type: text/html; charset=UTF-8" . "\r\n";
        if (!empty($toMail)) {
            $headers .= "Cc: " . $this->model->getConfig()->getFeedbackAddress() . "\r\n";
        }
        if (!empty($fromEmail)) {
            $headers .= "Reply-To: $fromName <$fromEmail>\r\n";
        }
        $service = $this->model->getConfig()->getServiceName();
        return $headers . "From: $fromName via $service <$sender>";
    }

    /**
     * Sends the user entered message through the php's mailer.
     * @param string $message content given by user.
     * @param string $messageSubject subject line given by user.
     * @param string $fromName senders own name.
     * @param string $fromEmail senders email address.
     * @param string $fromVocab which vocabulary is the feedback related to.
     */
    public function sendFeedback($request, $message, $messageSubject, $fromName = null, $fromEmail = null, $fromVocab = null, $toMail = null)
    {
        $toAddress = ($toMail) ? $toMail : $this->model->getConfig()->getFeedbackAddress();
        $messageSubject = "[" . $this->model->getConfig()->getServiceName() . "] " . $messageSubject;
        if ($fromVocab !== null && $fromVocab !== '') {
            $message = 'Feedback from vocab: ' . strtoupper($fromVocab) . "<br />" . $message;
        }
        $envelopeSender = $this->model->getConfig()->getFeedbackEnvelopeSender();
        // determine the sender address of the message
        $sender = $this->model->getConfig()->getFeedbackSender();
        if (empty($sender)) {
            $sender = $envelopeSender;
        }
        if (empty($sender)) {
            $sender = $this->model->getConfig()->getFeedbackAddress();
        }

        // determine sender name - default to "anonymous user" if not given by user
        if (empty($fromName)) {
            $fromName = "anonymous user";
        }
        $headers = $this->createFeedbackHeaders($fromName, $fromEmail, $toMail, $sender);
        $params = empty($envelopeSender) ? '' : "-f $envelopeSender";
        // adding some information about the user for debugging purposes.
        $message = $message . "<br /><br /> Debugging information:"
            . "<br />Timestamp: " . date(DATE_RFC2822)
            . "<br />User agent: " . $request->getServerConstant('HTTP_USER_AGENT')
            . "<br />Referer: " . $request->getServerConstant('HTTP_REFERER');

        try {
            mail($toAddress, $messageSubject, $message, $headers, $params);
        } catch (Exception $e) {
            header("HTTP/1.0 404 Not Found");
            $template = $this->twig->load('error.twig');
            if ($this->model->getConfig()->getLogCaughtExceptions()) {
                error_log('Caught exception: ' . $e->getMessage());
            }

            echo $template->render(
                array(
                    'languages' => $this->languages,
                )
            );

            return;
        }
    }

    /**
     * Invokes the about page for the Skosmos service.
     */
    public function invokeAboutPage($request)
    {
        $template = $this->twig->load('about.twig');
        $this->model->setLocale($request->getLang());
        $url = $request->getServerConstant('HTTP_HOST');

        echo $template->render(
            array(
                'languages' => $this->languages,
                'server_instance' => $url,
                'request' => $request,
            )
        );
    }

    /**
     * Invokes the search for concepts in all the available ontologies.
     */
    public function invokeGlobalSearch($request)
    {
        $lang = $request->getLang();
        $template = $this->twig->load('global-search.twig');
        $this->model->setLocale($request->getLang());

        $parameters = new ConceptSearchParameters($request, $this->model->getConfig());

        $vocabs = $request->getQueryParam('vocabs'); # optional
        // convert to vocids array to support multi-vocabulary search
        $vocids = ($vocabs !== null && $vocabs !== '') ? explode(' ', $vocabs) : null;
        $vocabObjects = array();
        if ($vocids) {
            foreach ($vocids as $vocid) {
                try {
                    $vocabObjects[] = $this->model->getVocabulary($vocid);
                } catch (ValueError $e) {
                    // fail fast with an error page if the vocabulary cannot be found
                    if ($this->model->getConfig()->getLogCaughtExceptions()) {
                        error_log('Caught exception: ' . $e->getMessage());
                    }
                    header("HTTP/1.0 400 Bad Request");
                    $this->invokeGenericErrorPage($request, $e->getMessage());
                    return;
                }
            }
        }
        $parameters->setVocabularies($vocabObjects);

        $counts = null;
        $searchResults = null;
        $errored = false;

        try {
            $countAndResults = $this->model->searchConceptsAndInfo($parameters);
            $counts = $countAndResults['count'];
            $searchResults = $countAndResults['results'];
        } catch (Exception $e) {
            $errored = true;
            header("HTTP/1.0 500 Internal Server Error");
            if ($this->model->getConfig()->getLogCaughtExceptions()) {
                error_log('Caught exception: ' . $e->getMessage());
            }
        }
        $vocabList = $this->model->getVocabularyList();
        $sortedVocabs = $this->model->getVocabularyList(false, true);
        $langList = $this->model->getLanguages($lang);

        echo $template->render(
            array(
                'search_count' => $counts,
                'languages' => $this->languages,
                'search_results' => $searchResults,
                'rest' => $parameters->getOffset() > 0,
                'global_search' => true,
                'search_failed' => $errored,
                'term' => $request->getQueryParamRaw('q'),
                'lang_list' => $langList,
                'vocabs' => str_replace(' ', '+', $vocabs),
                'vocab_list' => $vocabList,
                'sorted_vocabs' => $sortedVocabs,
                'request' => $request,
                'parameters' => $parameters
            )
        );
    }

    /**
     * Invokes the search for a single vocabs concepts.
     */
    public function invokeVocabularySearch($request)
    {
        $template = $this->twig->load('vocab-search.twig');
        $this->model->setLocale($request->getLang());
        $vocab = $request->getVocab();
        $searchResults = null;
        try {
            $vocabTypes = $this->model->getTypes($request->getVocabid(), $request->getLang());
        } catch (Exception $e) {
            header("HTTP/1.0 500 Internal Server Error");
            if ($this->model->getConfig()->getLogCaughtExceptions()) {
                error_log('Caught exception: ' . $e->getMessage());
            }

            echo $template->render(
                array(
                    'languages' => $this->languages,
                    'vocab' => $vocab,
                    'request' => $request,
                    'search_results' => $searchResults
                )
            );

            return;
        }

        $langcodes = $vocab->getConfig()->getShowLangCodes();
        $parameters = new ConceptSearchParameters($request, $this->model->getConfig());

        try {
            $countAndResults = $this->model->searchConceptsAndInfo($parameters);
            $counts = $countAndResults['count'];
            $searchResults = $countAndResults['results'];
        } catch (Exception $e) {
            header("HTTP/1.0 404 Not Found");
            if ($this->model->getConfig()->getLogCaughtExceptions()) {
                error_log('Caught exception: ' . $e->getMessage());
            }

            echo $template->render(
                array(
                    'languages' => $this->languages,
                    'vocab' => $vocab,
                    'term' => $request->getQueryParam('q'),
                    'search_results' => $searchResults
                )
            );
            return;
        }
        echo $template->render(
            array(
                'languages' => $this->languages,
                'vocab' => $vocab,
                'search_results' => $searchResults,
                'search_count' => $counts,
                'rest' => $parameters->getOffset() > 0,
                'limit_parent' => $parameters->getParentLimit(),
                'limit_type' =>  $request->getQueryParam('type') ? explode('+', $request->getQueryParam('type')) : null,
                'limit_group' => $parameters->getGroupLimit(),
                'limit_scheme' =>  $request->getQueryParam('scheme') ? explode('+', $request->getQueryParam('scheme')) : null,
                'group_index' => $vocab->listConceptGroups($request->getContentLang()),
                'parameters' => $parameters,
                'term' => $request->getQueryParamRaw('q'),
                'types' => $vocabTypes,
                'explicit_langcodes' => $langcodes,
                'request' => $request,
            )
        );
    }

    /**
     * Loads and renders the view containing a specific vocabulary.
     */
    public function invokeVocabularyHome($request)
    {
        $lang = $request->getLang();
        $this->model->setLocale($request->getLang());
        $vocab = $request->getVocab();

        $defaultView = $vocab->getConfig()->getDefaultSidebarView();
        // load template
        if ($defaultView === 'groups') {
            $this->invokeGroupIndex($request, true);
            return;
        } elseif ($defaultView === 'new') {
            $this->invokeChangeList($request);
        }
        $pluginParameters = json_encode($vocab->getConfig()->getPluginParameters());

        $template = $this->twig->load('vocab-home.twig');

        echo $template->render(
            array(
                'languages' => $this->languages,
                'vocab' => $vocab,
                'search_letter' => 'A',
                'active_tab' => $defaultView,
                'request' => $request,
                'plugin_params' => $pluginParameters
            )
        );
    }

    /**
     * Invokes a very generic errorpage.
     */
    public function invokeGenericErrorPage($request, $message = null)
    {
        $this->model->setLocale($request->getLang());
        header("HTTP/1.0 404 Not Found");
        $template = $this->twig->load('error.twig');
        echo $template->render(
            array(
                'languages' => $this->languages,
                'request' => $request,
                'vocab' => $request->getVocab(),
                'message' => $message,
                'requested_page' => filter_input(INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
            )
        );
    }
}