Passed
Pull Request — master (#85)
by Albert
13:00 queued 10:33
created

Alert   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 377
Duplicated Lines 0 %

Test Coverage

Coverage 95.1%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 91
c 1
b 0
f 0
dl 0
loc 377
ccs 97
cts 102
cp 0.951
rs 9.76
wmc 33

24 Methods

Rating   Name   Duplication   Size   Complexity  
A closeButton() 0 6 1
A dark() 0 3 1
A headerOptions() 0 6 1
A addClassNames() 0 6 1
A body() 0 6 1
A light() 0 3 1
A success() 0 3 1
A warning() 0 3 1
A header() 0 6 1
A encode() 0 6 1
A fade() 0 6 1
A headerTag() 0 6 1
A primary() 0 3 1
A options() 0 6 1
A info() 0 3 1
A danger() 0 3 1
A secondary() 0 3 1
A closeButtonTag() 0 6 1
A run() 0 16 3
A renderCloseButton() 0 21 4
A withoutCloseButton() 0 6 1
A prepareOptions() 0 21 4
A renderHeader() 0 12 2
A getId() 0 3 1
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
 * Alert renders an alert bootstrap component.
15
 *
16
 * For example,
17
 *
18
 * ```php
19
 * echo Alert::widget()
20
 *     ->options([
21
 *         'class' => 'alert-info',
22
 *     ])
23
 *     ->body('Say hello...');
24
 * ```
25
 *
26
 * @link https://getbootstrap.com/docs/5.0/components/alerts/
27
 */
28
final class Alert extends Widget
29
{
30
    private string $body = '';
31
    private ?string $header = null;
32
    private array $headerOptions = [];
33
    private string $headerTag = 'h4';
34
    private array $closeButton = [
35
        'class' => 'btn-close',
36
    ];
37
    private string $closeButtonTag = 'button';
38
    private bool $closeButtonInside = true;
39
    private bool $encode = false;
40
    private array $options = [];
41
    private array $classNames = [];
42
    private bool $fade = false;
43
44 17
    public function getId(?string $suffix = '-alert'): ?string
45
    {
46 17
        return $this->options['id'] ?? parent::getId($suffix);
47
    }
48
49 17
    protected function run(): string
50
    {
51 17
        $options = $this->prepareOptions();
52 17
        $tag = ArrayHelper::remove($options, 'tag', 'div');
53
54 17
        $content = Html::openTag($tag, $options);
55 17
        $content .= $this->renderHeader();
56 17
        $content .= $this->encode ? Html::encode($this->body) : $this->body;
57
58 17
        if ($this->closeButtonInside) {
59 7
            $content .= $this->renderCloseButton(false);
60
        }
61
62 17
        $content .= Html::closeTag($tag);
63
64 17
        return $content;
65
    }
66
67
    /**
68
     * The body content in the alert component. Alert widget will also be treated as the body content, and will be
69
     * rendered before this.
70
     *
71
     * @param string $value
72
     *
73
     * @return self
74
     */
75 17
    public function body(string $value): self
76
    {
77 17
        $new = clone $this;
78 17
        $new->body = $value;
79
80 17
        return $new;
81
    }
82
83
    /**
84
     * The header content in alert component
85
     *
86
     * @param string|null $header
87
     *
88
     * @return self
89
     */
90 1
    public function header(?string $header): self
91
    {
92 1
        $new = clone $this;
93 1
        $new->header = $header;
94
95 1
        return $new;
96
    }
97
98
    /**
99
     * The HTML attributes for the widget header tag. The following special options are recognized.
100
     *
101
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
102
     *
103
     * @param array $options
104
     *
105
     * @return self
106
     */
107 1
    public function headerOptions(array $options): self
108
    {
109 1
        $new = clone $this;
110 1
        $new->headerOptions = $options;
111
112 1
        return $new;
113
    }
114
115
    /**
116
     * Set tag name for header
117
     *
118
     * @param string $tag
119
     *
120
     * @return self
121
     */
122 1
    public function headerTag(string $tag): self
123
    {
124 1
        $new = clone $this;
125 1
        $new->headerTag = $tag;
126
127 1
        return $new;
128
    }
129
130
    /**
131
     * The options for rendering the close button tag.
132
     *
133
     * The close button is displayed in the header of the modal window. Clicking on the button will hide the modal
134
     * window. If {@see closeButtonEnabled} is false, no close button will be rendered.
135
     *
136
     * The following special options are supported:
137
     *
138
     * - tag: string, the tag name of the button. Defaults to 'button'.
139
     * - label: string, the label of the button. Defaults to '&times;'.
140
     *
141
     * The rest of the options will be rendered as the HTML attributes of the button tag.
142
     *
143
     * Please refer to the [Alert documentation](http://getbootstrap.com/components/#alerts) for the supported HTML
144
     * attributes.
145
     *
146
     * @param array $value
147
     *
148
     * @return self
149
     */
150 1
    public function closeButton(array $value): self
151
    {
152 1
        $new = clone $this;
153 1
        $new->closeButton = $value;
154
155 1
        return $new;
156
    }
157
158
    /**
159
     * Disable close button.
160
     *
161
     * @return self
162
     */
163 10
    public function withoutCloseButton(bool $value = false): self
164
    {
165 10
        $new = clone $this;
166 10
        $new->closeButtonInside = $value;
167
168 10
        return $new;
169
    }
170
171
    /**
172
     * Set close button tag
173
     *
174
     * @param string $tag
175
     *
176
     * @return self
177
     */
178 1
    public function closeButtonTag(string $tag): self
179
    {
180 1
        $new = clone $this;
181 1
        $new->closeButtonTag = $tag;
182
183 1
        return $new;
184
    }
185
186
    /**
187
     * The HTML attributes for the widget container tag. The following special options are recognized.
188
     *
189
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
190
     *
191
     * @param array $value
192
     *
193
     * @return self
194
     */
195 3
    public function options(array $value): self
196
    {
197 3
        $new = clone $this;
198 3
        $new->options = $value;
199
200 3
        return $new;
201
    }
202
203
    /**
204
     * Enable/Disable encode body
205
     *
206
     * @param bool $encode
207
     *
208
     * @return self
209
     */
210
    public function encode(bool $encode = true): self
211
    {
212
        $new = clone $this;
213
        $new->encode = $encode;
214
215
        return $new;
216
    }
217
218
    /**
219
     * Enable/Disable dissmiss animation
220
     *
221
     * @param bool $fade
222
     *
223
     * @return self
224
     */
225 2
    public function fade(bool $fade = true): self
226
    {
227 2
        $new = clone $this;
228 2
        $new->fade = $fade;
229
230 2
        return $new;
231
    }
232
233
    /**
234
     * Set type of alert, 'alert-success', 'alert-danger', 'custom-alert' etc
235
     *
236
     * @param string $type
237
     *
238
     * @return self
239
     */
240 9
    public function addClassNames(string ...$classNames): self
241
    {
242 9
        $new = clone $this;
243 9
        $new->classNames = array_filter($classNames, static fn ($name) => $name !== '');
244
245 9
        return $new;
246
    }
247
248
    /**
249
     * Short method for primary alert type
250
     *
251
     * @return self
252
     */
253 1
    public function primary(): self
254
    {
255 1
        return $this->addClassNames('alert-primary');
256
    }
257
258
    /**
259
     * Short method for secondary alert type
260
     *
261
     * @return self
262
     */
263 1
    public function secondary(): self
264
    {
265 1
        return $this->addClassNames('alert-secondary');
266
    }
267
268
    /**
269
     * Short method for success alert type
270
     *
271
     * @return self
272
     */
273 1
    public function success(): self
274
    {
275 1
        return $this->addClassNames('alert-success');
276
    }
277
278
    /**
279
     * Short method for danger alert type
280
     *
281
     * @return self
282
     */
283 1
    public function danger(): self
284
    {
285 1
        return $this->addClassNames('alert-danger');
286
    }
287
288
    /**
289
     * Short method for warning alert type
290
     *
291
     * @return self
292
     */
293 1
    public function warning(): self
294
    {
295 1
        return $this->addClassNames('alert-warning');
296
    }
297
298
    /**
299
     * Short method for info alert type
300
     *
301
     * @return self
302
     */
303 1
    public function info(): self
304
    {
305 1
        return $this->addClassNames('alert-info');
306
    }
307
308
    /**
309
     * Short method for light alert type
310
     *
311
     * @return self
312
     */
313 1
    public function light(): self
314
    {
315 1
        return $this->addClassNames('alert-light');
316
    }
317
318
    /**
319
     * Short method for dark alert type
320
     *
321
     * @return self
322
     */
323 1
    public function dark(): self
324
    {
325 1
        return $this->addClassNames('alert-dark');
326
    }
327
328
    /**
329
     * Renders the close button.
330
     *
331
     * @throws JsonException
332
     *
333
     * @return string the rendering result
334
     */
335 7
    public function renderCloseButton(bool $outside = true): ?string
336
    {
337 7
        $options = array_merge(
338 7
            $this->closeButton,
339
            [
340 7
                'aria-label' => 'Close',
341
                'data-bs-dismiss' => 'alert',
342
            ],
343
        );
344 7
        $label = ArrayHelper::remove($options, 'label', '');
345 7
        $encode = ArrayHelper::remove($options, 'encode', $this->encode);
346
347 7
        if ($this->closeButtonTag === 'button' && !isset($options['type'])) {
348 7
            $options['type'] = 'button';
349
        }
350
351 7
        if ($outside) {
352
            $options['data-bs-target'] = '#' . $this->getId();
353
        }
354
355 7
        return Html::tag($this->closeButtonTag, $label, $options)->encode($encode)->render();
356
    }
357
358
    /**
359
     * Render header tag
360
     *
361
     * @return string|null
362
     */
363 17
    private function renderHeader(): ?string
364
    {
365 17
        if ($this->header === null) {
366 16
            return null;
367
        }
368
369 1
        $options = $this->headerOptions;
370 1
        $encode = ArrayHelper::remove($options, 'encode', true);
371
372 1
        Html::addCssClass($options, ['alert-heading']);
373
374 1
        return Html::tag($this->headerTag, $this->header, $options)->encode($encode)->render();
375
    }
376
377
    /**
378
     * Prepare the widget options.
379
     *
380
     * This method returns the default values for various options.
381
     *
382
     * @return array
383
     */
384 17
    private function prepareOptions(): array
385
    {
386 17
        $options = $this->options;
387 17
        $options['id'] = $this->getId();
388 17
        $classNames = array_merge(['alert'], $this->classNames);
389
390 17
        if ($this->closeButtonInside) {
391 7
            $classNames[] = 'alert-dismissible';
392
        }
393
394 17
        if ($this->fade) {
395 2
            $classNames[] = 'fade show';
396
        }
397
398 17
        Html::addCssClass($options, $classNames);
399
400 17
        if (!isset($options['role'])) {
401 17
            $options['role'] = 'alert';
402
        }
403
404 17
        return $options;
405
    }
406
}
407