Passed
Push — develop ( 535216...2a3c68 )
by Paul
07:18
created

Builder::getFieldClassName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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