WebController::createFeedbackHeaders()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 4
nop 4
dl 0
loc 12
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Importing the dependencies.
5
 */
6
use Punic\Language;
7
use Symfony\Bridge\Twig\Extension\TranslationExtension;
8
9
/**
10
 * WebController is an extension of the Controller that handles all
11
 * the requests originating from the view of the website.
12
 */
13
class WebController extends Controller
14
{
15
    /**
16
     * Provides access to the templating engine.
17
     * @property object $twig the twig templating engine.
18
     */
19
    public $twig;
20
    public $honeypot;
21
    public $translator;
22
23
    /**
24
     * Constructor for the WebController.
25
     * @param Model $model
26
     */
27
    public function __construct($model)
28
    {
29
        parent::__construct($model);
30
31
        // initialize Twig templates
32
        $tmpDir = $model->getConfig()->getTemplateCache();
33
34
        // check if the cache pointed by config.ttl exists, if not we create it.
35
        if (!file_exists($tmpDir)) {
36
            mkdir($tmpDir);
37
        }
38
39
        // specify where to look for templates and cache
40
        $loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../view');
41
        // initialize Twig environment
42
        $this->twig = new \Twig\Environment($loader, array('cache' => $tmpDir,'auto_reload' => true));
43
        // used for setting the base href for the relative urls
44
        $this->twig->addGlobal("BaseHref", $this->getBaseHref());
45
46
        // pass the GlobalConfig object to templates so they can access configuration
47
        $this->twig->addGlobal("GlobalConfig", $this->model->getConfig());
48
49
        // setting the list of properties to be displayed in the search results
50
        $this->twig->addGlobal("PreferredProperties", array('skos:prefLabel', 'skos:narrower', 'skos:broader', 'skosmos:memberOf', 'skos:altLabel', 'skos:related'));
51
52
        // register a Twig filter for generating URLs for global pages (landing, about, feedback, vocab-home..)
53
        $this->twig->addExtension(new GlobalUrlExtension());
54
55
        // register a Twig filter for generating URLs for vocabulary resources (concepts and groups)
56
        $this->twig->addExtension(new LinkUrlExtension($model));
57
58
        // register a Twig filter for generating strings from language codes with CLDR
59
        $langFilter = new \Twig\TwigFilter('lang_name', function ($langcode, $lang) {
60
            return Language::getName($langcode, $lang);
61
        });
62
        $this->twig->addFilter($langFilter);
63
64
        $this->translator = $model->getTranslator();
65
        $this->twig->addExtension(new TranslationExtension($this->translator));
66
67
        // create the honeypot
68
        $this->honeypot = new \Honeypot();
69
        if (!$this->model->getConfig()->getHoneypotEnabled()) {
70
            $this->honeypot->disable();
71
        }
72
        $this->twig->addGlobal('honeypot', $this->honeypot);
73
    }
74
75
    /**
76
     * Guess the language of the user. Return a language string that is one
77
     * of the supported languages defined in the $LANGUAGES setting, e.g. "fi".
78
     * @param Request $request HTTP request
79
     * @param string $vocid identifier for the vocabulary eg. 'yso'.
80
     * @return string returns the language choice as a numeric string value
81
     */
82
    public function guessLanguage($request, $vocid = null)
83
    {
84
        // 1. select language based on SKOSMOS_LANGUAGE cookie
85
        $languageCookie = $request->getCookie('SKOSMOS_LANGUAGE');
86
        if ($languageCookie) {
87
            return $languageCookie;
88
        }
89
90
        // 2. if vocabulary given, select based on the default language of the vocabulary
91
        if ($vocid !== null && $vocid !== '') {
92
            try {
93
                $vocab = $this->model->getVocabulary($vocid);
94
                return $vocab->getConfig()->getDefaultLanguage();
95
            } catch (Exception $e) {
96
                // vocabulary id not found, move on to the next selection method
97
            }
98
        }
99
100
        // 3. select language based on Accept-Language header
101
        header('Vary: Accept-Language'); // inform caches that a decision was made based on Accept header
102
        $this->negotiator = new \Negotiation\LanguageNegotiator();
103
        $langcodes = array_keys($this->languages);
104
        // using a random language from the configured UI languages when there is no accept language header set
105
        $acceptLanguage = $request->getServerConstant('HTTP_ACCEPT_LANGUAGE') ? $request->getServerConstant('HTTP_ACCEPT_LANGUAGE') : $langcodes[0];
106
107
        $bestLang = $this->negotiator->getBest($acceptLanguage, $langcodes);
108
        if (isset($bestLang) && in_array($bestLang->getValue(), $langcodes)) {
109
            return $bestLang->getValue();
110
        }
111
112
        // show default site or prompt for language
113
        return $langcodes[0];
114
    }
115
116
    /**
117
     * Determines a css class that controls width and positioning of the vocabulary list element.
118
     * The layout is wider if the left/right box templates have not been provided.
119
     * @return string css class for the container eg. 'voclist-wide' or 'voclist-right'
120
     */
121
    private function listStyle()
122
    {
123
        $left = file_exists('view/left.inc');
124
        $right = file_exists('view/right.inc');
125
        $ret = 'voclist';
126
        if (!$left && !$right) {
127
            $ret .= '-wide';
128
        } elseif (!($left && $right) && ($right || $left)) {
129
            $ret .= ($right) ? '-left' : '-right';
130
        }
131
        return $ret;
132
    }
133
134
    /**
135
     * Loads and renders the landing page view.
136
     * @param Request $request
137
     */
138
    public function invokeLandingPage($request)
139
    {
140
        $this->model->setLocale($request->getLang());
141
        // load template
142
        $template = $this->twig->load('landing.twig');
143
        // set template variables
144
        $categoryLabel = $this->model->getClassificationLabel($request->getLang());
145
        $sortedVocabs = $this->model->getVocabularyList(false, true);
146
        $langList = $this->model->getLanguages($request->getLang());
147
        $listStyle = $this->listStyle();
148
149
        // render template
150
        echo $template->render(
151
            array(
152
                'sorted_vocabs' => $sortedVocabs,
153
                'category_label' => $categoryLabel,
154
                'languages' => $this->languages,
155
                'lang_list' => $langList,
156
                'request' => $request,
157
                'list_style' => $listStyle
158
            )
159
        );
160
    }
161
162
    /**
163
     * Invokes the concept page of a single concept in a specific vocabulary.
164
     *
165
     * @param Request $request
166
     */
167
    public function invokeVocabularyConcept(Request $request)
168
    {
169
        $lang = $request->getLang();
0 ignored issues
show
Unused Code introduced by
The assignment to $lang is dead and can be removed.
Loading history...
170
        $this->model->setLocale($request->getLang());
171
        $vocab = $request->getVocab();
172
173
        $langcodes = $vocab->getConfig()->getShowLangCodes();
174
        $uri = $vocab->getConceptURI($request->getUri()); // make sure it's a full URI
175
176
        $concept = $vocab->getConceptInfo($uri, $request->getContentLang());
177
        if (empty($concept)) {
178
            $this->invokeGenericErrorPage($request);
179
            return;
180
        }
181
        if ($this->notModified($concept)) {
182
            return;
183
        }
184
        $customLabels = $vocab->getConfig()->getPropertyLabelOverrides();
185
186
        $pluginParameters = json_encode($vocab->getConfig()->getPluginParameters());
187
        $template = $this->twig->load('concept.twig');
188
189
        $crumbs = $vocab->getBreadCrumbs($request->getContentLang(), $uri);
190
        echo $template->render(
191
            array(
192
            'concept' => $concept,
193
            'vocab' => $vocab,
194
            'concept_uri' => $uri,
195
            'languages' => $this->languages,
196
            'explicit_langcodes' => $langcodes,
197
            'visible_breadcrumbs' => $crumbs['breadcrumbs'],
198
            'hidden_breadcrumbs' => $crumbs['combined'],
199
            'request' => $request,
200
            'plugin_params' => $pluginParameters,
201
            'custom_labels' => $customLabels)
202
        );
203
    }
204
205
    /**
206
     * Invokes the feedback page with information of the users current vocabulary.
207
     */
208
    public function invokeFeedbackForm($request)
209
    {
210
        $template = $this->twig->load('feedback.twig');
211
        $this->model->setLocale($request->getLang());
212
        $vocabList = $this->model->getVocabularyList(false);
213
        $vocab = $request->getVocab();
214
215
        $feedbackSent = false;
216
        if ($request->getQueryParamPOST('message')) {
217
            $feedbackSent = true;
218
            $feedbackMsg = $request->getQueryParamPOST('message');
219
            $feedbackName = $request->getQueryParamPOST('name');
220
            $feedbackEmail = $request->getQueryParamPOST('email');
221
            $msgSubject = $request->getQueryParamPOST('msgsubject');
222
            $feedbackVocab = $request->getQueryParamPOST('vocab');
223
            $feedbackVocabEmail = ($feedbackVocab !== null && $feedbackVocab !== '') ?
224
                $this->model->getVocabulary($feedbackVocab)->getConfig()->getFeedbackRecipient() : null;
225
            // if the hidden field has been set a value we have found a spam bot
226
            // and we do not actually send the message.
227
            if ($this->honeypot->validateHoneypot($request->getQueryParamPOST('item-description')) &&
228
                $this->honeypot->validateHoneytime($request->getQueryParamPOST('user-captcha'), $this->model->getConfig()->getHoneypotTime())) {
229
                $this->sendFeedback($request, $feedbackMsg, $msgSubject, $feedbackName, $feedbackEmail, $feedbackVocab, $feedbackVocabEmail);
230
            }
231
        }
232
        echo $template->render(
233
            array(
234
                'languages' => $this->languages,
235
                'vocab' => $vocab,
236
                'vocabList' => $vocabList,
237
                'feedback_sent' => $feedbackSent,
238
                'request' => $request,
239
            )
240
        );
241
    }
242
243
    private function createFeedbackHeaders($fromName, $fromEmail, $toMail, $sender)
244
    {
245
        $headers = "MIME-Version: 1.0" . "\r\n";
246
        $headers .= "Content-type: text/html; charset=UTF-8" . "\r\n";
247
        if (!empty($toMail)) {
248
            $headers .= "Cc: " . $this->model->getConfig()->getFeedbackAddress() . "\r\n";
249
        }
250
        if (!empty($fromEmail)) {
251
            $headers .= "Reply-To: $fromName <$fromEmail>\r\n";
252
        }
253
        $service = $this->model->getConfig()->getServiceName();
254
        return $headers . "From: $fromName via $service <$sender>";
255
    }
256
257
    /**
258
     * Sends the user entered message through the php's mailer.
259
     * @param string $message content given by user.
260
     * @param string $messageSubject subject line given by user.
261
     * @param string $fromName senders own name.
262
     * @param string $fromEmail senders email address.
263
     * @param string $fromVocab which vocabulary is the feedback related to.
264
     */
265
    public function sendFeedback($request, $message, $messageSubject, $fromName = null, $fromEmail = null, $fromVocab = null, $toMail = null)
266
    {
267
        $toAddress = ($toMail) ? $toMail : $this->model->getConfig()->getFeedbackAddress();
268
        $messageSubject = "[" . $this->model->getConfig()->getServiceName() . "] " . $messageSubject;
269
        if ($fromVocab !== null && $fromVocab !== '') {
270
            $message = 'Feedback from vocab: ' . strtoupper($fromVocab) . "<br />" . $message;
271
        }
272
        $envelopeSender = $this->model->getConfig()->getFeedbackEnvelopeSender();
273
        // determine the sender address of the message
274
        $sender = $this->model->getConfig()->getFeedbackSender();
275
        if (empty($sender)) {
276
            $sender = $envelopeSender;
277
        }
278
        if (empty($sender)) {
279
            $sender = $this->model->getConfig()->getFeedbackAddress();
280
        }
281
282
        // determine sender name - default to "anonymous user" if not given by user
283
        if (empty($fromName)) {
284
            $fromName = "anonymous user";
285
        }
286
        $headers = $this->createFeedbackHeaders($fromName, $fromEmail, $toMail, $sender);
287
        $params = empty($envelopeSender) ? '' : "-f $envelopeSender";
288
        // adding some information about the user for debugging purposes.
289
        $message = $message . "<br /><br /> Debugging information:"
290
            . "<br />Timestamp: " . date(DATE_RFC2822)
291
            . "<br />User agent: " . $request->getServerConstant('HTTP_USER_AGENT')
292
            . "<br />Referer: " . $request->getServerConstant('HTTP_REFERER');
293
294
        try {
295
            mail($toAddress, $messageSubject, $message, $headers, $params);
296
        } catch (Exception $e) {
297
            header("HTTP/1.0 404 Not Found");
298
            $template = $this->twig->load('error.twig');
299
            if ($this->model->getConfig()->getLogCaughtExceptions()) {
300
                error_log('Caught exception: ' . $e->getMessage());
301
            }
302
303
            echo $template->render(
304
                array(
305
                    'languages' => $this->languages,
306
                )
307
            );
308
309
            return;
310
        }
311
    }
312
313
    /**
314
     * Invokes the about page for the Skosmos service.
315
     */
316
    public function invokeAboutPage($request)
317
    {
318
        $template = $this->twig->load('about.twig');
319
        $this->model->setLocale($request->getLang());
320
        $url = $request->getServerConstant('HTTP_HOST');
321
322
        echo $template->render(
323
            array(
324
                'languages' => $this->languages,
325
                'server_instance' => $url,
326
                'request' => $request,
327
            )
328
        );
329
    }
330
331
    /**
332
     * Invokes the search for concepts in all the available ontologies.
333
     */
334
    public function invokeGlobalSearch($request)
335
    {
336
        $lang = $request->getLang();
337
        $template = $this->twig->load('global-search.twig');
338
        $this->model->setLocale($request->getLang());
339
340
        $parameters = new ConceptSearchParameters($request, $this->model->getConfig());
341
342
        $vocabs = $request->getQueryParam('vocabs'); # optional
343
        // convert to vocids array to support multi-vocabulary search
344
        $vocids = ($vocabs !== null && $vocabs !== '') ? explode(' ', $vocabs) : null;
345
        $vocabObjects = array();
346
        if ($vocids) {
347
            foreach ($vocids as $vocid) {
348
                try {
349
                    $vocabObjects[] = $this->model->getVocabulary($vocid);
350
                } catch (ValueError $e) {
351
                    // fail fast with an error page if the vocabulary cannot be found
352
                    if ($this->model->getConfig()->getLogCaughtExceptions()) {
353
                        error_log('Caught exception: ' . $e->getMessage());
354
                    }
355
                    header("HTTP/1.0 400 Bad Request");
356
                    $this->invokeGenericErrorPage($request, $e->getMessage());
357
                    return;
358
                }
359
            }
360
        }
361
        $parameters->setVocabularies($vocabObjects);
362
363
        $counts = null;
364
        $searchResults = null;
365
        $errored = false;
366
367
        try {
368
            $countAndResults = $this->model->searchConceptsAndInfo($parameters);
369
            $counts = $countAndResults['count'];
370
            $searchResults = $countAndResults['results'];
371
        } catch (Exception $e) {
372
            $errored = true;
373
            header("HTTP/1.0 500 Internal Server Error");
374
            if ($this->model->getConfig()->getLogCaughtExceptions()) {
375
                error_log('Caught exception: ' . $e->getMessage());
376
            }
377
        }
378
        $vocabList = $this->model->getVocabularyList();
379
        $sortedVocabs = $this->model->getVocabularyList(false, true);
380
        $langList = $this->model->getLanguages($lang);
381
382
        echo $template->render(
383
            array(
384
                'search_count' => $counts,
385
                'languages' => $this->languages,
386
                'search_results' => $searchResults,
387
                'rest' => $parameters->getOffset() > 0,
388
                'global_search' => true,
389
                'search_failed' => $errored,
390
                'term' => $request->getQueryParamRaw('q'),
391
                'lang_list' => $langList,
392
                'vocabs' => isset($vocabs) ? str_replace(' ', '+', $vocabs) : null,
393
                'vocab_list' => $vocabList,
394
                'sorted_vocabs' => $sortedVocabs,
395
                'request' => $request,
396
                'parameters' => $parameters
397
            )
398
        );
399
    }
400
401
    /**
402
     * Invokes the search for a single vocabs concepts.
403
     */
404
    public function invokeVocabularySearch($request)
405
    {
406
        $template = $this->twig->load('vocab-search.twig');
407
        $this->model->setLocale($request->getLang());
408
        $vocab = $request->getVocab();
409
        $searchResults = null;
410
        try {
411
            $vocabTypes = $this->model->getTypes($request->getVocabid(), $request->getLang());
412
        } catch (Exception $e) {
413
            header("HTTP/1.0 500 Internal Server Error");
414
            if ($this->model->getConfig()->getLogCaughtExceptions()) {
415
                error_log('Caught exception: ' . $e->getMessage());
416
            }
417
418
            echo $template->render(
419
                array(
420
                    'languages' => $this->languages,
421
                    'vocab' => $vocab,
422
                    'request' => $request,
423
                    'search_results' => $searchResults
424
                )
425
            );
426
427
            return;
428
        }
429
430
        $langcodes = $vocab->getConfig()->getShowLangCodes();
431
        $parameters = new ConceptSearchParameters($request, $this->model->getConfig());
432
433
        try {
434
            $countAndResults = $this->model->searchConceptsAndInfo($parameters);
435
            $counts = $countAndResults['count'];
436
            $searchResults = $countAndResults['results'];
437
        } catch (Exception $e) {
438
            header("HTTP/1.0 404 Not Found");
439
            if ($this->model->getConfig()->getLogCaughtExceptions()) {
440
                error_log('Caught exception: ' . $e->getMessage());
441
            }
442
443
            echo $template->render(
444
                array(
445
                    'languages' => $this->languages,
446
                    'vocab' => $vocab,
447
                    'term' => $request->getQueryParam('q'),
448
                    'search_results' => $searchResults
449
                )
450
            );
451
            return;
452
        }
453
        echo $template->render(
454
            array(
455
                'languages' => $this->languages,
456
                'vocab' => $vocab,
457
                'search_results' => $searchResults,
458
                'search_count' => $counts,
459
                'rest' => $parameters->getOffset() > 0,
460
                'limit_parent' => $parameters->getParentLimit(),
461
                'limit_type' =>  $request->getQueryParam('type') ? explode('+', $request->getQueryParam('type')) : null,
462
                'limit_group' => $parameters->getGroupLimit(),
463
                'limit_scheme' =>  $request->getQueryParam('scheme') ? explode('+', $request->getQueryParam('scheme')) : null,
464
                'group_index' => $vocab->listConceptGroups($request->getContentLang()),
465
                'parameters' => $parameters,
466
                'term' => $request->getQueryParamRaw('q'),
467
                'types' => $vocabTypes,
468
                'explicit_langcodes' => $langcodes,
469
                'request' => $request,
470
            )
471
        );
472
    }
473
474
    /**
475
     * Loads and renders the view containing a specific vocabulary.
476
     */
477
    public function invokeVocabularyHome($request)
478
    {
479
        $lang = $request->getLang();
0 ignored issues
show
Unused Code introduced by
The assignment to $lang is dead and can be removed.
Loading history...
480
        $this->model->setLocale($request->getLang());
481
        $vocab = $request->getVocab();
482
483
        $defaultView = $vocab->getConfig()->getDefaultSidebarView();
484
485
        $pluginParameters = json_encode($vocab->getConfig()->getPluginParameters());
486
487
        $template = $this->twig->load('vocab-home.twig');
488
489
        echo $template->render(
490
            array(
491
                'languages' => $this->languages,
492
                'vocab' => $vocab,
493
                'search_letter' => 'A',
494
                'active_tab' => $defaultView,
495
                'request' => $request,
496
                'plugin_params' => $pluginParameters
497
            )
498
        );
499
    }
500
501
    /**
502
     * Invokes a very generic errorpage.
503
     */
504
    public function invokeGenericErrorPage($request, $message = null)
505
    {
506
        $this->model->setLocale($request->getLang());
507
        header("HTTP/1.0 404 Not Found");
508
        $template = $this->twig->load('error.twig');
509
        echo $template->render(
510
            array(
511
                'languages' => $this->languages,
512
                'request' => $request,
513
                'vocab' => $request->getVocab(),
514
                'message' => $message,
515
                'requested_page' => filter_input(INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
0 ignored issues
show
Bug introduced by
The constant FILTER_SANITIZE_FULL_SPECIAL_CHARS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
516
            )
517
        );
518
    }
519
}
520