*/ public static function abilities(): array { return [ new Ability( 'sarcio/sidebars-list', __('Look at the widget areas', 'sarcio'), Ability::READ, 'The theme’s widget areas (sidebars, footer columns) and the widgets in each: id, type and, for a text or block widget, its words. Block themes have none: their header and footer are template parts.', ['properties' => [], 'required' => [], 'type' => 'object'], static fn (array $args): bool => current_user_can('edit_theme_options'), static fn (array $args): array => self::list(), ), new Ability( 'sarcio/widget-text-update', __('Change the words in a widget', 'sarcio'), Ability::WRITE, 'Replace the content of a text or block widget by its id from sidebars-list (such as block-3 or text-2). Previewed first; undo puts the old content back.', ['properties' => ['content' => ['maxLength' => 20000, 'type' => 'string'], 'title' => ['maxLength' => 200, 'type' => 'string'], 'widgetId' => ['maxLength' => 60, 'minLength' => 1, 'type' => 'string']], 'required' => ['content', 'widgetId'], 'type' => 'object'], static fn (array $args): bool => current_user_can('edit_theme_options'), static fn (array $args): array => self::update($args), null, static fn (array $before, array $after): array => self::write((string) $before['widgetId'], (array) $before['instance']) + ['restored' => true], false, static fn (array $args, Overlay $overlay): array => self::stage($args, $overlay), ), ]; } /** @return array{0:string,1:int}|null option name and instance number */ private static function parse(string $widgetId): ?array { if (!preg_match('~^([a-z0-9_-]+?)-(\d+)$~', $widgetId, $m)) { return null; } return ['widget_' . $m[1], (int) $m[2]]; } private static function list(): array { global $wp_registered_sidebars; $areas = []; $placed = (array) get_option('sidebars_widgets', []); foreach ((array) $wp_registered_sidebars as $id => $sidebar) { $widgets = []; foreach ((array) ($placed[$id] ?? []) as $widgetId) { $widgets[] = self::describe((string) $widgetId); } $areas[] = ['id' => (string) $id, 'name' => (string) ($sidebar['name'] ?? $id), 'widgets' => $widgets]; } return ['areas' => $areas, 'inactive' => array_map([self::class, 'describe'], array_map('strval', (array) ($placed['wp_inactive_widgets'] ?? [])))]; } private static function describe(string $widgetId, ?Overlay $overlay = null): array { $parsed = self::parse($widgetId); $out = ['id' => $widgetId, 'type' => $parsed !== null ? substr($parsed[0], 7) : $widgetId]; if ($parsed === null) { return $out; } $instance = self::instance($widgetId, $overlay); if ($instance === null) { return $out; } if (isset($instance['content'])) { $out['content'] = (string) $instance['content']; $out['text'] = mb_substr(ContentAbilities::readable((string) $instance['content']), 0, 500); } elseif (isset($instance['text'])) { $out['content'] = (string) $instance['text']; $out['text'] = mb_substr(wp_strip_all_tags((string) $instance['text']), 0, 500); } if (isset($instance['title'])) { $out['title'] = (string) $instance['title']; } return $out; } /** @return array|null */ private static function instance(string $widgetId, ?Overlay $overlay = null): ?array { $parsed = self::parse($widgetId); if ($parsed === null) { return null; } $all = $overlay !== null ? $overlay->option($parsed[0], []) : get_option($parsed[0], []); $instance = is_array($all) ? ($all[$parsed[1]] ?? null) : null; return is_array($instance) ? $instance : null; } /** @param array $args */ private static function next(array $args, ?Overlay $overlay): array { $widgetId = (string) $args['widgetId']; $instance = self::instance($widgetId, $overlay); if ($instance === null) { return ['error' => __('There is no text or block widget with that id.', 'sarcio')]; } if (!array_key_exists('content', $instance) && !array_key_exists('text', $instance)) { return ['error' => __('That widget has no words to change; only text and block widgets do.', 'sarcio')]; } $after = $instance; $content = wp_kses_post((string) $args['content']); if (array_key_exists('content', $instance)) { $after['content'] = $content; } else { $after['text'] = $content; } if (array_key_exists('title', $args)) { $after['title'] = sanitize_text_field((string) $args['title']); } if ($after === $instance) { return ['error' => __('Nothing would change.', 'sarcio')]; } return ['after' => $after, 'before' => $instance, 'widgetId' => $widgetId]; } /** @param array $args */ private static function stage(array $args, Overlay $overlay): array { $next = self::next($args, $overlay); if (isset($next['error'])) { return $next; } [$option, $number] = self::parse($next['widgetId']); $all = $overlay->option($option, []); $all = is_array($all) ? $all : []; $all[$number] = $next['after']; $overlay->options[$option] = $all; return [ 'after' => (string) ($next['after']['content'] ?? $next['after']['text'] ?? ''), 'before' => (string) ($next['before']['content'] ?? $next['before']['text'] ?? ''), 'summary' => sprintf( /* translators: %s: the widget id. */ __('Change the words in the widget %s', 'sarcio'), $next['widgetId'], ), ]; } /** @param array $args */ private static function update(array $args): array { $next = self::next($args, null); if (isset($next['error'])) { return $next; } $written = self::write($next['widgetId'], $next['after']); if (isset($written['error'])) { return $written; } return [ 'journal' => [ 'after' => ['instance' => $next['after'], 'widgetId' => $next['widgetId']], 'before' => ['instance' => $next['before'], 'widgetId' => $next['widgetId']], 'summary' => sprintf( /* translators: %s: the widget id. */ __('Changed the words in the widget %s', 'sarcio'), $next['widgetId'], ), 'target' => 'widget:' . $next['widgetId'], ], 'widget' => self::describe($next['widgetId']), ]; } /** @param array $instance */ private static function write(string $widgetId, array $instance): array { $parsed = self::parse($widgetId); if ($parsed === null) { return ['error' => __('There is no widget with that id.', 'sarcio')]; } $all = get_option($parsed[0], []); $all = is_array($all) ? $all : []; $all[$parsed[1]] = $instance; update_option($parsed[0], $all); return ['widgetId' => $widgetId]; } }