+~:\[\]="\'(),*]+$/'; private const REFUSED = ['url(', 'expression', '@import', 'javascript:', '/*', '*/', '', '@', '!']; public function register(): void { add_action('wp_enqueue_scripts', [$this, 'enqueue'], 100); } public function enqueue(): void { $css = self::css(self::all()); if ($css === '') { return; } wp_register_style(self::HANDLE, false, [], SARCIO_WP_VERSION); wp_enqueue_style(self::HANDLE); wp_add_inline_style(self::HANDLE, wp_strip_all_tags($css)); } /** @return array */ public static function all(mixed $stored = null): array { $stored ??= get_option(self::OPTION, []); $out = []; foreach (is_array($stored) ? $stored : [] as $rule) { if (!is_array($rule)) { continue; } $clean = self::check((string) ($rule['selector'] ?? ''), (string) ($rule['property'] ?? ''), (string) ($rule['value'] ?? '')); if (is_array($clean)) { $out[self::id($clean['selector'], $clean['property'])] = $clean; } } return $out; } public static function id(string $selector, string $property): string { return substr(md5($selector . '|' . $property), 0, 12); } /** * The rule as it would be stored, or why it is refused. * * @return array{selector:string,property:string,value:string}|string */ public static function check(string $selector, string $property, string $value): array|string { $selector = trim(preg_replace('~\s+~', ' ', $selector) ?? ''); $property = strtolower(trim($property)); $value = trim($value); if ($selector === '' || strlen($selector) > self::MAX_SELECTOR || !preg_match(self::SELECTOR, $selector)) { return __('That selector is not one the assistant can style.', 'sarcio'); } if (!in_array($property, self::PROPERTIES, true)) { return __('That property is not one the assistant can set.', 'sarcio'); } if ($value === '' || strlen($value) > self::MAX_VALUE) { return __('That value is empty or too long.', 'sarcio'); } if (self::refused($selector, ['url(', 'expression', 'javascript:']) || self::refused($value, self::REFUSED)) { return __('That rule carries something a style rule cannot.', 'sarcio'); } if (substr_count($selector, '(') !== substr_count($selector, ')') || substr_count($selector, '[') !== substr_count($selector, ']')) { return __('That selector is not one the assistant can style.', 'sarcio'); } $declaration = $property . ': ' . $value; if (!function_exists('safecss_filter_attr') || safecss_filter_attr($declaration) !== $declaration) { return __('WordPress does not accept that value for this property.', 'sarcio'); } return ['property' => $property, 'selector' => $selector, 'value' => $value]; } /** @param array $needles */ private static function refused(string $text, array $needles): bool { $lower = strtolower($text); foreach ($needles as $needle) { if (str_contains($lower, $needle)) { return true; } } return false; } /** @param array $rules */ public static function css(array $rules): string { $bySelector = []; foreach ($rules as $rule) { $bySelector[$rule['selector']][] = $rule['property'] . ': ' . $rule['value']; } $css = ''; foreach ($bySelector as $selector => $declarations) { $css .= $selector . ' { ' . implode('; ', $declarations) . "; }\n"; } return str_contains($css, '