array_values( $items ), 'total' => $total, 'total_pages' => (int) ceil( $total / max( 1, $per_page ) ), 'page' => $page, ]; } /** * Creates an error. * * @param string $code Error code without prefix. * @param string $message Error message. * @param int $status HTTP status. * @param array $data Additional error data. * * @return WP_Error */ public static function error( string $code, string $message, int $status = 400, array $data = [] ): WP_Error { return new WP_Error( 'seeb_abilities_' . $code, $message, array_merge( $data, [ 'status' => $status ] ) ); } /** * Creates a permission error. * * @param string $message Error message. * * @return WP_Error */ public static function forbidden( string $message = '' ): WP_Error { return self::error( 'forbidden', '' !== $message ? $message : __( 'Sorry, you are not allowed to do that.', 'seeb-abilities' ), rest_authorization_required_code() ); } /** * Creates a not found error. * * @param string $message Error message. * * @return WP_Error */ public static function not_found( string $message ): WP_Error { return self::error( 'not_found', $message, 404 ); } /** * Post types that the content abilities work with: those shown in the REST API, without * attachments, patterns, templates and other internal types. * * @return string[] */ public static function post_types(): array { return array_values( array_diff( get_post_types( [ 'show_in_rest' => true ] ), self::EXCLUDED_POST_TYPES ) ); } /** * Post types whose content the block abilities can read and edit: the content post types, * patterns and navigation menus. * * @return string[] */ public static function block_post_types(): array { return array_merge( self::post_types(), [ 'wp_block', 'wp_navigation' ] ); } /** * Post types whose revisions can be listed and restored: the block post types, customized * templates, global styles and Additional CSS. * * @return string[] */ public static function revision_post_types(): array { return array_merge( self::block_post_types(), [ 'wp_template', 'wp_template_part', 'wp_global_styles', 'custom_css' ] ); } /** * Saves the current state of a post as a revision if it has none yet. Core only stores the * state after each update, so without this the first change could not be undone. * * @param int $post_id Post ID. * * @return void */ public static function ensure_revision( int $post_id ) { if ( wp_revisions_enabled( get_post( $post_id ) ) && ! wp_get_post_revisions( $post_id, [ 'fields' => 'ids' ] ) ) { wp_save_post_revision( $post_id ); } } /** * Whether the current user may see the content of a post. Password-protected posts need the * edit_post capability, since abilities can't ask for the password. * * @param WP_Post $post Post. * * @return bool */ public static function can_read_content( WP_Post $post ): bool { return '' === $post->post_password || current_user_can( 'edit_post', $post->ID ); } /** * Returns a post of one of the given post types. * * @param int $id Post ID. * @param string[]|null $types Allowed post types. Defaults to the content post types. * * @return WP_Post|WP_Error */ public static function get_post( int $id, ?array $types = null ) { $post = $id > 0 ? get_post( $id ) : null; if ( ! $post instanceof WP_Post || ! in_array( $post->post_type, $types ?? self::post_types(), true ) ) { /* translators: %d: post ID */ return self::not_found( sprintf( __( 'No post found with ID %d.', 'seeb-abilities' ), $id ) ); } return $post; } /** * Permission check for an ability that works on one post. * * @param array $input Ability input. * @param string $cap Meta capability, e.g. "edit_post". * @param string $key Input key that holds the post ID. * @param string[]|null $types Allowed post types. * * @return true|WP_Error */ public static function check_post( array $input, string $cap, string $key = 'id', ?array $types = null ) { $post = self::get_post( self::int( $input, $key ), $types ); if ( is_wp_error( $post ) ) { return $post; } return current_user_can( $cap, $post->ID ) ? true : self::forbidden(); } /** * Formats errors and warnings of a block validation as one message. * * @param array $issues Validation errors. * * @return string */ public static function issues_message( array $issues ): string { return implode( ' ', array_map( function ( $issue ) { return sprintf( '[%s] %s', $issue['path'], $issue['message'] ); }, $issues ) ); } }