get($data['tag']); $raw = null; if ($tag) { try { $raw = $tag->render(array_merge($tag->defaults(), $data['settings']), $ctx); } catch (\Throwable $e) { $raw = null; } } $raw = apply_filters('zadex/dynamic/value', $raw, $data, $ctx); return self::coerce($raw, $data, $type); } /** * Resolves every dynamic value in a settings array (Renderer::settings()). Nested dynamic values inside * composite values (background image, repeater item fields) are resolved too. * * @param array $settings * @param array $controls control id => definition */ public static function resolve_settings(array $settings, array $controls, Context $ctx, ?Registry $registry = null): array { foreach ($settings as $key => $value) { if (!is_array($value) || !Value::contains($value)) { continue; } $base = is_string($key) ? (preg_replace('/(::[a-z_]+)?(@[a-z_]+)?$/', '', $key) ?? $key) : (string) $key; $control = $controls[$base] ?? []; $type = (string) ($control['type'] ?? 'text'); if (Value::is($value)) { $settings[$key] = self::resolve($value, $ctx, $type, $registry); } elseif ($type === 'repeater') { $fields = []; foreach ((array) ($control['fields'] ?? []) as $f) { if (is_array($f) && isset($f['id'])) { $fields[$f['id']] = $f; } } foreach ($value as $i => $item) { if (is_array($item)) { $value[$i] = self::resolve_settings($item, $fields, $ctx, $registry); } } $settings[$key] = $value; } else { $settings[$key] = self::resolve_nested($value, $ctx, $registry, ''); } } return $settings; } /** Composite values: `image`, `poster` and `images[]` hold media, anything else text. */ private static function resolve_nested(array $value, Context $ctx, ?Registry $registry, string $parent, int $depth = 0): array { if ($depth > 6) { return $value; } foreach ($value as $k => $v) { if (!is_array($v)) { continue; } $media = in_array((string) $k, ['image', 'poster'], true) || $parent === 'images'; if (Value::is($v)) { $value[$k] = self::resolve($v, $ctx, $media ? 'media' : 'text', $registry); } elseif (Value::contains($v)) { $value[$k] = self::resolve_nested($v, $ctx, $registry, (string) $k, $depth + 1); } } return $value; } /** * @param mixed $raw * @param array{tag: string, settings: array, before: string, after: string, fallback: string} $data * @return mixed */ public static function coerce($raw, array $data, string $type) { $fallback = $data['fallback']; switch ($type) { case 'media': if (is_array($raw) && isset($raw['url']) && is_string($raw['url']) && $raw['url'] !== '') { return [ 'id' => (int) ($raw['id'] ?? 0), 'url' => esc_url_raw($raw['url']), 'alt' => is_string($raw['alt'] ?? null) ? sanitize_text_field($raw['alt']) : '', ]; } $url = is_scalar($raw) ? trim((string) $raw) : ''; if ($url === '') { $url = trim($fallback); } return ['id' => 0, 'url' => $url !== '' ? esc_url_raw($url) : '', 'alt' => '']; case 'url': $url = self::to_string($raw); $url = $url !== '' ? $data['before'] . $url . $data['after'] : $fallback; return ['url' => $url !== '' ? esc_url_raw(trim($url)) : '', 'target' => '', 'nofollow' => false]; case 'number': if (is_numeric($raw)) { return $raw + 0; } return is_numeric($fallback) ? $fallback + 0 : ''; case 'color': $color = self::color(self::to_string($raw)); return $color !== '' ? $color : self::color($fallback); default: $text = self::to_string($raw); $text = $text !== '' ? $data['before'] . $text . $data['after'] : $fallback; return wp_kses_post($text); } } /** @param mixed $raw */ private static function to_string($raw): string { if (is_array($raw)) { return isset($raw['url']) && is_string($raw['url']) ? $raw['url'] : ''; } if (is_bool($raw)) { return $raw ? '1' : ''; } return is_scalar($raw) ? (string) $raw : ''; } public static function color(string $value): string { $value = trim($value); if ($value === '') { return ''; } if (preg_match('/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i', $value) || preg_match('/^(rgba?|hsla?)\([\d\s%.,\/]+\)$/i', $value) || preg_match('/^var\(--[a-z0-9\-]+\)$/i', $value) || preg_match('/^[a-z]{3,20}$/i', $value)) { return $value; } return ''; } }