Completed
Pull Request — master (#2)
by René
03:21
created

Controller::getViewTemplate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 5
Bugs 0 Features 0
Metric Value
c 5
b 0
f 0
dl 0
loc 10
ccs 5
cts 5
cp 1
rs 9.4285
cc 2
eloc 5
nc 2
nop 0
crap 2
1
<?php
2
declare(strict_types = 1);
3
4
namespace Zortje\MVC\Controller;
5
6
use Zortje\MVC\Configuration\Configuration;
7
use Zortje\MVC\Controller\Exception\ControllerActionNonexistentException;
8
use Zortje\MVC\Controller\Exception\ControllerActionPrivateInsufficientAuthenticationException;
9
use Zortje\MVC\Controller\Exception\ControllerActionProtectedInsufficientAuthenticationException;
10
use Zortje\MVC\Storage\Cookie\Cookie;
11
use Zortje\MVC\User\User;
12
use Zortje\MVC\View\Render\HtmlRender;
13
14
/**
15
 * Class Controller
16
 *
17
 * @package Zortje\MVC\Controller
18
 */
19
class Controller
20
{
21
22
    /**
23
     * Controller action is publicly accessible
24
     */
25
    const ACTION_PUBLIC = 0;
26
27
    /**
28
     * Controller action requires authentication
29
     * Will redirect to sign in page if not authenticated
30
     */
31
    const ACTION_PROTECTED = 1;
32
33
    /**
34
     * Controller action requires authentication
35
     * Will result in an 404 if not authenticated
36
     */
37
    const ACTION_PRIVATE = 2;
38
39
    /**
40
     * @var array Controller action access rules
41
     */
42
    protected $access = [];
43
44
    /**
45
     * @var \PDO PDO
46
     */
47
    protected $pdo;
48
49
    /**
50
     * @var Configuration
51
     */
52
    protected $configuration;
53
54
    /**
55
     * @var Cookie
56
     */
57
    protected $cookie;
58
59
    /**
60
     * @var User|null User
61
     */
62
    protected $user;
63
64
    /**
65
     * @var string Controller action
66
     */
67
    protected $action;
68
69
    /**
70
     * @var array View variables
71
     */
72
    protected $variables = [];
73
74
    /**
75
     * @var bool Should render view for controller action
76
     */
77
    protected $render = true;
78
79
    /**
80
     * @var string File path for layout template file
81
     */
82
    protected $layout;
83
84
    /**
85
     * @var string File path for view template file
86
     */
87
    protected $view;
88
89
    /**
90
     * @var array Headers for output
91
     *
92
     * @todo JSON content type: `Content-Type: application/javascript; charset=utf-8`
93
     */
94
    protected $headers = [
95
        'content-type' => 'Content-Type: text/html; charset=utf-8'
96
    ];
97
98
    /**
99
     * Controller constructor.
100
     *
101
     * @param \PDO          $pdo
102
     * @param Configuration $configuration
103
     * @param Cookie        $cookie
104
     * @param User|null     $user
105
     */
106 1 View Code Duplication
    public function __construct(\PDO $pdo, Configuration $configuration, Cookie $cookie, User $user = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
107
    {
108 1
        $this->pdo           = $pdo;
109 1
        $this->configuration = $configuration;
110 1
        $this->cookie        = $cookie;
111 1
        $this->user          = $user;
112 1
    }
113
114
    /**
115
     * @return string Controller name without namespace
116
     */
117 1
    public function getShortName(): string
118
    {
119 1
        return str_replace('Controller', null, (new \ReflectionClass($this))->getShortName());
120
    }
121
122
    /**
123
     * @param string $action Controller action
124
     *
125
     * @throws ControllerActionNonexistentException
126
     * @throws ControllerActionPrivateInsufficientAuthenticationException
127
     * @throws ControllerActionProtectedInsufficientAuthenticationException
128
     */
129 4
    public function setAction(string $action)
130
    {
131
        /**
132
         * Check if method exists and that access has been defined
133
         */
134 4
        if (!method_exists($this, $action) || !isset($this->access[$action])) {
135 1
            throw new ControllerActionNonexistentException([get_class($this), $action]);
136
        }
137
138
        /**
139
         * Check controller action access level if user is not authenticated
140
         */
141 3
        if (!$this->user) {
142 3
            if ($this->access[$action] === self::ACTION_PRIVATE) {
143 1
                throw new ControllerActionPrivateInsufficientAuthenticationException([get_class($this), $action]);
144 2
            } elseif ($this->access[$action] === self::ACTION_PROTECTED) {
145 1
                throw new ControllerActionProtectedInsufficientAuthenticationException([get_class($this), $action]);
146
            }
147
        }
148
149
        /**
150
         * Set controller action
151
         */
152 1
        $this->action = $action;
153 1
    }
154
155
    /**
156
     * Call action
157
     *
158
     * @return array<string,array|string> Headers and output if render is enabled, otherwise FALSE
159
     *
160
     * @throws \LogicException If controller action is not set
161
     */
162
    public function callAction(): array
163
    {
164
        if (!isset($this->action)) {
165
            throw new \LogicException('Controller action must be set before being called');
166
        }
167
168
        /**
169
         * Before controller action hook
170
         */
171
        $this->beforeAction();
172
173
        /**
174
         * Call controller action
175
         */
176
        $action = $this->action;
177
178
        $this->$action();
0 ignored issues
show
Security Code Execution introduced by
$action can contain request data and is used in code execution context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
179
180
        /**
181
         * After controller action hook
182
         */
183
        $this->afterAction();
184
185
        /**
186
         * Render view
187
         */
188
        if ($this->render) {
189
            $render = new HtmlRender($this->variables);
190
191
            $output = $render->render(['_view' => $this->getViewTemplate(), '_layout' => $this->getLayoutTemplate()]);
192
193
            return [
194
                'headers'      => $this->headers,
195
                'cookie_token' => $this->cookie,
196
                'output'       => $output
197
            ];
198
        }
199
200
        return [];
201
    }
202
203
    /**
204
     * Before controller action hook
205
     *
206
     * Called right before controller action is called
207
     */
208
    protected function beforeAction()
209
    {
210
    }
211
212
    /**
213
     * After controller action hook
214
     *
215
     * Called right after controller action is called, but before rendering of the view
216
     */
217
    protected function afterAction()
218
    {
219
    }
220
221
    /**
222
     * Set view variable
223
     *
224
     * @param string $variable
225
     * @param mixed  $value
226
     */
227
    protected function set(string $variable, $value)
228
    {
229
        $this->variables[$variable] = $value;
230
    }
231
232
    /**
233
     * Get layout template
234
     *
235
     * @return string Layout template file path
236
     */
237 1
    protected function getLayoutTemplate(): string
238
    {
239 1
        $layout = $this->layout;
240
241 1
        if (empty($layout)) {
242
            $layout = 'View/Layout/default';
243
        }
244
245 1
        return "{$this->configuration->get('App.Path')}$layout.layout";
246
    }
247
248
    /**
249
     * Get view template
250
     *
251
     * @return string View template file path
252
     */
253 2
    protected function getViewTemplate(): string
254
    {
255 2
        $view = $this->view;
256
257 2
        if (empty($view)) {
258 1
            $view = sprintf('View/%s/%s', $this->getShortName(), $this->action);
259
        }
260
261 2
        return "{$this->configuration->get('App.Path')}$view.view";
262
    }
263
264
    /**
265
     * Set response code
266
     *
267
     * Supports 200 OK, 403 Forbidden, 404 Not Found & 500 Internal Server Error
268
     *
269
     * @param int $code HTTP response code
270
     *
271
     * @throws \InvalidArgumentException If unsupported code is provided
272
     */
273 5
    protected function setResponseCode(int $code)
274
    {
275
        switch ($code) {
276 5
            case 200:
277 1
                $text = 'OK';
278 1
                break;
279
280 4
            case 403:
281 1
                $text = 'Forbidden';
282 1
                break;
283
284 3
            case 404:
285 1
                $text = 'Not Found';
286 1
                break;
287
288 2
            case 500:
289 1
                $text = 'Internal Server Error';
290 1
                break;
291
292
            default:
293 1
                throw new \InvalidArgumentException("HTTP status '$code' is not implemented");
294
        }
295
296
        /**
297
         * Set header
298
         */
299
        // @todo test that running response code multiple times only results in one response code header
300 4
        $this->headers['response_code'] = "HTTP/1.1 $code $text";
301 4
    }
302
}
303