Skip to content

Conversation

@erseco
Copy link
Collaborator

@erseco erseco commented Dec 5, 2025

This pull request introduces a new "Archived" workflow status for Documentate documents, providing a formal way to lock published documents as historical records. It includes backend logic, UI enhancements, and admin actions for archiving and unarchiving documents, with clear permissions and user feedback. The changes also ensure that archived documents are visually distinct and appropriately locked for non-admin users.

New "Archived" Workflow Status

  • Added a custom post status archived for documentate_document, including backend registration, status transitions, and business rules enforcing that only admins can archive/unarchive, and only from the publish state. Archived documents are locked for editing by non-admins, similar to published documents. [1] [2] [3]
  • Updated workflow notices and status change reasons to cover archiving rules and restrictions, providing clear feedback for invalid transitions or permission errors.

Admin Actions and UI Controls

  • Added admin-only row actions and workflow metabox buttons to archive or unarchive documents, with secure nonce handling and status transitions. [1] [2] [3]
  • Provided helper methods for generating action URLs and handling requests for archiving/unarchiving. [1] [2]

Frontend and Visual Enhancements

  • Extended JavaScript logic to recognize and lock archived documents, displaying appropriate icons and messages for both admins and non-admins. [1] [2] [3] [4] [5]
  • Updated CSS to visually distinguish archived documents in the workflow metabox and list tables, including styling for archived status, notices, and action buttons. [1] [2] [3]

Other Improvements and Guidance

  • Added general development rules to CLAUDE.md emphasizing clean code, no workarounds, and maintaining UI/feature surfaces during early development.
  • Minor fix to use raw post title field for merge fields, ensuring case transformation works as intended.

These changes collectively introduce a robust, maintainable, and user-friendly archiving workflow, setting a solid foundation for future scalability and clarity in document lifecycle management.

Comment on lines +50 to +122
public static function import_fixture_file( $filename ) {
$base_dir = self::$plugin_dir;
$paths = array(
$base_dir . 'fixtures/' . $filename,
$base_dir . $filename,
);
$source = '';
foreach ( $paths as $p ) {
if ( file_exists( $p ) && is_readable( $p ) ) {
$source = $p;
break;
}
}
if ( '' === $source ) {
return 0;
}

$hash = @md5_file( $source );
if ( $hash ) {
$found = get_posts(
array(
'post_type' => 'attachment',
'post_status' => 'inherit',
'meta_key' => '_documentate_fixture_hash',
'meta_value' => $hash,
'fields' => 'ids',
'numberposts' => 1,
)
);
if ( ! empty( $found ) ) {
return intval( $found[0] );
}
}

$contents = @file_get_contents( $source );
if ( false === $contents ) {
return 0;
}

$upload = wp_upload_bits( basename( $source ), null, $contents );
if ( ! empty( $upload['error'] ) ) {
return 0;
}

$filetype = wp_check_filetype_and_ext( $upload['file'], basename( $upload['file'] ) );
$attachment = array(
'post_mime_type' => $filetype['type'] ? $filetype['type'] : 'application/octet-stream',
'post_title' => sanitize_file_name( basename( $source ) ),
'post_content' => '',
'post_status' => 'inherit',
);
$attach_id = wp_insert_attachment( $attachment, $upload['file'] );
if ( ! $attach_id ) {
return 0;
}

// Generate and save attachment metadata (for images).
if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
require_once ABSPATH . 'wp-admin/includes/image.php';
}
$attach_data = wp_generate_attachment_metadata( $attach_id, $upload['file'] );
if ( ! empty( $attach_data ) ) {
wp_update_attachment_metadata( $attach_id, $attach_data );
}

// Tag as fixture to allow reuse.
if ( $hash ) {
update_post_meta( $attach_id, '_documentate_fixture_hash', $hash );
}
update_post_meta( $attach_id, '_documentate_fixture_name', basename( $source ) );

return intval( $attach_id );
}

Check warning

Code scanning / PHPMD

Code Size Rules: NPathComplexity Warning

The method import_fixture_file() has an NPath complexity of 3072. The configured NPath complexity threshold is 500.
Comment on lines +147 to +233
public static function maybe_seed_default_doc_types() {
if ( ! taxonomy_exists( 'documentate_doc_type' ) ) {
return;
}

self::ensure_default_media();

$definitions = self::get_doc_type_definitions();

if ( empty( $definitions ) ) {
return;
}

foreach ( $definitions as $definition ) {
$slug = $definition['slug'];
$template_id = intval( $definition['template_id'] );
if ( $template_id <= 0 ) {
continue;
}

$term = get_term_by( 'slug', $slug, 'documentate_doc_type' );
$term_id = $term instanceof WP_Term ? intval( $term->term_id ) : 0;

if ( $term_id <= 0 ) {
$created = wp_insert_term(
$definition['name'],
'documentate_doc_type',
array(
'slug' => $slug,
'description' => $definition['description'],
)
);

if ( is_wp_error( $created ) ) {
continue;
}

$term_id = intval( $created['term_id'] );
}

if ( $term_id <= 0 ) {
continue;
}

$fixture_key = get_term_meta( $term_id, '_documentate_fixture', true );
if ( ! empty( $fixture_key ) && $fixture_key !== $definition['fixture_key'] ) {
continue;
}

update_term_meta( $term_id, '_documentate_fixture', $definition['fixture_key'] );
update_term_meta( $term_id, 'documentate_type_color', $definition['color'] );
update_term_meta( $term_id, 'documentate_type_template_id', $template_id );

$path = get_attached_file( $template_id );
if ( ! $path ) {
continue;
}

$extractor = new Documentate\DocType\SchemaExtractor();
$storage = new Documentate\DocType\SchemaStorage();

$existing_schema = $storage->get_schema( $term_id );
$template_hash = @md5_file( $path );

if ( ! empty( $existing_schema ) && $template_hash && isset( $existing_schema['meta']['hash'] ) && $template_hash === $existing_schema['meta']['hash'] ) {
$template_type = isset( $existing_schema['meta']['template_type'] ) ? (string) $existing_schema['meta']['template_type'] : strtolower( (string) pathinfo( $path, PATHINFO_EXTENSION ) );
update_term_meta( $term_id, 'documentate_type_template_type', $template_type );
continue;
}

$schema = $extractor->extract( $path );
if ( is_wp_error( $schema ) ) {
continue;
}

$schema['meta']['template_id'] = $template_id;
$schema['meta']['template_type'] = isset( $schema['meta']['template_type'] ) ? (string) $schema['meta']['template_type'] : strtolower( (string) pathinfo( $path, PATHINFO_EXTENSION ) );
$schema['meta']['template_name'] = basename( $path );
if ( empty( $schema['meta']['hash'] ) && $template_hash ) {
$schema['meta']['hash'] = $template_hash;
}

update_term_meta( $term_id, 'documentate_type_template_type', $schema['meta']['template_type'] );

$storage->save_schema( $term_id, $schema );
}
}

Check warning

Code scanning / PHPMD

Code Size Rules: CyclomaticComplexity Warning

The method maybe_seed_default_doc_types() has a Cyclomatic Complexity of 21. The configured cyclomatic complexity threshold is 15.
Comment on lines +147 to +233
public static function maybe_seed_default_doc_types() {
if ( ! taxonomy_exists( 'documentate_doc_type' ) ) {
return;
}

self::ensure_default_media();

$definitions = self::get_doc_type_definitions();

if ( empty( $definitions ) ) {
return;
}

foreach ( $definitions as $definition ) {
$slug = $definition['slug'];
$template_id = intval( $definition['template_id'] );
if ( $template_id <= 0 ) {
continue;
}

$term = get_term_by( 'slug', $slug, 'documentate_doc_type' );
$term_id = $term instanceof WP_Term ? intval( $term->term_id ) : 0;

if ( $term_id <= 0 ) {
$created = wp_insert_term(
$definition['name'],
'documentate_doc_type',
array(
'slug' => $slug,
'description' => $definition['description'],
)
);

if ( is_wp_error( $created ) ) {
continue;
}

$term_id = intval( $created['term_id'] );
}

if ( $term_id <= 0 ) {
continue;
}

$fixture_key = get_term_meta( $term_id, '_documentate_fixture', true );
if ( ! empty( $fixture_key ) && $fixture_key !== $definition['fixture_key'] ) {
continue;
}

update_term_meta( $term_id, '_documentate_fixture', $definition['fixture_key'] );
update_term_meta( $term_id, 'documentate_type_color', $definition['color'] );
update_term_meta( $term_id, 'documentate_type_template_id', $template_id );

$path = get_attached_file( $template_id );
if ( ! $path ) {
continue;
}

$extractor = new Documentate\DocType\SchemaExtractor();
$storage = new Documentate\DocType\SchemaStorage();

$existing_schema = $storage->get_schema( $term_id );
$template_hash = @md5_file( $path );

if ( ! empty( $existing_schema ) && $template_hash && isset( $existing_schema['meta']['hash'] ) && $template_hash === $existing_schema['meta']['hash'] ) {
$template_type = isset( $existing_schema['meta']['template_type'] ) ? (string) $existing_schema['meta']['template_type'] : strtolower( (string) pathinfo( $path, PATHINFO_EXTENSION ) );
update_term_meta( $term_id, 'documentate_type_template_type', $template_type );
continue;
}

$schema = $extractor->extract( $path );
if ( is_wp_error( $schema ) ) {
continue;
}

$schema['meta']['template_id'] = $template_id;
$schema['meta']['template_type'] = isset( $schema['meta']['template_type'] ) ? (string) $schema['meta']['template_type'] : strtolower( (string) pathinfo( $path, PATHINFO_EXTENSION ) );
$schema['meta']['template_name'] = basename( $path );
if ( empty( $schema['meta']['hash'] ) && $template_hash ) {
$schema['meta']['hash'] = $template_hash;
}

update_term_meta( $term_id, 'documentate_type_template_type', $schema['meta']['template_type'] );

$storage->save_schema( $term_id, $schema );
}
}

Check warning

Code scanning / PHPMD

Code Size Rules: NPathComplexity Warning

The method maybe_seed_default_doc_types() has an NPath complexity of 41476. The configured NPath complexity threshold is 500.
Comment on lines +335 to +440
public static function maybe_seed_demo_documents() {
if ( ! post_type_exists( 'documentate_document' ) || ! taxonomy_exists( 'documentate_doc_type' ) ) {
return;
}

$should_seed = (bool) get_option( 'documentate_seed_demo_documents', false );
if ( ! $should_seed ) {
return;
}

// Check if demo documents already exist - if so, skip seeding.
$existing_demos = get_posts(
array(
'post_type' => 'documentate_document',
'post_status' => 'any',
'posts_per_page' => 1,
'fields' => 'ids',
'meta_query' => array(
'relation' => 'OR',
array(
'key' => '_documentate_demo_key',
'compare' => 'EXISTS',
),
array(
'key' => '_documentate_demo_type_id',
'compare' => 'EXISTS',
),
),
)
);

if ( ! empty( $existing_demos ) ) {
delete_option( 'documentate_seed_demo_documents' );
return;
}

self::maybe_seed_default_doc_types();

// Get the Resolución Administrativa document type.
$term = get_term_by( 'slug', 'resolucion-administrativa', 'documentate_doc_type' );
if ( $term instanceof WP_Term ) {
// Create the 3 specific demo documents for Resolución Administrativa.
self::create_resolucion_demo_documents( $term );
}

// Create specific demo document for Autorización de viaje.
$autorizacion_term = get_term_by( 'slug', 'autorizacion-viaje', 'documentate_doc_type' );
if ( $autorizacion_term instanceof WP_Term ) {
self::create_specific_demo_documents( $autorizacion_term, self::get_autorizacion_viaje_demo() );
}

// Create specific demo document for Gastos suplidos.
$gastos_term = get_term_by( 'slug', 'gastos-suplidos', 'documentate_doc_type' );
if ( $gastos_term instanceof WP_Term ) {
self::create_specific_demo_documents( $gastos_term, self::get_gastos_suplidos_demo() );
}

// Create specific demo document for Propuesta de gasto.
$propuesta_term = get_term_by( 'slug', 'propuesta-gasto', 'documentate_doc_type' );
if ( $propuesta_term instanceof WP_Term ) {
self::create_specific_demo_documents( $propuesta_term, self::get_propuesta_gasto_demo() );
}

// Create specific demo document for Convocatoria de reunión.
$convocatoria_term = get_term_by( 'slug', 'convocatoria-reunion', 'documentate_doc_type' );
if ( $convocatoria_term instanceof WP_Term ) {
self::create_specific_demo_documents( $convocatoria_term, self::get_convocatoria_reunion_demo() );
}

// Also create demo documents for other document types (advanced demos).
$exclude_ids = array();
if ( $term instanceof WP_Term ) {
$exclude_ids[] = $term->term_id;
}
if ( $autorizacion_term instanceof WP_Term ) {
$exclude_ids[] = $autorizacion_term->term_id;
}
if ( $gastos_term instanceof WP_Term ) {
$exclude_ids[] = $gastos_term->term_id;
}
if ( $propuesta_term instanceof WP_Term ) {
$exclude_ids[] = $propuesta_term->term_id;
}
if ( $convocatoria_term instanceof WP_Term ) {
$exclude_ids[] = $convocatoria_term->term_id;
}

$terms = get_terms(
array(
'taxonomy' => 'documentate_doc_type',
'hide_empty' => false,
'exclude' => $exclude_ids,
)
);

if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
foreach ( $terms as $other_term ) {
if ( self::demo_document_exists( $other_term->term_id ) ) {
continue;
}
self::create_demo_document_for_type( $other_term );
}
}

delete_option( 'documentate_seed_demo_documents' );
}

Check warning

Code scanning / PHPMD

Code Size Rules: CyclomaticComplexity Warning

The method maybe_seed_demo_documents() has a Cyclomatic Complexity of 19. The configured cyclomatic complexity threshold is 15.
Comment on lines +335 to +440
public static function maybe_seed_demo_documents() {
if ( ! post_type_exists( 'documentate_document' ) || ! taxonomy_exists( 'documentate_doc_type' ) ) {
return;
}

$should_seed = (bool) get_option( 'documentate_seed_demo_documents', false );
if ( ! $should_seed ) {
return;
}

// Check if demo documents already exist - if so, skip seeding.
$existing_demos = get_posts(
array(
'post_type' => 'documentate_document',
'post_status' => 'any',
'posts_per_page' => 1,
'fields' => 'ids',
'meta_query' => array(
'relation' => 'OR',
array(
'key' => '_documentate_demo_key',
'compare' => 'EXISTS',
),
array(
'key' => '_documentate_demo_type_id',
'compare' => 'EXISTS',
),
),
)
);

if ( ! empty( $existing_demos ) ) {
delete_option( 'documentate_seed_demo_documents' );
return;
}

self::maybe_seed_default_doc_types();

// Get the Resolución Administrativa document type.
$term = get_term_by( 'slug', 'resolucion-administrativa', 'documentate_doc_type' );
if ( $term instanceof WP_Term ) {
// Create the 3 specific demo documents for Resolución Administrativa.
self::create_resolucion_demo_documents( $term );
}

// Create specific demo document for Autorización de viaje.
$autorizacion_term = get_term_by( 'slug', 'autorizacion-viaje', 'documentate_doc_type' );
if ( $autorizacion_term instanceof WP_Term ) {
self::create_specific_demo_documents( $autorizacion_term, self::get_autorizacion_viaje_demo() );
}

// Create specific demo document for Gastos suplidos.
$gastos_term = get_term_by( 'slug', 'gastos-suplidos', 'documentate_doc_type' );
if ( $gastos_term instanceof WP_Term ) {
self::create_specific_demo_documents( $gastos_term, self::get_gastos_suplidos_demo() );
}

// Create specific demo document for Propuesta de gasto.
$propuesta_term = get_term_by( 'slug', 'propuesta-gasto', 'documentate_doc_type' );
if ( $propuesta_term instanceof WP_Term ) {
self::create_specific_demo_documents( $propuesta_term, self::get_propuesta_gasto_demo() );
}

// Create specific demo document for Convocatoria de reunión.
$convocatoria_term = get_term_by( 'slug', 'convocatoria-reunion', 'documentate_doc_type' );
if ( $convocatoria_term instanceof WP_Term ) {
self::create_specific_demo_documents( $convocatoria_term, self::get_convocatoria_reunion_demo() );
}

// Also create demo documents for other document types (advanced demos).
$exclude_ids = array();
if ( $term instanceof WP_Term ) {
$exclude_ids[] = $term->term_id;
}
if ( $autorizacion_term instanceof WP_Term ) {
$exclude_ids[] = $autorizacion_term->term_id;
}
if ( $gastos_term instanceof WP_Term ) {
$exclude_ids[] = $gastos_term->term_id;
}
if ( $propuesta_term instanceof WP_Term ) {
$exclude_ids[] = $propuesta_term->term_id;
}
if ( $convocatoria_term instanceof WP_Term ) {
$exclude_ids[] = $convocatoria_term->term_id;
}

$terms = get_terms(
array(
'taxonomy' => 'documentate_doc_type',
'hide_empty' => false,
'exclude' => $exclude_ids,
)
);

if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
foreach ( $terms as $other_term ) {
if ( self::demo_document_exists( $other_term->term_id ) ) {
continue;
}
self::create_demo_document_for_type( $other_term );
}
}

delete_option( 'documentate_seed_demo_documents' );
}

Check warning

Code scanning / PHPMD

Code Size Rules: NPathComplexity Warning

The method maybe_seed_demo_documents() has an NPath complexity of 61440. The configured NPath complexity threshold is 500.
Comment on lines +1413 to +1571
public static function generate_demo_scalar_value( $slug, $type, $data_type, $index = 1, $context = array() ) {
$slug = strtolower( (string) $slug );
$type = sanitize_key( $type );
$data_type = sanitize_key( $data_type );
$index = max( 1, absint( $index ) );

$document_title = isset( $context['document_title'] ) ? (string) $context['document_title'] : __( 'Demo resolution', 'documentate' );
$number_value = (string) ( 1 + $index );

if ( 'date' === $data_type ) {
$month = max( 1, min( 12, $index ) );
$day = max( 1, min( 28, 10 + $index ) );
return sprintf( '2025-%02d-%02d', $month, $day );
}

if ( 'number' === $data_type ) {
return $number_value;
}

if ( 'boolean' === $data_type ) {
return ( $index % 2 ) ? '1' : '0';
}

if ( false !== strpos( $slug, 'email' ) ) {
return 'demo' . $index . '@ejemplo.es';
}

if ( false !== strpos( $slug, 'phone' ) || false !== strpos( $slug, 'tel' ) ) {
return '+3460000000' . $index;
}

if ( false !== strpos( $slug, 'dni' ) ) {
return '1234567' . $index . 'A';
}

if ( false !== strpos( $slug, 'url' ) || false !== strpos( $slug, 'sitio' ) || false !== strpos( $slug, 'web' ) ) {
return 'https://ejemplo.es/recurso-' . $index;
}

if ( false !== strpos( $slug, 'nombre' ) || false !== strpos( $slug, 'name' ) ) {
return ( 1 === $index ) ? 'Jane Doe' : 'John Smith';
}

if ( false !== strpos( $slug, 'title' ) || false !== strpos( $slug, 'titulo' ) || 'post_title' === $slug ) {
if ( 'post_title' === $slug ) {
return $document_title;
}

/* translators: %d: item sequence number. */
return sprintf( __( 'Demo item %d', 'documentate' ), $index );
}

if ( false !== strpos( $slug, 'summary' ) || false !== strpos( $slug, 'resumen' ) ) {
/* translators: %d: item sequence number. */
return sprintf( __( 'Demo summary %d with brief information.', 'documentate' ), $index );
}

if ( false !== strpos( $slug, 'objeto' ) ) {
return __( 'Subject of the example resolution to illustrate the workflow.', 'documentate' );
}

if ( false !== strpos( $slug, 'antecedentes' ) ) {
return __( 'Background facts written with test content.', 'documentate' );
}

if ( false !== strpos( $slug, 'fundamentos' ) ) {
return __( 'Legal grounds for testing with generic references.', 'documentate' );
}

if ( false !== strpos( $slug, 'resuelv' ) ) {
return '<p>' . __( 'First. Approve the demo action.', 'documentate' ) . '</p><p>' . __( 'Second. Notify interested parties.', 'documentate' ) . '</p>';
}

if ( false !== strpos( $slug, 'observaciones' ) ) {
return __( 'Additional observations to complete the template.', 'documentate' );
}

// Repeater "gastos" fields (table row repeater).
if ( false !== strpos( $slug, 'proveedor' ) ) {
return ( 1 === $index ) ? 'Suministros Ejemplo S.L.' : 'Servicios Demo S.A.';
}

if ( 'cif' === $slug ) {
return ( 1 === $index ) ? 'B12345678' : 'A87654321';
}

if ( false !== strpos( $slug, 'factura' ) ) {
return sprintf( '%03d/2025', 100 + $index );
}

if ( false !== strpos( $slug, 'importe' ) ) {
return ( 1 === $index ) ? '1250' : '3475.50';
}

// Fields for autorizacionviaje.odt template.
if ( false !== strpos( $slug, 'lugar' ) ) {
return 'Madrid';
}

if ( false !== strpos( $slug, 'invitante' ) ) {
return 'Ministerio de Educación';
}

if ( false !== strpos( $slug, 'temas' ) ) {
return 'Discusión de programas de innovación educativa y coordinación interterritorial.';
}

if ( false !== strpos( $slug, 'pagador' ) ) {
return 'Consejería de Educación del Gobierno de Canarias';
}

if ( false !== strpos( $slug, 'apellido1' ) ) {
return ( 1 === $index ) ? 'García' : 'Rodríguez';
}

if ( false !== strpos( $slug, 'apellido2' ) ) {
return ( 1 === $index ) ? 'López' : 'Martínez';
}

// Fields for gastossuplidos.odt template.
if ( false !== strpos( $slug, 'iban' ) ) {
return 'ES9121000418450200051332';
}

if ( false !== strpos( $slug, 'nombre_completo' ) ) {
return ( 1 === $index ) ? 'María García López' : 'Juan Rodríguez Martínez';
}

if ( false !== strpos( $slug, 'body' ) || false !== strpos( $slug, 'cuerpo' ) ) {
$rich = '<h3>' . __( 'Test heading', 'documentate' ) . '</h3>';
$rich .= '<p>' . __( 'First paragraph with example text.', 'documentate' ) . '</p>';
/* translators: 1: bold text label, 2: italic text label, 3: underline text label. */
$rich .= '<p>' . sprintf( __( 'Second paragraph with %1$s, %2$s and %3$s.', 'documentate' ), '<strong>' . __( 'bold', 'documentate' ) . '</strong>', '<em>' . __( 'italics', 'documentate' ) . '</em>', '<u>' . __( 'underline', 'documentate' ) . '</u>' ) . '</p>';
$rich .= '<ul><li>' . __( 'Item one', 'documentate' ) . '</li><li>' . __( 'Item two', 'documentate' ) . '</li></ul>';
$rich .= '<table><tr><th>' . __( 'Col 1', 'documentate' ) . '</th><th>' . __( 'Col 2', 'documentate' ) . '</th></tr><tr><td>' . __( 'Data A1', 'documentate' ) . '</td><td>' . __( 'Data A2', 'documentate' ) . '</td></tr><tr><td>' . __( 'Data B1', 'documentate' ) . '</td><td>' . __( 'Data B2', 'documentate' ) . '</td></tr></table>';
return $rich;
}

// Generic HTML content fields: enrich demo data with formatted HTML.
if (
false !== strpos( $slug, 'content' ) ||
false !== strpos( $slug, 'contenido' ) ||
false !== strpos( $slug, 'html' )
) {
$rich = '<h3>' . __( 'Test heading', 'documentate' ) . '</h3>';
$rich .= '<p>' . __( 'First paragraph with example text.', 'documentate' ) . '</p>';
/* translators: 1: bold text label, 2: italic text label, 3: underline text label. */
$rich .= '<p>' . sprintf( __( 'Second paragraph with %1$s, %2$s and %3$s.', 'documentate' ), '<strong>' . __( 'bold', 'documentate' ) . '</strong>', '<em>' . __( 'italics', 'documentate' ) . '</em>', '<u>' . __( 'underline', 'documentate' ) . '</u>' ) . '</p>';
$rich .= '<ul><li>' . __( 'Item one', 'documentate' ) . '</li><li>' . __( 'Item two', 'documentate' ) . '</li></ul>';
$rich .= '<table><tr><th>' . __( 'Col 1', 'documentate' ) . '</th><th>' . __( 'Col 2', 'documentate' ) . '</th></tr><tr><td>' . __( 'Data A1', 'documentate' ) . '</td><td>' . __( 'Data A2', 'documentate' ) . '</td></tr><tr><td>' . __( 'Data B1', 'documentate' ) . '</td><td>' . __( 'Data B2', 'documentate' ) . '</td></tr></table>';
return $rich;
}

if ( false !== strpos( $slug, 'keywords' ) || false !== strpos( $slug, 'palabras' ) ) {
return __( 'keywords, tags, demo', 'documentate' );
}

return __( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'documentate' );
}

Check warning

Code scanning / PHPMD

Code Size Rules: NPathComplexity Warning

The method generate_demo_scalar_value() has an NPath complexity of 3715041853440. The configured NPath complexity threshold is 500.
Comment on lines +1413 to +1571
public static function generate_demo_scalar_value( $slug, $type, $data_type, $index = 1, $context = array() ) {
$slug = strtolower( (string) $slug );
$type = sanitize_key( $type );
$data_type = sanitize_key( $data_type );
$index = max( 1, absint( $index ) );

$document_title = isset( $context['document_title'] ) ? (string) $context['document_title'] : __( 'Demo resolution', 'documentate' );
$number_value = (string) ( 1 + $index );

if ( 'date' === $data_type ) {
$month = max( 1, min( 12, $index ) );
$day = max( 1, min( 28, 10 + $index ) );
return sprintf( '2025-%02d-%02d', $month, $day );
}

if ( 'number' === $data_type ) {
return $number_value;
}

if ( 'boolean' === $data_type ) {
return ( $index % 2 ) ? '1' : '0';
}

if ( false !== strpos( $slug, 'email' ) ) {
return 'demo' . $index . '@ejemplo.es';
}

if ( false !== strpos( $slug, 'phone' ) || false !== strpos( $slug, 'tel' ) ) {
return '+3460000000' . $index;
}

if ( false !== strpos( $slug, 'dni' ) ) {
return '1234567' . $index . 'A';
}

if ( false !== strpos( $slug, 'url' ) || false !== strpos( $slug, 'sitio' ) || false !== strpos( $slug, 'web' ) ) {
return 'https://ejemplo.es/recurso-' . $index;
}

if ( false !== strpos( $slug, 'nombre' ) || false !== strpos( $slug, 'name' ) ) {
return ( 1 === $index ) ? 'Jane Doe' : 'John Smith';
}

if ( false !== strpos( $slug, 'title' ) || false !== strpos( $slug, 'titulo' ) || 'post_title' === $slug ) {
if ( 'post_title' === $slug ) {
return $document_title;
}

/* translators: %d: item sequence number. */
return sprintf( __( 'Demo item %d', 'documentate' ), $index );
}

if ( false !== strpos( $slug, 'summary' ) || false !== strpos( $slug, 'resumen' ) ) {
/* translators: %d: item sequence number. */
return sprintf( __( 'Demo summary %d with brief information.', 'documentate' ), $index );
}

if ( false !== strpos( $slug, 'objeto' ) ) {
return __( 'Subject of the example resolution to illustrate the workflow.', 'documentate' );
}

if ( false !== strpos( $slug, 'antecedentes' ) ) {
return __( 'Background facts written with test content.', 'documentate' );
}

if ( false !== strpos( $slug, 'fundamentos' ) ) {
return __( 'Legal grounds for testing with generic references.', 'documentate' );
}

if ( false !== strpos( $slug, 'resuelv' ) ) {
return '<p>' . __( 'First. Approve the demo action.', 'documentate' ) . '</p><p>' . __( 'Second. Notify interested parties.', 'documentate' ) . '</p>';
}

if ( false !== strpos( $slug, 'observaciones' ) ) {
return __( 'Additional observations to complete the template.', 'documentate' );
}

// Repeater "gastos" fields (table row repeater).
if ( false !== strpos( $slug, 'proveedor' ) ) {
return ( 1 === $index ) ? 'Suministros Ejemplo S.L.' : 'Servicios Demo S.A.';
}

if ( 'cif' === $slug ) {
return ( 1 === $index ) ? 'B12345678' : 'A87654321';
}

if ( false !== strpos( $slug, 'factura' ) ) {
return sprintf( '%03d/2025', 100 + $index );
}

if ( false !== strpos( $slug, 'importe' ) ) {
return ( 1 === $index ) ? '1250' : '3475.50';
}

// Fields for autorizacionviaje.odt template.
if ( false !== strpos( $slug, 'lugar' ) ) {
return 'Madrid';
}

if ( false !== strpos( $slug, 'invitante' ) ) {
return 'Ministerio de Educación';
}

if ( false !== strpos( $slug, 'temas' ) ) {
return 'Discusión de programas de innovación educativa y coordinación interterritorial.';
}

if ( false !== strpos( $slug, 'pagador' ) ) {
return 'Consejería de Educación del Gobierno de Canarias';
}

if ( false !== strpos( $slug, 'apellido1' ) ) {
return ( 1 === $index ) ? 'García' : 'Rodríguez';
}

if ( false !== strpos( $slug, 'apellido2' ) ) {
return ( 1 === $index ) ? 'López' : 'Martínez';
}

// Fields for gastossuplidos.odt template.
if ( false !== strpos( $slug, 'iban' ) ) {
return 'ES9121000418450200051332';
}

if ( false !== strpos( $slug, 'nombre_completo' ) ) {
return ( 1 === $index ) ? 'María García López' : 'Juan Rodríguez Martínez';
}

if ( false !== strpos( $slug, 'body' ) || false !== strpos( $slug, 'cuerpo' ) ) {
$rich = '<h3>' . __( 'Test heading', 'documentate' ) . '</h3>';
$rich .= '<p>' . __( 'First paragraph with example text.', 'documentate' ) . '</p>';
/* translators: 1: bold text label, 2: italic text label, 3: underline text label. */
$rich .= '<p>' . sprintf( __( 'Second paragraph with %1$s, %2$s and %3$s.', 'documentate' ), '<strong>' . __( 'bold', 'documentate' ) . '</strong>', '<em>' . __( 'italics', 'documentate' ) . '</em>', '<u>' . __( 'underline', 'documentate' ) . '</u>' ) . '</p>';
$rich .= '<ul><li>' . __( 'Item one', 'documentate' ) . '</li><li>' . __( 'Item two', 'documentate' ) . '</li></ul>';
$rich .= '<table><tr><th>' . __( 'Col 1', 'documentate' ) . '</th><th>' . __( 'Col 2', 'documentate' ) . '</th></tr><tr><td>' . __( 'Data A1', 'documentate' ) . '</td><td>' . __( 'Data A2', 'documentate' ) . '</td></tr><tr><td>' . __( 'Data B1', 'documentate' ) . '</td><td>' . __( 'Data B2', 'documentate' ) . '</td></tr></table>';
return $rich;
}

// Generic HTML content fields: enrich demo data with formatted HTML.
if (
false !== strpos( $slug, 'content' ) ||
false !== strpos( $slug, 'contenido' ) ||
false !== strpos( $slug, 'html' )
) {
$rich = '<h3>' . __( 'Test heading', 'documentate' ) . '</h3>';
$rich .= '<p>' . __( 'First paragraph with example text.', 'documentate' ) . '</p>';
/* translators: 1: bold text label, 2: italic text label, 3: underline text label. */
$rich .= '<p>' . sprintf( __( 'Second paragraph with %1$s, %2$s and %3$s.', 'documentate' ), '<strong>' . __( 'bold', 'documentate' ) . '</strong>', '<em>' . __( 'italics', 'documentate' ) . '</em>', '<u>' . __( 'underline', 'documentate' ) . '</u>' ) . '</p>';
$rich .= '<ul><li>' . __( 'Item one', 'documentate' ) . '</li><li>' . __( 'Item two', 'documentate' ) . '</li></ul>';
$rich .= '<table><tr><th>' . __( 'Col 1', 'documentate' ) . '</th><th>' . __( 'Col 2', 'documentate' ) . '</th></tr><tr><td>' . __( 'Data A1', 'documentate' ) . '</td><td>' . __( 'Data A2', 'documentate' ) . '</td></tr><tr><td>' . __( 'Data B1', 'documentate' ) . '</td><td>' . __( 'Data B2', 'documentate' ) . '</td></tr></table>';
return $rich;
}

if ( false !== strpos( $slug, 'keywords' ) || false !== strpos( $slug, 'palabras' ) ) {
return __( 'keywords, tags, demo', 'documentate' );
}

return __( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'documentate' );
}

Check warning

Code scanning / PHPMD

Code Size Rules: ExcessiveMethodLength Warning

The method generate_demo_scalar_value() has 159 lines of code. Current threshold is set to 150. Avoid really long methods.
Comment on lines +2384 to +2458
public function add_admin_filters( $post_type, $which ) {
if ( 'documentate_document' !== $post_type || 'top' !== $which ) {
return;
}

// Author filter.
$authors = get_users(
array(
'has_published_posts' => array( 'documentate_document' ),
'fields' => array( 'ID', 'display_name' ),
'orderby' => 'display_name',
)
);

if ( ! empty( $authors ) ) {
$current_author = isset( $_GET['author'] ) ? absint( $_GET['author'] ) : 0;
echo '<select name="author" id="filter-by-author">';
echo '<option value="">' . esc_html__( 'All authors', 'documentate' ) . '</option>';
foreach ( $authors as $author ) {
printf(
'<option value="%d"%s>%s</option>',
absint( $author->ID ),
selected( $current_author, $author->ID, false ),
esc_html( $author->display_name )
);
}
echo '</select>';
}

// Document type filter (taxonomy dropdown).
$doc_types = get_terms(
array(
'taxonomy' => 'documentate_doc_type',
'hide_empty' => false,
)
);

if ( ! is_wp_error( $doc_types ) && ! empty( $doc_types ) ) {
$current_type = isset( $_GET['documentate_doc_type'] ) ? sanitize_text_field( wp_unslash( $_GET['documentate_doc_type'] ) ) : '';
echo '<select name="documentate_doc_type" id="filter-by-doc-type">';
echo '<option value="">' . esc_html__( 'All document types', 'documentate' ) . '</option>';
foreach ( $doc_types as $doc_type ) {
printf(
'<option value="%s"%s>%s</option>',
esc_attr( $doc_type->slug ),
selected( $current_type, $doc_type->slug, false ),
esc_html( $doc_type->name )
);
}
echo '</select>';
}

// Category filter (if taxonomy exists).
$categories = get_terms(
array(
'taxonomy' => 'category',
'hide_empty' => false,
)
);

if ( ! is_wp_error( $categories ) && ! empty( $categories ) ) {
$current_cat = isset( $_GET['category_name'] ) ? sanitize_text_field( wp_unslash( $_GET['category_name'] ) ) : '';
echo '<select name="category_name" id="filter-by-category">';
echo '<option value="">' . esc_html__( 'All categories', 'documentate' ) . '</option>';
foreach ( $categories as $category ) {
printf(
'<option value="%s"%s>%s</option>',
esc_attr( $category->slug ),
selected( $current_cat, $category->slug, false ),
esc_html( $category->name )
);
}
echo '</select>';
}
}

Check warning

Code scanning / PHPMD

Code Size Rules: NPathComplexity Warning

The method add_admin_filters() has an NPath complexity of 540. The configured NPath complexity threshold is 500.
Comment on lines +2466 to +2542
public function apply_admin_filters( $query ) {
if ( ! is_admin() || ! $query->is_main_query() ) {
return;
}

$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( ! $screen || 'edit-documentate_document' !== $screen->id ) {
return;
}

// Hide archived posts unless specifically requesting them.
$post_status = $query->get( 'post_status' );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$show_archived = isset( $_GET['post_status'] ) && 'archived' === sanitize_key( $_GET['post_status'] );

if ( empty( $post_status ) && ! $show_archived ) {
// Default view: exclude archived.
$query->set( 'post_status', array( 'publish', 'pending', 'draft', 'private', 'future' ) );
}

$orderby = $query->get( 'orderby' );

// Handle sorting by author.
if ( 'author_name' === $orderby ) {
$query->set( 'orderby', 'author' );
}

// Handle sorting by document type.
if ( 'doc_type' === $orderby ) {
add_filter(
'posts_clauses',
function ( $clauses, $wp_query ) {
global $wpdb;

if ( $wp_query->get( 'orderby' ) !== 'doc_type' ) {
return $clauses;
}

$order = strtoupper( $wp_query->get( 'order' ) ) === 'ASC' ? 'ASC' : 'DESC';

$clauses['join'] .= " LEFT JOIN {$wpdb->term_relationships} AS dtr ON ({$wpdb->posts}.ID = dtr.object_id)";
$clauses['join'] .= " LEFT JOIN {$wpdb->term_taxonomy} AS dtt ON (dtr.term_taxonomy_id = dtt.term_taxonomy_id AND dtt.taxonomy = 'documentate_doc_type')";
$clauses['join'] .= " LEFT JOIN {$wpdb->terms} AS dt ON (dtt.term_id = dt.term_id)";
$clauses['orderby'] = "dt.name {$order}, " . $clauses['orderby'];

return $clauses;
},
10,
2
);
}

// Handle sorting by category.
if ( 'category_name' === $orderby ) {
add_filter(
'posts_clauses',
function ( $clauses, $wp_query ) {
global $wpdb;

if ( $wp_query->get( 'orderby' ) !== 'category_name' ) {
return $clauses;
}

$order = strtoupper( $wp_query->get( 'order' ) ) === 'ASC' ? 'ASC' : 'DESC';

$clauses['join'] .= " LEFT JOIN {$wpdb->term_relationships} AS ctr ON ({$wpdb->posts}.ID = ctr.object_id)";
$clauses['join'] .= " LEFT JOIN {$wpdb->term_taxonomy} AS ctt ON (ctr.term_taxonomy_id = ctt.term_taxonomy_id AND ctt.taxonomy = 'category')";
$clauses['join'] .= " LEFT JOIN {$wpdb->terms} AS ct ON (ctt.term_id = ct.term_id)";
$clauses['orderby'] = "ct.name {$order}, " . $clauses['orderby'];

return $clauses;
},
10,
2
);
}
}

Check warning

Code scanning / PHPMD

Code Size Rules: CyclomaticComplexity Warning

The method apply_admin_filters() has a Cyclomatic Complexity of 16. The configured cyclomatic complexity threshold is 15.
Comment on lines +2466 to +2542
public function apply_admin_filters( $query ) {
if ( ! is_admin() || ! $query->is_main_query() ) {
return;
}

$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( ! $screen || 'edit-documentate_document' !== $screen->id ) {
return;
}

// Hide archived posts unless specifically requesting them.
$post_status = $query->get( 'post_status' );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$show_archived = isset( $_GET['post_status'] ) && 'archived' === sanitize_key( $_GET['post_status'] );

if ( empty( $post_status ) && ! $show_archived ) {
// Default view: exclude archived.
$query->set( 'post_status', array( 'publish', 'pending', 'draft', 'private', 'future' ) );
}

$orderby = $query->get( 'orderby' );

// Handle sorting by author.
if ( 'author_name' === $orderby ) {
$query->set( 'orderby', 'author' );
}

// Handle sorting by document type.
if ( 'doc_type' === $orderby ) {
add_filter(
'posts_clauses',
function ( $clauses, $wp_query ) {
global $wpdb;

if ( $wp_query->get( 'orderby' ) !== 'doc_type' ) {
return $clauses;
}

$order = strtoupper( $wp_query->get( 'order' ) ) === 'ASC' ? 'ASC' : 'DESC';

$clauses['join'] .= " LEFT JOIN {$wpdb->term_relationships} AS dtr ON ({$wpdb->posts}.ID = dtr.object_id)";
$clauses['join'] .= " LEFT JOIN {$wpdb->term_taxonomy} AS dtt ON (dtr.term_taxonomy_id = dtt.term_taxonomy_id AND dtt.taxonomy = 'documentate_doc_type')";
$clauses['join'] .= " LEFT JOIN {$wpdb->terms} AS dt ON (dtt.term_id = dt.term_id)";
$clauses['orderby'] = "dt.name {$order}, " . $clauses['orderby'];

return $clauses;
},
10,
2
);
}

// Handle sorting by category.
if ( 'category_name' === $orderby ) {
add_filter(
'posts_clauses',
function ( $clauses, $wp_query ) {
global $wpdb;

if ( $wp_query->get( 'orderby' ) !== 'category_name' ) {
return $clauses;
}

$order = strtoupper( $wp_query->get( 'order' ) ) === 'ASC' ? 'ASC' : 'DESC';

$clauses['join'] .= " LEFT JOIN {$wpdb->term_relationships} AS ctr ON ({$wpdb->posts}.ID = ctr.object_id)";
$clauses['join'] .= " LEFT JOIN {$wpdb->term_taxonomy} AS ctt ON (ctr.term_taxonomy_id = ctt.term_taxonomy_id AND ctt.taxonomy = 'category')";
$clauses['join'] .= " LEFT JOIN {$wpdb->terms} AS ct ON (ctt.term_id = ct.term_id)";
$clauses['orderby'] = "ct.name {$order}, " . $clauses['orderby'];

return $clauses;
},
10,
2
);
}
}

Check warning

Code scanning / PHPMD

Code Size Rules: NPathComplexity Warning

The method apply_admin_filters() has an NPath complexity of 2700. The configured NPath complexity threshold is 500.
@codecov
Copy link

codecov bot commented Dec 5, 2025

Codecov Report

❌ Patch coverage is 86.08573% with 198 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
includes/class-documentate-demo-data.php 92.22% 82 Missing ⚠️
includes/class-documentate-admin-helper.php 14.08% 61 Missing ⚠️
.../custom-post-types/class-documentate-documents.php 73.13% 54 Missing ⚠️
documentate.php 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants