JezK
Edit File: Shortcode.php
<?php namespace GeminiLabs\SiteReviews\Shortcodes; use GeminiLabs\SiteReviews\Contracts\ShortcodeContract; use GeminiLabs\SiteReviews\Database\ShortcodeOptionManager; use GeminiLabs\SiteReviews\Defaults\DefaultsAbstract; use GeminiLabs\SiteReviews\Helper; use GeminiLabs\SiteReviews\Helpers\Arr; use GeminiLabs\SiteReviews\Helpers\Cast; use GeminiLabs\SiteReviews\Helpers\Str; use GeminiLabs\SiteReviews\Modules\Html\Builder; use GeminiLabs\SiteReviews\Modules\Multilingual; use GeminiLabs\SiteReviews\Modules\Sanitizer; use GeminiLabs\SiteReviews\Modules\Style; abstract class Shortcode implements ShortcodeContract { public array $args; public string $debug; public string $description; public string $from; public string $name; public string $tag; public function __construct() { $this->args = []; $this->debug = ''; $this->description = $this->description(); $this->from = ''; $this->name = $this->name(); $this->tag = $this->tag(); } /** * The attributes added to the unwrapped rendered root HTML element. */ public function attributes(array $values, string $from = 'function'): array { $attributes = $this->defaults()->dataAttributes($values); $attributes = wp_parse_args($attributes, [ 'class' => $this->classAttr($values['class'] ?? '', isWrapper: false), 'data-from' => ($values['from'] ?? '') ?: $from, 'data-shortcode' => $this->tag, 'id' => $values['id'] ?? '', ]); unset($attributes['data-class']); unset($attributes['data-id']); unset($attributes['data-form_id']); $attributes = glsr()->filterArray("shortcode/attributes/{$this->tag}", $attributes, $this); $attributes = glsr()->filterArray('shortcode/attributes', $attributes, $this); $attributes = array_map('esc_attr', $attributes); return $attributes; } public function build(array $args = [], string $from = 'shortcode', bool $isWrapped = true): string { $this->normalize($args, $from); $template = $this->buildTemplate(); if (empty($template)) { return ''; } $attributes = $this->attributes($this->args, $this->from); $html = glsr(Builder::class)->div($template, $attributes); $rendered = $this->debug.$html; if ($isWrapped) { return $this->wrap($rendered, $args); // pass the original $args here } return $rendered; } public function buildCallback(array $args = []): string { $this->enqueue(); glsr()->action('shortcode/enqueue', $this); return $this->build($args); } public function classAttr(string $classAttr, bool $isWrapper = false): string { $prefixes = [ 'has-', 'is-', 'items-', ]; $rootClasses = [ glsr(Style::class)->styleClasses(), ]; $wrapClasses = [ Str::dashCase("{$this->from}-{$this->tag}"), ]; foreach (array_filter(explode(' ', $classAttr)) as $class) { $isPrefixed = !empty(array_filter($prefixes, fn ($p) => str_starts_with($class, $p))); if ($isPrefixed) { $wrapClasses[] = $class; continue; } $rootClasses[] = $class; } $classes = $isWrapper ? $wrapClasses : $rootClasses; return glsr(Sanitizer::class)->sanitizeAttrClass(implode(' ', $classes)); } public function defaults(): DefaultsAbstract { $classname = str_replace('Shortcodes\\', 'Defaults\\', get_class($this)); $classname = str_replace('Shortcode', 'Defaults', $classname); return glsr($classname); } public function enqueue(): void { } public function hasVisibleFields(array $args = []): bool { if (!empty($args)) { $this->normalize($args); } $defaults = $this->options('hide'); $hide = $this->args['hide'] ?? []; $hide = array_flip(Arr::consolidate($hide)); unset($defaults['if_empty'], $hide['if_empty']); return !empty(array_diff_key($defaults, $hide)); } public function normalize(array $args, string $from = ''): ShortcodeContract { $this->args = []; $this->from = ($args['from'] ?? $from) ?: $this->from; $args = glsr()->filterArray("shortcode/args/{$this->tag}", $args, $this); $args = glsr()->filterArray('shortcode/args', $args, $this); $args = $this->defaults()->unguardedRestrict($args); foreach ($args as $key => $value) { $method = Helper::buildMethodName('normalize', $key); if (method_exists($this, $method)) { $value = call_user_func([$this, $method], $value); } $this->args[$key] = $value; } return $this; } /** * Returns the options for a shortcode setting. Results are filtered * by the "site-reviews/shortcode/options/{$options}" hook. */ public function options(string $option, array $args = []): array { $args['option'] = $option; $args['shortcode'] = $this->tag; return glsr(ShortcodeOptionManager::class)->get($option, $args); } public function register(): void { $shortcode = (new \ReflectionClass($this))->getShortName(); $shortcode = Str::snakeCase($shortcode); $shortcode = str_replace('_shortcode', '', $shortcode); add_shortcode($shortcode, [$this, 'buildCallback']); glsr()->alias($shortcode, fn () => glsr(get_class($this))); glsr()->append('shortcodes', get_class($this), $shortcode); } /** * Returns the filtered shortcode settings configuration. */ public function settings(): array { $config = $this->config(); $config = glsr()->filterArray("shortcode/config/{$this->tag}", $config, $this); $config = glsr()->filterArray('shortcode/config', $config, $this); return $config; } public function tag(): string { $shortName = (new \ReflectionClass($this))->getShortName(); return Str::snakeCase(str_replace('Shortcode', '', $shortName)); } /** * @param array $args The unmodified $args as passed to the build method */ public function wrap(string $html, array $args = []): string { return glsr(Builder::class)->div($html, $this->wrapperAttributes($args)); } /** * Returns the unfiltered shortcode settings configuration. */ abstract protected function config(): array; protected function debug(array $data = []): void { if (empty($this->args['debug']) || 'shortcode' !== $this->from) { return; } $data = wp_parse_args($data, [ 'args' => $this->args, 'shortcode' => $this->tag, ]); ksort($data); ob_start(); glsr_debug($data); $this->debug = ob_get_clean(); } protected function hideOptions(): array { return []; } /** * @param string $value */ protected function normalizeAssignedPosts($value): string { $values = Cast::toArray($value); $postTypes = []; foreach ($values as $postType) { if (!is_numeric($postType) && post_type_exists((string) $postType)) { $postTypes[] = $postType; } } $values = glsr(Sanitizer::class)->sanitizePostIds($values); $values = glsr(Multilingual::class)->getPostIdsForAllLanguages($values); $values = array_merge($values, $postTypes); return implode(',', $values); } /** * @param string $value */ protected function normalizeAssignedTerms($value): string { $values = glsr(Sanitizer::class)->sanitizeTermIds($value); $values = glsr(Multilingual::class)->getTermIdsForAllLanguages($values); return implode(',', $values); } /** * @param string $value */ protected function normalizeAssignedUsers($value): string { $values = glsr(Sanitizer::class)->sanitizeUserIds($value); return implode(',', $values); } protected function normalizeClass(string $value): string { return $this->classAttr($value, isWrapper: false); } /** * @param string|array $value */ protected function normalizeHide($value): array { $hideKeys = array_keys($this->options('hide')); return array_filter(Cast::toArray($value), fn ($value) => in_array($value, $hideKeys) ); } protected function wrapperAttributes(array $args): array { $attributes = [ 'class' => $this->classAttr($args['class'] ?? '', isWrapper: true), 'style' => $args['style'] ?? '', ]; $attributes = glsr()->filterArray('shortcode/wrap/attributes', $attributes, $args, $this); return array_filter([ 'class' => glsr(Sanitizer::class)->sanitizeAttrClass($attributes['class'] ?? ''), 'style' => glsr(Sanitizer::class)->sanitizeAttrStyle($attributes['style'] ?? ''), ]); } }