Passed
Push — master ( 566566...d0f53d )
by Alexander
02:18
created

Modal::encodeTags()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bootstrap5;
6
7
use JsonException;
8
use Yiisoft\Arrays\ArrayHelper;
9
use Yiisoft\Html\Html;
10
11
use function array_merge;
12
13
/**
14
 * Modal renders a modal window that can be toggled by clicking on a button.
15
 *
16
 * The following example will show the content enclosed between the {@see begin()} and {@see end()} calls within the
17
 * modal window:
18
 *
19
 * ```php
20
 * Modal::widget()
21
 *     ->title('<h2>Hello world</h2>')
22
 *     ->toggleButton(['label' => 'click me'])
23
 *     ->begin();
24
 *
25
 * echo 'Say hello...';
26
 *
27
 * echo Modal::end();
28
 * ```
29
 */
30
final class Modal extends Widget
31
{
32
    /**
33
     * The additional css class of large modal
34
     */
35
    public const SIZE_LARGE = 'modal-lg';
36
37
    /**
38
     * The additional css class of small modal
39
     */
40
    public const SIZE_SMALL = 'modal-sm';
41
42
    /**
43
     * The additional css class of default modal
44
     */
45
    public const SIZE_DEFAULT = '';
46
47
    private string $title = '';
48
    private array $titleOptions = [];
49
    private array $headerOptions = [];
50
    private array $bodyOptions = [];
51
    private string $footer = '';
52
    private array $footerOptions = [];
53
    private string $size = '';
54
    private array $closeButton = [];
55
    private bool $closeButtonEnabled = true;
56
    private array $toggleButton = [];
57
    private bool $toggleButtonEnabled = true;
58
    private array $options = [];
59
    private bool $encodeTags = false;
60
61 13
    public function begin(): string
62
    {
63 13
        parent::begin();
64
65 13
        if (!isset($this->options['id'])) {
66 13
            $this->options['id'] = "{$this->getId()}-modal";
67
        }
68
69 13
        if ($this->encodeTags === false) {
70 12
            $this->options['encode'] = false;
71
        }
72
73 13
        $this->initOptions();
74
75
        return
76 13
            $this->renderToggleButton() . "\n" .
77 13
            Html::beginTag('div', $this->options) . "\n" .
78 13
            Html::beginTag('div', ['class' => 'modal-dialog ' . $this->size]) . "\n" .
79 13
            Html::beginTag('div', ['class' => 'modal-content']) . "\n" .
80 13
            $this->renderHeader() . "\n" .
81 13
            $this->renderBodyBegin() . "\n";
82
    }
83
84 13
    protected function run(): string
85
    {
86
        return
87 13
            "\n" . $this->renderBodyEnd() .
88 13
            "\n" . $this->renderFooter() .
89 13
            "\n" . Html::endTag('div') . // modal-content
90 13
            "\n" . Html::endTag('div') . // modal-dialog
91 13
            "\n" . Html::endTag('div');
92
    }
93
94
    /**
95
     * Body options.
96
     *
97
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
98
     *
99
     * @param array $value
100
     *
101
     * @return $this
102
     */
103 1
    public function bodyOptions(array $value): self
104
    {
105 1
        $new = clone $this;
106 1
        $new->bodyOptions = $value;
107
108 1
        return $new;
109
    }
110
111
    /**
112
     * The options for rendering the close button tag.
113
     *
114
     * The close button is displayed in the header of the modal window. Clicking on the button will hide the modal
115
     * window. If {@see closeButtonEnabled} is false, no close button will be rendered.
116
     *
117
     * The following special options are supported:
118
     *
119
     * - tag: string, the tag name of the button. Defaults to 'button'.
120
     * - label: string, the label of the button. Defaults to '&times;'.
121
     *
122
     * The rest of the options will be rendered as the HTML attributes of the button tag. Please refer to the
123
     * [Modal plugin help](http://getbootstrap.com/javascript/#modals) for the supported HTML attributes.
124
     *
125
     * @param array $value
126
     *
127
     * @return $this
128
     */
129 1
    public function closeButton(array $value): self
130
    {
131 1
        $new = clone $this;
132 1
        $new->closeButton = $value;
133
134 1
        return $new;
135
    }
136
137
    /**
138
     * Disable close button.
139
     *
140
     * @return $this
141
     */
142 1
    public function withoutCloseButton(): self
143
    {
144 1
        $new = clone $this;
145 1
        $new->closeButtonEnabled = false;
146
147 1
        return $new;
148
    }
149
150
    /**
151
     * The footer content in the modal window.
152
     *
153
     * @param string $value
154
     *
155
     * @return $this
156
     */
157 2
    public function footer(string $value): self
158
    {
159 2
        $new = clone $this;
160 2
        $new->footer = $value;
161
162 2
        return $new;
163
    }
164
165
    /**
166
     * Additional footer options.
167
     *
168
     * @param array $value
169
     *
170
     * @return $this
171
     *
172
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
173
     */
174 1
    public function footerOptions(array $value): self
175
    {
176 1
        $new = clone $this;
177 1
        $new->footerOptions = $value;
178
179 1
        return $new;
180
    }
181
182
    /**
183
     * Additional header options.
184
     *
185
     * @param array $value
186
     *
187
     * @return $this
188
     *
189
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
190
     */
191 1
    public function headerOptions(array $value): self
192
    {
193 1
        $new = clone $this;
194 1
        $new->headerOptions = $value;
195
196 1
        return $new;
197
    }
198
199
    /**
200
     * @param array $value the HTML attributes for the widget container tag. The following special options are
201
     * recognized.
202
     *
203
     * @return $this
204
     *
205
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
206
     */
207 1
    public function options(array $value): self
208
    {
209 1
        $new = clone $this;
210 1
        $new->options = $value;
211
212 1
        return $new;
213
    }
214
215
    /**
216
     * The title content in the modal window.
217
     *
218
     * @param string $value
219
     *
220
     * @return $this
221
     */
222 2
    public function title(string $value): self
223
    {
224 2
        $new = clone $this;
225 2
        $new->title = $value;
226
227 2
        return $new;
228
    }
229
230
    /**
231
     * Additional title options.
232
     *
233
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
234
     *
235
     * @param array $value
236
     *
237
     * @return $this
238
     */
239 1
    public function titleOptions(array $value): self
240
    {
241 1
        $new = clone $this;
242 1
        $new->titleOptions = $value;
243
244 1
        return $new;
245
    }
246
247
    /**
248
     * The options for rendering the toggle button tag.
249
     *
250
     * The toggle button is used to toggle the visibility of the modal window. If {@see toggleButtonEnabled} is false,
251
     * no toggle button will be rendered.
252
     *
253
     * The following special options are supported:
254
     *
255
     * - tag: string, the tag name of the button. Defaults to 'button'.
256
     * - label: string, the label of the button. Defaults to 'Show'.
257
     *
258
     * The rest of the options will be rendered as the HTML attributes of the button tag. Please refer to the
259
     * [Modal plugin help](http://getbootstrap.com/javascript/#modals) for the supported HTML attributes.
260
     *
261
     * @param array $value
262
     *
263
     * @return $this
264
     */
265 1
    public function toggleButton(array $value): self
266
    {
267 1
        $new = clone $this;
268 1
        $new->toggleButton = $value;
269
270 1
        return $new;
271
    }
272
273
    /**
274
     * Disable toggle button.
275
     *
276
     * @return $this
277
     */
278 1
    public function withoutToggleButton(): self
279
    {
280 1
        $new = clone $this;
281 1
        $new->toggleButtonEnabled = false;
282
283 1
        return $new;
284
    }
285
286
    /**
287
     * The modal size. Can be {@see SIZE_LARGE} or {@see SIZE_SMALL}, or null for default.
288
     *
289
     * @param string $value
290
     *
291
     * @return $this
292
     */
293 1
    public function size(string $value): self
294
    {
295 1
        $new = clone $this;
296 1
        $new->size = $value;
297
298 1
        return $new;
299
    }
300
301
    /**
302
     * Allows you to enable the encoding tags html.
303
     *
304
     * @return self
305
     */
306 1
    public function encodeTags(): self
307
    {
308 1
        $new = clone $this;
309 1
        $new->encodeTags = true;
310
311 1
        return $new;
312
    }
313
314
    /**
315
     * Renders the header HTML markup of the modal.
316
     *
317
     * @throws JsonException
318
     *
319
     * @return string the rendering result
320
     */
321 13
    private function renderHeader(): string
322
    {
323 13
        $button = $this->renderCloseButton();
324
325 13
        if ($this->title !== '') {
326 2
            Html::addCssClass($this->titleOptions, ['titleOptions' => 'modal-title']);
327
        }
328
329 13
        $header = ($this->title === '') ? '' : Html::tag('h5', $this->title, $this->titleOptions);
330
331 13
        if ($button !== null) {
332 12
            $header .= "\n" . $button;
333 1
        } elseif ($header === '') {
334 1
            return '';
335
        }
336
337 12
        Html::addCssClass($this->headerOptions, ['headerOptions' => 'modal-header']);
338
339 12
        if ($this->encodeTags === false) {
340 11
            $this->headerOptions['encode'] = false;
341
        }
342
343 12
        return Html::div($header, $this->headerOptions);
344
    }
345
346
    /**
347
     * Renders the opening tag of the modal body.
348
     *
349
     * @throws JsonException
350
     *
351
     * @return string the rendering result
352
     */
353 13
    private function renderBodyBegin(): string
354
    {
355 13
        Html::addCssClass($this->bodyOptions, ['widget' => 'modal-body']);
356
357 13
        if ($this->encodeTags === false) {
358 12
            $this->bodyOptions['encode'] = false;
359
        }
360
361 13
        return Html::beginTag('div', $this->bodyOptions);
362
    }
363
364
    /**
365
     * Renders the closing tag of the modal body.
366
     *
367
     * @return string the rendering result
368
     */
369 13
    private function renderBodyEnd(): string
370
    {
371 13
        return Html::endTag('div');
372
    }
373
374
    /**
375
     * Renders the HTML markup for the footer of the modal.
376
     *
377
     * @throws JsonException
378
     *
379
     * @return string the rendering result
380
     */
381 13
    private function renderFooter(): string
382
    {
383 13
        if ($this->footer === '') {
384 11
            return '';
385
        }
386
387 2
        Html::addCssClass($this->footerOptions, ['widget' => 'modal-footer']);
388
389 2
        if ($this->encodeTags === false) {
390 2
            $this->footerOptions['encode'] = false;
391
        }
392
393 2
        return Html::div($this->footer, $this->footerOptions);
394
    }
395
396
    /**
397
     * Renders the toggle button.
398
     *
399
     * @throws JsonException
400
     *
401
     * @return string the rendering result
402
     */
403 13
    private function renderToggleButton(): ?string
404
    {
405 13
        if ($this->toggleButtonEnabled === false) {
406 1
            return null;
407
        }
408
409 12
        $tag = ArrayHelper::remove($this->toggleButton, 'tag', 'button');
410 12
        $label = ArrayHelper::remove($this->toggleButton, 'label', 'Show');
411
412 12
        if ($this->encodeTags === false) {
413 11
            $this->toggleButton['encode'] = false;
414
        }
415
416 12
        return Html::tag($tag, $label, $this->toggleButton);
417
    }
418
419
    /**
420
     * Renders the close button.
421
     *
422
     * @throws JsonException
423
     *
424
     * @return string the rendering result
425
     */
426 13
    private function renderCloseButton(): ?string
427
    {
428 13
        if ($this->closeButtonEnabled === false) {
429 1
            return null;
430
        }
431
432 12
        $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button');
433 12
        $label = ArrayHelper::remove($this->closeButton, 'label', Html::span('&times;', [
434 12
            'aria-hidden' => 'true',
435
        ]));
436
437 12
        if ($this->encodeTags === false) {
438 11
            $this->closeButton['encode'] = false;
439
        }
440
441 12
        return Html::tag($tag, $label, $this->closeButton);
442
    }
443
444
    /**
445
     * Initializes the widget options.
446
     *
447
     * This method sets the default values for various options.
448
     */
449 13
    private function initOptions(): void
450
    {
451 13
        $this->options = array_merge([
452 13
            'class' => 'fade',
453
            'role' => 'dialog',
454
            'tabindex' => -1,
455
            'aria-hidden' => 'true',
456 13
        ], $this->options);
457
458
        /** @psalm-suppress InvalidArgument */
459 13
        Html::addCssClass($this->options, ['widget' => 'modal']);
460
461 13
        $this->titleOptions = array_merge([
462 13
            'id' => $this->options['id'] . '-label',
463 13
        ], $this->titleOptions);
464
465 13
        if (!isset($this->options['aria-label'], $this->options['aria-labelledby']) && $this->title !== '') {
466 2
            $this->options['aria-labelledby'] = $this->titleOptions['id'];
467
        }
468
469 13
        if ($this->closeButtonEnabled !== false) {
470 12
            Html::addCssClass($this->closeButton, ['closeButton' => 'close']);
471
472 12
            $this->closeButton = array_merge([
473 12
                'data-bs-dismiss' => 'modal',
474
                'type' => 'button',
475 12
            ], $this->closeButton);
476
        }
477
478 13
        if ($this->toggleButton !== []) {
479 1
            $this->toggleButton = array_merge([
480 1
                'data-bs-toggle' => 'modal',
481
                'type' => 'button',
482 1
            ], $this->toggleButton);
483 1
            if (!isset($this->toggleButton['data-bs-target']) && !isset($this->toggleButton['href'])) {
484 1
                $this->toggleButton['data-bs-target'] = '#' . $this->options['id'];
485
            }
486
        }
487 13
    }
488
}
489