WebController   F
last analyzed

Complexity

Total Complexity 62

Size/Duplication

Total Lines 500
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 62
eloc 280
c 1
b 0
f 0
dl 0
loc 500
rs 3.44

13 Methods

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

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