Completed
Push — master ( a895be...5e3bd7 )
by Osma
02:01
created

Controller   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 226
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 5

Importance

Changes 0
Metric Value
wmc 38
lcom 3
cbo 5
dl 0
loc 226
rs 9.36
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 21 2
A setLanguageProperties() 0 11 2
A negotiateFormat() 0 15 4
A guessBaseHref() 0 18 4
A getBaseHref() 0 4 2
C linkUrlFilter() 0 38 14
A returnError() 0 6 1
A sendNotModifiedHeader() 0 12 4
A getIfModifiedSince() 0 9 2
A sendHeader() 0 4 1
1
<?php
2
3
/**
4
 * Handles all the requests from the user and changes the view accordingly.
5
 */
6
class Controller
7
{
8
    /**
9
     * The controller has to know the model to access the data stored there.
10
     * @var Model $model contains the Model object.
11
     */
12
    public $model;
13
14
    protected $negotiator;
15
16
    protected $languages;
17
18
    /**
19
     * Initializes the Model object.
20
     */
21
    public function __construct($model)
22
    {
23
        $this->model = $model;
24
        $this->negotiator = new \Negotiation\Negotiator();
25
26
        // Specify the location of the translation tables
27
        bindtextdomain('skosmos', 'resource/translations');
28
        bind_textdomain_codeset('skosmos', 'UTF-8');
29
30
        // Choose domain for translations
31
        textdomain('skosmos');
32
33
        // Build arrays of language information, with 'locale' and 'name' keys
34
        $this->languages = array();
35
        foreach ($this->model->getConfig()->getLanguages() as $langcode => $locale) {
36
            $this->languages[$langcode] = array('locale' => $locale);
37
            $this->setLanguageProperties($langcode);
38
            $this->languages[$langcode]['name'] = gettext('in_this_language');
39
            $this->languages[$langcode]['lemma'] = Punic\Language::getName($langcode, $langcode);
40
        }
41
    }
42
43
    /**
44
     * Sets the locale language properties from the parameter (used by gettext and some Model classes).
45
     * @param string $lang language parameter eg. 'fi' for Finnish.
46
     */
47
    public function setLanguageProperties($lang)
48
    {
49
        if (array_key_exists($lang, $this->languages)) {
50
            $locale = $this->languages[$lang]['locale'];
51
            putenv("LANGUAGE=$locale");
52
            putenv("LC_ALL=$locale");
53
            setlocale(LC_ALL, $locale);
54
        } else {
55
            trigger_error("Unsupported language '$lang', not setting locale", E_USER_WARNING);
56
        }
57
    }
58
59
    /**
60
     * Negotiate a MIME type according to the proposed format, the list of valid
61
     * formats, and an optional proposed format.
62
     * As a side effect, set the HTTP Vary header if a choice was made based on
63
     * the Accept header.
64
     * @param array $choices possible MIME types as strings
65
     * @param string $accept HTTP Accept header value
66
     * @param string $format proposed format
67
     * @return string selected format, or null if negotiation failed
68
     */
69
    protected function negotiateFormat($choices, $accept, $format)
70
    {
71
        if ($format) {
72
            if (!in_array($format, $choices)) {
73
                return null;
74
            }
75
            return $format;
76
        }
77
78
        // if there was no proposed format, negotiate a suitable format
79
        header('Vary: Accept'); // inform caches that a decision was made based on Accept header
80
        $best = $this->negotiator->getBest($accept, $choices);
81
        $format = ($best !== null) ? $best->getValue() : null;
82
        return $format;
83
    }
84
85
    private function isSecure()
86
    {
87
        if ($protocol = filter_input(INPUT_SERVER, 'HTTP_X_FORWARDED_PROTO', FILTER_SANITIZE_STRING)) {
88
            return \in_array(strtolower($protocol), ['https', 'on', 'ssl', '1'], true);
89
        }
90
91
        return filter_input(INPUT_SERVER, 'HTTPS', FILTER_SANITIZE_STRING) !== null;
92
    }
93
94
    private function guessBaseHref()
95
    {
96
        $script_name = filter_input(INPUT_SERVER, 'SCRIPT_NAME', FILTER_SANITIZE_STRING);
97
        $script_filename = filter_input(INPUT_SERVER, 'SCRIPT_FILENAME', FILTER_SANITIZE_STRING);
98
        $script_filename = realpath($script_filename); // resolve any symlinks (see #274)
99
        $script_filename = str_replace("\\", "/", $script_filename); // fixing windows paths with \ (see #309)
100
        $base_dir = __DIR__; // Absolute path to your installation, ex: /var/www/mywebsite
101
        $base_dir = str_replace("\\", "/", $base_dir); // fixing windows paths with \ (see #309)
102
        $doc_root = preg_replace("!{$script_name}$!", '', $script_filename);
103
        $base_url = preg_replace("!^{$doc_root}!", '', $base_dir);
104
        $base_url = str_replace('/controller', '/', $base_url);
105
        $protocol = $this->isSecure() ? 'https' : 'http';
106
        $port = filter_input(INPUT_SERVER, 'SERVER_PORT', FILTER_SANITIZE_STRING);
107
        $disp_port = ($port == 80 || $port == 443) ? '' : ":$port";
108
        $domain = filter_input(INPUT_SERVER, 'SERVER_NAME', FILTER_SANITIZE_STRING);
109
        $full_url = "$protocol://{$domain}{$disp_port}{$base_url}";
110
        return $full_url;
111
    }
112
113
    public function getBaseHref()
114
    {
115
        return ($this->model->getConfig()->getBaseHref() !== null) ? $this->model->getConfig()->getBaseHref() : $this->guessBaseHref();
116
    }
117
118
    /**
119
     * Creates Skosmos links from uris.
120
     * @param string $uri
121
     * @param Vocabulary $vocab
122
     * @param string $lang
123
     * @param string $type
124
     * @param string $clang content
125
     * @param string $term
126
     * @throws Exception if the vocabulary ID is not found in configuration
127
     * @return string containing the Skosmos link
128
     */
129
    public function linkUrlFilter($uri, $vocab, $lang, $type = 'page', $clang = null, $term = null) {
130
        // $vocab can either be null, a vocabulary id (string) or a Vocabulary object
131
        if ($vocab === null) {
132
            // target vocabulary is unknown, best bet is to link to the plain URI
133
            return $uri;
134
        } elseif (is_string($vocab)) {
135
            $vocid = $vocab;
136
            $vocab = $this->model->getVocabulary($vocid);
137
        } else {
138
            $vocid = $vocab->getId();
139
        }
140
141
        $params = array();
142
        if (isset($clang) && $clang !== $lang) {
143
            $params['clang'] = $clang;
144
        }
145
146
        if (isset($term)) {
147
            $params['q'] = $term;
148
        }
149
150
        // case 1: URI within vocabulary namespace: use only local name
151
        $localname = $vocab->getLocalName($uri);
152
        if ($localname !== $uri && $localname === urlencode($localname)) {
153
            // check that the prefix stripping worked, and there are no problematic chars in localname
154
            $paramstr = count($params) > 0 ? '?' . http_build_query($params) : '';
155
            if ($type && $type !== '' && $type !== 'vocab' && !($localname === '' && $type === 'page')) {
156
                return "$vocid/$lang/$type/$localname" . $paramstr;
157
            }
158
159
            return "$vocid/$lang/$localname" . $paramstr;
160
        }
161
162
        // case 2: URI outside vocabulary namespace, or has problematic chars
163
        // pass the full URI as parameter instead
164
        $params['uri'] = $uri;
165
        return "$vocid/$lang/$type/?" . http_build_query($params);
166
    }
167
168
    /**
169
     * Echos an error message when the request can't be fulfilled.
170
     * @param string $code
171
     * @param string $status
172
     * @param string $message
173
     */
174
    protected function returnError($code, $status, $message)
175
    {
176
        header("HTTP/1.0 $code $status");
177
        header("Content-type: text/plain; charset=utf-8");
178
        echo "$code $status : $message";
179
    }
180
181
    /**
182
     * If the $modifiedDate is a valid DateTime, and if the $_SERVER variable contains the right info, and
183
     * if the $modifiedDate is not more recent than the latest value in $_SERVER, then this function sets the
184
     * HTTP 304 not modified and returns true..
185
     *
186
     * If the $modifiedDate is still valid, then it sets the Last-Modified header, to be used by the browser for
187
     * subsequent requests, and returns false.
188
     *
189
     * Otherwise, it returns false.
190
     *
191
     * @param DateTime $modifiedDate the last modified date to be compared against server's modified since information
192
     * @return bool whether it sent the HTTP 304 not modified headers or not (useful for sending the response without
193
     *              further actions)
194
     */
195
    protected function sendNotModifiedHeader($modifiedDate): bool
196
    {
197
        if ($modifiedDate) {
198
            $ifModifiedSince = $this->getIfModifiedSince();
199
            $this->sendHeader("Last-Modified: " . $modifiedDate->format('Y-m-d H:i:s'));
200
            if (!is_null($ifModifiedSince) && $ifModifiedSince >= $modifiedDate) {
201
                $this->sendHeader("HTTP/1.0 304 Not Modified");
202
                return true;
203
            }
204
        }
205
        return false;
206
    }
207
208
    /**
209
     * @return DateTime|null a DateTime object if the value exists in the $_SERVER variable, null otherwise
210
     */
211
    protected function getIfModifiedSince()
212
    {
213
        $ifModifiedSince = null;
214
        if (isset($_SERVER["HTTP_IF_MODIFIED_SINCE"])) {
215
            // example value set by a browser: "2019-04-13 08:28:23"
216
            $ifModifiedSince = DateTime::createFromFormat("Y-m-d H:i:s", $_SERVER["HTTP_IF_MODIFIED_SINCE"]);
217
        }
218
        return $ifModifiedSince;
219
    }
220
221
    /**
222
     * Sends HTTP headers. Simply calls PHP built-in header function. But being
223
     * a function here, it can easily be tested/mocked.
224
     *
225
     * @param $header string header to be sent
226
     */
227
    protected function sendHeader($header)
228
    {
229
        header($header);
230
    }
231
}
232