← All changes
|
modules/mcp/abilities/build-composition/xml-parser.php
+64
-2
4.3.0-beta1
→
4.3.3
View file →
| @@ -21,13 +21,13 @@ | ||
| 21 | 21 | libxml_clear_errors(); |
| 22 | 22 | libxml_use_internal_errors( $previous ); |
| 23 | 23 | |
| 24 | 24 | if ( ! $loaded ) { |
| 25 | - $message = $errors ? $errors[0]->message : 'Unknown XML error.'; | |
| 25 | + $message = $errors ? $this->describe_libxml_error( trim( $errors[0]->message ) ) : 'Unknown XML error.'; | |
| 26 | 26 | return new \WP_Error( |
| 27 | 27 | 'invalid_xml', |
| 28 | 28 | /* translators: %s: XML parse error message */ |
| 29 | - sprintf( __( 'Failed to parse xml_structure: %s', 'elementor' ), trim( $message ) ), | |
| 29 | + sprintf( __( 'Failed to parse xml_structure: %s', 'elementor' ), $message ), | |
| 30 | 30 | [ 'status' => \WP_Http::BAD_REQUEST ] |
| 31 | 31 | ); |
| 32 | 32 | } |
| 33 | 33 | |
| @@ -33,8 +33,24 @@ | ||
| 33 | 33 | |
| 34 | 34 | return $dom; |
| 35 | 35 | } |
| 36 | 36 | |
| 37 | + private function describe_libxml_error( string $message ): string { | |
| 38 | + $root = preg_quote( self::COMPOSITION_ROOT_TAG, '/' ); | |
| 39 | + | |
| 40 | + if ( preg_match( '/tag mismatch: ' . $root . ' line \d+ and (\S+)$/', $message, $matches ) ) { | |
| 41 | + /* translators: %s: XML tag name */ | |
| 42 | + return sprintf( __( 'Closing tag </%s> has no matching opening tag.', 'elementor' ), $matches[1] ); | |
| 43 | + } | |
| 44 | + | |
| 45 | + if ( preg_match( '/tag mismatch: (\S+) line \d+ and ' . $root . '$/', $message, $matches ) ) { | |
| 46 | + /* translators: %s: XML tag name */ | |
| 47 | + return sprintf( __( 'Element <%s> is not closed.', 'elementor' ), $matches[1] ); | |
| 48 | + } | |
| 49 | + | |
| 50 | + return $message; | |
| 51 | + } | |
| 52 | + | |
| 37 | 53 | public function get_root( \DOMDocument $dom ): ?\DOMElement { |
| 38 | 54 | return $dom->documentElement ?? null; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- DOMDocument API. |
| 39 | 55 | } |
| 40 | 56 | |
| @@ -91,6 +107,52 @@ | ||
| 91 | 107 | $descendants[] = $node; |
| 92 | 108 | } |
| 93 | 109 | } |
| 94 | 110 | return $descendants; |
| 111 | + } | |
| 112 | + | |
| 113 | + public function collect_duplicate_configuration_id_errors( \DOMDocument $dom ): array { | |
| 114 | + $counts = []; | |
| 115 | + | |
| 116 | + foreach ( $this->iterate_all_descendants( $dom ) as $node ) { | |
| 117 | + $configuration_id = $this->get_configuration_id( $node ); | |
| 118 | + if ( null === $configuration_id || '' === $configuration_id ) { | |
| 119 | + continue; | |
| 120 | + } | |
| 121 | + | |
| 122 | + if ( ! isset( $counts[ $configuration_id ] ) ) { | |
| 123 | + $counts[ $configuration_id ] = 0; | |
| 124 | + } | |
| 125 | + | |
| 126 | + $counts[ $configuration_id ]++; | |
| 127 | + } | |
| 128 | + | |
| 129 | + $errors = []; | |
| 130 | + foreach ( $counts as $configuration_id => $count ) { | |
| 131 | + if ( $count <= 1 ) { | |
| 132 | + continue; | |
| 133 | + } | |
| 134 | + | |
| 135 | + $errors[] = sprintf( | |
| 136 | + /* translators: %s: duplicate configuration-id value */ | |
| 137 | + __( 'Duplicate configuration-id "%s". Each element must have a unique configuration-id.', 'elementor' ), | |
| 138 | + $configuration_id | |
| 139 | + ); | |
| 140 | + } | |
| 141 | + | |
| 142 | + return $errors; | |
| 143 | + } | |
| 144 | + | |
| 145 | + public function validate_unique_configuration_ids( \DOMDocument $dom ): ?\WP_Error { | |
| 146 | + $errors = $this->collect_duplicate_configuration_id_errors( $dom ); | |
| 147 | + | |
| 148 | + if ( empty( $errors ) ) { | |
| 149 | + return null; | |
| 150 | + } | |
| 151 | + | |
| 152 | + return new \WP_Error( | |
| 153 | + 'elementor_duplicate_configuration_id', | |
| 154 | + implode( ' ', $errors ), | |
| 155 | + [ 'status' => \WP_Http::BAD_REQUEST ] | |
| 156 | + ); | |
| 95 | 157 | } |
| 96 | 158 | } |