Test Failed
Push — develop ( 006c9f...4b441a )
by Paul
13:29
created

Shortcode::wrap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 10
rs 10
ccs 0
cts 7
cp 0
cc 1
nc 1
nop 2
crap 2
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Shortcodes;
4
5
use GeminiLabs\SiteReviews\Contracts\ShortcodeContract;
6
use GeminiLabs\SiteReviews\Database\ShortcodeOptionManager;
7
use GeminiLabs\SiteReviews\Defaults\DefaultsAbstract;
8
use GeminiLabs\SiteReviews\Helper;
9
use GeminiLabs\SiteReviews\Helpers\Arr;
10
use GeminiLabs\SiteReviews\Helpers\Cast;
11
use GeminiLabs\SiteReviews\Helpers\Str;
12
use GeminiLabs\SiteReviews\Modules\Html\Builder;
13
use GeminiLabs\SiteReviews\Modules\Multilingual;
14
use GeminiLabs\SiteReviews\Modules\Rating;
15
use GeminiLabs\SiteReviews\Modules\Sanitizer;
16
use GeminiLabs\SiteReviews\Modules\Style;
17
18
abstract class Shortcode implements ShortcodeContract
19
{
20
    public array $args;
21
    public string $debug;
22
    public string $description;
23
    public string $from;
24 8
    public string $name;
25
    public string $preset;
26 8
    public string $tag;
27
28
    public function __construct()
29
    {
30
        $this->args = [];
31
        $this->debug = '';
32
        $this->description = $this->description();
33
        $this->from = '';
34
        $this->name = $this->name();
35
        $this->preset = '';
36
        $this->tag = $this->tag();
37
    }
38
39
    public function attributes(array $values, string $from = 'function'): array
40
    {
41
        $attributes = $this->defaults()->dataAttributes($values);
42
        $attributes = wp_parse_args($attributes, [
43
            'class' => glsr(Style::class)->styleClasses($values['class'] ?? ''),
44
            'data-from' => $from,
45
            'data-shortcode' => $this->tag,
46
            'id' => $values['id'] ?? '',
47
        ]);
48
        unset($attributes['data-class']);
49
        unset($attributes['data-id']);
50
        unset($attributes['data-form_id']);
51
        $attributes = glsr()->filterArray("shortcode/attributes/{$this->tag}", $attributes, $this);
52
        $attributes = glsr()->filterArray('shortcode/attributes', $attributes, $this);
53
        $attributes = array_map('esc_attr', $attributes);
54
        return $attributes;
55
    }
56
57
    public function build(array $args = [], string $from = 'shortcode', bool $isWrapped = true): string
58
    {
59
        $this->normalize(wp_parse_args($args), $from);
60
        $template = $this->buildTemplate();
61
        $attributes = $this->attributes($this->args, $from);
62
        $html = glsr(Builder::class)->div($template, $attributes);
63
        $rendered = sprintf('%s%s', $this->debug, $html);
64
        if ($isWrapped) {
65
            return $this->wrap($rendered);
66
        }
67
        return $rendered;
68
    }
69
70
    public function defaults(): DefaultsAbstract
71
    {
72
        $classname = str_replace('Shortcodes\\', 'Defaults\\', get_class($this));
73
        $classname = str_replace('Shortcode', 'Defaults', $classname);
74
        return glsr($classname);
75
    }
76
77
    public function hasVisibleFields(array $args = []): bool
78
    {
79
        if (!empty($args)) {
80
            $this->normalize($args);
81
        }
82
        $defaults = $this->options('hide');
83 8
        $hide = $this->args['hide'] ?? [];
84
        $hide = array_flip(Arr::consolidate($hide));
85 8
        unset($defaults['if_empty'], $hide['if_empty']);
86 8
        return !empty(array_diff_key($defaults, $hide));
87
    }
88
89
    public function normalize(array $args, string $from = ''): ShortcodeContract
90
    {
91
        if (!empty($from)) {
92
            $this->from = $from;
93
        }
94
        $this->args = [];
95
        $args = glsr()->filterArray("shortcode/args/{$this->tag}", $args, $this);
96
        $args = glsr()->filterArray('shortcode/args', $args, $this);
97
        $args = $this->defaults()->unguardedRestrict($args);
98
        $this->preset = $this->stylePreset($args['class']);
99
        foreach ($args as $key => $value) {
100
            $method = Helper::buildMethodName('normalize', $key);
101
            if (method_exists($this, $method)) {
102
                $value = call_user_func([$this, $method], $value);
103
            }
104
            $this->args[$key] = $value;
105
        }
106
        return $this;
107
    }
108
109
    /**
110
     * Returns the options for a shortcode setting. Results are filtered
111
     * by the "site-reviews/shortcode/options/{$options}" hook.
112
     */
113
    public function options(string $option, array $args = []): array
114
    {
115
        $args['option'] = $option;
116
        $args['shortcode'] = $this->tag;
117
        return call_user_func([glsr(ShortcodeOptionManager::class), $option], $args);
118
    }
119
120
    public function register(): void
121
    {
122
        if (!function_exists('add_shortcode')) {
123
            return;
124
        }
125
        $shortcode = (new \ReflectionClass($this))->getShortName();
126
        $shortcode = Str::snakeCase($shortcode);
127
        $shortcode = str_replace('_shortcode', '', $shortcode);
128
        add_shortcode($shortcode, fn ($atts) => $this->build($atts));
129
        glsr()->append('shortcodes', get_class($this), $shortcode);
130
    }
131
132
    /**
133
     * Returns the filtered shortcode settings configuration.
134
     */
135
    public function settings(): array
136
    {
137
        $config = $this->config();
138
        $config = glsr()->filterArray("shortcode/config/{$this->tag}", $config, $this);
139
        $config = glsr()->filterArray('shortcode/config', $config, $this);
140
        return $config;
141
    }
142
143
    public function tag(): string
144
    {
145
        return Str::snakeCase(
146
            str_replace('Shortcode', '', (new \ReflectionClass($this))->getShortName())
147
        );
148
    }
149
150
    public function wrap(string $renderedHtml, array $attributes = []): string
151
    {
152
        $classes = [
153
            Str::dashCase("{$this->from}-{$this->tag}"),
154
            Arr::getAs('string', $attributes, 'class'),
155
            $this->preset,
156
        ];
157
        $classAttr = implode(' ', $classes);
158
        $attributes['class'] = glsr(Sanitizer::class)->sanitizeAttrClass($classAttr);
159
        return glsr(Builder::class)->div($renderedHtml, $attributes);
160
    }
161
162
    /**
163
     * Returns the unfiltered shortcode settings configuration.
164
     */
165
    abstract protected function config(): array;
166
167
    protected function debug(array $data = []): void
168
    {
169
        if (empty($this->args['debug']) || 'shortcode' !== $this->from) {
170
            return;
171
        }
172
        $data = wp_parse_args($data, [
173
            'args' => $this->args,
174
            'shortcode' => $this->tag,
175
        ]);
176
        ksort($data);
177
        ob_start();
178
        glsr_debug($data);
179
        $this->debug = ob_get_clean();
180
    }
181
182
    protected function hideOptions(): array
183
    {
184
        return [];
185
    }
186
187
    /**
188
     * @param string $value
189
     */
190
    protected function normalizeAssignedPosts($value): string
191
    {
192
        $values = Cast::toArray($value);
193
        $postTypes = [];
194
        foreach ($values as $postType) {
195
            if (!is_numeric($postType) && post_type_exists((string) $postType)) {
196
                $postTypes[] = $postType;
197
            }
198
        }
199
        $values = glsr(Sanitizer::class)->sanitizePostIds($values);
200
        $values = glsr(Multilingual::class)->getPostIdsForAllLanguages($values);
201
        $values = array_merge($values, $postTypes);
202
        return implode(',', $values);
203
    }
204
205
    /**
206
     * @param string $value
207
     */
208
    protected function normalizeAssignedTerms($value): string
209
    {
210
        $values = glsr(Sanitizer::class)->sanitizeTermIds($value);
211
        $values = glsr(Multilingual::class)->getTermIdsForAllLanguages($values);
212
        return implode(',', $values);
213
    }
214
215
    /**
216
     * @param string $value
217 8
     */
218
    protected function normalizeAssignedUsers($value): string
219 8
    {
220 8
        $values = glsr(Sanitizer::class)->sanitizeUserIds($value);
221 8
        return implode(',', $values);
222
    }
223
224
    protected function normalizeClass(string $value): string
225
    {
226
        $values = $this->parseClassAttr($value)['custom'];
227
        return implode(',', $values);
228
    }
229
230
    /**
231
     * @param string|array $value
232
     */
233
    protected function normalizeHide($value): array
234
    {
235
        $hideKeys = array_keys($this->options('hide'));
236
        return array_filter(Cast::toArray($value),
237
            fn ($value) => in_array($value, $hideKeys)
238
        );
239
    }
240
241
    /**
242
     * @param string $value
243
     */
244
    protected function normalizeLabels($value): array
245
    {
246
        $defaults = [
247
            __('Excellent', 'site-reviews'),
248
            __('Very good', 'site-reviews'),
249
            __('Average', 'site-reviews'),
250
            __('Poor', 'site-reviews'),
251
            __('Terrible', 'site-reviews'),
252
        ];
253
        $maxRating = Rating::max();
254
        $defaults = array_pad(array_slice($defaults, 0, $maxRating), $maxRating, '');
255
        $labels = array_map('trim', explode(',', $value));
256
        foreach ($defaults as $i => $label) {
257
            if (!empty($labels[$i])) {
258
                $defaults[$i] = $labels[$i];
259
            }
260
        }
261
        return array_combine(range($maxRating, 1), $defaults);
262
    }
263
264
    protected function parseClassAttr(string $classAttr): array
265
    {
266
        $prefixes = [
267
            'is-custom-',
268
            'is-style-',
269
            'items-justified-',
270
        ];
271
        $values = array_filter(explode(' ', trim($classAttr)),
272
            fn ($val) => !empty($val)
273
        );
274
        $custom = [];
275
        $styles = [];
276
        foreach ($values as $value) {
277
            foreach ($prefixes as $prefix) {
278
                if (str_starts_with($value, $prefix)) {
279
                    $styles[] = $value;
280
                    continue 2; // Skip to next value
281
                }
282
            }
283
            $custom[] = $value;
284
        }
285
        return compact('custom', 'styles');
286
    }
287
288
    protected function stylePreset(string $classAttr): string
289
    {
290
        $values = $this->parseClassAttr($classAttr)['styles'];
291
        $styles = array_filter($values, fn ($value) => str_starts_with($value, 'is-style-'));
292
        $others = array_filter($values, fn ($value) => !str_starts_with($value, 'is-style-'));
293
        $merged = array_merge(
294
            [array_shift($styles) ?: 'is-style-default'],
295
            $others
296
        );
297
        return implode(' ', $merged);
298
    }
299
}
300