Conversation
There was a problem hiding this comment.
💡 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".
| 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' ) ) ); |
There was a problem hiding this comment.
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 Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
This pull request introduces several improvements focused on error handling, code organization, and testability in the
Documentateplugin, 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:
class-documentate-admin-helper.phpto use dedicated response preparation methods and returnWP_Errorobjects on failure instead of immediately callingwp_die(). This centralizes error handling and improves maintainability. [1] [2] [3] [4]Converter Page Rendering Improvements:
class-documentate-admin-helper.phpto separate header preparation and template path resolution into their own methods, improving readability and allowing easier testing. [1] [2]WP-CLI Command Structure & Testability:
Documentate_WPCLIto no longer extendWP_CLI_Commanddirectly, 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:
Makefileto 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:
.gitattributesfile to be excluded from export, helping to reduce package size and prevent unnecessary files from being distributed. [1] [2]