Completed
Push — master ( fe21f6...32ec36 )
by Konstantinos
03:59
created

HTMLController::getPreviousURL()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0263
Metric Value
dl 0
loc 15
ccs 6
cts 7
cp 0.8571
rs 9.4286
cc 3
eloc 7
nc 3
nop 0
crap 3.0263
1
<?php
2
3
use BZIon\Form\Creator\ConfirmationFormCreator;
4
use BZIon\Twig\AppGlobal;
5
use BZIon\Twig\ModelFetcher;
6
use Symfony\Component\HttpFoundation\RedirectResponse;
7
use Symfony\Component\HttpFoundation\Response;
8
9
/**
10
 * @package BZiON\Controllers
11
 */
12
abstract class HTMLController extends Controller
13
{
14
    /**
15
     * Whether twig has been prepared
16
     * @var bool
17
     */
18
    public $twigReady = false;
19
20
    /**
21
     * Prepare the twig global variables
22
     */
23 1
    private function addTwigGlobals()
24
    {
25 1
        if ($this->twigReady) {
26
            return;
27
        }
28
29 1
        $request = $this->getRequest();
30
31
        // Add global variables to the twig templates
32 1
        $twig = $this->container->get('twig');
33 1
        $twig->addGlobal("me",      $this->getMe());
34 1
        $twig->addGlobal("model",   new ModelFetcher());
35 1
        $twig->addGlobal("request", $request);
36 1
        $twig->addGlobal("session", $request->getSession());
37
38 1
        $twig->addGlobal("app", new AppGlobal($this->parent, $this->container));
39
40 1
        $this->prepareTwig();
41
42 1
        $this->twigReady = true;
43 1
    }
44
45 1
    protected function prepareTwig()
46
    {
47 1
    }
48
49
    /**
50
     * {@inheritdoc}
51
     * @param string $view
52
     */
53 1
    protected function render($view, $parameters = array())
54
    {
55 1
        $this->addTwigGlobals();
56
57 1
        return parent::render($view, $parameters);
58
    }
59
60
    /**
61
     * {@inheritdoc}
62
     *
63
     * @throws ModelNotFoundException
64
     */
65 1
    protected function findModelInParameters($modelParameter, $routeParameters)
66
    {
67 1
        $model = parent::findModelInParameters($modelParameter, $routeParameters);
68
69 1
        if (!$model instanceof Model || $modelParameter->getName() === "me") {
70
            // `$me` can be invalid if, for example, no user is currently logged
71
            // in - in this case we can just pass the invalid Player model to
72
            // the controller without complaining
73 1
            return $model;
74 1
        } elseif (!$this->canSee($model)) {
75
            // If the model is not supposed to be visible to the player
76
            // requesting it, pretend it's not there
77 1
            throw new ModelNotFoundException($model->getTypeForHumans());
78
        }
79
80 1
        return $model;
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86 1
    public function callAction($action = null)
87
    {
88 1
        $response = parent::callAction($action);
89 1
        if (!$response->isRedirection() && !$response->isNotFound()) {
90 1
            $this->saveURL();
91
        }
92
93 1
        return $response;
94
    }
95
96
    /**
97
     * Save the URL of the current page so that the user can be redirected back to it
98
     * if they login
99
     */
100 1
    protected function saveURL()
101
    {
102 1
        $session = $this->getRequest()->getSession();
103
104 1
        $urls = $session->get('previous_paths', array());
105 1
        array_unshift($urls, $this->getRequest()->getPathInfo());
106
107
        // No need to have more than 4 urls stored on the array
108 1
        while (count($urls) > 4) {
109 1
            array_pop($urls);
110
        }
111
112
        // Store the URLs in the session, removing any duplicate entries
113 1
        $session->set('previous_paths', array_unique($urls));
114 1
    }
115
116
    /**
117
     * Returns the path to the home page
118
     * @return string
119
     */
120 1
    protected function getHomeURL()
121
    {
122 1
        return Service::getGenerator()->generate('index');
123
    }
124
125
    /**
126
     * Returns the URL of the previous page
127
     * @return string
128
     */
129 1
    protected function getPreviousURL()
130
    {
131 1
        $request = $this->getRequest();
132
133 1
        $urls = $request->getSession()->get('previous_paths', array());
134 1
        foreach ($urls as $url) {
135 1
            if ($url != $request->getPathInfo()) {
136
                // Don't redirect to the same page
137 1
                return $request->getBaseUrl() . $url;
138
            }
139
        }
140
141
        // No stored URLs found, just redirect them to the home page
142
        return $this->getHomeURL();
143
    }
144
145
    /**
146
     * Returns a redirect response to the previous page
147
     * @return RedirectResponse
148
     */
149
    protected function goBack()
150
    {
151
        return new RedirectResponse($this->getPreviousURL());
152
    }
153
154
    /**
155
     * Returns a redirect response to the home page
156
     * @return RedirectResponse
157
     */
158 1
    protected function goHome()
159
    {
160 1
        return new RedirectResponse($this->getHomeURL());
161
    }
162
163
    /**
164
     * Get the session's flash bag
165
     * @return Symfony\Component\HttpFoundation\Session\Flash\FlashBag
166
     */
167 1
    public static function getFlashBag()
168
    {
169 1
        return self::getRequest()->getSession()->getFlashBag();
170
    }
171
172
    /**
173
     * Find out whether the currently logged in user can see a model
174
     *
175
     * Apart from the permissions of the user, this method takes the request
176
     * query into consideration to find out if the user wants to see deleted
177
     * models or not.
178
     *
179
     * @param  Model Model Model in question
180
     * @return bool
181
     */
182 1
    public static function canSee($model)
183
    {
184 1
        if (!$model instanceof PermissionModel) {
185
            return !$model->isDeleted();
186
        }
187
188 1
        return static::getMe()->canSee($model, static::getRequest()->get('showDeleted'));
189
    }
190
191
    /**
192
     * Assert that the user is logged in
193
     * @param  string        $message The message to show if the user is not logged in
194
     * @throws HTTPException
195
     * @return void
196
     */
197 1
    protected function requireLogin(
198
        $message = "You need to be signed in to do this"
199
    ) {
200 1
        if (!$this->getMe()->isValid()) {
201 1
            throw new ForbiddenException($message);
202
        }
203 1
    }
204
205
    /**
206
     * Show a confirmation (Yes, No) form to the user
207
     *
208
     * @param  callable $onYes          What to do if the user clicks on "Yes"
209
     * @param  string   $message        The message to show to the user, asking
210
     *                                  them to confirm their action (RAW text
211
     *                                  is shown - don't forget to properly
212
     *                                  escape your parameters!)
213
     * @param  string   $action         The text to show on the "Yes" button
214
     * @param  string   $successMessage A message to add on the session's
215
     *                                  flashbag on success (flashbags
216
     *                                  automatically escape text)
217
     * @param  callable $onNo           What to do if the user presses "No" -
218
     *                                  defaults to redirecting them back
219
     * @return mixed    The response
220
     */
221 1
    protected function showConfirmationForm(
222
        $onYes,
223
        $message = "Are you sure you want to do this?",
224
        $successMessage = "Operation completed successfully",
225
        $action = "Yes",
226
        $onNo = null
227
    ) {
228 1
        $creator = new ConfirmationFormCreator($action, $this->getPreviousURL());
229 1
        $form = $creator->create()->handleRequest($this->getRequest());
230
231 1
        if ($form->isValid()) {
232 1
            if ($form->get($action)->isClicked()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Form\FormInterface as the method isClicked() does only exist in the following implementations of said interface: Symfony\Component\Form\SubmitButton.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
233 1
                $return = $onYes();
234
235
                // If no exceptions are thrown, show a success message
236 1
                $this->getFlashBag()->add('success', $successMessage);
237
238 1
                return $return;
239 1
            } elseif (!$onNo) {
240
                // We didn't get told about what to do when the user presses no,
241
                // just get them back where they were
242 1
                return new RedirectResponse($form->get('original_url')->getData());
243
            } else {
244
                return $onNo();
245
            }
246
        }
247
248 1
        return $this->render('confirmation.html.twig', array(
249 1
            'form'    => $form->createView(),
250 1
            'message' => $message
251
        ));
252
    }
253
254
    /**
255
     * Decompose a list of object IDs into the corresponding IDs
256
     *
257
     * @param string $query  The user's query
258
     * @param array  $types  A list of the acceptable model types (will NOT be sanitized)
259
     * @param bool   $models Whether to return an array of models instead of an array of IDs
260
     * @param int|null $max  The largest number of models to accept, or null for infinite models
261
     *
262
     * @throws BadRequestException
263
     */
264
    protected function decompose($query, array $types, $models = true, $max = null) {
265
        $query = explode(',', $query);
266
267
        if ($max !== null && count($query) > $max) {
268
            throw new \BadRequestException("Too many objects provided");
269
        }
270
271
        $result = array();
272
        $firstType = reset($types);
273
274
        if (!$models) {
275
            // Make sure the result array has a subarray for each type
276
            foreach ($types as $type) {
277
                $result[$type] = array();
278
            }
279
        }
280
281
282
        foreach ($query as $object) {
283
            if ($object === '') {
284
                continue;
285
            }
286
287
            $object = explode(':', $object, 3);
288
            if (count($object) === 2) {
289
                $class = ucfirst($object[0]);
290
                $id = (int) $object[1];
291
292
                if (!in_array($class, $types)) {
293
                    throw new \BadRequestException("Invalid object type");
294
                }
295
296
                if ($models) {
297
                    $this->assertVisibility($result[] = $class::get($id));
298
                } else {
299
                    $result[$class][] = $id;
300
                }
301
            } elseif (count($object) === 1) {
302
                // No type was provided
303
                if (count('types') > 1) {
304
                    throw new \BadRequestException(
305
                        "You need to provide the type of the object"
306
                    );
307
                }
308
309
                if ($models) {
310
                    $this->assertVisibility($result[] = $firstType::get($id));
0 ignored issues
show
Bug introduced by
The variable $id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
311
                } else {
312
                    $result[$firstType][] = (int)$object[0];
313
                }
314
            } else {
315
                throw new \BadRequestException("Malformed object");
316
            }
317
        }
318
319
        return $result;
320
    }
321
322
    /**
323
     * Throw an innocent exception if a player can't see a Model or if it
324
     * doesn't exist
325
     *
326
     * @param $model The model to test
327
     *
328
     * @throws BadRequestException
329
     */
330
    private function assertVisibility(PermissionModel $model)
331
    {
332
        if (!$this->getMe()->canSee($model)) {
333
            throw new \BadRequestException("Invalid object provided");
334
        }
335
    }
336
}
337