Completed
Push — master ( 3c8880...e2c218 )
by Dmitry
06:42 queued 02:46
created

ModalButton::initOptions()   C

Complexity

Conditions 12
Paths 74

Size

Total Lines 69
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 12
eloc 44
nc 74
nop 0
dl 0
loc 69
ccs 0
cts 59
cp 0
crap 156
rs 5.7089
c 1
b 0
f 1

How to fix   Long Method    Complexity   

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
 * HiPanel core package
5
 *
6
 * @link      https://hipanel.com/
7
 * @package   hipanel-core
8
 * @license   BSD-3-Clause
9
 * @copyright Copyright (c) 2014-2016, HiQDev (http://hiqdev.com/)
10
 */
11
12
namespace hipanel\widgets;
13
14
use Yii;
15
use yii\base\InvalidConfigException;
16
use yii\base\Model;
17
use yii\base\Widget;
18
use yii\bootstrap\ActiveForm;
19
use yii\bootstrap\Modal;
20
use yii\db\ActiveRecord;
21
use yii\helpers\ArrayHelper;
22
use yii\helpers\Html;
23
use yii\helpers\Inflector;
24
use yii\helpers\Json;
25
use yii\web\JsExpression;
26
27
/**
28
 * Class ModalButton.
29
 *
30
 * Renders [[Modal]] widget in form with custom toggle button.
31
 */
32
class ModalButton extends Widget
33
{
34
    /**
35
     * Toggle button will be placed outside of the form.
36
     */
37
    const BUTTON_OUTSIDE = 1;
38
39
    /**
40
     * Toggle button will be rendered inside of the form with [[Modal]] widget.
41
     */
42
    const BUTTON_IN_MODAL = 2;
43
44
    /**
45
     * Submit with HTML POST request.
46
     */
47
    const SUBMIT_HTML = 0;
48
49
    /**
50
     * Submit using PJAX.
51
     */
52
    const SUBMIT_PJAX = 1;
53
54
    /**
55
     * Submit using AJAX.
56
     */
57
    const SUBMIT_AJAX = 2;
58
59
    /**
60
     * @var ActiveRecord the model. Is required.
61
     */
62
    public $model;
63
64
    /**
65
     * @var string Used to generate form action URL and HTML id of modal.
66
     * May be set manually, otherwise will be extracted from the model
67
     */
68
    public $scenario;
69
70
    /**
71
     * @var string Model scenario before widget run
72
     */
73
    protected $_oldScenario;
74
75
    /**
76
     * @var array|ActiveForm The options for the HTML form.
77
     * The following special options are supported:
78
     *
79
     * - action: string|array, the action, that will be passed as first argument to [[Html::beginForm()]]
80
     * - method: string, the method, that will be passed as second argument to [[Html::beginForm()]]
81
     *
82
     * The rest of the options will be passed to the [[ActiveForm::begin()]] method
83
     *
84
     * If the property was not false, it will contain [[ActiveForm]] instance after [[ModalButton::begin()]].
85
     */
86
    public $form = [];
87
88
    /**
89
     * @var array The options for rendering toggle button
90
     * The toggle button is used to toggle the visibility of the modal window.
91
     * If this property is false, no toggle button will be rendered.
92
     *
93
     * The following special options are supported:
94
     *
95
     * - tag: string, the tag name of the button. Defaults to 'button'.
96
     * - label: string, the label of the button. Defaults to 'Show'.
97
     *
98
     * The rest of the options will be rendered as the HTML attributes of the button tag.
99
     */
100
    public $button = [];
101
102
    /**
103
     * @var string|callable The body of modal window.
104
     * If callable - will get the only argument - [[$this->model]]
105
     */
106
    public $body;
107
108
    /**
109
     * @var array HTML options that will be passed to the [[Modal]] widget
110
     * When ```footer``` is array, the following special options are supported:
111
     * - tag: string, the tag name of the button. Defaults to 'button'.
112
     * - label: string, the label of the button. Defaults to 'Show'.
113
     *
114
     * The rest of the options will be rendered as the HTML attributes of the button tag.
115
     */
116
    public $modal = [];
117
118
    /**
119
     * @var integer determines the way of form submit.
120
     * @see [[SUBMIT_HTML]]
121
     * @see [[SUBMIT_PJAX]]
122
     * @see [[SUBMIT_AJAX]]
123
     */
124
    public $submit = self::SUBMIT_PJAX;
125
126
    /**
127
     * @var array options that will be passed to ajax submit JS
128
     * @see [[registerAjaxSubmit]]
129
     */
130
    public $ajaxOptions = [];
131
132
    /**
133
     * {@inheritdoc}
134
     * @throws InvalidConfigException
135
     */
136
    public function init()
137
    {
138
        $this->initOptions();
139
140
        if ($this->button['position'] === static::BUTTON_OUTSIDE && isset($this->button['label'])) {
141
            $this->renderButton();
142
        }
143
144
        if ($this->form !== false) {
145
            $this->beginForm();
146
        }
147
148
        $this->beginModal();
149
150
        if (isset($this->body)) {
151
            if ($this->body instanceof \Closure) {
152
                echo call_user_func($this->body, $this->model);
153
            } else {
154
                echo $this->body;
155
            }
156
        }
157
    }
158
159
    /**
160
     * Initialization of options.
161
     * @throws InvalidConfigException
162
     */
163
    protected function initOptions()
164
    {
165
        if (!($this->model instanceof Model)) {
166
            throw new InvalidConfigException('Model is required');
167
        }
168
169
        if (!($this->model->getPrimaryKey())) {
170
            throw new InvalidConfigException('Model has empty primary key');
171
        }
172
173
        if ($this->button !== false) {
174
            $this->button['position'] = isset($this->button['position']) ? $this->button['position'] : static::BUTTON_OUTSIDE;
175
        }
176
177
        if (empty($this->scenario)) {
178
            $this->scenario = $this->model->scenario;
179
        } else {
180
            $this->_oldScenario = $this->model->scenario;
181
            $this->model->scenario = $this->scenario;
182
        }
183
184
        if ($this->form !== false) {
185
            $formConfig = [
186
                'method' => 'POST',
187
                'action' => $this->scenario,
188
                'options' => [
189
                    'class' => 'inline',
190
                    'data' => [
191
                        'modal-form' => true,
192
                    ],
193
                ],
194
            ];
195
            if ($this->submit === static::SUBMIT_PJAX) {
196
                $formConfig['options'] = ArrayHelper::merge($formConfig['options'], [
197
                    'data' => ['pjax' => 1, 'pjax-push' => 0],
198
                ]);
199
            } elseif ($this->submit === static::SUBMIT_AJAX) {
200
                $formConfig['options'] = ArrayHelper::merge($formConfig['options'], [
201
                    'data' => ['ajax-submit' => 1],
202
                ]);
203
204
                $this->registerAjaxSubmit();
205
            }
206
207
            $this->form = ArrayHelper::merge($formConfig, $this->form);
208
        }
209
210
        if (is_array($footer = $this->modal['footer'])) {
211
            $tag = ArrayHelper::remove($footer, 'tag', 'input');
212
            $label = ArrayHelper::remove($footer, 'label', 'OK');
213
            $footer = ArrayHelper::merge([
214
                'data-modal-submit' => true,
215
                'data-loading-text' => '<i class="fa fa-circle-o-notch fa-spin"></i> ' . Yii::t('hipanel', 'loading'),
216
            ], $footer);
217
218
            if ($tag === 'input') {
219
                $footer['type']  = 'submit';
220
                $footer['value'] = $label;
221
            }
222
223
            $this->modal['footer'] = Html::tag($tag, $label, $footer);
224
            $this->registerFooterButtonScript();
225
        }
226
227
        $this->modal = ArrayHelper::merge([
228
            'id' => $this->getModalId(),
229
            'toggleButton' => ($this->button['position'] === static::BUTTON_IN_MODAL) ? $this->button : false,
230
        ], $this->modal);
231
    }
232
233
    /**
234
     * Runs widget.
235
     */
236
    public function run()
237
    {
238
        $this->endModal();
239
240
        if ($this->form !== false) {
241
            $this->endForm();
242
        }
243
244
        if ($this->_oldScenario !== null) {
245
            $this->model->scenario = $this->_oldScenario;
246
        }
247
    }
248
249
    /**
250
     * Renders toggle button.
251
     */
252
    public function renderButton()
253
    {
254
        if (($button = $this->button) !== false) {
255
            $tag = ArrayHelper::remove($button, 'tag', 'a');
256
            $label = ArrayHelper::remove($button, 'label',
257
                Inflector::camel2words(Inflector::id2camel($this->scenario)));
258
            if ($tag === 'button' && !isset($button['type'])) {
259
                $toggleButton['type'] = 'button';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$toggleButton was never initialized. Although not strictly required by PHP, it is generally a good practice to add $toggleButton = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
260
            }
261
262
            if ($button['disabled']) {
263
                $button = ArrayHelper::merge([
264
                    'onClick' => new JsExpression('return false'),
265
                ], $button);
266
            } else {
267
                $button = ArrayHelper::merge([
268
                    'data-toggle' => 'modal',
269
                    'data-target' => "#{$this->getModalId()}",
270
                ], $button);
271
            }
272
273
            if ($tag === 'a' && empty($button['href'])) {
274
                $button['href'] = '#';
275
            }
276
277
            echo Html::tag($tag, $label, $button);
278
        }
279
    }
280
281
    /**
282
     * Constructs model ID, using [[$model]] primary key, or ID of the widget and scenario.
283
     * @return string format: ```modal_{id}_{scenario}```
284
     */
285
    public function getModalId()
286
    {
287
        $id = $this->model->getPrimaryKey() ?: $this->id;
288
289
        return "modal_{$id}_{$this->scenario}";
290
    }
291
292
    /**
293
     * Begins form.
294
     */
295
    public function beginForm()
296
    {
297
        $this->form = ActiveForm::begin($this->form);
0 ignored issues
show
Bug introduced by
It seems like $this->form can also be of type object<yii\bootstrap\ActiveForm>; however, yii\base\Widget::begin() does only seem to accept array, 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...
298
        echo Html::activeHiddenInput($this->model, 'id');
299
    }
300
301
    /**
302
     * Ends form.
303
     */
304
    public function endForm()
305
    {
306
        $this->form->end();
307
    }
308
309
    /**
310
     * Begins modal widget.
311
     */
312
    public function beginModal()
313
    {
314
        Modal::begin($this->modal);
315
    }
316
317
    /**
318
     * Ends modal widget.
319
     */
320
    public function endModal()
321
    {
322
        Modal::end();
323
    }
324
325
    /**
326
     * Registers JavaScript for ajax submit.
327
     * @return void
328
     */
329
    public function registerAjaxSubmit()
330
    {
331
        $view = Yii::$app->view;
332
333
        $options = ArrayHelper::merge([
334
            'type' => new JsExpression("form.attr('method')"),
335
            'url' => new JsExpression("form.attr('action')"),
336
            'data' => new JsExpression('form.serialize()'),
337
        ], $this->ajaxOptions);
338
        $options = Json::encode($options);
339
340
        $view->registerJs(<<<JS
341
            $('form[data-ajax-submit]').on('submit', function(event) {
342
                var form = $(this);
343
                if (event.eventPhase === 2) {
344
                    $.ajax($options);
345
                    $('.modal-backdrop').remove();
346
                }
347
                event.preventDefault();
348
            });
349
JS
350
        );
351
    }
352
353
    public function registerFooterButtonScript()
354
    {
355
        $view = Yii::$app->view;
356
        $view->registerJs("
357
            $('form[data-modal-form]').on('beforeSubmit', function (e) {
358
                var submit = $(this).find('[data-modal-submit]');
359
                if (!submit) return true;
360
                submit.button('loading');
361
            });
362
        ");
363
    }
364
}
365