WebController   F
last analyzed

Complexity

Total Complexity 63

Size/Duplication

Total Lines 516
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 63
eloc 288
c 2
b 0
f 0
dl 0
loc 516
rs 3.36

13 Methods

Rating   Name   Duplication   Size   Complexity  
A invokeLandingPage() 0 20 1
A invokeVocabularyConcept() 0 35 3
B invokeGlobalSearch() 0 63 10
C sendFeedback() 0 45 10
B invokeVocabularySearch() 0 66 7
A invokeGenericErrorPage() 0 12 1
A createFeedbackHeaders() 0 12 3
B invokeFeedbackForm() 0 31 6
B listStyle() 0 11 8
B guessLanguage() 0 32 8
A invokeVocabularyHome() 0 20 1
A invokeAboutPage() 0 11 1
A __construct() 0 59 4

How to fix   Complexity   

Complex Class

Complex classes like WebController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WebController, and based on these observations, apply Extract Interface, too.

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