Completed
Push — master ( 2731ff...16fe27 )
by Nate
03:36 queued 11s
created

UserIndexesController::init()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 55

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 55
ccs 0
cts 46
cp 0
rs 8.9818
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * @copyright  Copyright (c) Flipbox Digital Limited
5
 * @license    https://flipboxfactory.com/software/organization/license
6
 * @link       https://www.flipboxfactory.com/software/organization/
7
 */
8
9
namespace flipbox\organizations\cp\controllers;
10
11
use Craft;
12
use craft\base\Element;
13
use craft\base\ElementAction;
14
use craft\base\ElementActionInterface;
15
use craft\base\ElementInterface;
16
use craft\controllers\BaseElementsController;
17
use craft\elements\db\ElementQuery;
18
use craft\elements\db\ElementQueryInterface;
19
use craft\elements\User;
20
use craft\events\ElementActionEvent;
21
use craft\events\RegisterElementHtmlAttributesEvent;
22
use craft\helpers\ElementHelper;
23
use flipbox\organizations\events\handlers\RegisterOrganizationUserElementActions;
24
use flipbox\organizations\events\handlers\RegisterOrganizationUserElementDefaultTableAttributes;
25
use flipbox\organizations\events\handlers\RegisterOrganizationUserElementTableAttributes;
26
use flipbox\organizations\events\handlers\SetOrganizationUserElementTableAttributeHtml;
27
use yii\base\Event;
28
use yii\web\BadRequestHttpException;
29
use yii\web\ForbiddenHttpException;
30
use yii\web\Response;
31
32
/**
33
 * @author Flipbox Factory <[email protected]>
34
 * @since 1.0.0
35
 */
36
class UserIndexesController extends BaseElementsController
37
{
38
39
    // Properties
40
    // =========================================================================
41
42
    /**
43
     * @var string|null
44
     */
45
    private $elementType;
46
47
    /**
48
     * @var string|null
49
     */
50
    private $context;
51
52
    /**
53
     * @var string|null
54
     */
55
    private $sourceKey;
56
57
    /**
58
     * @var array|null
59
     */
60
    private $source;
61
62
    /**
63
     * @var array|null
64
     */
65
    private $viewState;
66
67
    /**
68
     * @var ElementQueryInterface|null
69
     */
70
    private $elementQuery;
71
72
    /**
73
     * @var ElementActionInterface[]|null
74
     */
75
    private $actions;
76
77
    // Public Methods
78
    // =========================================================================
79
80
    /**
81
     * @inheritdoc
82
     */
83
    public function beforeAction($action)
84
    {
85
        if (!parent::beforeAction($action)) {
86
            return false;
87
        }
88
89
        $this->elementType = $this->elementType();
90
        $this->context = $this->context();
91
        $this->sourceKey = Craft::$app->getRequest()->getParam('source');
92
        $this->source = $this->source();
93
        $this->viewState = $this->viewState();
94
        $this->elementQuery = $this->elementQuery();
95
96
//        if ($this->context === 'index' && $this->sourceKey !== null) {
97
        if ($this->sourceKey !== null) {
98
            $this->actions = $this->availableActions();
99
        }
100
101
        return true;
102
    }
103
104
    /**
105
     * Returns the element query that’s defining which elements will be returned in the current request.
106
     *
107
     * Other components can fetch this like so:
108
     *
109
     * ```php
110
     * $criteria = Craft::$app->controller->getElementQuery();
111
     * ```
112
     *
113
     * @return ElementQueryInterface
114
     */
115
    public function getElementQuery(): ElementQueryInterface
116
    {
117
        return $this->elementQuery;
118
    }
119
120
    /**
121
     * Renders and returns an element index container, plus its first batch of elements.
122
     *
123
     * @return Response
124
     */
125
    public function actionGetElements(): Response
126
    {
127
//        $includeActions = ($this->context === 'index');
128
        $includeActions = true;
129
        $responseData = $this->elementResponseData(true, $includeActions);
130
131
        return $this->asJson($responseData);
132
    }
133
134
    /**
135
     * Renders and returns a subsequent batch of elements for an element index.
136
     *
137
     * @return Response
138
     */
139
    public function actionGetMoreElements(): Response
140
    {
141
        $responseData = $this->elementResponseData(false, false);
142
143
        return $this->asJson($responseData);
144
    }
145
146
    /**
147
     * Performs an action on one or more selected elements.
148
     *
149
     * @return Response
150
     * @throws BadRequestHttpException if the requested element action is not supported by the element type, or
151
     * its parameters didn’t validate
152
     */
153
    public function actionPerformAction(): Response
154
    {
155
        $this->requirePostRequest();
156
157
        $requestService = Craft::$app->getRequest();
158
        $elementsService = Craft::$app->getElements();
159
160
        $actionClass = $requestService->getRequiredBodyParam('elementAction');
161
        $elementIds = $requestService->getRequiredBodyParam('elementIds');
162
163
        // Find that action from the list of available actions for the source
164
        if (!empty($this->actions)) {
165
            /** @var ElementAction $availableAction */
166
            foreach ($this->actions as $availableAction) {
167
                if ($actionClass === get_class($availableAction)) {
168
                    $action = $availableAction;
169
                    break;
170
                }
171
            }
172
        }
173
174
        /** @noinspection UnSafeIsSetOverArrayInspection - FP */
175
        if (!isset($action)) {
176
            throw new BadRequestHttpException('Element action is not supported by the element type');
177
        }
178
179
        // Check for any params in the post data
180
        foreach ($action->settingsAttributes() as $paramName) {
181
            $paramValue = $requestService->getBodyParam($paramName);
182
183
            if ($paramValue !== null) {
184
                $action->$paramName = $paramValue;
185
            }
186
        }
187
188
        // Make sure the action validates
189
        if (!$action->validate()) {
190
            throw new BadRequestHttpException('Element action params did not validate');
191
        }
192
193
        // Perform the action
194
        /** @var ElementQuery $actionCriteria */
195
        $actionCriteria = clone $this->elementQuery;
196
        $actionCriteria->offset = 0;
197
        $actionCriteria->limit = null;
198
        $actionCriteria->orderBy = null;
199
        $actionCriteria->positionedAfter = null;
200
        $actionCriteria->positionedBefore = null;
201
        $actionCriteria->id = $elementIds;
202
203
        // Fire a 'beforePerformAction' event
204
        $event = new ElementActionEvent([
205
            'action' => $action,
206
            'criteria' => $actionCriteria
207
        ]);
208
209
        $elementsService->trigger($elementsService::EVENT_BEFORE_PERFORM_ACTION, $event);
210
211
        if ($event->isValid) {
212
            $success = $action->performAction($actionCriteria);
213
            $message = $action->getMessage();
214
215
            if ($success) {
216
                // Fire an 'afterPerformAction' event
217
                $elementsService->trigger($elementsService::EVENT_AFTER_PERFORM_ACTION, new ElementActionEvent([
218
                    'action' => $action,
219
                    'criteria' => $actionCriteria
220
                ]));
221
            }
222
        } else {
223
            $success = false;
224
            $message = $event->message;
225
        }
226
227
        // Respond
228
        $responseData = [
229
            'success' => $success,
230
            'message' => $message,
231
        ];
232
233
        if ($success) {
234
            // Send a new set of elements
235
            $responseData = array_merge($responseData, $this->elementResponseData(
236
                true,
237
                true
238
            ));
239
        }
240
241
        return $this->asJson($responseData);
242
    }
243
244
    /**
245
     * Returns the source tree HTML for an element index.
246
     */
247
    public function actionGetSourceTreeHtml()
248
    {
249
        $this->requireAcceptsJson();
250
251
        $sources = Craft::$app->getElementIndexes()->getSources($this->elementType, $this->context);
252
253
        return $this->asJson([
254
            'html' => $this->getView()->renderTemplate('_elements/sources', [
255
                'sources' => $sources
256
            ])
257
        ]);
258
    }
259
260
    // Private Methods
261
    // =========================================================================
262
263
    /**
264
     * Returns the selected source info.
265
     *
266
     * @return array|null
267
     * @throws ForbiddenHttpException if the user is not permitted to access the requested source
268
     */
269
    private function source()
270
    {
271
        if ($this->sourceKey === null) {
272
            return null;
273
        }
274
275
        $source = ElementHelper::findSource($this->elementType, $this->sourceKey, $this->context);
276
277
        if ($source === null) {
278
            // That wasn't a valid source, or the user doesn't have access to it in this context
279
            throw new ForbiddenHttpException('User not permitted to access this source');
280
        }
281
282
        return $source;
283
    }
284
285
    /**
286
     * Returns the current view state.
287
     *
288
     * @return array
289
     */
290
    private function viewState(): array
291
    {
292
        $viewState = Craft::$app->getRequest()->getParam('viewState', []);
293
294
        if (empty($viewState['mode'])) {
295
            $viewState['mode'] = 'table';
296
        }
297
298
        return $viewState;
299
    }
300
301
    /**
302
     * Returns the element query based on the current params.
303
     *
304
     * @return ElementQueryInterface
305
     */
306
    private function elementQuery(): ElementQueryInterface
307
    {
308
        /** @var string|ElementInterface $elementType */
309
        $elementType = $this->elementType;
310
        $query = $elementType::find();
311
312
        $request = Craft::$app->getRequest();
313
314
        // Does the source specify any criteria attributes?
315
        if (isset($this->source['criteria'])) {
316
            Craft::configure($query, $this->source['criteria']);
317
        }
318
319
        // Override with the request's params
320
        if ($criteria = $request->getBodyParam('criteria')) {
321
            Craft::configure($query, $criteria);
322
        }
323
324
        // Exclude descendants of the collapsed element IDs
325
        $collapsedElementIds = $request->getParam('collapsedElementIds');
326
327
        if ($collapsedElementIds) {
328
            $descendantQuery = (clone $query)
329
                ->offset(null)
330
                ->limit(null)
331
                ->orderBy(null)
332
                ->positionedAfter(null)
333
                ->positionedBefore(null)
334
                ->anyStatus();
335
336
            // Get the actual elements
337
            /** @var Element[] $collapsedElements */
338
            $collapsedElements = (clone $descendantQuery)
339
                ->id($collapsedElementIds)
340
                ->orderBy(['lft' => SORT_ASC])
341
                ->all();
342
343
            if (!empty($collapsedElements)) {
344
                $descendantIds = [];
345
346
                foreach ($collapsedElements as $element) {
347
                    // Make sure we haven't already excluded this one, because its ancestor is collapsed as well
348
                    if (in_array($element->id, $descendantIds, false)) {
349
                        continue;
350
                    }
351
352
                    $elementDescendantIds = (clone $descendantQuery)
353
                        ->descendantOf($element)
354
                        ->ids();
355
356
                    $descendantIds = array_merge($descendantIds, $elementDescendantIds);
357
                }
358
359
                if (!empty($descendantIds)) {
360
                    $query->andWhere(['not', ['elements.id' => $descendantIds]]);
361
                }
362
            }
363
        }
364
365
        return $query;
366
    }
367
368
    /**
369
     * Returns the element data to be returned to the client.
370
     *
371
     * @param bool $includeContainer Whether the element container should be included in the response data
372
     * @param bool $includeActions Whether info about the available actions should be included in the response data
373
     * @return array
374
     */
375
    private function elementResponseData(bool $includeContainer, bool $includeActions): array
376
    {
377
        $responseData = [];
378
379
        $view = $this->getView();
380
381
        // Get the action head/foot HTML before any more is added to it from the element HTML
382
        if ($includeActions) {
383
            $responseData['actions'] = $this->actionData();
384
            $responseData['actionsHeadHtml'] = $view->getHeadHtml();
385
            $responseData['actionsFootHtml'] = $view->getBodyHtml();
386
        }
387
388
        $disabledElementIds = Craft::$app->getRequest()->getParam('disabledElementIds', []);
389
        $showCheckboxes = !empty($this->actions);
390
        /** @var string|ElementInterface $elementType */
391
        $elementType = $this->elementType;
392
393
        $responseData['html'] = $elementType::indexHtml(
394
            $this->elementQuery,
395
            $disabledElementIds,
396
            $this->viewState,
397
            //            $this->sourceKey,
398
            'organizations:' . $this->sourceKey,
399
            $this->context,
400
            $includeContainer,
401
            $showCheckboxes
402
        );
403
404
        $responseData['headHtml'] = $view->getHeadHtml();
405
        $responseData['footHtml'] = $view->getBodyHtml();
406
407
        return $responseData;
408
    }
409
410
    /**
411
     * Returns the available actions for the current source.
412
     *
413
     * @return ElementActionInterface[]|null
414
     */
415
    private function availableActions()
416
    {
417
        if (Craft::$app->getRequest()->isMobileBrowser()) {
418
            return null;
419
        }
420
421
        /** @var string|ElementInterface $elementType */
422
        $elementType = $this->elementType;
423
        $actions = $elementType::actions($this->sourceKey);
424
425
        foreach ($actions as $i => $action) {
426
            // $action could be a string or config array
427
            if (!$action instanceof ElementActionInterface) {
428
                $actions[$i] = $action = Craft::$app->getElements()->createAction($action);
429
430
                if ($actions[$i] === null) {
431
                    unset($actions[$i]);
432
                }
433
            }
434
435
            /** @var ElementActionInterface $action */
436
            $action->setElementType($elementType);
0 ignored issues
show
Bug introduced by
It seems like $elementType defined by $this->elementType on line 422 can also be of type object<craft\base\ElementInterface>; however, craft\base\ElementAction...rface::setElementType() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
437
        }
438
439
        return array_values($actions);
440
    }
441
442
    /**
443
     * Returns the data for the available actions.
444
     *
445
     * @return array|null
446
     */
447
    private function actionData()
448
    {
449
        if (empty($this->actions)) {
450
            return null;
451
        }
452
453
        $actionData = [];
454
455
        /** @var ElementAction $action */
456
        foreach ($this->actions as $action) {
457
            $actionData[] = [
458
                'type' => get_class($action),
459
                'destructive' => $action->isDestructive(),
460
                'name' => $action->getTriggerLabel(),
461
                'trigger' => $action->getTriggerHtml(),
462
                'confirm' => $action->getConfirmationMessage(),
463
            ];
464
        }
465
466
        return $actionData;
467
    }
468
469
470
    /**
471
     * @inheritdoc
472
     */
473
    public function init()
474
    {
475
        Event::on(
476
            User::class,
477
            User::EVENT_REGISTER_ACTIONS,
478
            [
479
                RegisterOrganizationUserElementActions::class,
480
                'handle'
481
            ]
482
        );
483
484
        // Add attributes the user index
485
        Event::on(
486
            User::class,
487
            User::EVENT_REGISTER_DEFAULT_TABLE_ATTRIBUTES,
488
            [
489
                RegisterOrganizationUserElementDefaultTableAttributes::class,
490
                'handle'
491
            ]
492
        );
493
494
        // Add attributes the user index
495
        Event::on(
496
            User::class,
497
            User::EVENT_REGISTER_TABLE_ATTRIBUTES,
498
            [
499
                RegisterOrganizationUserElementTableAttributes::class,
500
                'handle'
501
            ]
502
        );
503
504
        // Add 'organizations' on the user html element
505
        Event::on(
506
            User::class,
507
            User::EVENT_SET_TABLE_ATTRIBUTE_HTML,
508
            [
509
                SetOrganizationUserElementTableAttributeHtml::class,
510
                'handle'
511
            ]
512
        );
513
514
        // Add 'organizations' on the user html element
515
        Event::on(
516
            User::class,
517
            User::EVENT_REGISTER_HTML_ATTRIBUTES,
518
            function (RegisterElementHtmlAttributesEvent $event) {
519
                $event->htmlAttributes['data-organization'] = $event->data['organization'] ?? null;
520
            },
521
            [
522
                'organization' => $this->getOrganizationFromRequest()
523
            ]
524
        );
525
526
        parent::init();
527
    }
528
529
    /**
530
     * @return mixed
531
     */
532
    private function getOrganizationFromRequest()
533
    {
534
        return Craft::$app->getRequest()->getParam('organization');
535
    }
536
}
537