feat: initial ACRIB WordPress deployment

- WordPress 6.9.4 (es_ES) with Kadence theme
- Homepage: Hero, La Asociación, Pilares, Beneficios, Eventos, Miembros, Hazte Miembro, Contacto
- Brand identity: #13294b navy, #a12932 burgundy, #c69c48 gold
- Fonts: Raleway (headings) + Source Sans 3 (body) + Lato (UI)
- Plugins: Kadence Blocks, Polylang, Contact Form 7
- Custom CSS with full brand styling and responsive layout
- HTTPS enforced via wp-config.php proxy detection
This commit is contained in:
Malin
2026-05-19 19:25:59 +02:00
commit f3ff7b7186
6119 changed files with 1984255 additions and 0 deletions

View File

@@ -0,0 +1,687 @@
<?php declare( strict_types=1 );
namespace KadenceWP\KadenceBlocks\Optimizer\Rest;
use KadenceWP\KadenceBlocks\Optimizer\Path\Path;
use KadenceWP\KadenceBlocks\Optimizer\Response\WebsiteAnalysis;
use KadenceWP\KadenceBlocks\Optimizer\Status\Status;
use KadenceWP\KadenceBlocks\Optimizer\Store\Contracts\Store;
use KadenceWP\KadenceBlocks\Traits\Permalink_Trait;
use WP_Error;
use WP_Http;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
/**
* REST API controller for managing Kadence Blocks optimizer data.
*
* This controller provides endpoints for storing, retrieving, and deleting
* performance optimization analysis data for posts. It handles website analysis
* results that can be used to optimize page performance.
*
* Endpoints:
* - POST /kb-optimizer/v1/optimize - Store optimizer data for a post
* - GET /kb-optimizer/v1/optimize - Retrieve optimizer data for a post
* - DELETE /kb-optimizer/v1/optimize - Delete optimizer data for a post
* - POST /kb-optimizer/v1/optimize/posts-metadata - Get metadata for multiple posts
* - DELETE /kb-optimizer/v1/optimize/bulk/delete - Delete optimizer data for multiple posts
*
* All endpoints require the user to have edit permissions for the specified post.
*/
final class Optimize_Rest_Controller extends WP_REST_Controller {
use Permalink_Trait;
public const ROUTE = '/kb-optimizer/v1/optimize';
public const POST_PATH = 'post_path';
public const POST_ID = 'post_id';
public const POST_IDS = 'post_ids';
public const POST_PATHS = 'post_paths';
public const RESULTS = 'results';
private Store $store;
private Schema $schema_loader;
private Status $status;
public function __construct(
Store $store,
Schema $schema_loader,
Status $status
) {
$this->namespace = 'kb-optimizer/v1';
$this->rest_base = 'optimize';
$this->store = $store;
$this->schema_loader = $schema_loader;
$this->status = $status;
}
public function register_routes(): void {
register_rest_route(
$this->namespace,
"/$this->rest_base",
[
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'create_item' ],
'permission_callback' => [ $this, 'create_item_permissions_check' ],
'args' => $this->get_create_params(),
],
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_item' ],
'permission_callback' => [ $this, 'get_item_permissions_check' ],
'args' => $this->get_default_params(),
],
[
'methods' => WP_REST_Server::DELETABLE,
'callback' => [ $this, 'delete_item' ],
'permission_callback' => [ $this, 'delete_item_permissions_check' ],
'args' => $this->get_default_params(),
],
'schema' => [ $this->schema_loader, 'get_item_schema' ],
]
);
register_rest_route(
$this->namespace,
"/$this->rest_base/posts-metadata",
[
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'get_posts_metadata' ],
'permission_callback' => [ $this, 'get_posts_metadata_permissions_check' ],
'args' => $this->get_posts_metadata_params(),
],
]
);
register_rest_route(
$this->namespace,
"/$this->rest_base/bulk/delete",
[
[
'methods' => WP_REST_Server::DELETABLE,
'callback' => [ $this, 'bulk_delete_items' ],
'permission_callback' => [ $this, 'bulk_delete_items_permissions_check' ],
'args' => $this->get_bulk_delete_params(),
],
]
);
}
/**
* Save optimizer data for a post.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
$post_id = $request->get_param( self::POST_ID );
$post_path = $request->get_param( self::POST_PATH );
$results = $request->get_param( self::RESULTS );
$analysis = WebsiteAnalysis::from( $results );
if ( $this->status->is_excluded( $post_id ) ) {
return new WP_Error(
'rest_kb_optimizer_create_failed_excluded',
__( 'This post is excluded from optimization.', 'kadence-blocks' ),
[
'status' => WP_Http::INTERNAL_SERVER_ERROR,
'post_id' => $post_id,
'post_path' => $post_path,
]
);
}
if ( get_post_status( $post_id ) !== 'publish' ) {
return new WP_Error(
'rest_kb_optimizer_create_failed_not_published',
__( 'The post must be published to be optimized.', 'kadence-blocks' ),
[
'status' => WP_Http::INTERNAL_SERVER_ERROR,
'post_id' => $post_id,
'post_path' => $post_path,
]
);
}
$path = new Path( $post_path, $post_id );
if ( $this->store->set( $path, $analysis ) ) {
$ref = wp_get_referer();
// If we're coming from the edit post page, don't double update the post.
if ( $ref && ! str_contains( $ref, 'post.php' ) ) {
$this->update_post( $post_id );
}
return new WP_REST_Response(
[
'data' => [
'success' => true,
'post_id' => $post_id,
'post_path' => $post_path,
],
],
WP_Http::CREATED
);
}
return new WP_Error(
'rest_kb_optimizer_create_failed',
__( 'Failed to store optimizer data.', 'kadence-blocks' ),
[
'status' => WP_Http::INTERNAL_SERVER_ERROR,
'post_id' => $post_id,
'post_path' => $post_path,
]
);
}
/**
* Get optimizer data for a post.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
$post_path = $request->get_param( self::POST_PATH );
$post_id = $request->get_param( self::POST_ID );
$path = new Path( $post_path, $post_id );
$analysis = $this->store->get( $path );
if ( ! $analysis ) {
return new WP_Error(
'rest_kb_optimizer_read_not_found',
__( 'Sorry, we couldn\'t find optimizer data.', 'kadence-blocks' ),
[
'status' => WP_Http::NOT_FOUND,
'post_path' => $post_path,
]
);
}
return new WP_REST_Response(
[
'data' => $analysis->toArray(),
],
WP_Http::OK
);
}
/**
* Delete optimizer data for a post.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function delete_item( $request ) {
$post_id = $request->get_param( self::POST_ID );
$post_path = $request->get_param( self::POST_PATH );
// Include post_id in Path for reliable status synchronization.
$path = new Path( $post_path, $post_id );
if ( $this->store->delete( $path ) ) {
// Run wp_update post to signal to caching plugins to update their cache.
$this->update_post( $post_id );
return new WP_REST_Response(
[
'data' => [
'success' => true,
'post_id' => $post_id,
'post_path' => $post_path,
],
],
WP_Http::OK
);
}
return new WP_Error(
'rest_kb_optimizer_delete_failed',
__( 'Failed to delete optimizer data.', 'kadence-blocks' ),
[
'status' => WP_Http::INTERNAL_SERVER_ERROR,
'post_id' => $post_id,
'post_path' => $post_path,
]
);
}
/**
* Get metadata for multiple posts to prepare for bulk optimization.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response
*/
public function get_posts_metadata( WP_REST_Request $request ): WP_REST_Response {
$post_ids = $request->get_param( self::POST_IDS );
$posts_data = [];
$errors = [];
foreach ( $post_ids as $post_id ) {
$post = get_post( $post_id );
if ( ! $post ) {
$errors[] = [
'post_id' => $post_id,
'message' => __( 'Post not found.', 'kadence-blocks' ),
];
continue;
}
$post_url = get_permalink( $post_id );
$post_path = $this->get_post_path( $post_id );
// Skip posts without pretty permalinks.
if ( ! $post_url || empty( $post_path ) || str_contains( $post_path, '?' ) ) {
$errors[] = [
'post_id' => $post_id,
'message' => __( 'Post has no rewrite rules or is not public.', 'kadence-blocks' ),
];
continue;
}
$posts_data[] = [
'post_id' => $post_id,
'url' => $post_url,
'post_path' => $post_path,
];
}
return new WP_REST_Response(
[
'data' => $posts_data,
'errors' => $errors,
],
WP_Http::OK
);
}
/**
* Delete optimizer data for multiple posts at once.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response
*/
public function bulk_delete_items( WP_REST_Request $request ): WP_REST_Response {
$post_ids = $request->get_param( self::POST_IDS );
$post_paths = $request->get_param( self::POST_PATHS );
$results = [
'successful' => [],
'failed' => [],
];
foreach ( $post_ids as $index => $post_id ) {
$post_path = $post_paths[ $index ] ?? null;
if ( ! $post_path ) {
$results['failed'][] = [
'post_id' => $post_id,
'message' => __( 'Missing post path.', 'kadence-blocks' ),
];
continue;
}
$path = new Path( $post_path, $post_id );
// Check if optimization data exists.
if ( ! $this->store->has( $path ) ) {
// No data to delete - consider this a success.
$this->store->delete( $path );
$results['successful'][] = $post_id;
continue;
}
if ( $this->store->delete( $path ) ) {
// Run wp_update_post to signal to caching plugins to update their cache.
$this->update_post( $post_id );
$results['successful'][] = $post_id;
} else {
$results['failed'][] = [
'post_id' => $post_id,
'message' => __( 'Failed to delete optimizer data.', 'kadence-blocks' ),
];
}
}
return new WP_REST_Response(
[
'data' => $results,
],
WP_Http::OK
);
}
/**
* Checks if a given request has access to create items.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return true|WP_Error True if the request has access to create items, WP_Error object
* otherwise.
*/
public function create_item_permissions_check( $request ) {
$post_id = $request->get_param( self::POST_ID );
if ( $post_id && current_user_can( 'edit_post', $post_id ) ) {
return true;
}
$post = get_post( $post_id );
if ( ! $post ) {
return new WP_Error(
'rest_kb_optimizer_post_does_not_exist',
__( 'Sorry, we could not find that post_id.', 'kadence-blocks' ),
[
'status' => WP_Http::NOT_FOUND,
'post_id' => $post_id,
]
);
}
return new WP_Error(
'rest_kb_optimizer_create_forbidden',
__( 'Sorry, you are not allowed to store optimizer data.', 'kadence-blocks' ),
[
'status' => rest_authorization_required_code(),
'post_id' => $post_id,
]
);
}
/**
* Checks if a given request has access to get a specific item.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function get_item_permissions_check( $request ) {
$post_id = $request->get_param( self::POST_ID );
if ( $post_id && current_user_can( 'edit_post', $post_id ) ) {
return true;
}
$post = get_post( $post_id );
if ( ! $post ) {
return new WP_Error(
'rest_kb_optimizer_post_does_not_exist',
__( 'Sorry, we could not find that post_id.', 'kadence-blocks' ),
[
'status' => WP_Http::NOT_FOUND,
'post_id' => $post_id,
]
);
}
return new WP_Error(
'rest_kb_optimizer_read_forbidden',
__( 'Sorry, you are not allowed to access optimizer data.', 'kadence-blocks' ),
[
'status' => rest_authorization_required_code(),
'post_id' => $post_id,
]
);
}
/**
* Checks if a given request has access to delete optimization data.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return true|WP_Error True if the request has access to delete the item, WP_Error object
* otherwise.
*/
public function delete_item_permissions_check( $request ) {
$post_id = $request->get_param( self::POST_ID );
if ( $post_id && current_user_can( 'delete_post', $post_id ) ) {
return true;
}
$post = get_post( $post_id );
if ( ! $post ) {
return new WP_Error(
'rest_kb_optimizer_post_does_not_exist',
__( 'Sorry, we could not find that post_id.', 'kadence-blocks' ),
[
'status' => WP_Http::NOT_FOUND,
'post_id' => $post_id,
]
);
}
return new WP_Error(
'rest_kb_optimizer_delete_forbidden',
__( 'Sorry, you do not have permission to delete data for this post.', 'kadence-blocks' ),
[
'status' => rest_authorization_required_code(),
'post_id' => $post_id,
]
);
}
/**
* Checks if a given request has access to get posts metadata.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return true|WP_Error True if the request has access, WP_Error object otherwise.
*/
public function get_posts_metadata_permissions_check( $request ) {
$post_ids = $request->get_param( self::POST_IDS );
// Admins and editors can edit all posts - skip individual checks.
if ( current_user_can( 'edit_others_posts' ) ) {
return true;
}
// For contributors/authors, verify they can edit each post.
foreach ( $post_ids as $post_id ) {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return new WP_Error(
'rest_kb_optimizer_bulk_forbidden',
__( 'Sorry, you are not allowed to access optimizer data for all selected posts.', 'kadence-blocks' ),
[
'status' => rest_authorization_required_code(),
'post_id' => $post_id,
]
);
}
}
return true;
}
/**
* Checks if a given request has access to bulk delete optimization data.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return true|WP_Error True if the request has access, WP_Error object otherwise.
*/
public function bulk_delete_items_permissions_check( WP_REST_Request $request ) {
$post_ids = $request->get_param( self::POST_IDS );
// Admins and editors can delete all posts - skip individual checks.
if ( current_user_can( 'edit_others_posts' ) ) {
return true;
}
// For contributors/authors, verify they can delete each post.
foreach ( $post_ids as $post_id ) {
if ( ! current_user_can( 'delete_post', $post_id ) ) {
return new WP_Error(
'rest_kb_optimizer_bulk_delete_forbidden',
__( 'Sorry, you are not allowed to delete optimizer data for all selected posts.', 'kadence-blocks' ),
[
'status' => rest_authorization_required_code(),
'post_id' => $post_id,
]
);
}
}
return true;
}
/**
* Get the default arguments for write methods (POST, DELETE).
*
* Requires both post_path and post_id for reliable status synchronization.
*
* @return array<string, array{
* type: string,
* description: string,
* minimum: positive-int,
* required: true,
* sanitize_callback: callable,
* }>
*/
private function get_default_params(): array {
return [
self::POST_PATH => [
'type' => 'string',
'description' => __( 'The relative post path', 'kadence-blocks' ),
'required' => true,
'sanitize_callback' => 'sanitize_text_field',
],
self::POST_ID => [
'type' => 'integer',
'description' => __( 'The post ID associated with the optimization data.', 'kadence-blocks' ),
'minimum' => 1,
'required' => true,
'sanitize_callback' => static fn( $value ) => (int) $value,
],
];
}
/**
* Get the arguments for the create method.
*
* @return array<string, array{
* type: string,
* description: string,
* minimum: positive-int,
* required: true,
* sanitize_callback: callable,
* properties: array<string, mixed>
* }>
*/
private function get_create_params(): array {
$params = $this->get_default_params();
$params[ self::RESULTS ] = [
'type' => 'object',
'properties' => rest_get_endpoint_args_for_schema( $this->schema_loader->get_item_schema() ),
];
return $params;
}
/**
* Get the arguments for the posts metadata endpoint.
*
* @return array<string, array{
* type: string,
* description: string,
* required: true,
* items: array<string, mixed>,
* sanitize_callback: callable
* }>
*/
private function get_posts_metadata_params(): array {
return [
self::POST_IDS => [
'type' => 'array',
'description' => __( 'Array of post IDs to get metadata for.', 'kadence-blocks' ),
'required' => true,
'items' => [
'type' => 'integer',
'minimum' => 1,
],
'sanitize_callback' => static fn( $value ) => array_map( 'intval', $value ),
],
];
}
/**
* Get the arguments for the bulk delete endpoint.
*
* @return array<string, array{
* type: string,
* description: string,
* required: true,
* items: array<string, mixed>,
* sanitize_callback: callable
* }>
*/
private function get_bulk_delete_params(): array {
return [
self::POST_IDS => [
'type' => 'array',
'description' => __( 'Array of post IDs to delete optimization data for.', 'kadence-blocks' ),
'required' => true,
'items' => [
'type' => 'integer',
'minimum' => 1,
],
'sanitize_callback' => static fn( $value ) => array_map( 'intval', $value ),
],
self::POST_PATHS => [
'type' => 'array',
'description' => __( 'Array of post paths corresponding to the post IDs.', 'kadence-blocks' ),
'required' => true,
'items' => [
'type' => 'string',
],
'sanitize_callback' => static fn( $value ) => array_map( 'sanitize_text_field', $value ),
],
];
}
/**
* Update a post to hopefully force caching plugins to refresh their cache.
*
* @param int $post_id The post ID.
*
* @return int|WP_Error
*/
private function update_post( int $post_id ) {
$post = get_post( $post_id );
if ( ! $post ) {
return new WP_Error(
'rest_kb_optimizer_update_post_failed',
__( 'Unable to find post to update', 'kadence-blocks' ),
[
'status' => WP_Http::INTERNAL_SERVER_ERROR,
'post_id' => $post_id,
]
);
}
return wp_update_post( $post );
}
}

View File

@@ -0,0 +1,18 @@
<?php declare( strict_types=1 );
namespace KadenceWP\KadenceBlocks\Optimizer\Rest;
use KadenceWP\KadenceBlocks\Optimizer\Rest\Optimize_Rest_Controller;
use KadenceWP\KadenceBlocks\StellarWP\ProphecyMonorepo\Container\Contracts\Provider as Provider_Contract;
final class Provider extends Provider_Contract {
public function register(): void {
add_action(
'rest_api_init',
function (): void {
$this->container->get( Optimize_Rest_Controller::class )->register_routes();
}
);
}
}

View File

@@ -0,0 +1,233 @@
<?php declare( strict_types=1 );
namespace KadenceWP\KadenceBlocks\Optimizer\Rest;
/**
* Schema definition class for the Performance Optimizer REST API endpoints.
*/
final class Schema {
/**
* Get the parameter schema definitions for the REST API response.
*
* Defines the structure and validation rules for desktop/mobile viewport data,
* including sections, images, and performance metrics used by the optimizer.
*
* @return array Schema parameter definitions with type validation and descriptions.
*/
public function get_params(): array {
// Definition for the 'section' object, reused for desktop and mobile.
$section_properties = [
'id' => [
'description' => __( 'The ID attribute of the section element.', 'kadence-blocks' ),
'type' => 'string',
],
'height' => [
'description' => __( 'The height of the section in pixels.', 'kadence-blocks' ),
'type' => 'number',
],
'tagName' => [
'description' => __( 'The HTML tag name of the section element.', 'kadence-blocks' ),
'type' => 'string',
],
'className' => [
'description' => __( 'The CSS classes assigned to this section.', 'kadence-blocks' ),
'type' => 'string',
],
'path' => [
'description' => __( 'The CSS selector path to the element.', 'kadence-blocks' ),
'type' => 'string',
],
'isAboveFold' => [
'description' => __( 'Whether the section is located above the fold.', 'kadence-blocks' ),
'type' => 'boolean',
],
'hasImages' => [
'description' => __( 'Whether the section contains any image elements.', 'kadence-blocks' ),
'type' => 'boolean',
],
'hasBackground' => [
'description' => __( 'Whether the section has a background image.', 'kadence-blocks' ),
'type' => 'boolean',
],
];
// Definition for the 'image' object.
$image_properties = [
'path' => [
'description' => __( 'The CSS selector path to the image element.', 'kadence-blocks' ),
'type' => 'string',
'required' => true,
],
'src' => [
'description' => __( 'The source URL of the image.', 'kadence-blocks' ),
'type' => 'string',
'format' => 'uri',
'required' => true,
],
'srcset' => [
'description' => __( 'An array of sources for different resolutions.', 'kadence-blocks' ),
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'url' => [
'type' => 'string',
'format' => 'uri',
'required' => true,
],
'width' => [
'type' => 'integer',
'required' => true,
],
],
],
],
'width' => [
'description' => __( 'The rendered width of the image in pixels.', 'kadence-blocks' ),
'type' => 'integer',
'required' => true,
],
'height' => [
'description' => __( 'The rendered height of the image in pixels.', 'kadence-blocks' ),
'type' => 'integer',
'required' => true,
],
'widthAttr' => [
'description' => __( 'The img element width attribute value.', 'kadence-blocks' ),
'type' => 'string',
'required' => false,
],
'heightAttr' => [
'description' => __( 'The img element height attribute value.', 'kadence-blocks' ),
'type' => 'string',
'required' => false,
],
'naturalWidth' => [
'description' => __( 'The intrinsic width of the image file in pixels.', 'kadence-blocks' ),
'type' => 'integer',
],
'naturalHeight' => [
'description' => __( 'The intrinsic height of the image file in pixels.', 'kadence-blocks' ),
'type' => 'integer',
],
'aspectRatio' => [
'description' => __( 'The aspect ratio of the image.', 'kadence-blocks' ),
'type' => 'number',
],
'alt' => [
'description' => __( 'The alt text of the image.', 'kadence-blocks' ),
'type' => [ 'string', 'null' ],
],
'class' => [
'description' => __( 'The class attribute of the image element.', 'kadence-blocks' ),
'type' => [ 'string', 'null' ],
],
'loading' => [
'description' => __( 'The loading attribute of the image (e.g., \'lazy\', \'eager\', \'auto\').', 'kadence-blocks' ),
'type' => 'string',
],
'decoding' => [
'description' => __( 'The decoding hint for the image (e.g., \'async\', \'sync\', \'auto\').', 'kadence-blocks' ),
'type' => 'string',
],
'sizes' => [
'description' => __( 'The sizes attribute of the image.', 'kadence-blocks' ),
'type' => [ 'string', 'null' ],
],
'computedStyle' => [
'description' => __( 'The computed CSS styles of the image element.', 'kadence-blocks' ),
'type' => 'object',
'properties' => [
'width' => [ 'type' => 'string' ],
'height' => [ 'type' => 'string' ],
'objectFit' => [ 'type' => 'string' ],
'objectPosition' => [ 'type' => 'string' ],
],
],
'optimalSizes' => [
'description' => __( 'A calculated optimal sizes string.', 'kadence-blocks' ),
'type' => 'string',
],
];
// Definition for the viewport-specific data (desktop/mobile).
$viewport_data_properties = [
'criticalImages' => [
'description' => __( 'A list of above the fold image URLs.', 'kadence-blocks' ),
'type' => 'array',
'items' => [
'type' => 'string',
'format' => 'uri',
],
'required' => true,
],
'backgroundImages' => [
'description' => __( 'A list of URLs for background images.', 'kadence-blocks' ),
'type' => 'array',
'items' => [
'type' => 'string',
'format' => 'uri',
],
'required' => true,
],
'sections' => [
'description' => __( 'An array of layout sections.', 'kadence-blocks' ),
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => $section_properties,
],
'required' => true,
],
];
// The main schema structure.
return [
'isStale' => [
'description' => __( 'Whether this data is stale or expired.', 'kadence-blocks' ),
'type' => 'boolean',
'required' => false,
'default' => false,
],
'desktop' => [
'description' => __( 'Performance and layout data for the desktop viewport.', 'kadence-blocks' ),
'type' => 'object',
'properties' => $viewport_data_properties,
'required' => true,
],
'mobile' => [
'description' => __( 'Performance and layout data for the mobile viewport.', 'kadence-blocks' ),
'type' => 'object',
'properties' => $viewport_data_properties,
'required' => true,
],
'images' => [
'description' => __( 'An array of all image objects found on the page.', 'kadence-blocks' ),
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => $image_properties,
],
'required' => true,
],
];
}
/**
* Get the complete JSON Schema item definition.
*
* Returns a full JSON Schema (draft-07) compliant structure that can be used
* for REST API response validation and documentation generation.
*
* @return array Complete JSON Schema definition with metadata and property specifications.
*/
public function get_item_schema(): array {
return [
'$schema' => 'http://json-schema.org/draft-07/schema#',
'title' => __( 'Page Analysis Data', 'kadence-blocks' ),
'type' => 'object',
'properties' => $this->get_params(),
];
}
}