Skip to content

Changes#73

Merged
erseco merged 39 commits intomainfrom
feature/better-samples
Dec 2, 2025
Merged

Changes#73
erseco merged 39 commits intomainfrom
feature/better-samples

Conversation

@erseco
Copy link
Copy Markdown
Collaborator

@erseco erseco commented Dec 1, 2025

This pull request updates the default document templates and demo data for the Documentate plugin, focusing on transitioning from generic test templates (plantilla.odt/plantilla.docx) to domain-specific ones for "Resolución Administrativa" and related demo documents. It also ensures that tests and seed data use these new templates and document types.

Template and Demo Document Updates

  • Replaces the default ODT and DOCX templates with resolucion.odt and demo-wp-documentate.docx throughout the codebase and tests, making the demo and seed data more representative of real-world administrative resolutions. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]

Demo Data and Document Type Seeding

  • Updates the default seeded document type to "Resolución Administrativa" with a new slug, name, description, and associated template, replacing the previous generic test types.
  • Adds logic to create three specific demo documents for the "Resolución Administrativa" type, each with realistic structured fields and annexes, improving the quality and relevance of seeded demo content.
  • Ensures demo documents for other document types are also created, but excludes the new "Resolución Administrativa" type from this generic seeding.

Documentation

  • Updates documentation references to reflect the new template files used in the fixtures directory.

These changes make the plugin's seeded content and tests more realistic and domain-specific, improving both developer experience and demo usability.

@erseco erseco self-assigned this Dec 1, 2025
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@codecov
Copy link
Copy Markdown

codecov bot commented Dec 1, 2025

Add summary and text report steps to show detailed information when
PHPMD finds issues, instead of just directing to Security tab.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Split documentate_get_resolucion_demo_data() into 3 helper functions
to comply with PHPMD ExcessiveMethodLength rule (max 150 lines).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread documentate.php Fixed
Comment thread documentate.php Fixed
Comment on lines 2071 to 2192
@@ -2001,9 +2118,10 @@ private static function convert_table_node_to_docx( DOMDocument $doc, DOMElement
* @param DOMElement|null $base_rpr Base run properties.
* @param array<string,bool> $formatting Formatting flags.
* @param array<string,mixed>|null $relationships Relationships context.
* @param string|null $alignment Cell alignment (left, center, right, justify).
* @return array<int,DOMElement> Array of paragraph elements.
*/
private static function convert_cell_content_to_docx( DOMDocument $doc, $children, $base_rpr, array $formatting, &$relationships ) {
private static function convert_cell_content_to_docx( DOMDocument $doc, $children, $base_rpr, array $formatting, &$relationships, $alignment = null ) {
$result = array();
$current_runs = array();

@@ -2027,7 +2145,7 @@ private static function convert_cell_content_to_docx( DOMDocument $doc, $childre
if ( 'ul' === $tag || 'ol' === $tag ) {
// Flush any accumulated inline runs.
if ( ! empty( $current_runs ) ) {
$result[] = self::create_paragraph_from_runs( $doc, $current_runs, $base_rpr );
$result[] = self::create_paragraph_from_runs( $doc, $current_runs, $base_rpr, $alignment );
$current_runs = array();
}
// Convert list to paragraphs.
@@ -2039,7 +2157,7 @@ private static function convert_cell_content_to_docx( DOMDocument $doc, $childre
if ( 'table' === $tag ) {
// Flush any accumulated inline runs.
if ( ! empty( $current_runs ) ) {
$result[] = self::create_paragraph_from_runs( $doc, $current_runs, $base_rpr );
$result[] = self::create_paragraph_from_runs( $doc, $current_runs, $base_rpr, $alignment );
$current_runs = array();
}
// Convert nested table.
@@ -2053,12 +2171,17 @@ private static function convert_cell_content_to_docx( DOMDocument $doc, $childre
if ( 'p' === $tag || 'div' === $tag ) {
// Flush any accumulated inline runs.
if ( ! empty( $current_runs ) ) {
$result[] = self::create_paragraph_from_runs( $doc, $current_runs, $base_rpr );
$result[] = self::create_paragraph_from_runs( $doc, $current_runs, $base_rpr, $alignment );
$current_runs = array();
}
// Extract paragraph-specific alignment or use cell alignment.
$p_alignment = self::extract_text_alignment( $child );
if ( null === $p_alignment ) {
$p_alignment = $alignment;
}
// Convert paragraph content.
$p_runs = self::collect_runs_from_children( $doc, $child->childNodes, $base_rpr, $formatting, $relationships );
$result[] = self::create_paragraph_from_runs( $doc, $p_runs, $base_rpr );
$result[] = self::create_paragraph_from_runs( $doc, $p_runs, $base_rpr, $p_alignment );
continue;
}

@@ -2068,7 +2191,7 @@ private static function convert_cell_content_to_docx( DOMDocument $doc, $childre

// Flush remaining inline runs.

Check warning

Code scanning / PHPMD

Code Size Rules: CyclomaticComplexity Warning

The method convert_cell_content_to_docx() has a Cyclomatic Complexity of 16. The configured cyclomatic complexity threshold is 15.
Comment on lines 2071 to 2192
@@ -2001,9 +2118,10 @@ private static function convert_table_node_to_docx( DOMDocument $doc, DOMElement
* @param DOMElement|null $base_rpr Base run properties.
* @param array<string,bool> $formatting Formatting flags.
* @param array<string,mixed>|null $relationships Relationships context.
* @param string|null $alignment Cell alignment (left, center, right, justify).
* @return array<int,DOMElement> Array of paragraph elements.
*/
private static function convert_cell_content_to_docx( DOMDocument $doc, $children, $base_rpr, array $formatting, &$relationships ) {
private static function convert_cell_content_to_docx( DOMDocument $doc, $children, $base_rpr, array $formatting, &$relationships, $alignment = null ) {
$result = array();
$current_runs = array();

@@ -2027,7 +2145,7 @@ private static function convert_cell_content_to_docx( DOMDocument $doc, $childre
if ( 'ul' === $tag || 'ol' === $tag ) {
// Flush any accumulated inline runs.
if ( ! empty( $current_runs ) ) {
$result[] = self::create_paragraph_from_runs( $doc, $current_runs, $base_rpr );
$result[] = self::create_paragraph_from_runs( $doc, $current_runs, $base_rpr, $alignment );
$current_runs = array();
}
// Convert list to paragraphs.
@@ -2039,7 +2157,7 @@ private static function convert_cell_content_to_docx( DOMDocument $doc, $childre
if ( 'table' === $tag ) {
// Flush any accumulated inline runs.
if ( ! empty( $current_runs ) ) {
$result[] = self::create_paragraph_from_runs( $doc, $current_runs, $base_rpr );
$result[] = self::create_paragraph_from_runs( $doc, $current_runs, $base_rpr, $alignment );
$current_runs = array();
}
// Convert nested table.
@@ -2053,12 +2171,17 @@ private static function convert_cell_content_to_docx( DOMDocument $doc, $childre
if ( 'p' === $tag || 'div' === $tag ) {
// Flush any accumulated inline runs.
if ( ! empty( $current_runs ) ) {
$result[] = self::create_paragraph_from_runs( $doc, $current_runs, $base_rpr );
$result[] = self::create_paragraph_from_runs( $doc, $current_runs, $base_rpr, $alignment );
$current_runs = array();
}
// Extract paragraph-specific alignment or use cell alignment.
$p_alignment = self::extract_text_alignment( $child );
if ( null === $p_alignment ) {
$p_alignment = $alignment;
}
// Convert paragraph content.
$p_runs = self::collect_runs_from_children( $doc, $child->childNodes, $base_rpr, $formatting, $relationships );
$result[] = self::create_paragraph_from_runs( $doc, $p_runs, $base_rpr );
$result[] = self::create_paragraph_from_runs( $doc, $p_runs, $base_rpr, $p_alignment );
continue;
}

@@ -2068,7 +2191,7 @@ private static function convert_cell_content_to_docx( DOMDocument $doc, $childre

// Flush remaining inline runs.

Check warning

Code scanning / PHPMD

Code Size Rules: NPathComplexity Warning

The method convert_cell_content_to_docx() has an NPath complexity of 1924. The configured NPath complexity threshold is 500.
Comment on lines +603 to +714
public static function apply_odt_metadata( $odt_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_odt_zip_missing', __( 'ZipArchive is not available for metadata.', 'documentate' ) );
}

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

$xml = $zip->getFromName( 'meta.xml' );
if ( false === $xml ) {
$zip->close();
return new WP_Error( 'documentate_meta_missing', __( 'meta.xml not found in ODT.', 'documentate' ) );
}

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

$xpath = new DOMXPath( $dom );
$xpath->registerNamespace( 'office', self::ODF_OFFICE_NS );
$xpath->registerNamespace( 'dc', self::DC_NS );
$xpath->registerNamespace( 'meta', self::ODF_META_NS );

// Find office:meta element.
$office_meta_list = $xpath->query( '//office:meta' );
if ( 0 === $office_meta_list->length ) {
$zip->close();
return new WP_Error( 'documentate_meta_element', __( 'office:meta element not found.', 'documentate' ) );
}
$office_meta = $office_meta_list->item( 0 );

// Helper to set or update an element.
$set_element = function ( $ns_uri, $local_name, $value ) use ( $dom, $xpath, $office_meta ) {
if ( empty( $value ) ) {
return;
}
$prefix = 'dc' === substr( $local_name, 0, 2 ) || in_array( $local_name, array( 'title', 'subject', 'creator' ), true ) ? 'dc' : 'meta';
$query = ".//{$prefix}:{$local_name}";
$nodes = $xpath->query( $query, $office_meta );

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 ) );
$office_meta->appendChild( $new_el );
}
};

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

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

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

// Set keywords - remove existing and add new ones.
if ( ! empty( $metadata['keywords'] ) ) {
// Remove existing keyword elements.
$existing_keywords = $xpath->query( './/meta:keyword', $office_meta );
foreach ( $existing_keywords as $kw ) {
$office_meta->removeChild( $kw );
}

// Add new keyword elements.
$keywords_array = array_map( 'trim', explode( ',', $metadata['keywords'] ) );
$keywords_array = array_filter( $keywords_array );
foreach ( $keywords_array as $keyword ) {
$kw_el = $dom->createElementNS( self::ODF_META_NS, 'meta:keyword' );
$kw_el->appendChild( $dom->createTextNode( $keyword ) );
$office_meta->appendChild( $kw_el );
}
}

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

return true;
}

Check warning

Code scanning / PHPMD

Code Size Rules: CyclomaticComplexity Warning

The method apply_odt_metadata() has a Cyclomatic Complexity of 21. The configured cyclomatic complexity threshold is 15.
Comment on lines +603 to +714
public static function apply_odt_metadata( $odt_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_odt_zip_missing', __( 'ZipArchive is not available for metadata.', 'documentate' ) );
}

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

$xml = $zip->getFromName( 'meta.xml' );
if ( false === $xml ) {
$zip->close();
return new WP_Error( 'documentate_meta_missing', __( 'meta.xml not found in ODT.', 'documentate' ) );
}

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

$xpath = new DOMXPath( $dom );
$xpath->registerNamespace( 'office', self::ODF_OFFICE_NS );
$xpath->registerNamespace( 'dc', self::DC_NS );
$xpath->registerNamespace( 'meta', self::ODF_META_NS );

// Find office:meta element.
$office_meta_list = $xpath->query( '//office:meta' );
if ( 0 === $office_meta_list->length ) {
$zip->close();
return new WP_Error( 'documentate_meta_element', __( 'office:meta element not found.', 'documentate' ) );
}
$office_meta = $office_meta_list->item( 0 );

// Helper to set or update an element.
$set_element = function ( $ns_uri, $local_name, $value ) use ( $dom, $xpath, $office_meta ) {
if ( empty( $value ) ) {
return;
}
$prefix = 'dc' === substr( $local_name, 0, 2 ) || in_array( $local_name, array( 'title', 'subject', 'creator' ), true ) ? 'dc' : 'meta';
$query = ".//{$prefix}:{$local_name}";
$nodes = $xpath->query( $query, $office_meta );

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 ) );
$office_meta->appendChild( $new_el );
}
};

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

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

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

// Set keywords - remove existing and add new ones.
if ( ! empty( $metadata['keywords'] ) ) {
// Remove existing keyword elements.
$existing_keywords = $xpath->query( './/meta:keyword', $office_meta );
foreach ( $existing_keywords as $kw ) {
$office_meta->removeChild( $kw );
}

// Add new keyword elements.
$keywords_array = array_map( 'trim', explode( ',', $metadata['keywords'] ) );
$keywords_array = array_filter( $keywords_array );
foreach ( $keywords_array as $keyword ) {
$kw_el = $dom->createElementNS( self::ODF_META_NS, 'meta:keyword' );
$kw_el->appendChild( $dom->createTextNode( $keyword ) );
$office_meta->appendChild( $kw_el );
}
}

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

return true;
}

Check warning

Code scanning / PHPMD

Code Size Rules: NPathComplexity Warning

The method apply_odt_metadata() has an NPath complexity of 184320. The configured NPath complexity threshold is 500.
Comment thread includes/class-documentate-opentbs.php Fixed
Comment on lines +726 to +821
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 +726 to +821
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.
@erseco erseco merged commit 22c3acf into main Dec 2, 2025
5 checks passed
@erseco erseco deleted the feature/better-samples branch December 2, 2025 20:39
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