*/ public static function abilities(): array { $id = ['minimum' => 1, 'type' => 'integer']; return [ new Ability( 'sarcio/revisions-list', __('Look at a page’s earlier versions', 'sarcio'), Ability::READ, 'The saved revisions of a page, post or product, newest first: id, date, who saved it and the first words. Use a revision id with revision-restore.', ['properties' => ['id' => $id, 'limit' => ['maximum' => 50, 'minimum' => 1, 'type' => 'integer']], 'required' => ['id'], 'type' => 'object'], static fn (array $args): bool => isset($args['id']) && self::canEdit((int) $args['id']), static fn (array $args): array => self::list($args), ), new Ability( 'sarcio/styles-revisions-list', __('Look at earlier versions of the site styles', 'sarcio'), Ability::READ, 'The saved revisions of the block theme’s site styles (colours, fonts, spacing set in the Site Editor), newest first, with the id to use with revision-restore and the id of the styles themselves.', ['properties' => ['limit' => ['maximum' => 50, 'minimum' => 1, 'type' => 'integer']], 'required' => [], 'type' => 'object'], static fn (array $args): bool => current_user_can('edit_theme_options'), static fn (array $args): array => self::stylesList($args), ), new Ability( 'sarcio/revision-restore', __('Put a page back to an earlier version', 'sarcio'), Ability::WRITE, 'Restore a page, post or product to one of its saved revisions (title, content and excerpt). Made when the person confirms; undo puts back what the page held just before.', ['properties' => ['id' => $id, 'revisionId' => $id], 'required' => ['id', 'revisionId'], 'type' => 'object'], static fn (array $args): bool => isset($args['id']) && self::canEdit((int) $args['id']), static fn (array $args): array => self::restore($args), null, static fn (array $before, array $after): array => self::undo($before), ), ]; } private static function canEdit(int $id): bool { $post = get_post($id); if ($post !== null && (string) $post->post_type === 'wp_global_styles') { return current_user_can('edit_theme_options'); } return current_user_can('edit_post', $id); } public static function globalStylesId(): int { return class_exists('\WP_Theme_JSON_Resolver') && method_exists('\WP_Theme_JSON_Resolver', 'get_user_global_styles_post_id') ? (int) \WP_Theme_JSON_Resolver::get_user_global_styles_post_id() : 0; } /** @param array $args */ private static function stylesList(array $args): array { $id = self::globalStylesId(); if ($id <= 0) { return ['error' => __('This theme keeps no site styles to go back to (only a block theme does).', 'sarcio')]; } return self::list(['id' => $id, 'limit' => $args['limit'] ?? 20]) + ['stylesId' => $id]; } /** @param array $args */ private static function list(array $args): array { $post = get_post((int) $args['id']); if ($post === null) { return ['error' => __('There is no page or post with that id.', 'sarcio')]; } $revisions = wp_get_post_revisions((int) $args['id'], ['posts_per_page' => (int) ($args['limit'] ?? 20)]); $out = []; foreach (is_array($revisions) ? $revisions : [] as $revision) { $author = function_exists('get_userdata') ? get_userdata((int) $revision->post_author) : null; $out[] = [ 'author' => is_object($author) && isset($author->display_name) ? (string) $author->display_name : '', 'date' => (string) $revision->post_date, 'excerpt' => mb_substr(ContentAbilities::readable((string) $revision->post_content), 0, 200), 'id' => (int) $revision->ID, 'title' => (string) $revision->post_title, ]; } return ['current' => ['date' => (string) $post->post_modified, 'title' => (string) $post->post_title], 'revisions' => $out]; } /** @param array $args */ private static function restore(array $args): array { $post = get_post((int) $args['id']); $revision = get_post((int) $args['revisionId']); if ($post === null || $revision === null || (string) $revision->post_type !== 'revision' || (int) $revision->post_parent !== (int) $post->ID) { return ['error' => __('That revision does not belong to that page.', 'sarcio')]; } $before = ['content' => (string) $post->post_content, 'excerpt' => (string) $post->post_excerpt, 'id' => (int) $post->ID, 'title' => (string) $post->post_title]; $result = wp_restore_post_revision((int) $revision->ID); if (!$result) { return ['error' => __('WordPress could not restore that revision.', 'sarcio')]; } return [ 'journal' => [ 'after' => ['id' => (int) $post->ID, 'revisionId' => (int) $revision->ID, 'title' => (string) $revision->post_title], 'before' => $before, 'summary' => sprintf( /* translators: 1: the page title, 2: the revision date. */ __('Put “%1$s” back to its version of %2$s', 'sarcio'), (string) $post->post_title, (string) $revision->post_date, ), 'target' => 'post:' . $post->ID, ], 'restored' => (int) $revision->ID, ]; } /** @param array $before */ private static function undo(array $before): array { $id = (int) ($before['id'] ?? 0); if ($id <= 0 || get_post($id) === null) { return ['error' => __('That page no longer exists.', 'sarcio')]; } $result = wp_update_post(['ID' => $id, 'post_content' => (string) ($before['content'] ?? ''), 'post_excerpt' => (string) ($before['excerpt'] ?? ''), 'post_title' => (string) ($before['title'] ?? '')], true); return is_wp_error($result) ? ['error' => sanitize_text_field($result->get_error_message())] : ['id' => $id, 'restored' => true]; } }