Skip to content

Fix array schema retrieval for annex repeaters#38

Merged
erseco merged 2 commits intomainfrom
feature/add-array-fields-for-annexes-support-b76qhb
Oct 14, 2025
Merged

Fix array schema retrieval for annex repeaters#38
erseco merged 2 commits intomainfrom
feature/add-array-fields-for-annexes-support-b76qhb

Conversation

@erseco
Copy link
Copy Markdown
Collaborator

@erseco erseco commented Oct 14, 2025

Summary

  • preserve stored repeatable array definitions when loading a document type schema
  • add a helper to humanize labels for sanitized schema entries and propagate array item metadata
  • cover the array schema retrieval path with a dedicated unit test

Testing

  • ./vendor/bin/phpcs --standard=.phpcs.xml.dist includes/custom-post-types/class-resolate-documents.php tests/unit/includes/custom-post-types/ResolateDocumentsArrayFieldsTest.php (fails: ./vendor/bin/phpcs not found in container)

https://chatgpt.com/codex/tasks/task_e_68ee30cf252083229149dd9f656091e1

@erseco erseco merged commit 43c80f2 into main Oct 14, 2025
1 of 2 checks passed
Comment on lines +291 to +372
private function build_schema_from_fields( $fields ) {
$raw_schema = Resolate_Template_Parser::build_schema_from_field_definitions( $fields );
if ( ! is_array( $raw_schema ) ) {
return array();
}

$schema = array();
foreach ( $raw_schema as $entry ) {
if ( ! is_array( $entry ) || empty( $entry['slug'] ) ) {
continue;
}

$slug = sanitize_key( $entry['slug'] );
$label = isset( $entry['label'] ) ? sanitize_text_field( $entry['label'] ) : $this->humanize_slug( $slug );
$type = isset( $entry['type'] ) ? sanitize_key( $entry['type'] ) : 'textarea';
$placeholder = isset( $entry['placeholder'] ) ? $this->sanitize_placeholder_name( $entry['placeholder'] ) : $slug;
$data_type = isset( $entry['data_type'] ) ? sanitize_key( $entry['data_type'] ) : 'text';

if ( '' === $slug ) {
continue;
}
if ( '' === $label ) {
$label = $this->humanize_slug( $slug );
}
if ( '' === $placeholder ) {
$placeholder = $slug;
}

if ( 'array' === $type ) {
$item_schema = array();
if ( isset( $entry['item_schema'] ) && is_array( $entry['item_schema'] ) ) {
foreach ( $entry['item_schema'] as $key => $item ) {
$item_key = sanitize_key( $key );
if ( '' === $item_key ) {
continue;
}
$item_label = isset( $item['label'] ) ? sanitize_text_field( $item['label'] ) : $this->humanize_slug( $item_key );
$item_type = isset( $item['type'] ) ? sanitize_key( $item['type'] ) : 'textarea';
if ( ! in_array( $item_type, array( 'single', 'textarea', 'rich' ), true ) ) {
$item_type = 'textarea';
}
$item_data_type = isset( $item['data_type'] ) ? sanitize_key( $item['data_type'] ) : 'text';
if ( ! in_array( $item_data_type, array( 'text', 'number', 'boolean', 'date' ), true ) ) {
$item_data_type = 'text';
}
$item_schema[ $item_key ] = array(
'label' => $item_label,
'type' => $item_type,
'data_type' => $item_data_type,
);
}
}

$schema[] = array(
'slug' => $slug,
'label' => $label,
'type' => 'array',
'placeholder' => $placeholder,
'data_type' => 'array',
'item_schema' => $item_schema,
);
continue;
}

if ( ! in_array( $type, array( 'single', 'textarea', 'rich' ), true ) ) {
$type = 'textarea';
}
if ( ! in_array( $data_type, array( 'text', 'number', 'boolean', 'date' ), true ) ) {
$data_type = 'text';
}

$schema[] = array(
'slug' => $slug,
'label' => $label,
'type' => $type,
'placeholder' => $placeholder,
'data_type' => $data_type,
);
}

return $schema;
}

Check warning

Code scanning / PHPMD

Code Size Rules: CyclomaticComplexity Warning

The method build_schema_from_fields() has a Cyclomatic Complexity of 24. The configured cyclomatic complexity threshold is 10.
Comment on lines +291 to +372
private function build_schema_from_fields( $fields ) {
$raw_schema = Resolate_Template_Parser::build_schema_from_field_definitions( $fields );
if ( ! is_array( $raw_schema ) ) {
return array();
}

$schema = array();
foreach ( $raw_schema as $entry ) {
if ( ! is_array( $entry ) || empty( $entry['slug'] ) ) {
continue;
}

$slug = sanitize_key( $entry['slug'] );
$label = isset( $entry['label'] ) ? sanitize_text_field( $entry['label'] ) : $this->humanize_slug( $slug );
$type = isset( $entry['type'] ) ? sanitize_key( $entry['type'] ) : 'textarea';
$placeholder = isset( $entry['placeholder'] ) ? $this->sanitize_placeholder_name( $entry['placeholder'] ) : $slug;
$data_type = isset( $entry['data_type'] ) ? sanitize_key( $entry['data_type'] ) : 'text';

if ( '' === $slug ) {
continue;
}
if ( '' === $label ) {
$label = $this->humanize_slug( $slug );
}
if ( '' === $placeholder ) {
$placeholder = $slug;
}

if ( 'array' === $type ) {
$item_schema = array();
if ( isset( $entry['item_schema'] ) && is_array( $entry['item_schema'] ) ) {
foreach ( $entry['item_schema'] as $key => $item ) {
$item_key = sanitize_key( $key );
if ( '' === $item_key ) {
continue;
}
$item_label = isset( $item['label'] ) ? sanitize_text_field( $item['label'] ) : $this->humanize_slug( $item_key );
$item_type = isset( $item['type'] ) ? sanitize_key( $item['type'] ) : 'textarea';
if ( ! in_array( $item_type, array( 'single', 'textarea', 'rich' ), true ) ) {
$item_type = 'textarea';
}
$item_data_type = isset( $item['data_type'] ) ? sanitize_key( $item['data_type'] ) : 'text';
if ( ! in_array( $item_data_type, array( 'text', 'number', 'boolean', 'date' ), true ) ) {
$item_data_type = 'text';
}
$item_schema[ $item_key ] = array(
'label' => $item_label,
'type' => $item_type,
'data_type' => $item_data_type,
);
}
}

$schema[] = array(
'slug' => $slug,
'label' => $label,
'type' => 'array',
'placeholder' => $placeholder,
'data_type' => 'array',
'item_schema' => $item_schema,
);
continue;
}

if ( ! in_array( $type, array( 'single', 'textarea', 'rich' ), true ) ) {
$type = 'textarea';
}
if ( ! in_array( $data_type, array( 'text', 'number', 'boolean', 'date' ), true ) ) {
$data_type = 'text';
}

$schema[] = array(
'slug' => $slug,
'label' => $label,
'type' => $type,
'placeholder' => $placeholder,
'data_type' => $data_type,
);
}

return $schema;
}

Check warning

Code scanning / PHPMD

Code Size Rules: NPathComplexity Warning

The method build_schema_from_fields() has an NPath complexity of 208898. The configured NPath complexity threshold is 200.
Comment on lines 436 to +481
@@ -404,81 +453,81 @@ private static function build_output_path( $post_id, $extension ) {
*
* @return string Absolute directory path.
*/
private static function ensure_output_dir() {
$upload_dir = wp_upload_dir();
$dir = trailingslashit( $upload_dir['basedir'] ) . 'resolate';
if ( ! is_dir( $dir ) ) {
wp_mkdir_p( $dir );
}

return $dir;
}

/**
* Sanitize placeholders preserving TinyButStrong supported characters.
*
* @param string $placeholder Placeholder name.
* @return string
*/
private static function sanitize_placeholder_name( $placeholder ) {
$placeholder = (string) $placeholder;
$placeholder = preg_replace( '/[^A-Za-z0-9._:-]/', '', $placeholder );
return $placeholder;
}

/**
* Normalize a field value based on the detected data type.
*
* @param string $value Original value.
* @param string $data_type Detected data type.
* @return mixed
*/
private static function normalize_field_value( $value, $data_type ) {
$value = is_string( $value ) ? trim( $value ) : $value;
$data_type = sanitize_key( $data_type );

switch ( $data_type ) {
case 'number':
if ( '' === $value ) {
return '';
}
if ( is_numeric( $value ) ) {
return 0 + $value;
}
$filtered = preg_replace( '/[^0-9.,\-]/', '', (string) $value );
if ( '' === $filtered ) {
return '';
}
$normalized = str_replace( ',', '.', $filtered );
if ( is_numeric( $normalized ) ) {
return 0 + $normalized;
}
return $value;
case 'boolean':
if ( is_bool( $value ) ) {
return $value ? 1 : 0;
}
$value = strtolower( (string) $value );
if ( in_array( $value, array( '1', 'true', 'si', 'sí', 'yes', 'on' ), true ) ) {
return 1;
}
if ( in_array( $value, array( '0', 'false', 'no', 'off' ), true ) ) {
return 0;
}
return '' === $value ? 0 : 0;
case 'date':
if ( '' === $value ) {
return '';
}
$timestamp = strtotime( (string) $value );
if ( false === $timestamp ) {
return $value;
}
return wp_date( 'Y-m-d', $timestamp );
default:
return $value;
}
}
private static function ensure_output_dir() {
$upload_dir = wp_upload_dir();
$dir = trailingslashit( $upload_dir['basedir'] ) . 'resolate';
if ( ! is_dir( $dir ) ) {
wp_mkdir_p( $dir );
}

return $dir;
}

/**
* Sanitize placeholders preserving TinyButStrong supported characters.
*
* @param string $placeholder Placeholder name.
* @return string
*/
private static function sanitize_placeholder_name( $placeholder ) {
$placeholder = (string) $placeholder;
$placeholder = preg_replace( '/[^A-Za-z0-9._:-]/', '', $placeholder );
return $placeholder;
}

/**
* Normalize a field value based on the detected data type.
*
* @param string $value Original value.
* @param string $data_type Detected data type.
* @return mixed
*/
private static function normalize_field_value( $value, $data_type ) {
$value = is_string( $value ) ? trim( $value ) : $value;
$data_type = sanitize_key( $data_type );

switch ( $data_type ) {
case 'number':
if ( '' === $value ) {
return '';
}
if ( is_numeric( $value ) ) {
return 0 + $value;
}

Check warning

Code scanning / PHPMD

Code Size Rules: CyclomaticComplexity Warning

The method normalize_field_value() has a Cyclomatic Complexity of 16. The configured cyclomatic complexity threshold is 10.
Comment on lines +148 to +352
public static function build_schema_from_field_definitions( $fields ) {
if ( ! is_array( $fields ) ) {
return array();
}

$array_defs = array();
$array_order = array();
$repeat_hints = array();
$pending = array();
$order_counter = 0;

foreach ( $fields as $index => $field ) {
if ( ! is_array( $field ) ) {
continue;
}

$placeholder = isset( $field['placeholder'] ) ? trim( (string) $field['placeholder'] ) : '';
$parameters = isset( $field['parameters'] ) && is_array( $field['parameters'] ) ? $field['parameters'] : array();
$label = isset( $field['label'] ) ? sanitize_text_field( $field['label'] ) : '';
$data_type = isset( $field['data_type'] ) ? sanitize_key( $field['data_type'] ) : 'text';

$array_match = self::detect_array_placeholder_with_index( $placeholder );
if ( $array_match ) {
$base = $array_match['base'];
$key = $array_match['key'];

if ( '' === $base || '' === $key ) {
continue;
}

$repeat_hints[ $base ] = true;

if ( ! isset( $array_defs[ $base ] ) ) {
$array_defs[ $base ] = array(
'slug' => $base,
'label' => self::humanize_key( $base ),
'type' => 'array',
'placeholder' => $base,
'data_type' => 'array',
'item_schema' => array(),
'_order' => $order_counter++,
);
$array_order[] = $base;
}

if ( '' === $label ) {
$label = self::humanize_key( $array_match['raw_key'] );
}

if ( ! isset( $array_defs[ $base ]['item_schema'][ $key ] ) ) {
$item_data_type = self::detect_data_type( $placeholder, $parameters );
if ( '' === $item_data_type ) {
$item_data_type = 'text';
}

$array_defs[ $base ]['item_schema'][ $key ] = array(
'label' => $label,
'type' => self::infer_array_item_type( $key, $item_data_type ),
'data_type' => $item_data_type,
);
}

continue;
}

if ( isset( $parameters['repeat'] ) ) {
$repeat_base = sanitize_key( $parameters['repeat'] );
if ( '' !== $repeat_base ) {
$repeat_hints[ $repeat_base ] = true;
}
}

$pending[] = array(
'field' => $field,
'placeholder' => $placeholder,
'parameters' => $parameters,
'label' => $label,
'data_type' => $data_type,
'index' => $index,
);
}

$schema = array();
$scalar_fields = array();

foreach ( $pending as $entry ) {
$placeholder = $entry['placeholder'];
$parameters = $entry['parameters'];
$label = $entry['label'];
$data_type = $entry['data_type'];

$dot_match = self::detect_array_placeholder_without_index( $placeholder );
if ( $dot_match && ( isset( $repeat_hints[ $dot_match['base'] ] ) || isset( $array_defs[ $dot_match['base'] ] ) ) ) {
$base = $dot_match['base'];
$key = $dot_match['key'];

if ( '' === $base || '' === $key ) {
continue;
}

if ( ! isset( $array_defs[ $base ] ) ) {
$array_defs[ $base ] = array(
'slug' => $base,
'label' => self::humanize_key( $base ),
'type' => 'array',
'placeholder' => $base,
'data_type' => 'array',
'item_schema' => array(),
'_order' => $order_counter++,
);
$array_order[] = $base;
}

if ( ! isset( $array_defs[ $base ]['item_schema'][ $key ] ) ) {
$item_data_type = self::detect_data_type( $placeholder, $parameters );
if ( '' === $label ) {
$label = self::humanize_key( $dot_match['raw_key'] );
}
if ( '' === $item_data_type ) {
$item_data_type = 'text';
}
$array_defs[ $base ]['item_schema'][ $key ] = array(
'label' => ( '' !== $label ) ? $label : self::humanize_key( $dot_match['raw_key'] ),
'type' => self::infer_array_item_type( $key, $item_data_type ),
'data_type' => $item_data_type,
);
}

continue;
}

$slug = isset( $entry['field']['slug'] ) ? sanitize_key( $entry['field']['slug'] ) : '';
if ( '' === $slug ) {
$slug = sanitize_key( $placeholder );
}

if ( '' === $slug ) {
continue;
}

$normalized_placeholder = '';
if ( '' !== $placeholder ) {
$normalized_placeholder = preg_replace( '/[^A-Za-z0-9._:-]/', '', $placeholder );
}
if ( '' === $label ) {
$source = '' !== $normalized_placeholder ? $normalized_placeholder : $slug;
$label = self::humanize_key( $source );
}

if ( '' === $normalized_placeholder ) {
$normalized_placeholder = $slug;
}

if ( ! in_array( $data_type, array( 'text', 'number', 'boolean', 'date' ), true ) ) {
$data_type = 'text';
}

if ( in_array( $slug, array( 'onshow', 'ondata', 'block', 'var' ), true ) ) {
continue;
}

$scalar_fields[] = array(
'slug' => $slug,
'label' => $label,
'placeholder' => $normalized_placeholder,
'data_type' => $data_type,
'_order' => $entry['index'],
);
}

if ( ! empty( $array_defs ) ) {
uasort(
$array_defs,
static function ( $a, $b ) {
$order_a = isset( $a['_order'] ) ? intval( $a['_order'] ) : 0;
$order_b = isset( $b['_order'] ) ? intval( $b['_order'] ) : 0;
return $order_a <=> $order_b;
}
);

foreach ( $array_defs as $base => $def ) {
unset( $def['_order'] );
$schema[] = $def;
}
}

if ( ! empty( $scalar_fields ) ) {
usort(
$scalar_fields,
static function ( $a, $b ) {
$order_a = isset( $a['_order'] ) ? intval( $a['_order'] ) : 0;
$order_b = isset( $b['_order'] ) ? intval( $b['_order'] ) : 0;
return $order_a <=> $order_b;
}
);

foreach ( $scalar_fields as $field ) {
unset( $field['_order'] );
$field['type'] = 'textarea';
$schema[] = $field;
}
}

return $schema;
}

Check warning

Code scanning / PHPMD

Code Size Rules: CyclomaticComplexity Warning

The method build_schema_from_field_definitions() has a Cyclomatic Complexity of 50. The configured cyclomatic complexity threshold is 10.
Comment on lines +148 to +352
public static function build_schema_from_field_definitions( $fields ) {
if ( ! is_array( $fields ) ) {
return array();
}

$array_defs = array();
$array_order = array();
$repeat_hints = array();
$pending = array();
$order_counter = 0;

foreach ( $fields as $index => $field ) {
if ( ! is_array( $field ) ) {
continue;
}

$placeholder = isset( $field['placeholder'] ) ? trim( (string) $field['placeholder'] ) : '';
$parameters = isset( $field['parameters'] ) && is_array( $field['parameters'] ) ? $field['parameters'] : array();
$label = isset( $field['label'] ) ? sanitize_text_field( $field['label'] ) : '';
$data_type = isset( $field['data_type'] ) ? sanitize_key( $field['data_type'] ) : 'text';

$array_match = self::detect_array_placeholder_with_index( $placeholder );
if ( $array_match ) {
$base = $array_match['base'];
$key = $array_match['key'];

if ( '' === $base || '' === $key ) {
continue;
}

$repeat_hints[ $base ] = true;

if ( ! isset( $array_defs[ $base ] ) ) {
$array_defs[ $base ] = array(
'slug' => $base,
'label' => self::humanize_key( $base ),
'type' => 'array',
'placeholder' => $base,
'data_type' => 'array',
'item_schema' => array(),
'_order' => $order_counter++,
);
$array_order[] = $base;
}

if ( '' === $label ) {
$label = self::humanize_key( $array_match['raw_key'] );
}

if ( ! isset( $array_defs[ $base ]['item_schema'][ $key ] ) ) {
$item_data_type = self::detect_data_type( $placeholder, $parameters );
if ( '' === $item_data_type ) {
$item_data_type = 'text';
}

$array_defs[ $base ]['item_schema'][ $key ] = array(
'label' => $label,
'type' => self::infer_array_item_type( $key, $item_data_type ),
'data_type' => $item_data_type,
);
}

continue;
}

if ( isset( $parameters['repeat'] ) ) {
$repeat_base = sanitize_key( $parameters['repeat'] );
if ( '' !== $repeat_base ) {
$repeat_hints[ $repeat_base ] = true;
}
}

$pending[] = array(
'field' => $field,
'placeholder' => $placeholder,
'parameters' => $parameters,
'label' => $label,
'data_type' => $data_type,
'index' => $index,
);
}

$schema = array();
$scalar_fields = array();

foreach ( $pending as $entry ) {
$placeholder = $entry['placeholder'];
$parameters = $entry['parameters'];
$label = $entry['label'];
$data_type = $entry['data_type'];

$dot_match = self::detect_array_placeholder_without_index( $placeholder );
if ( $dot_match && ( isset( $repeat_hints[ $dot_match['base'] ] ) || isset( $array_defs[ $dot_match['base'] ] ) ) ) {
$base = $dot_match['base'];
$key = $dot_match['key'];

if ( '' === $base || '' === $key ) {
continue;
}

if ( ! isset( $array_defs[ $base ] ) ) {
$array_defs[ $base ] = array(
'slug' => $base,
'label' => self::humanize_key( $base ),
'type' => 'array',
'placeholder' => $base,
'data_type' => 'array',
'item_schema' => array(),
'_order' => $order_counter++,
);
$array_order[] = $base;
}

if ( ! isset( $array_defs[ $base ]['item_schema'][ $key ] ) ) {
$item_data_type = self::detect_data_type( $placeholder, $parameters );
if ( '' === $label ) {
$label = self::humanize_key( $dot_match['raw_key'] );
}
if ( '' === $item_data_type ) {
$item_data_type = 'text';
}
$array_defs[ $base ]['item_schema'][ $key ] = array(
'label' => ( '' !== $label ) ? $label : self::humanize_key( $dot_match['raw_key'] ),
'type' => self::infer_array_item_type( $key, $item_data_type ),
'data_type' => $item_data_type,
);
}

continue;
}

$slug = isset( $entry['field']['slug'] ) ? sanitize_key( $entry['field']['slug'] ) : '';
if ( '' === $slug ) {
$slug = sanitize_key( $placeholder );
}

if ( '' === $slug ) {
continue;
}

$normalized_placeholder = '';
if ( '' !== $placeholder ) {
$normalized_placeholder = preg_replace( '/[^A-Za-z0-9._:-]/', '', $placeholder );
}
if ( '' === $label ) {
$source = '' !== $normalized_placeholder ? $normalized_placeholder : $slug;
$label = self::humanize_key( $source );
}

if ( '' === $normalized_placeholder ) {
$normalized_placeholder = $slug;
}

if ( ! in_array( $data_type, array( 'text', 'number', 'boolean', 'date' ), true ) ) {
$data_type = 'text';
}

if ( in_array( $slug, array( 'onshow', 'ondata', 'block', 'var' ), true ) ) {
continue;
}

$scalar_fields[] = array(
'slug' => $slug,
'label' => $label,
'placeholder' => $normalized_placeholder,
'data_type' => $data_type,
'_order' => $entry['index'],
);
}

if ( ! empty( $array_defs ) ) {
uasort(
$array_defs,
static function ( $a, $b ) {
$order_a = isset( $a['_order'] ) ? intval( $a['_order'] ) : 0;
$order_b = isset( $b['_order'] ) ? intval( $b['_order'] ) : 0;
return $order_a <=> $order_b;
}
);

foreach ( $array_defs as $base => $def ) {
unset( $def['_order'] );
$schema[] = $def;
}
}

if ( ! empty( $scalar_fields ) ) {
usort(
$scalar_fields,
static function ( $a, $b ) {
$order_a = isset( $a['_order'] ) ? intval( $a['_order'] ) : 0;
$order_b = isset( $b['_order'] ) ? intval( $b['_order'] ) : 0;
return $order_a <=> $order_b;
}
);

foreach ( $scalar_fields as $field ) {
unset( $field['_order'] );
$field['type'] = 'textarea';
$schema[] = $field;
}
}

return $schema;
}

Check warning

Code scanning / PHPMD

Code Size Rules: NPathComplexity Warning

The method build_schema_from_field_definitions() has an NPath complexity of 96592348314. The configured NPath complexity threshold is 200.
Comment on lines +561 to +612
'type' => 'textarea',
'data_type' => 'text',
);
}

if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
return $schema;
}

/**
* Render an array field with repeatable items.
*
* @param string $slug Field slug.
* @param string $label Field label.
* @param array $item_schema Item schema definition.
* @param array $items Current values.
* @return void
*/
private function render_array_field( $slug, $label, $item_schema, $items ) {
$slug = sanitize_key( $slug );
$label = sanitize_text_field( $label );
$field_id = 'resolate-array-' . $slug;
$items = is_array( $items ) ? $items : array();
$item_schema = is_array( $item_schema ) ? $item_schema : array();

echo '<div class="resolate-array-field" data-array-field="' . esc_attr( $slug ) . '" style="margin-bottom:24px;">';
echo '<div class="resolate-array-heading" style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;gap:12px;">';
echo '<span class="resolate-array-title" style="font-weight:600;font-size:15px;">' . esc_html( $label ) . '</span>';
echo '<button type="button" class="button button-secondary resolate-array-add" data-array-target="' . esc_attr( $slug ) . '">' . esc_html__( 'Añadir elemento', 'resolate' ) . '</button>';
echo '</div>';

echo '<div class="resolate-array-items" id="' . esc_attr( $field_id ) . '" data-field="' . esc_attr( $slug ) . '">';
foreach ( $items as $index => $values ) {
$values = is_array( $values ) ? $values : array();
$this->render_array_field_item( $slug, (string) $index, $item_schema, $values );
}
echo '</div>';

// Handle type selection (lock after set).
if ( isset( $_POST['resolate_type_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['resolate_type_nonce'] ) ), 'resolate_type_nonce' ) ) {
$posted = isset( $_POST['resolate_doc_type'] ) ? intval( $_POST['resolate_doc_type'] ) : 0;
$assigned = wp_get_post_terms( $post_id, 'resolate_doc_type', array( 'fields' => 'ids' ) );
$current = ( ! is_wp_error( $assigned ) && ! empty( $assigned ) ) ? intval( $assigned[0] ) : 0;
if ( $current <= 0 && $posted > 0 ) {
wp_set_post_terms( $post_id, array( $posted ), 'resolate_doc_type', false );
echo '<template class="resolate-array-template" data-field="' . esc_attr( $slug ) . '">';
$this->render_array_field_item( $slug, '__INDEX__', $item_schema, array(), true );
echo '</template>';
echo '</div>';
}

/**
* Render a single repeatable array item row.
*
* @param string $slug Field slug.
* @param string $index Item index.
* @param array $item_schema Item schema definition.
* @param array $values Current values.
* @param bool $is_template Whether the row is a template placeholder.
* @return void

Check warning

Code scanning / PHPMD

Code Size Rules: NPathComplexity Warning

The method save_meta_boxes() has an NPath complexity of 252. The configured NPath complexity threshold is 200.
Comment on lines +614 to +656
private function render_array_field_item( $slug, $index, $item_schema, $values, $is_template = false ) {
$slug = sanitize_key( $slug );
$index_attr = (string) $index;
$item_schema = is_array( $item_schema ) ? $item_schema : array();
$values = is_array( $values ) ? $values : array();

echo '<div class="resolate-array-item" data-index="' . esc_attr( $index_attr ) . '" draggable="true" style="border:1px solid #e5e5e5;padding:16px;margin-bottom:12px;background:#fff;">';
echo '<div class="resolate-array-item-toolbar" style="display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px;">';
echo '<span class="resolate-array-handle" role="button" tabindex="0" aria-label="' . esc_attr__( 'Mover elemento', 'resolate' ) . '" style="cursor:move;user-select:none;">≡</span>';
echo '<button type="button" class="button-link-delete resolate-array-remove">' . esc_html__( 'Eliminar', 'resolate' ) . '</button>';
echo '</div>';

foreach ( $item_schema as $key => $definition ) {
$item_key = sanitize_key( $key );
if ( '' === $item_key ) {
continue;
}

$field_name = 'tpl_fields[' . $slug . '][' . $index_attr . '][' . $item_key . ']';
$field_id = 'resolate-' . $slug . '-' . $item_key . '-' . $index_attr;
$label = isset( $definition['label'] ) ? sanitize_text_field( $definition['label'] ) : $this->humanize_unknown_field_label( $item_key );
$type = isset( $definition['type'] ) ? sanitize_key( $definition['type'] ) : 'textarea';
$value = isset( $values[ $item_key ] ) ? (string) $values[ $item_key ] : '';

if ( ! in_array( $type, array( 'single', 'textarea', 'rich' ), true ) ) {
$type = 'textarea';
}

echo '<div class="resolate-array-field-control" style="margin-bottom:12px;">';
echo '<label for="' . esc_attr( $field_id ) . '" style="font-weight:600;display:block;margin-bottom:4px;">' . esc_html( $label ) . '</label>';

if ( 'single' === $type ) {
echo '<input type="text" class="widefat" id="' . esc_attr( $field_id ) . '" name="' . esc_attr( $field_name ) . '" value="' . esc_attr( $value ) . '" />';
} else {
$rows = ( 'rich' === $type ) ? 8 : 4;
echo '<textarea class="widefat" rows="' . esc_attr( (string) $rows ) . '" id="' . esc_attr( $field_id ) . '" name="' . esc_attr( $field_name ) . '">' . esc_textarea( $value ) . '</textarea>';
}

echo '</div>';
}

echo '</div>';
}

Check warning

Code scanning / PHPMD

Code Size Rules: CyclomaticComplexity Warning

The method render_array_field_item() has a Cyclomatic Complexity of 11. The configured cyclomatic complexity threshold is 10.
Comment on lines +614 to +656
private function render_array_field_item( $slug, $index, $item_schema, $values, $is_template = false ) {
$slug = sanitize_key( $slug );
$index_attr = (string) $index;
$item_schema = is_array( $item_schema ) ? $item_schema : array();
$values = is_array( $values ) ? $values : array();

echo '<div class="resolate-array-item" data-index="' . esc_attr( $index_attr ) . '" draggable="true" style="border:1px solid #e5e5e5;padding:16px;margin-bottom:12px;background:#fff;">';
echo '<div class="resolate-array-item-toolbar" style="display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px;">';
echo '<span class="resolate-array-handle" role="button" tabindex="0" aria-label="' . esc_attr__( 'Mover elemento', 'resolate' ) . '" style="cursor:move;user-select:none;">≡</span>';
echo '<button type="button" class="button-link-delete resolate-array-remove">' . esc_html__( 'Eliminar', 'resolate' ) . '</button>';
echo '</div>';

foreach ( $item_schema as $key => $definition ) {
$item_key = sanitize_key( $key );
if ( '' === $item_key ) {
continue;
}

$field_name = 'tpl_fields[' . $slug . '][' . $index_attr . '][' . $item_key . ']';
$field_id = 'resolate-' . $slug . '-' . $item_key . '-' . $index_attr;
$label = isset( $definition['label'] ) ? sanitize_text_field( $definition['label'] ) : $this->humanize_unknown_field_label( $item_key );
$type = isset( $definition['type'] ) ? sanitize_key( $definition['type'] ) : 'textarea';
$value = isset( $values[ $item_key ] ) ? (string) $values[ $item_key ] : '';

if ( ! in_array( $type, array( 'single', 'textarea', 'rich' ), true ) ) {
$type = 'textarea';
}

echo '<div class="resolate-array-field-control" style="margin-bottom:12px;">';
echo '<label for="' . esc_attr( $field_id ) . '" style="font-weight:600;display:block;margin-bottom:4px;">' . esc_html( $label ) . '</label>';

if ( 'single' === $type ) {
echo '<input type="text" class="widefat" id="' . esc_attr( $field_id ) . '" name="' . esc_attr( $field_name ) . '" value="' . esc_attr( $value ) . '" />';
} else {
$rows = ( 'rich' === $type ) ? 8 : 4;
echo '<textarea class="widefat" rows="' . esc_attr( (string) $rows ) . '" id="' . esc_attr( $field_id ) . '" name="' . esc_attr( $field_name ) . '">' . esc_textarea( $value ) . '</textarea>';
}

echo '</div>';
}

echo '</div>';
}

Check warning

Code scanning / PHPMD

Code Size Rules: NPathComplexity Warning

The method render_array_field_item() has an NPath complexity of 388. The configured NPath complexity threshold is 200.
Comment on lines 991 to -1024
@@ -874,9 +1004,32 @@ private function get_structured_field_values( $post_id ) {
if ( '' === $value ) {
continue;
}
$type = isset( $row['type'] ) ? sanitize_key( $row['type'] ) : 'textarea';
$type = isset( $row['type'] ) ? sanitize_key( $row['type'] ) : 'textarea';
if ( 'array' === $type ) {
$encoded = '';
$stored = get_post_meta( $post_id, 'resolate_field_' . $slug, true );
if ( is_string( $stored ) && '' !== $stored ) {
$encoded = (string) $stored;
} else {
$legacy = get_post_meta( $post_id, 'resolate_' . $slug, true );
if ( empty( $legacy ) && 'annexes' === $slug ) {
$legacy = get_post_meta( $post_id, 'resolate_annexes', true );
}
if ( is_array( $legacy ) && ! empty( $legacy ) ) {
$encoded = wp_json_encode( $legacy );
}
}

if ( '' !== $encoded ) {
$fallback[ $slug ] = array(
'value' => $encoded,
'type' => 'array',
);
}
continue;
}
if ( ! in_array( $type, array( 'single', 'textarea', 'rich' ), true ) ) {
$type = 'textarea';

Check warning

Code scanning / PHPMD

Code Size Rules: CyclomaticComplexity Warning

The method get_term_schema() has a Cyclomatic Complexity of 27. The configured cyclomatic complexity threshold is 10.
Comment on lines 991 to -1024
@@ -874,9 +1004,32 @@ private function get_structured_field_values( $post_id ) {
if ( '' === $value ) {
continue;
}
$type = isset( $row['type'] ) ? sanitize_key( $row['type'] ) : 'textarea';
$type = isset( $row['type'] ) ? sanitize_key( $row['type'] ) : 'textarea';
if ( 'array' === $type ) {
$encoded = '';
$stored = get_post_meta( $post_id, 'resolate_field_' . $slug, true );
if ( is_string( $stored ) && '' !== $stored ) {
$encoded = (string) $stored;
} else {
$legacy = get_post_meta( $post_id, 'resolate_' . $slug, true );
if ( empty( $legacy ) && 'annexes' === $slug ) {
$legacy = get_post_meta( $post_id, 'resolate_annexes', true );
}
if ( is_array( $legacy ) && ! empty( $legacy ) ) {
$encoded = wp_json_encode( $legacy );
}
}

if ( '' !== $encoded ) {
$fallback[ $slug ] = array(
'value' => $encoded,
'type' => 'array',
);
}
continue;
}
if ( ! in_array( $type, array( 'single', 'textarea', 'rich' ), true ) ) {
$type = 'textarea';

Check warning

Code scanning / PHPMD

Code Size Rules: NPathComplexity Warning

The method get_term_schema() has an NPath complexity of 2162692. The configured NPath complexity threshold is 200.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting

Comment on lines +156 to +178
}

/**
* It should expose array schema definitions when reading the term configuration.
*/
public function test_get_term_schema_returns_array_definition() {
$term = wp_insert_term( 'Tipo Esquema', 'resolate_doc_type' );
$term_id = intval( $term['term_id'] );
$schema = $this->get_annex_schema();
update_term_meta( $term_id, 'schema', $schema );
update_term_meta( $term_id, 'resolate_type_fields', $schema );

$result = Resolate_Documents::get_term_schema( $term_id );

$this->assertNotEmpty( $result );
$this->assertSame( 'annexes', $result[0]['slug'] );
$this->assertSame( 'array', $result[0]['type'] );
$this->assertSame( 'array', $result[0]['data_type'] );
$this->assertArrayHasKey( 'item_schema', $result[0] );
$this->assertArrayHasKey( 'number', $result[0]['item_schema'] );
$this->assertSame( 'single', $result[0]['item_schema']['number']['type'] );
$this->assertSame( 'Número', $result[0]['item_schema']['number']['label'] );
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move schema test inside test class

The new test_get_term_schema_returns_array_definition() is declared after the closing brace of ResolateDocumentsArrayFieldsTest. Because it uses public function outside any class definition, the file will cause a PHP parse error as soon as the test suite is loaded, preventing any tests from running. Place this method back inside the class (before its final }) so the test file remains syntactically valid.

Useful? React with 👍 / 👎.

@erseco erseco deleted the feature/add-array-fields-for-annexes-support-b76qhb branch November 30, 2025 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants