Passed
Push — master ( 4aa208...f1ad34 )
by Paul
05:58
created

Builder::normalize()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 3
c 2
b 0
f 0
dl 0
loc 6
ccs 3
cts 4
cp 0.75
rs 10
cc 2
nc 2
nop 2
crap 2.0625
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Modules\Html;
4
5
use GeminiLabs\SiteReviews\Defaults\FieldDefaults;
6
use GeminiLabs\SiteReviews\Helper;
7
use GeminiLabs\SiteReviews\Helpers\Arr;
8
use GeminiLabs\SiteReviews\Helpers\Cast;
9
use GeminiLabs\SiteReviews\Helpers\Str;
10
11
/**
12
 * This class generates raw HTML tags without additional DOM markup.
13
 *
14
 * @method string a(string|array ...$params)
15
 * @method string button(string|array ...$params)
16
 * @method string div(string|array ...$params)
17
 * @method string i(string|array ...$params)
18
 * @method string img(string|array ...$params)
19
 * @method string input(string|array ...$params)
20
 * @method string label(string|array ...$params)
21
 * @method string option(string|array ...$params)
22
 * @method string p(string|array ...$params)
23
 * @method string select(string|array ...$params)
24
 * @method string small(string|array ...$params)
25
 * @method string span(string|array ...$params)
26
 * @method string textarea(string|array ...$params)
27
 */
28
class Builder
29
{
30
    const INPUT_TYPES = [
31
        'checkbox', 'date', 'datetime-local', 'email', 'file', 'hidden', 'image', 'month',
32
        'number', 'password', 'radio', 'range', 'reset', 'search', 'submit', 'tel', 'text', 'time',
33
        'url', 'week',
34
    ];
35
36
    const TAGS_FORM = [
37
        'input', 'select', 'textarea',
38
    ];
39
40
    const TAGS_SINGLE = [
41
        'img',
42
    ];
43
44
    const TAGS_STRUCTURE = [
45
        'div', 'form', 'nav', 'ol', 'section', 'ul',
46
    ];
47
48
    const TAGS_TEXT = [
49
        'a', 'button', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'i', 'label', 'li', 'option', 'p', 'pre',
50
        'small', 'span',
51
    ];
52
53
    /**
54
     * @var \GeminiLabs\SiteReviews\Arguments
55
     */
56
    public $args;
57
58
    /**
59
     * @var bool
60
     */
61
    public $render = false;
62
63
    /**
64
     * @var string
65
     */
66
    public $tag;
67
68
    /**
69
     * @var string
70
     */
71
    public $type;
72
73
    /**
74
     * @param string $method
75
     * @param array $methodArgs
76
     * @return string|void
77
     */
78 6
    public function __call($method, $methodArgs)
79
    {
80 6
        $instance = new static();
81 6
        $args = call_user_func_array([$instance, 'prepareArgs'], $methodArgs);
82 6
        $tag = Str::dashCase($method);
83 6
        $result = $instance->build($tag, $args);
84 6
        if (!$instance->render) {
85 6
            return $result;
86
        }
87
        echo $result;
88
    }
89
90
    /**
91
     * @param string $property
92
     * @param mixed $value
93
     * @return void
94
     */
95
    public function __set($property, $value)
96
    {
97
        $method = Helper::buildMethodName($property, 'set');
98
        if (method_exists($this, $method)) {
99
            call_user_func([$this, $method], $value);
100
        }
101
    }
102
103
    /**
104
     * @return string
105
     */
106 6
    public function build($tag, array $args = [])
107
    {
108 6
        $this->setArgs($args, $tag);
109 6
        $this->setTag($tag);
110 6
        glsr()->action('builder', $this);
111 6
        $result = $this->isHtmlTag($this->tag)
112 6
            ? $this->buildElement()
113 6
            : $this->buildCustom($tag);
114 6
        return glsr()->filterString('builder/result', $result, $this);
115
    }
116
117
    /**
118
     * @return void|string
119
     */
120 6
    public function buildClosingTag()
121
    {
122 6
        return '</'.$this->tag.'>';
123
    }
124
125
    /**
126
     * @param string $tag
127
     * @return void|string
128
     */
129
    public function buildCustom($tag)
130
    {
131
        if (class_exists($className = $this->getFieldClassName($tag))) {
132
            return (new $className($this))->build();
133
        }
134
        glsr_log()->error("Field [$className] missing.");
135
    }
136
137
    /**
138
     * @return string
139
     */
140 6
    public function buildDefaultElement($text = '')
141
    {
142 6
        $text = Helper::ifEmpty($text, $this->args->text, $strict = true);
143 6
        return $this->buildOpeningTag().$text.$this->buildClosingTag();
144
    }
145
146
    /**
147
     * @return void|string
148
     */
149 6
    public function buildElement()
150
    {
151 6
        if (in_array($this->tag, static::TAGS_SINGLE)) {
152
            return $this->buildOpeningTag();
153
        }
154 6
        if (in_array($this->tag, static::TAGS_FORM)) {
155
            return $this->buildFormElement();
156
        }
157 6
        return $this->buildDefaultElement();
158
    }
159
160
    /**
161
     * @return void|string
162
     */
163
    public function buildFormElement()
164
    {
165
        $method = Helper::buildMethodName($this->tag, 'buildForm');
166
        return $this->$method();
167
    }
168
169
    /**
170
     * @return void|string
171
     */
172 6
    public function buildOpeningTag()
173
    {
174 6
        $attributes = glsr(Attributes::class)->{$this->tag}($this->args->toArray())->toString();
175 6
        return '<'.trim($this->tag.' '.$attributes).'>';
176
    }
177
178
    /**
179
     * @return string
180
     */
181
    public function raw(array $field)
182
    {
183
        unset($field['label']);
184
        return $this->{$field['type']}($field);
185
    }
186
187
    /**
188
     * @param array $args
189
     * @param string $type
190
     * @return void
191
     */
192 6
    public function setArgs($args = [], $type = '')
193
    {
194 6
        $args = Arr::consolidate($args);
195 6
        if (!empty($args)) {
196 6
            $args = $this->normalize($args, $type);
197 6
            $options = glsr()->args($args)->options;
198 6
            $args = glsr(FieldDefaults::class)->merge($args);
199 6
            if (is_array($options)) {
200
                // Merging reindexes the options array, this may not be desirable
201
                // if the array is indexed so here we restore the original options array.
202
                // It's a messy hack, but it will have to do for now.
203
                $args['options'] = $options;
204
            }
205
        }
206 6
        $args = glsr()->filterArray('builder/'.$type.'/args', $args, $this);
207 6
        $this->args = glsr()->args($args);
208 6
    }
209
210
    /**
211
     * @param bool $bool
212
     * @return void
213
     */
214
    public function setRender($bool)
215
    {
216
        $this->render = Cast::toBool($bool);
217
    }
218
219
    /**
220
     * @param string $tag
221
     * @return void
222
     */
223 6
    public function setTag($tag)
224
    {
225 6
        $tag = Cast::toString($tag);
226 6
        $this->tag = Helper::ifTrue(in_array($tag, static::INPUT_TYPES), 'input', $tag);
227 6
    }
228
229
    /**
230
     * @return string|void
231
     */
232
    protected function buildFormInput()
233
    {
234
        if (!in_array($this->args->type, ['checkbox', 'radio'])) {
235
            return $this->buildFormLabel().$this->buildOpeningTag();
236
        }
237
        return empty($this->args->options)
238
            ? $this->buildFormInputChoice()
239
            : $this->buildFormInputChoices();
240
    }
241
242
    /**
243
     * @return string|void
244
     */
245
    protected function buildFormInputChoice()
246
    {
247
        if ($label = Helper::ifEmpty($this->args->text, $this->args->label)) {
248
            return $this->buildFormLabel([
249
                'text' => $this->buildOpeningTag().' '.$label,
250
            ]);
251
        }
252
        return $this->buildOpeningTag();
253
    }
254
255
    /**
256
     * @return string|void
257
     */
258
    protected function buildFormInputChoices()
259
    {
260
        $index = 0;
261
        return array_reduce(array_keys($this->args->options), function ($carry, $value) use (&$index) {
262
            return $carry.$this->input([
263
                'checked' => in_array($value, $this->args->cast('value', 'array')),
264
                'class' => $this->args->class,
265
                'id' => $this->indexedId(++$index),
266
                'label' => $this->args->options[$value],
267
                'name' => $this->args->name,
268
                'required' => $this->args->required,
269
                'tabindex' => $this->args->tabindex,
270
                'type' => $this->args->type,
271
                'value' => $value,
272
            ]);
273
        });
274
    }
275
276
    /**
277
     * @return void|string
278
     */
279
    protected function buildFormLabel(array $customArgs = [])
280
    {
281
        if (!empty($this->args->label) && 'hidden' !== $this->args->type) {
282
            return $this->label(wp_parse_args($customArgs, [
283
                'for' => $this->args->id,
284
                'text' => $this->args->label,
285
            ]));
286
        }
287
    }
288
289
    /**
290
     * @return string|void
291
     */
292
    protected function buildFormSelect()
293
    {
294
        return $this->buildFormLabel().$this->buildDefaultElement($this->buildFormSelectOptions());
295
    }
296
297
    /**
298
     * @return string|void
299
     */
300
    protected function buildFormSelectOptions()
301
    {
302
        $options = $this->args->cast('options', 'array');
303
        if ($this->args->placeholder) {
304
            $options = Arr::prepend($options, $this->args->placeholder, '');
305
        }
306
        return array_reduce(array_keys($options), function ($carry, $key) use ($options) {
307
            return $carry.$this->option([
308
                'selected' => $this->args->cast('value', 'string') === Cast::toString($key),
309
                'text' => $options[$key],
310
                'value' => $key,
311
            ]);
312
        });
313
    }
314
315
    /**
316
     * @return string|void
317
     */
318
    protected function buildFormTextarea()
319
    {
320
        return $this->buildFormLabel().$this->buildDefaultElement($this->args->cast('value', 'string'));
321
    }
322
323
    /**
324
     * @return string
325
     */
326
    protected function indexedId($index)
327
    {
328
        return Helper::ifTrue(count($this->args->options) > 1,
329
            $this->args->id.'-'.$index,
330
            $this->args->id
331
        );
332
    }
333
334
    /**
335
     * @param string $tag
336
     * @return bool
337
     */
338 6
    protected function isHtmlTag($tag)
339
    {
340 6
        return in_array($tag, array_merge(
341 6
            static::TAGS_FORM,
342 6
            static::TAGS_SINGLE,
343 6
            static::TAGS_STRUCTURE,
344 6
            static::TAGS_TEXT
345
        ));
346
    }
347
348
    /**
349
     * @param string $tag
350
     * @return string
351
     */
352 6
    protected function getFieldClassName($tag)
353
    {
354 6
        $className = Helper::buildClassName($tag, __NAMESPACE__.'\Fields');
355 6
        return glsr()->filterString('builder/field/'.$tag, $className);
356
    }
357
358
    /**
359
     * @return array
360
     */
361 6
    protected function normalize(array $args, $type)
362
    {
363 6
        if (class_exists($className = $this->getFieldClassName($type))) {
364
            $args = $className::merge($args);
365
        }
366 6
        return $args;
367
    }
368
369
    /**
370
     * @param string|array ...$params
371
     * @return array
372
     */
373 6
    protected function prepareArgs(...$params)
374
    {
375 6
        if (is_array($parameter1 = array_shift($params))) {
376 6
            return $parameter1;
377
        }
378 6
        $parameter2 = Arr::consolidate(array_shift($params));
379 6
        if (is_scalar($parameter1)) {
380 6
            $parameter2['text'] = $parameter1;
381
        }
382 6
        return $parameter2;
383
    }
384
}
385