Completed
Push — master ( d5e11a...3b8e42 )
by Dmitrijs
02:43
created

Card::renderTitleContent()   B

Complexity

Conditions 7
Paths 33

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 18
rs 8.8333
c 0
b 0
f 0
cc 7
nc 33
nop 1
1
<?php
2
/**
3
 * @link https://github.com/DMGPage/yii2-materialize
4
 * @copyright Copyright (c) 2018 Dmitrijs Reinmanis
5
 * @license https://github.com/DMGPage/yii2-materialize/blob/master/LICENSE
6
 */
7
8
namespace dmgpage\yii2materialize\widgets;
9
10
use dmgpage\yii2materialize\helpers\Html;
11
use yii\helpers\ArrayHelper;
12
use yii\base\InvalidConfigException;
13
use dmgpage\yii2materialize\assets\MaterializeExtraAsset;
14
15
/**
16
 * Cards are a convenient means of displaying content composed of different types of objects.
17
 * They’re also well-suited for presenting similar objects whose size or supported actions can vary considerably,
18
 * like photos with captions of variable length.
19
 *
20
 * You can use Cards like this:
21
 *
22
 * ```php
23
 * echo Card::widget([
24
 *     'columnOptions' => ['class' => 's12 m3'],
25
 *     'cardOptions' => ['class' => 'light-blue darken-4'],
26
 *     'image' => [
27
 *         'title' => 'Image Card Title',
28
 *         'url' => 'https://materializecss.com/images/sample-1.jpg',
29
 *         'fab' => [
30
 *             'type' => ButtonType::FLOATING,
31
 *             'size' => Size::MEDIUM,
32
 *             'waves' => Waves::LIGHT,
33
 *             'icon' => ['name' => 'add'],
34
 *             'options' => ['class' => 'light-blue accent-2']
35
 *         ]
36
 *     ],
37
 *     'content' => [
38
 *         'value' => '<p>I am a very simple card. I am good at containing small bits of information. '
39
 *             . 'I am convenient because I require little markup to use effectively.</p>',
40
 *         'options' => ['class' => 'white-text']
41
 *     ],
42
 *     'actions' => [
43
 *         ['label' => 'This is a link #1'],
44
 *         ['label' => 'This is a link #2']
45
 *     ],
46
 * ]);
47
 * ```
48
 */
49
class Card extends Widget
50
{
51
    /**
52
     * @var array the HTML attributes for the column container tag of the card view.
53
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
54
     */
55
    public $columnOptions = ['class' => 's12 m6'];
56
57
    /**
58
     * @var array the HTML attributes for the content wrapper tag of the card view.
59
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
60
     */
61
    public $cardOptions = [];
62
63
    /**
64
     * @var bool whether to HTML-encode the link labels and card title
65
     */
66
    public $encodeLabels = true;
67
68
    /**
69
     * @var array a list of attributes to be displayed in the card content tag. Item should be an array
70
     * of the following structure:
71
     * - title: string, title for the card image. Value will be HTML-encoded.
72
     *   You can change this, by setting extra attribute ["encode" => false] in "titleOptions" attribute
73
     * - value: string, the HTML content for the card body. It will NOT be HTML-encoded.
74
     *   Therefore you can pass in HTML code. If this is coming from end users,
75
     *   you should consider encode() it to prevent XSS attacks.
76
     * - options: array, the HTML attributes for the card content tag.
77
     * - titleOptions: array the HTML attributes for the title tag. You can specify 'icon' attribute
78
     *   (name for the icon), if you want to add icon to the right side of the title.
79
     */
80
    public $content = [];
81
82
    /**
83
     * @var array a list of attributes to be displayed in the card image tag. Item should be an array
84
     * of the following structure:
85
     * - title: string, title for the card image. Value will be HTML-encoded.
86
     *   You can change this, by setting extra attribute ["encode" => false] in "titleOptions" attribute
87
     * - url: string the image URL. This parameter will be processed by [[Url::to()]].
88
     * - fab: array list of attributes for floating action button. Value will be passed to [[Button]] widget.
89
     *   You can set extra param 'url' to render it as link.
90
     * - options: array, the HTML attributes for the card image tag.
91
     * - titleOptions: array the HTML attributes for the title tag. You can specify 'icon' attribute
92
     *   (name for the icon), if you want to add icon to the right side of the title.
93
     * - imageOptions: array the HTML attributes for the image tag.
94
     */
95
    public $image = [];
96
97
    /**
98
     * @var array list of card action items. Each action item should be an array of the following structure:
99
     * - label: string, specifies the action item label. When [[encodeLabels]] is true, the label
100
     *   will be HTML-encoded.
101
     * - encode: boolean, optional, whether this item`s label should be HTML-encoded. This param will override
102
     *   global [[encodeLabels]] param.
103
     * - url: string or array, optional, specifies the URL of the action item. It will be processed by [[Url::to]].
104
     * - icon: string or array, optional, icon name or array with 'name' and 'options'.
105
     * - options: array, optional, the HTML attributes for the action container tag.
106
     */
107
    public $actions = [];
108
109
    /**
110
     * @var array the HTML attributes for the action wrapper tag of the card view.
111
     * You can use attribute "sticky" to change, whether card actions must be always visible.
112
     * Default value is false.
113
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
114
     */
115
    public $actionOptions = [];
116
117
    /**
118
     * @var array a list of attributes to be displayed in the card reveal tag. Item should be an array
119
     * of the following structure:
120
     * - title: string, title for the card image. Value will be HTML-encoded.
121
     *   You can change this, by setting extra attribute ["encode" => false] in "titleOptions" attribute
122
     * - value: string, the HTML content for the card body. It will NOT be HTML-encoded.
123
     *   Therefore you can pass in HTML code. If this is coming from end users,
124
     *   you should consider encode() it to prevent XSS attacks.
125
     * - options: array, the HTML attributes for the card content tag.
126
     * - titleOptions: array the HTML attributes for the title tag. You can specify 'icon' attribute
127
     *   (name for the icon), if you want to add icon to the right side of the title.
128
     */
129
    public $reveal = [];
130
131
    /**
132
     * @var bool whether image should be on left side. Default value is false
133
     */
134
    public $horizontal = false;
135
136
    /**
137
     * Initializes the widget.
138
     * @return void
139
     */
140
    public function init()
141
    {
142
        parent::init();
143
144
        $contentData = $this->content;
145
        $useStickyActions = isset($this->actionOptions['sticky']) ? $this->actionOptions['sticky'] : false;
146
        $cardContentOptions = isset($this->content['options']) ? $this->content['options'] : [];
147
        Html::addCssClass($cardContentOptions, ['class' => 'card-content']);
148
149
        if ($this->horizontal) {
150
            Html::addCssClass($this->cardOptions, ['card', 'horizontal']);
151
        } elseif ($useStickyActions) {
152
            Html::addCssClass($this->cardOptions, ['card', 'sticky-action']);
153
            unset($this->actionOptions['sticky']);
154
        } else {
155
            Html::addCssClass($this->cardOptions, ['card']);
156
        }
157
158
        $html = Html::beginGridRow($this->options);
159
        $html .= Html::beginGridCol($this->columnOptions);
160
        $html .= Html::beginTag('div', $this->cardOptions);
161
        $html .= $this->renderImageContent($contentData);
0 ignored issues
show
Unused Code introduced by
The call to dmgpage\yii2materialize\...d::renderImageContent() has too many arguments starting with $contentData. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

161
        $html .= $this->/** @scrutinizer ignore-call */ renderImageContent($contentData);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
162
163
        // Create stacked content, if horizontal attribute is true
164
        if ($this->horizontal) {
165
            $html .= Html::beginTag('div', ['class' => 'card-stacked']);
166
        }
167
168
        // Add reviel button to content title, if reveal attribute is not empty
169
        if (!empty($this->reveal)) {
170
            Html::addCssClass($contentData['titleOptions'], 'activator');
171
            $addIcon = !isset($contentData['titleOptions']['icon']);
172
            $contentData['titleOptions']['icon'] = $addIcon
173
                ? 'more_vert'
174
                : $contentData['titleOptions']['icon'];
175
        }
176
177
        $html .= Html::beginTag('div', $cardContentOptions);
178
        $html .= $this->renderTitleContent($contentData);
179
180
        echo $html;
181
    }
182
183
    /**
184
     * Renders the widget.
185
     * @return void
186
     */
187
    public function run()
188
    {
189
        $this->registerPlugin('card');
190
191
        $html = isset($this->content['value']) ? $this->content['value'] : '';
192
        $html .= Html::endTag('div'); // ends card-content tag
193
194
        if (!empty($this->actions)) {
195
            Html::addCssClass($this->actionOptions, ['class' => 'card-action']);
196
            $html .= Html::beginTag('div', $this->actionOptions);
197
198
            foreach ($this->actions as $action) {
199
                $html .= $this->renderActionItem($action);
200
            }
201
202
            $html .= Html::endTag('div');
203
        }
204
205
        if ($this->horizontal) {
206
            $html .= Html::endTag('div'); //ends card-stacked tag
207
        }
208
209
        // Add card reveal tag
210
        $html .= $this->renderRevealContent();
211
        $html .= Html::endTag('div'); //ends card tag
212
        $html .= Html::endGridCol();
213
        $html .= Html::endGridRow();
214
215
        echo $html;
216
    }
217
218
    /**
219
     * Renders card content title tag content.
220
     *
221
     * @param array $source data source for title and options
222
     * @return string the rendering result
223
     */
224
    protected function renderTitleContent($source)
225
    {
226
        $html = '';
227
228
        if (isset($source['title'])) {
229
            $titleValue = $source['title'];
230
            $titleOptions = isset($source['titleOptions']) ? $source['titleOptions'] : [];
231
            $encode = isset($titleOptions['encode']) ? $titleOptions['encode'] : $this->encodeLabels;
232
            $icon = isset($titleOptions['icon']) ? $titleOptions['icon'] : null;
233
            unset($titleOptions['encode']);
234
            Html::addCssClass($titleOptions, 'card-title');
235
            $title = $encode ? Html::encode($titleValue) : $titleValue;
236
            $title .= !empty($icon) ? Html::icon($icon, ['class' => 'material-icons right']) : '';
237
            unset($titleOptions['icon']);
238
            $html .= Html::tag('span', $title, $titleOptions);
239
        }
240
241
        return $html;
242
    }
243
244
    /**
245
     * Renders a single card action item.
246
     *
247
     * @param array $link the link to be rendered. It must contain the "label" element. The "url" and "icon" element is optional.
248
     * @return string the rendering result
249
     * @throws InvalidConfigException if `$link` does not have "label" element.
250
     */
251
    protected function renderActionItem($link)
252
    {
253
        $encodeLabel = ArrayHelper::remove($link, 'encode', $this->encodeLabels);
254
255
        if (array_key_exists('label', $link)) {
256
            $label = $encodeLabel ? Html::encode($link['label']) : $link['label'];
257
        } else {
258
            throw new InvalidConfigException('The "label" element is required for each link.');
259
        }
260
261
        // Add icon to label text
262
        if (isset($link['icon'])) {
263
            $view = $this->getView();
264
            MaterializeExtraAsset::register($view);
265
266
            // Has issues on positioning: https://github.com/google/material-design-icons/issues/206
267
            $label = $this->renderIcon($link['icon']) . $label;
268
        }
269
270
        $options = $link['options'];
271
272
        if (isset($link['url'])) {
273
            return Html::a($label, $link['url'], $options);
274
        } else {
275
            return Html::a($label, '#', $options);
276
        }
277
    }
278
279
    /**
280
     * Renders card-image tag content.
281
     * @return string the rendering result
282
     */
283
    protected function renderImageContent()
284
    {
285
        $html = '';
286
287
        if (!empty($this->image)) {
288
            $contentData = $this->image;
289
            $fabData = isset($contentData['fab']) ? $contentData['fab'] : [];
290
            $options = isset($contentData['options']) ? $contentData['options'] : [];
291
            $imageOptions = isset($contentData['imageOptions']) ? $contentData['imageOptions'] : [];
292
            $imageUrl = isset($contentData['url']) ? $contentData['url'] : [];
293
            Html::addCssClass($options, ['class' => 'card-image']);
294
295
            $html .= Html::beginTag('div', $options);
296
            $html .= Html::img($imageUrl, $imageOptions);
297
            $html .= $this->renderTitleContent($contentData);
298
            $html .= $this->renderActionButton($fabData);
299
            $html .= Html::endTag('div');
300
        }
301
302
        return $html;
303
    }
304
305
    /**
306
     * Renders floating action button tag.
307
     *
308
     * @param array $config attribute values and options for Button widget.
309
     * @return string the rendering result
310
     */
311
    protected function renderActionButton($config)
312
    {
313
        $html = '';
314
315
        if (!empty($config)) {
316
            if (!isset($config['options'])) {
317
                $config['options'] = ['class' => 'halfway-fab'];
318
            } else {
319
                Html::addCssClass($config['options'], 'halfway-fab');
320
            }
321
322
            $html .= Button::widget($config);
323
        }
324
325
        return $html;
326
    }
327
328
    /**
329
     * Renders card-reveal tag content.
330
     * @return string the rendering result
331
     */
332
    protected function renderRevealContent()
333
    {
334
        $html = '';
335
336
        if (!empty($this->reveal)) {
337
            $contentData = $this->reveal;
338
            $options = isset($contentData['options']) ? $contentData['options'] : [];
339
            $addIcon = !isset($contentData['titleOptions']['icon']);
340
            $contentData['titleOptions']['icon'] = $addIcon
341
                ? 'close'
342
                : $contentData['titleOptions']['icon'];
343
            Html::addCssClass($options, ['class' => 'card-reveal']);
344
345
            $html .= Html::beginTag('div', $options);
346
            $html .= $this->renderTitleContent($contentData);
347
            $html .= isset($contentData['value']) ? $contentData['value'] : '';
348
            $html .= Html::endTag('div');
349
        }
350
351
        return $html;
352
    }
353
}
354