*/ public static function abilities(): array { $allowed = static fn (array $args): bool => current_user_can('edit_theme_options'); $rule = [ 'properties' => [ 'property' => ['enum' => StyleRules::PROPERTIES, 'type' => 'string'], 'selector' => ['maxLength' => StyleRules::MAX_SELECTOR, 'minLength' => 1, 'type' => 'string'], 'value' => ['maxLength' => StyleRules::MAX_VALUE, 'minLength' => 1, 'type' => 'string'], ], 'required' => ['selector', 'property', 'value'], 'type' => 'object', ]; return [ new Ability( 'sarcio/style-rules-list', __('Look at the style rules', 'sarcio'), Ability::READ, 'The style rules Sarcio keeps for this site: each one a CSS selector, one property and its value, printed on every page after the theme’s styles.', ['properties' => [], 'type' => 'object'], $allowed, static fn (array $args): array => ['rules' => array_values(StyleRules::all(Stage::active() ? Stage::overlay()->option(StyleRules::OPTION, []) : null))], ), new Ability( 'sarcio/style-rule-set', __('Set a style rule', 'sarcio'), Ability::WRITE, 'Set one property for elements matching a CSS selector (e.g. selector ".site-header .button", property "background-color", value "#1f6f4a"). One property per rule; setting the same selector and property again replaces the value. Values are plain CSS values: no url(), no !important, no semicolons or braces. Prefer the site styles for colours, fonts and spacing that apply site-wide; use a rule for one element the site styles cannot reach.', $rule, $allowed, static fn (array $args): array => self::write(self::withRule(StyleRules::all(), $args), $args), null, static fn (array $before, array $after): array => self::restore($before), false, static fn (array $args, Overlay $overlay): array => self::stage(self::withRule(StyleRules::all($overlay->option(StyleRules::OPTION, [])), $args), $overlay, $args), ), new Ability( 'sarcio/style-rule-remove', __('Remove a style rule', 'sarcio'), Ability::WRITE, 'Remove the style rule for a selector and property, as style-rules-list shows them.', [ 'properties' => [ 'property' => ['maxLength' => 40, 'minLength' => 1, 'type' => 'string'], 'selector' => ['maxLength' => StyleRules::MAX_SELECTOR, 'minLength' => 1, 'type' => 'string'], ], 'required' => ['selector', 'property'], 'type' => 'object', ], $allowed, static fn (array $args): array => self::write(self::withoutRule(StyleRules::all(), $args), $args), null, static fn (array $before, array $after): array => self::restore($before), false, static fn (array $args, Overlay $overlay): array => self::stage(self::withoutRule(StyleRules::all($overlay->option(StyleRules::OPTION, [])), $args), $overlay, $args), ), ]; } /** * @param array $rules * @param array $args * @return array{rules:array,before:array}|array{error:string} */ private static function withRule(array $rules, array $args): array { $checked = StyleRules::check((string) ($args['selector'] ?? ''), (string) ($args['property'] ?? ''), (string) ($args['value'] ?? '')); if (is_string($checked)) { return ['error' => $checked]; } $before = $rules; $id = StyleRules::id($checked['selector'], $checked['property']); if (!isset($rules[$id]) && count($rules) >= StyleRules::MAX_RULES) { return ['error' => __('The site has as many style rules as it can keep. Remove one first.', 'sarcio')]; } $rules[$id] = $checked; return ['before' => $before, 'rules' => $rules]; } /** * @param array $rules * @param array $args */ private static function withoutRule(array $rules, array $args): array { $selector = trim(preg_replace('~\s+~', ' ', (string) ($args['selector'] ?? '')) ?? ''); $id = StyleRules::id($selector, strtolower(trim((string) ($args['property'] ?? '')))); if (!isset($rules[$id])) { return ['error' => __('There is no style rule for that selector and property.', 'sarcio')]; } $before = $rules; unset($rules[$id]); return ['before' => $before, 'rules' => $rules]; } /** @param array $change @param array $args */ private static function write(array $change, array $args): array { if (isset($change['error'])) { return ['error' => $change['error']]; } if ($change['rules'] === $change['before']) { return ['error' => __('Nothing would change.', 'sarcio')]; } update_option(StyleRules::OPTION, array_values($change['rules']), true); return [ 'journal' => [ 'after' => ['rules' => array_values($change['rules'])], 'before' => ['rules' => array_values($change['before'])], 'summary' => self::summary($change, $args), 'target' => 'style_rules', ], 'saved' => true, ]; } /** @param array $change @param array $args */ private static function stage(array $change, Overlay $overlay, array $args): array { if (isset($change['error'])) { return ['error' => $change['error']]; } if ($change['rules'] === $change['before']) { return ['error' => __('Nothing would change.', 'sarcio')]; } $overlay->options[StyleRules::OPTION] = array_values($change['rules']); return [ 'after' => StyleRules::css($change['rules']), 'before' => StyleRules::css($change['before']), 'summary' => self::summary($change, $args), ]; } /** @param array $before */ private static function restore(array $before): array { update_option(StyleRules::OPTION, array_values(StyleRules::all($before['rules'] ?? [])), true); return ['restored' => true]; } /** @param array $change @param array $args */ private static function summary(array $change, array $args): string { $selector = (string) ($args['selector'] ?? ''); $property = (string) ($args['property'] ?? ''); return count($change['rules']) < count($change['before']) /* translators: 1: a CSS property, 2: a CSS selector. */ ? sprintf(__('Removed the %1$s style rule for %2$s', 'sarcio'), $property, $selector) /* translators: 1: a CSS property, 2: a CSS selector, 3: the value. */ : sprintf(__('Set %1$s on %2$s to %3$s', 'sarcio'), $property, $selector, (string) ($args['value'] ?? '')); } }