Base::multiInput()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 11
c 1
b 0
f 0
nc 3
nop 3
dl 0
loc 19
rs 9.9
1
<?php
2
3
namespace GeminiLabs\Castor\Forms\Fields;
4
5
use Exception;
6
use GeminiLabs\Castor\Services\Normalizer;
7
8
abstract class Base
9
{
10
    /**
11
     * @var array
12
     */
13
    protected $args;
14
15
    /**
16
     * @var array
17
     */
18
    protected $dependencies = [];
19
20
    /**
21
     * Whether the field has multiple values.
22
     *
23
     * @var bool
24
     */
25
    protected $multi = false;
26
27
    /**
28
     * Whether the field is rendered outside of the form table.
29
     *
30
     * @var bool
31
     */
32
    protected $outside = false;
33
34
    /**
35
     * The field element tag (i.e. "input").
36
     *
37
     * @var string
38
     */
39
    protected $element;
40
41
    public function __construct(array $args = [])
42
    {
43
        $this->args = $args;
44
    }
45
46
    /**
47
     * @param string $property
48
     *
49
     * @return mixed
50
     * @throws Exception
51
     */
52
    public function __get($property)
53
    {
54
        switch ($property) {
55
            case 'args':
56
            case 'dependencies':
57
            case 'element':
58
            case 'multi':
59
            case 'outside':
60
                return $this->$property;
61
        }
62
        throw new Exception(sprintf('Invalid %s property: %s', __CLASS__, $property));
63
    }
64
65
    /**
66
     * Generate the field description.
67
     *
68
     * @param bool $paragraph
69
     *
70
     * @return string|null
71
     */
72
    public function generateDescription($paragraph = true)
73
    {
74
        if (!isset($this->args['desc']) || !$this->args['desc']) {
75
            return;
76
        }
77
78
        $tag = ((bool) $paragraph || 'p' == $paragraph) ? 'p' : 'span';
79
80
        return sprintf('<%1$s class="description">%2$s</%1$s>', $tag, $this->args['desc']);
81
    }
82
83
    /**
84
     * Generate the field label.
85
     *
86
     * @return string|null
87
     */
88
    public function generateLabel()
89
    {
90
        if (empty($this->args['label'])) {
91
            return;
92
        }
93
94
        $for = (bool) $this->args['id']
95
            ? " for=\"{$this->args['id']}\""
96
            : '';
97
98
        return sprintf('<label%s>%s</label>', $for, $this->args['label']);
99
    }
100
101
    /**
102
     * Render this field type.
103
     *
104
     * @return string
105
     */
106
    abstract public function render();
107
108
    /**
109
     * Convert a value to camel case.
110
     *
111
     * @param string $value
112
     *
113
     * @return string
114
     */
115
    protected function camelCase($value)
116
    {
117
        $value = ucwords(str_replace(['-', '_'], ' ', $value));
118
119
        return lcfirst(str_replace(' ', '', $value));
120
    }
121
122
    /**
123
     * Implode the field attributes.
124
     *
125
     * @return string
126
     */
127
    protected function implodeAttributes($defaults = [])
128
    {
129
        return $this->normalize($defaults, 'implode');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->normalize($defaults, 'implode') also could return the type array which is incompatible with the documented return type string.
Loading history...
130
    }
131
132
    /**
133
     * Implode multi-field items.
134
     *
135
     * @return string|null
136
     */
137
    protected function implodeOptions($method = 'select_option', $default = null)
138
    {
139
        $this->args['default'] ?: $this->args['default'] = $default;
140
141
        $method = $this->camelCase($method);
142
143
        $method = method_exists($this, $method)
144
            ? $method
145
            : 'selectOption';
146
147
        $i = 0;
148
149
        if ('singleInput' === $method) {
150
            if (!isset($this->args['options']) || empty($this->args['options'])) {
151
                return;
152
            }
153
154
            // hack to make sure unset single checkbox values start at 1 instead of 0
155
            if (0 === key($this->args['options'])) {
156
                $options = ['1' => $this->args['options'][0]];
157
                $this->args['options'] = $options;
158
            }
159
160
            return $this->singleInput();
161
        }
162
163
        return array_reduce(array_keys($this->args['options']), function ($carry, $key) use (&$i, $method) {
164
            return $carry .= $this->$method($key, $i++);
165
        });
166
    }
167
168
    /**
169
     * Normalize attributes for this specific field type.
170
     *
171
     * @param bool|string $implode
172
     *
173
     * @return array|string
174
     */
175
    protected function normalize(array $defaults = [], $implode = false)
176
    {
177
        $args = $this->mergeAttributesWith($defaults);
178
179
        $normalize = new Normalizer();
180
181
        return ($this->element && method_exists($normalize, $this->element))
182
            ? $normalize->{$this->element}($args, $implode)
183
            : ((bool) $implode ? '' : []);
184
    }
185
186
    /**
187
     * Merge and overwrite empty $this->args values with $defaults.
188
     *
189
     * @return array
190
     */
191
    protected function mergeAttributesWith(array $defaults)
192
    {
193
        // similar to array_merge except overwrite empty values
194
        foreach ($defaults as $key => $value) {
195
            if (isset($this->args[$key]) && !empty($this->args[$key])) {
196
                continue;
197
            }
198
            $this->args[$key] = $value;
199
        }
200
201
        $attributes = $this->args['attributes'];
202
203
        // prioritize $attributes over $this->args, don't worry about duplicates
204
        return array_merge($this->args, $attributes);
205
    }
206
207
    /**
208
     * Generate checkboxes and radios.
209
     *
210
     * @param string $optionKey
211
     * @param string $number
212
     * @param string $type
213
     *
214
     * @return string|null
215
     */
216
    protected function multiInput($optionKey, $number, $type = 'radio')
217
    {
218
        $args = $this->multiInputArgs($type, $optionKey, $number);
219
220
        if (!$args) {
221
            return;
222
        }
223
224
        $attributes = '';
225
226
        foreach ($args['attributes'] as $key => $val) {
227
            $attributes .= sprintf('%s="%s" ', $key, $val);
228
        }
229
230
        return sprintf('<li><label for="%s"><input %s%s/> %s</label></li>',
231
            $args['attributes']['id'],
232
            $attributes,
233
            checked($args['value'], $args['attributes']['value'], false),
234
            $args['label']
235
        );
236
    }
237
238
    /**
239
     * Build the checkbox/radio args.
240
     *
241
     * @param string $type
242
     * @param string $optionName
243
     * @param string $number
244
     *
245
     * @return array|null
246
     */
247
    protected function multiInputArgs($type, $optionName, $number)
248
    {
249
        $defaults = [
250
            'class' => '',
251
            'name' => '',
252
            'type' => $type,
253
            'value' => '',
254
        ];
255
256
        $args = [];
257
258
        $value = $this->args['options'][$optionName];
259
260
        if (is_array($value)) {
261
            $args = $value;
262
        }
263
264
        if (is_string($value)) {
265
            $label = $value;
266
        }
267
268
        isset($args['name']) ?: $args['name'] = $optionName;
269
        isset($args['value']) ?: $args['value'] = $optionName;
270
271
        $args = wp_parse_args($args, $defaults);
272
273
        if (!isset($label) || '' === $args['name']) {
274
            return;
275
        }
276
277
        $args['id'] = $this->args['id']."-{$number}";
278
        $args['name'] = $this->args['name'].('checkbox' === $type && $this->multi ? '[]' : '');
279
280
        $args = array_filter($args, function ($value) {
281
            return '' !== $value;
282
        });
283
284
        if (is_array($this->args['value'])) {
285
            if (in_array($args['value'], $this->args['value'])) {
286
                $this->args['default'] = $args['value'];
287
            }
288
        } elseif ($this->args['value']) {
289
            $this->args['default'] = $this->args['value'];
290
        } elseif ('radio' == $type && !$this->args['default']) {
291
            $this->args['default'] = 0;
292
        }
293
294
        return [
295
            'attributes' => $args,
296
            'label' => $label,
297
            'value' => $this->args['default'],
298
        ];
299
    }
300
301
    /**
302
     * Generate checkboxes.
303
     *
304
     * @param string $optionKey
305
     * @param string $number
306
     *
307
     * @return string|null
308
     */
309
    protected function multiInputCheckbox($optionKey, $number)
310
    {
311
        return $this->multiInput($optionKey, $number, 'checkbox');
312
    }
313
314
    /**
315
     * Generate select options.
316
     *
317
     * @param string $optionKey
318
     *
319
     * @return string
320
     */
321
    protected function selectOption($optionKey)
322
    {
323
        return sprintf('<option value="%s"%s>%s</option>',
324
            $optionKey,
325
            selected($this->args['value'], $optionKey, false),
326
            $this->args['options'][$optionKey]
327
        );
328
    }
329
330
    /**
331
     * Generate a single checkbox.
332
     *
333
     * @param string $type
334
     *
335
     * @return string|null
336
     */
337
    protected function singleInput($type = 'checkbox')
338
    {
339
        $optionKey = key($this->args['options']);
340
341
        $args = $this->multiInputArgs($type, $optionKey, 1);
342
343
        if (!$args) {
344
            return;
345
        }
346
347
        $atts = $this->normalize();
348
        $atts = wp_parse_args($args['attributes'], $atts);
0 ignored issues
show
Bug introduced by
It seems like $atts can also be of type string; however, parameter $defaults of wp_parse_args() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

348
        $atts = wp_parse_args($args['attributes'], /** @scrutinizer ignore-type */ $atts);
Loading history...
349
350
        $attributes = '';
351
352
        foreach ($atts as $key => $val) {
353
            $attributes .= sprintf('%s="%s" ', $key, $val);
354
        }
355
356
        return sprintf('<label for="%s"><input %s%s/> %s</label>',
357
            $atts['id'],
358
            $attributes,
359
            checked($args['value'], $atts['value'], false),
360
            $args['label']
361
        );
362
    }
363
}
364