HTMLController::showConfirmationForm()   B
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 35
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4.074

Importance

Changes 0
Metric Value
dl 0
loc 35
ccs 10
cts 12
cp 0.8333
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 23
nc 4
nop 8
crap 4.074

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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
8
/**
9
 * @package BZiON\Controllers
10
 */
11
abstract class HTMLController extends Controller
12
{
13
    /**
14
     * Whether twig has been prepared
15
     * @var bool
16
     */
17
    public $twigReady = false;
18
19
    /**
20
     * Prepare the twig global variables
21
     */
22 1
    private function addTwigGlobals()
23
    {
24 1
        if ($this->twigReady) {
25
            return;
26
        }
27
28 1
        $request = $this->getRequest();
29
30
        // Add global variables to the twig templates
31 1
        $twig = $this->container->get('twig');
32 1
        $twig->addGlobal("me",      $this->getMe());
33 1
        $twig->addGlobal("model",   new ModelFetcher());
34 1
        $twig->addGlobal("request", $request);
35 1
        $twig->addGlobal("session", $request->getSession());
36
37 1
        $twig->addGlobal("app", new AppGlobal($this->parent, $this->container));
38
39 1
        $this->prepareTwig();
40
41 1
        $this->twigReady = true;
42 1
    }
43
44 1
    protected function prepareTwig()
45
    {
46 1
    }
47
48
    /**
49
     * {@inheritdoc}
50
     * @param string $view
51
     */
52 1
    protected function render($view, $parameters = array())
53
    {
54 1
        $this->addTwigGlobals();
55
56 1
        return parent::render($view, $parameters);
57
    }
58
59
    /**
60
     * {@inheritdoc}
61
     *
62
     * @throws ModelNotFoundException
63
     */
64 1
    protected function findModelInParameters($modelParameter, $routeParameters)
65
    {
66 1
        $model = parent::findModelInParameters($modelParameter, $routeParameters);
67
68 1
        // Special action to restore objects meaning we should always be able to see the model we're working with.
69
        $restoring = ($routeParameters->get('_action') === 'restore');
70
71
        if (!$model instanceof Model || $modelParameter->getName() === "me") {
72 1
            // `$me` can be invalid if, for example, no user is currently logged
73 1
            // in - in this case we can just pass the invalid Player model to
74
            // the controller without complaining
75
            return $model;
76 1
        } elseif (!$this->canSee($model, $restoring)) {
77
            // If the model is not supposed to be visible to the player
78
            // requesting it, pretend it's not there
79 1
            throw new ModelNotFoundException($model->getTypeForHumans());
80
        }
81
82
        return $model;
83
    }
84
85 1
    /**
86
     * {@inheritdoc}
87 1
     */
88 1
    public function callAction($action = null)
89 1
    {
90 1
        $response = parent::callAction($action);
91 1
        if (!$response->isRedirection()
92
            && !$response->isNotFound()
93
            && !$this->getRequest()->isXmlHttpRequest()) {
94 1
            $this->saveURL();
95
        }
96
97
        return $response;
98
    }
99
100
    /**
101 1
     * Save the URL of the current page so that the user can be redirected back to it
102
     * if they login
103 1
     */
104
    protected function saveURL()
105 1
    {
106 1
        $session = $this->getRequest()->getSession();
107
108
        $urls = $session->get('previous_paths', array());
109 1
        array_unshift($urls, $this->getRequest()->getPathInfo());
110 1
111
        // No need to have more than 4 urls stored on the array
112
        while (count($urls) > 4) {
113
            array_pop($urls);
114 1
        }
115 1
116
        // Store the URLs in the session, removing any duplicate entries
117
        $session->set('previous_paths', array_unique($urls));
118
    }
119
120
    /**
121 1
     * Returns the path to the home page
122
     * @return string
123 1
     */
124
    protected function getHomeURL()
125
    {
126
        return Service::getGenerator()->generate('index');
127
    }
128
129
    /**
130 1
     * Returns the URL of the previous page
131
     * @return string
132 1
     */
133
    protected function getPreviousURL()
134 1
    {
135 1
        $request = $this->getRequest();
136 1
137
        $urls = $request->getSession()->get('previous_paths', array());
138 1
        foreach ($urls as $url) {
139
            if ($url != $request->getPathInfo()) {
140
                // Don't redirect to the same page
141
                return $request->getBaseUrl() . $url;
142
            }
143
        }
144
145
        // No stored URLs found, just redirect them to the home page
146
        return $this->getHomeURL();
147
    }
148
149
    /**
150
     * Returns a redirect response to the previous page
151
     * @return RedirectResponse
152
     */
153
    protected function goBack()
154
    {
155
        return new RedirectResponse($this->getPreviousURL());
156
    }
157
158
    /**
159 1
     * Returns a redirect response to the home page
160
     * @return RedirectResponse
161 1
     */
162
    protected function goHome()
163
    {
164
        return new RedirectResponse($this->getHomeURL());
165
    }
166
167
    /**
168 1
     * Get the current pagination value
169
     *
170 1
     * @return int Always a positive value greater than 0
171
     */
172
    protected function getCurrentPage()
173
    {
174
        $page = intval(self::getRequest()->get('page', 1));
175
176
        return (($page <= 0) ? 1 : $page);
177
    }
178
179
    /**
180
     * Get the session's flash bag
181
     * @return Symfony\Component\HttpFoundation\Session\Flash\FlashBag
182
     */
183 1
    public static function getFlashBag()
184
    {
185 1
        return self::getRequest()->getSession()->getFlashBag();
186
    }
187
188
    /**
189 1
     * Find out whether the currently logged in user can see a model
190
     *
191
     * Apart from the permissions of the user, this method takes the request
192
     * query into consideration to find out if the user wants to see deleted
193
     * models or not.
194
     *
195
     * @param  Model $model     Model in question
196
     * @param  bool  $restoring If we're attempting to restore the object, we should always be able to see it
197
     * @return bool
198 1
     */
199
    public static function canSee($model, $restoring = false)
200
    {
201 1
        if (!$model instanceof PermissionModel) {
202 1
            return !$model->isDeleted();
203
        }
204 1
205
        return static::getMe()->canSee($model, (static::getRequest()->get('showDeleted') || $restoring));
206
    }
207
208
    /**
209
     * Assert that the user is logged in
210
     * @param  string        $message The message to show if the user is not logged in
211
     * @throws HTTPException
212
     * @return void
213
     */
214
    protected function requireLogin(
215
        $message = "You need to be signed in to do this"
216
    ) {
217
        if (!$this->getMe()->isValid()) {
218
            throw new ForbiddenException($message);
219
        }
220
    }
221
222
    /**
223
     * Show a confirmation (Yes, No) form to the user
224
     *
225 1
     * @param  callable $onYes          What to do if the user clicks on "Yes"
226
     * @param  string   $message        The message to show to the user, asking them to confirm their action (RAW text
227
     *                                  is shown - don't forget to properly escape your parameters!)
228
     * @param  string   $action         The text to show on the "Yes" button
229
     * @param  string   $successMessage A message to add on the session's flashbag on success (flashbags automatically escape text)
230
     * @param  callable $onNo           What to do if the user presses "No" - defaults to redirecting them back
231
     * @param  string   $view           The view to redirect to
232
     * @param  bool     $noButton       Whether to show a "No" instead of a "Cancel" button
233
     * @param  bool     $destructive    Whether or not to mark the action button as being a destructive operation
234 1
     *
235 1
     * @return mixed    The response
236
     */
237 1
    protected function showConfirmationForm(
238 1
        $onYes,
239 1
        $message = "Are you sure you want to do this?",
240
        $successMessage = "Operation completed successfully",
241
        $action = "Yes",
242 1
        $onNo = null,
243
        $view = 'confirmation.html.twig',
244 1
        $noButton = false,
245 1
        $destructive = false
246
    ) {
247
        $creator = new ConfirmationFormCreator($action, $this->getPreviousURL(), $noButton, $destructive);
248 1
        $form = $creator->create()->handleRequest($this->getRequest());
249
250
        if ($form->isValid()) {
251
            if ($form->get('confirm')->isClicked()) {
252
                $return = $onYes();
253
254 1
                // If no exceptions are thrown, show a success message
255 1
                $this->getFlashBag()->add('success', $successMessage);
256 1
257
                return $return;
258
            } elseif (!$onNo) {
259
                // We didn't get told about what to do when the user presses no,
260
                // just get them back where they were
261
                return new RedirectResponse($form->get('original_url')->getData());
262
            } else {
263
                return $onNo($form->get('original_url')->getData());
264
            }
265
        }
266
267
        return $this->render($view, array(
268
            'form'    => $form->createView(),
269
            'message' => $message
270
        ));
271
    }
272
273
    /**
274
     * Decompose a list of object IDs into the corresponding IDs
275
     *
276
     * @param string $query  The user's query
277
     * @param array  $types  A list of the acceptable model types (will NOT be sanitized)
278
     * @param bool   $models Whether to return an array of models instead of an array of IDs
279
     * @param int|null $max  The largest number of models to accept, or null for infinite models
280
     *
281
     * @throws BadRequestException
282
     * @return int[]|Model[] An array of models or IDs (depending on the $models parameter)
283
     */
284
    protected function decompose($query, array $types, $models = true, $max = null)
285
    {
286
        $query = explode(',', $query);
287
288
        if ($max !== null && count($query) > $max) {
289
            throw new \BadRequestException("Too many objects provided");
290
        }
291
292
        $result = array();
293
        $firstType = reset($types);
294
295
        if (!$models) {
296
            // Make sure the result array has a subarray for each type
297
            foreach ($types as $type) {
298
                $result[$type] = array();
299
            }
300
        }
301
302
        foreach ($query as $object) {
303
            if ($object === '') {
304
                continue;
305
            }
306
307
            $object = explode(':', $object, 3);
308
            if (count($object) === 2) {
309
                $class = ucfirst($object[0]);
310
                $id = (int) $object[1];
311
312
                if (!in_array($class, $types)) {
313
                    throw new \BadRequestException("Invalid object type");
314
                }
315
316
                if ($models) {
317
                    $this->assertVisibility($result[] = $class::get($id));
318
                } else {
319
                    $result[$class][] = $id;
320
                }
321
            } elseif (count($object) === 1) {
322
                // No type was provided
323
                if (count('types') > 1) {
324
                    throw new \BadRequestException(
325
                        "You need to provide the type of the object"
326
                    );
327
                }
328
329
                if ($models) {
330
                    $this->assertVisibility($result[] = $firstType::get($object[0]));
331
                } else {
332
                    $result[$firstType][] = (int) $object[0];
333
                }
334
            } else {
335
                throw new \BadRequestException("Malformed object");
336
            }
337
        }
338
339
        return $result;
340
    }
341
342
    /**
343
     * Throw an innocent exception if a player can't see a Model or if it
344
     * doesn't exist
345
     *
346
     * @param PermissionModel $model The model to test
347
     *
348
     * @throws BadRequestException
349
     */
350
    private function assertVisibility(PermissionModel $model)
351
    {
352
        if (!$this->getMe()->canSee($model)) {
353
            throw new \BadRequestException("Invalid object provided");
354
        }
355
    }
356
}
357