Skip to content

Increase coverage#72

Open
erseco wants to merge 6 commits intomainfrom
feature/increase-coverage-to-90
Open

Increase coverage#72
erseco wants to merge 6 commits intomainfrom
feature/increase-coverage-to-90

Conversation

@erseco
Copy link
Collaborator

@erseco erseco commented Dec 1, 2025

This pull request introduces several improvements focused on error handling, code organization, and testability in the Documentate plugin, especially in the admin helper and WP-CLI integration. The main changes include refactoring admin helper methods for better error management, restructuring WP-CLI command registration for improved testability, and enhancing the coverage summary reporting. Below are the most significant updates grouped by theme:

Admin Helper Error Handling & Refactoring:

  • Refactored PDF preview and stream handlers in class-documentate-admin-helper.php to use dedicated response preparation methods and return WP_Error objects on failure instead of immediately calling wp_die(). This centralizes error handling and improves maintainability. [1] [2] [3] [4]
  • Added helper methods for AJAX document generation, separating nonce verification, permission checks, and error building, making the logic clearer and more robust. [1] [2] [3]

Converter Page Rendering Improvements:

  • Refactored the converter page rendering in class-documentate-admin-helper.php to separate header preparation and template path resolution into their own methods, improving readability and allowing easier testing. [1] [2]

WP-CLI Command Structure & Testability:

  • Changed Documentate_WPCLI to no longer extend WP_CLI_Command directly, and moved command registration inside a conditional block, allowing easier mocking and testing of CLI methods. Also, abstracted WP-CLI output and confirmation methods for test overrides. [1] [2] [3]

Test Coverage Reporting:

  • Updated the Makefile to use a new AWK script (scripts/coverage-summary.awk) for coverage summary reporting, providing more readable and targeted output. Added the AWK script to the repository. [1] [2]

Packaging & Export Ignore Updates:

  • Added several new paths to the .gitattributes file to be excluded from export, helping to reduce package size and prevent unnecessary files from being distributed. [1] [2]

Copy link

@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 "@codex address that feedback".

Comment on lines 900 to +980
public function ajax_generate_document() {
// Verify nonce first before accessing any other POST data.
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'documentate_generate' ) ) {
wp_send_json_error( array( 'message' => __( 'Security check failed.', 'documentate' ) ) );

Choose a reason for hiding this comment

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

P1 Badge Align AJAX generate nonce with issued nonce action

The new nonce check in ajax_generate_document() now verifies against the fixed action 'documentate_generate', but the configuration sent to the front‑end still issues nonces using the post‑specific action 'documentate_generate_' . $post_id (see build_actions_script_config()). Any document generation request made from the UI will therefore fail nonce verification and return the “Security check failed.” JSON error, blocking downloads/previews for all posts. The handler should validate against the same post‑scoped action it generates.

Useful? React with 👍 / 👎.

@codecov
Copy link

codecov bot commented Dec 1, 2025

Codecov Report

❌ Patch coverage is 42.71845% with 59 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
includes/class-documentate-admin-helper.php 43.75% 45 Missing ⚠️
includes/class-documentate-wpcli.php 39.13% 14 Missing ⚠️

📢 Thoughts on this report? Let us know!

Comment on lines 460 to 511
protected function prepare_preview_stream_response( $post_id, $nonce, $user_id ) {
if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) {
wp_die( esc_html__( 'Insufficient permissions.', 'documentate' ) );
return new WP_Error( 'documentate_permission', __( 'Insufficient permissions.', 'documentate' ) );
}

if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'documentate_preview_stream_' . $post_id ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
wp_die( esc_html__( 'Invalid nonce.', 'documentate' ) );
if ( ! wp_verify_nonce( $nonce, 'documentate_preview_stream_' . $post_id ) ) {
return new WP_Error( 'documentate_nonce', __( 'Invalid nonce.', 'documentate' ) );
}

$user_id = get_current_user_id();
if ( $user_id <= 0 ) {
wp_die( esc_html__( 'User not authenticated.', 'documentate' ) );
return new WP_Error( 'documentate_auth', __( 'User not authenticated.', 'documentate' ) );
}

$key = $this->get_preview_stream_transient_key( $post_id, $user_id );
$filename = get_transient( $key );

if ( false === $filename || '' === $filename ) {
$this->ensure_document_generator();
$result = Documentate_Document_Generator::generate_pdf( $post_id );
if ( is_wp_error( $result ) ) {
wp_die( esc_html__( 'Could not generate the PDF for preview.', 'documentate' ) );
return new WP_Error( 'documentate_generate', __( 'Could not generate the PDF for preview.', 'documentate' ) );
}

$filename = basename( $result );
$this->remember_preview_stream_file( $post_id, $filename );
}

$filename = sanitize_file_name( (string) $filename );
if ( '' === $filename ) {
wp_die( esc_html__( 'Preview file not available.', 'documentate' ) );
return new WP_Error( 'documentate_missing', __( 'Preview file not available.', 'documentate' ) );
}

$upload_dir = wp_upload_dir();
$path = trailingslashit( $upload_dir['basedir'] ) . 'documentate/' . $filename;

$fs = $this->get_wp_filesystem();
if ( is_wp_error( $fs ) ) {
wp_die( esc_html( $fs->get_error_message() ) );
return $fs;
}

if ( ! $fs->exists( $path ) || ! $fs->is_readable( $path ) ) {
wp_die( esc_html__( 'Could not access the generated PDF file.', 'documentate' ) );
return new WP_Error( 'documentate_access', __( 'Could not access the generated PDF file.', 'documentate' ) );
}

return array(
'path' => $path,
'filename' => $filename,
);
}

/**
* Send PDF response headers and content.

Check warning

Code scanning / PHPMD

Code Size Rules: NPathComplexity Warning

The method prepare_preview_stream_response() has an NPath complexity of 576. The configured NPath complexity threshold is 500.
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 782 to 877
public static function apply_docx_metadata( $docx_path, $metadata ) {
if ( empty( $metadata ) || ! is_array( $metadata ) ) {
return true;
}

// Check if any metadata value is non-empty.
$has_values = false;
foreach ( $metadata as $value ) {
if ( ! empty( $value ) ) {
$has_values = true;
break;
}
}
if ( ! $has_values ) {
return true;
}

if ( ! class_exists( 'ZipArchive' ) ) {
return new WP_Error( 'documentate_docx_zip_missing', __( 'ZipArchive is not available for metadata.', 'documentate' ) );
}

$zip = new ZipArchive();
if ( true !== $zip->open( $docx_path ) ) {
return new WP_Error( 'documentate_docx_open_failed', __( 'Could not open the DOCX file for metadata.', 'documentate' ) );
}

$xml = $zip->getFromName( 'docProps/core.xml' );
if ( false === $xml ) {
$zip->close();
return new WP_Error( 'documentate_core_missing', __( 'docProps/core.xml not found in DOCX.', 'documentate' ) );
}

$dom = self::create_xml_document( $xml );
if ( ! $dom ) {
$zip->close();
return new WP_Error( 'documentate_core_parse', __( 'Could not parse core.xml.', 'documentate' ) );
}

$xpath = new DOMXPath( $dom );
$xpath->registerNamespace( 'cp', self::CP_NS );
$xpath->registerNamespace( 'dc', self::DC_NS );

// Find cp:coreProperties element.
$core_props_list = $xpath->query( '//cp:coreProperties' );
if ( 0 === $core_props_list->length ) {
$zip->close();
return new WP_Error( 'documentate_core_element', __( 'cp:coreProperties element not found.', 'documentate' ) );
}
$core_props = $core_props_list->item( 0 );

// Helper to set or update an element.
$set_element = function ( $ns_uri, $prefix, $local_name, $value ) use ( $dom, $xpath, $core_props ) {
if ( empty( $value ) ) {
return;
}
$query = ".//{$prefix}:{$local_name}";
$nodes = $xpath->query( $query, $core_props );

if ( $nodes->length > 0 ) {
// Update existing element.
$nodes->item( 0 )->textContent = $value;
} else {
// Create new element.
$new_el = $dom->createElementNS( $ns_uri, "{$prefix}:{$local_name}" );
$new_el->appendChild( $dom->createTextNode( $value ) );
$core_props->appendChild( $new_el );
}
};

// Set title.
if ( ! empty( $metadata['title'] ) ) {
$set_element( self::DC_NS, 'dc', 'title', $metadata['title'] );
}

// Set subject.
if ( ! empty( $metadata['subject'] ) ) {
$set_element( self::DC_NS, 'dc', 'subject', $metadata['subject'] );
}

// Set creator (author).
if ( ! empty( $metadata['author'] ) ) {
$set_element( self::DC_NS, 'dc', 'creator', $metadata['author'] );
}

// Set keywords (DOCX uses cp:keywords as comma-separated string).
if ( ! empty( $metadata['keywords'] ) ) {
$set_element( self::CP_NS, 'cp', 'keywords', $metadata['keywords'] );
}

// Save updated core.xml.
$updated_xml = $dom->saveXML();
$zip->addFromString( 'docProps/core.xml', $updated_xml );
$zip->close();

return true;
}

Check warning

Code scanning / PHPMD

Code Size Rules: CyclomaticComplexity Warning

The method apply_docx_metadata() has a Cyclomatic Complexity of 17. The configured cyclomatic complexity threshold is 15.
Comment on lines 782 to 877
public static function apply_docx_metadata( $docx_path, $metadata ) {
if ( empty( $metadata ) || ! is_array( $metadata ) ) {
return true;
}

// Check if any metadata value is non-empty.
$has_values = false;
foreach ( $metadata as $value ) {
if ( ! empty( $value ) ) {
$has_values = true;
break;
}
}
if ( ! $has_values ) {
return true;
}

if ( ! class_exists( 'ZipArchive' ) ) {
return new WP_Error( 'documentate_docx_zip_missing', __( 'ZipArchive is not available for metadata.', 'documentate' ) );
}

$zip = new ZipArchive();
if ( true !== $zip->open( $docx_path ) ) {
return new WP_Error( 'documentate_docx_open_failed', __( 'Could not open the DOCX file for metadata.', 'documentate' ) );
}

$xml = $zip->getFromName( 'docProps/core.xml' );
if ( false === $xml ) {
$zip->close();
return new WP_Error( 'documentate_core_missing', __( 'docProps/core.xml not found in DOCX.', 'documentate' ) );
}

$dom = self::create_xml_document( $xml );
if ( ! $dom ) {
$zip->close();
return new WP_Error( 'documentate_core_parse', __( 'Could not parse core.xml.', 'documentate' ) );
}

$xpath = new DOMXPath( $dom );
$xpath->registerNamespace( 'cp', self::CP_NS );
$xpath->registerNamespace( 'dc', self::DC_NS );

// Find cp:coreProperties element.
$core_props_list = $xpath->query( '//cp:coreProperties' );
if ( 0 === $core_props_list->length ) {
$zip->close();
return new WP_Error( 'documentate_core_element', __( 'cp:coreProperties element not found.', 'documentate' ) );
}
$core_props = $core_props_list->item( 0 );

// Helper to set or update an element.
$set_element = function ( $ns_uri, $prefix, $local_name, $value ) use ( $dom, $xpath, $core_props ) {
if ( empty( $value ) ) {
return;
}
$query = ".//{$prefix}:{$local_name}";
$nodes = $xpath->query( $query, $core_props );

if ( $nodes->length > 0 ) {
// Update existing element.
$nodes->item( 0 )->textContent = $value;
} else {
// Create new element.
$new_el = $dom->createElementNS( $ns_uri, "{$prefix}:{$local_name}" );
$new_el->appendChild( $dom->createTextNode( $value ) );
$core_props->appendChild( $new_el );
}
};

// Set title.
if ( ! empty( $metadata['title'] ) ) {
$set_element( self::DC_NS, 'dc', 'title', $metadata['title'] );
}

// Set subject.
if ( ! empty( $metadata['subject'] ) ) {
$set_element( self::DC_NS, 'dc', 'subject', $metadata['subject'] );
}

// Set creator (author).
if ( ! empty( $metadata['author'] ) ) {
$set_element( self::DC_NS, 'dc', 'creator', $metadata['author'] );
}

// Set keywords (DOCX uses cp:keywords as comma-separated string).
if ( ! empty( $metadata['keywords'] ) ) {
$set_element( self::CP_NS, 'cp', 'keywords', $metadata['keywords'] );
}

// Save updated core.xml.
$updated_xml = $dom->saveXML();
$zip->addFromString( 'docProps/core.xml', $updated_xml );
$zip->close();

return true;
}

Check warning

Code scanning / PHPMD

Code Size Rules: NPathComplexity Warning

The method apply_docx_metadata() has an NPath complexity of 36864. The configured NPath complexity threshold is 500.
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.
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.

1 participant