Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 24 additions & 52 deletions includes/abilities/class-scf-field-abilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,60 +104,24 @@ private function ability_name( $action ) {
}

/**
* Gets the entity schema.
* Gets the composed field schema with oneOf variants for each field type.
*
* Uses SCF_Schema_Builder to build a schema where each field type has
* its own complete variant, allowing WordPress Abilities to validate
* type-specific properties.
*
* @since 6.8.0
*
* @return array
*/
private function get_field_schema() {
if ( null === $this->field_schema ) {
$schema_path = ACF_PATH . 'schemas/field.schema.json';
$schema_content = file_get_contents( $schema_path );
$this->field_schema = json_decode( $schema_content, true );

// Resolve $ref references.
$this->field_schema = $this->resolve_schema_refs( $this->field_schema );
$builder = acf_get_instance( 'SCF_Schema_Builder' );
$this->field_schema = $builder->compose_field_schema();
}
return $this->field_schema;
}

/**
* Recursively resolves $ref references in a JSON schema.
*
* @since 6.8.0
*
* @param array $schema The schema to resolve.
* @return array The resolved schema.
*/
private function resolve_schema_refs( array $schema ) {
// If this object has a $ref, resolve it.
if ( isset( $schema['$ref'] ) ) {
$ref = $schema['$ref'];
if ( strpos( $ref, '#/definitions/' ) === 0 ) {
$def_name = substr( $ref, 14 );
$full = $this->get_field_schema();
$definition = isset( $full['definitions'][ $def_name ] ) ? $full['definitions'][ $def_name ] : array();
if ( empty( $definition ) ) {
_doing_it_wrong( __METHOD__, 'Unresolvable $ref: ' . esc_html( $ref ), '6.8.0' );
return $schema;
}
// Merge resolved definition with any additional properties.
unset( $schema['$ref'] );
$schema = array_merge( $definition, $schema );
}
}

// Recurse into nested arrays/objects.
foreach ( $schema as $key => $value ) {
if ( is_array( $value ) ) {
$schema[ $key ] = $this->resolve_schema_refs( $value );
}
}

return $schema;
}

/**
* Gets the SCF identifier schema.
*
Expand All @@ -183,8 +147,8 @@ private function get_scf_identifier_schema() {
*/
private function get_internal_fields_schema() {
$validator = new SCF_JSON_Schema_Validator();
$schema = $validator->load_schema( 'internal-fields' );
return json_decode( wp_json_encode( $schema->definitions->fieldInternalFields ), true );
$schema = $validator->load_schema( 'internal-properties' );
return json_decode( wp_json_encode( $schema->definitions->fieldInternalProperties ), true );
}

/**
Expand All @@ -193,8 +157,9 @@ private function get_internal_fields_schema() {
* Used for output schemas of GET/LIST/CREATE/UPDATE/DUPLICATE abilities.
* Export uses get_field_schema() directly (no internal fields).
*
* The field schema uses oneOf at the top level with the actual field definition
* in definitions.field. Internal fields are merged into that definition.
* The composed field schema uses oneOf at the top level with each variant
* containing merged base + type-specific properties. Internal fields are
* merged into each variant's properties.
*
* @since 6.8.0
*
Expand All @@ -204,11 +169,18 @@ private function get_field_with_internal_fields_schema() {
$schema = $this->get_field_schema();
$internal = $this->get_internal_fields_schema();

// Merge internal fields into the field definition's properties.
$schema['definitions']['field']['properties'] = array_merge(
$schema['definitions']['field']['properties'],
$internal['properties']
);
// Merge internal fields into each oneOf variant's properties.
if ( isset( $schema['oneOf'] ) ) {
foreach ( $schema['oneOf'] as &$variant ) {
if ( isset( $variant['properties'] ) ) {
$variant['properties'] = array_merge(
$variant['properties'],
$internal['properties']
);
}
}
unset( $variant );
}

return $schema;
}
Expand Down
65 changes: 9 additions & 56 deletions includes/abilities/class-scf-internal-post-type-abilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,67 +146,20 @@ private function get_entity_schema() {
$validator = new SCF_JSON_Schema_Validator();
$schema = $validator->load_schema( $this->schema_name() );

// Convert to array for processing.
$schema_array = json_decode( wp_json_encode( $schema ), true );

// Convert hook_name to camelCase for schema definition key (post_type → postType).
$def_key = lcfirst( str_replace( ' ', '', ucwords( $this->entity_name() ) ) );
$entity = json_decode( wp_json_encode( $schema->definitions->$def_key ), true );
$definitions = json_decode( wp_json_encode( $schema->definitions ), true );
$def_key = lcfirst( str_replace( ' ', '', ucwords( $this->entity_name() ) ) );
$entity = $schema_array['definitions'][ $def_key ] ?? array();

// Resolve $ref references for WordPress Abilities API compatibility.
$this->entity_schema = $this->resolve_schema_refs( $entity, $definitions );
$builder = acf_get_instance( 'SCF_Schema_Builder' );
$this->entity_schema = $builder->resolve_refs( $entity, $schema_array );
}
return $this->entity_schema;
}

/**
* Recursively resolves $ref references in a schema.
*
* WordPress Abilities API doesn't understand JSON Schema $ref,
* so we need to inline referenced definitions.
*
* @param array $schema The schema or schema fragment to process.
* @param array $definitions All available definitions from the schema.
* @return array Schema with $ref resolved.
*/
private function resolve_schema_refs( $schema, $definitions ) {
if ( ! is_array( $schema ) ) {
return $schema;
}

// If this is a $ref, resolve it.
if ( isset( $schema['$ref'] ) ) {
$ref = $schema['$ref'];
// Extract definition name from "#/definitions/name".
if ( preg_match( '#^\#/definitions/(.+)$#', $ref, $matches ) ) {
$def_name = $matches[1];
if ( isset( $definitions[ $def_name ] ) ) {
// Recursively resolve refs in the referenced definition.
return $this->resolve_schema_refs( $definitions[ $def_name ], $definitions );
}
}

// Log warning for unresolvable $ref.
_doing_it_wrong(
__METHOD__,
esc_html(
sprintf(
/* translators: %s: The unresolvable JSON Schema $ref value */
__( 'Could not resolve schema $ref: %s', 'secure-custom-fields' ),
$ref
)
),
'6.8.0'
);
return $schema;
}

// Recursively process all array elements.
foreach ( $schema as $key => $value ) {
$schema[ $key ] = $this->resolve_schema_refs( $value, $definitions );
}

return $schema;
}

/**
* Gets the SCF identifier schema.
*
Expand All @@ -227,8 +180,8 @@ private function get_scf_identifier_schema() {
*/
private function get_internal_fields_schema() {
$validator = new SCF_JSON_Schema_Validator();
$schema = $validator->load_schema( 'internal-fields' );
return json_decode( wp_json_encode( $schema->definitions->internalFields ), true );
$schema = $validator->load_schema( 'internal-properties' );
return json_decode( wp_json_encode( $schema->definitions->internalProperties ), true );
}

/**
Expand Down
2 changes: 1 addition & 1 deletion includes/class-scf-json-schema-validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class SCF_JSON_Schema_Validator {
*
* @var array
*/
public const REQUIRED_SCHEMAS = array( 'post-type', 'taxonomy', 'ui-options-page', 'field-group', 'internal-fields', 'scf-identifier' );
public const REQUIRED_SCHEMAS = array( 'post-type', 'taxonomy', 'ui-options-page', 'field-group', 'internal-properties', 'scf-identifier' );

/**
* The last validation errors.
Expand Down
Loading
Loading