PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.3
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.3
4.9.3 4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 All 201 releases
betterdocs / includes / Admin / Importer / Parsers / WXR_Parser_Regex.php

WXR_Parser_Regex.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.3, at includes/Admin/Importer/Parsers/WXR_Parser_Regex.php

391 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- inherited WP Importer hook contract preserved for compat with external listeners.
3 namespace WPDeveloper\BetterDocs\Admin\Importer\Parsers;
4
5 use WP_Error;
6
7 if ( ! defined( 'ABSPATH' ) ) {
8 exit; // Exit if accessed directly.
9 }
10
11 /**
12 * WordPress extended RSS file parser implementations
13 * Originally made by WordPress part of WordPress/Importer.
14 * https://plugins.trac.wordpress.org/browser/wordpress-importer/trunk/parsers/class-wxr-parser-regex.php
15 *
16 * What was done (by Elementor):
17 * Reformat of the code.
18 * Changed text domain.
19 * Changed methods visibility.
20 */
21
22 /**
23 * WXR Parser that uses regular expressions. Fallback for installs without an XML parser.
24 */
25 class WXR_Parser_Regex {
26 /**
27 * @var bool
28 */
29 private $has_gzip;
30
31 private $authors = [];
32 private $posts = [];
33 private $categories = [];
34 private $tags = [];
35 private $terms = [];
36 private $base_url = '';
37 private $base_blog_url = '';
38
39 /**
40 * @param string $file
41 *
42 * @return array|WP_Error
43 */
44 public function parse( $file ) {
45 $wxr_version = '';
46 $in_multiline = false;
47
48 $multiline_content = '';
49
50 $multiline_tags = [
51 'item' => [
52 'posts',
53 function ( $post ) {
54 return $this->process_post( $post );
55 },
56 ],
57 'wp:category' => [
58 'categories',
59 function ( $category ) {
60 return $this->process_category( $category );
61 },
62 ],
63 'wp:tag' => [
64 'tags',
65 function ( $tag ) {
66 return $this->process_tag( $tag );
67 },
68 ],
69 'wp:term' => [
70 'terms',
71 function ( $term ) {
72 return $this->process_term( $term );
73 },
74 ],
75 ];
76
77 $fp = $this->fopen( $file, 'r' );
78 if ( $fp ) {
79 while ( ! $this->feof( $fp ) ) {
80 $importline = rtrim( $this->fgets( $fp ) );
81
82 if ( ! $wxr_version && preg_match( '|<wp:wxr_version>(\d+\.\d+)</wp:wxr_version>|', $importline, $version ) ) {
83 $wxr_version = $version[1];
84 }
85
86 if ( false !== strpos( $importline, '<wp:base_site_url>' ) ) {
87 preg_match( '|<wp:base_site_url>(.*?)</wp:base_site_url>|is', $importline, $url );
88 $this->base_url = $url[1];
89 continue;
90 }
91
92 if ( false !== strpos( $importline, '<wp:base_blog_url>' ) ) {
93 preg_match( '|<wp:base_blog_url>(.*?)</wp:base_blog_url>|is', $importline, $blog_url );
94 $this->base_blog_url = $blog_url[1];
95 continue;
96 } else {
97 $this->base_blog_url = $this->base_url;
98 }
99
100 if ( false !== strpos( $importline, '<wp:author>' ) ) {
101 preg_match( '|<wp:author>(.*?)</wp:author>|is', $importline, $author );
102 $a = $this->process_author( $author[1] );
103 $this->authors[ $a['author_login'] ] = $a;
104 continue;
105 }
106
107 foreach ( $multiline_tags as $tag => $handler ) {
108 // Handle multi-line tags on a singular line.
109 if ( preg_match( '|<' . $tag . '>(.*?)</' . $tag . '>|is', $importline, $matches ) ) {
110 $this->{$handler[0]}[] = call_user_func( $handler[1], $matches[1] );
111
112 continue;
113 }
114
115 $pos = strpos( $importline, "<$tag>" );
116
117 if ( false !== $pos ) {
118 // Take note of any content after the opening tag.
119 $multiline_content = trim( substr( $importline, $pos + strlen( $tag ) + 2 ) );
120
121 // We don't want to have this line added to `$is_multiline` below.
122 $importline = '';
123 $in_multiline = $tag;
124
125 continue;
126 }
127
128 $pos = strpos( $importline, "</$tag>" );
129
130 if ( false !== $pos ) {
131 $in_multiline = false;
132 $multiline_content .= trim( substr( $importline, 0, $pos ) );
133
134 $this->{$handler[0]}[] = call_user_func( $handler[1], $multiline_content );
135 }
136 }
137
138 if ( $in_multiline && $importline ) {
139 $multiline_content .= $importline . "\n";
140 }
141 }
142
143 $this->fclose( $fp );
144 }
145
146 if ( ! $wxr_version ) {
147 return new WP_Error( 'WXR_parse_error', esc_html__( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'betterdocs' ) );
148 }
149
150 return [
151 'authors' => $this->authors,
152 'posts' => $this->posts,
153 'categories' => $this->categories,
154 'tags' => $this->tags,
155 'terms' => $this->terms,
156 'base_url' => $this->base_url,
157 'base_blog_url' => $this->base_blog_url,
158 'version' => $wxr_version,
159 ];
160 }
161
162 private function process_category( $category ) {
163 $term = [
164 'term_id' => $this->get_tag( $category, 'wp:term_id' ),
165 'cat_name' => $this->get_tag( $category, 'wp:cat_name' ),
166 'category_nicename' => $this->get_tag( $category, 'wp:category_nicename' ),
167 'category_parent' => $this->get_tag( $category, 'wp:category_parent' ),
168 'category_description' => $this->get_tag( $category, 'wp:category_description' ),
169 ];
170
171 $term_meta = $this->process_meta( $category, 'wp:termmeta' );
172 if ( ! empty( $term_meta ) ) {
173 $term['termmeta'] = $term_meta;
174 }
175
176 return $term;
177 }
178
179 private function process_tag( $tag ) {
180 $term = [
181 'term_id' => $this->get_tag( $tag, 'wp:term_id' ),
182 'tag_name' => $this->get_tag( $tag, 'wp:tag_name' ),
183 'tag_slug' => $this->get_tag( $tag, 'wp:tag_slug' ),
184 'tag_description' => $this->get_tag( $tag, 'wp:tag_description' ),
185 ];
186
187 $term_meta = $this->process_meta( $tag, 'wp:termmeta' );
188 if ( ! empty( $term_meta ) ) {
189 $term['termmeta'] = $term_meta;
190 }
191
192 return $term;
193 }
194
195 private function process_term( $term ) {
196 $term_data = [
197 'term_id' => $this->get_tag( $term, 'wp:term_id' ),
198 'term_taxonomy' => $this->get_tag( $term, 'wp:term_taxonomy' ),
199 'slug' => $this->get_tag( $term, 'wp:term_slug' ),
200 'term_parent' => $this->get_tag( $term, 'wp:term_parent' ),
201 'term_name' => $this->get_tag( $term, 'wp:term_name' ),
202 'term_description' => $this->get_tag( $term, 'wp:term_description' ),
203 ];
204
205 $term_meta = $this->process_meta( $term, 'wp:termmeta' );
206 if ( ! empty( $term_meta ) ) {
207 $term_data['termmeta'] = $term_meta;
208 }
209
210 return $term_data;
211 }
212
213 private function process_meta( $string, $tag ) {
214 $parsed_meta = [];
215
216 preg_match_all( "|<$tag>(.+?)</$tag>|is", $string, $meta );
217
218 if ( ! isset( $meta[1] ) ) {
219 return $parsed_meta;
220 }
221
222 foreach ( $meta[1] as $m ) {
223 $parsed_meta[] = [
224 'key' => $this->get_tag( $m, 'wp:meta_key' ),
225 'value' => $this->get_tag( $m, 'wp:meta_value' ),
226 ];
227 }
228
229 return $parsed_meta;
230 }
231
232 private function process_author( $a ) {
233 return [
234 'author_id' => $this->get_tag( $a, 'wp:author_id' ),
235 'author_login' => $this->get_tag( $a, 'wp:author_login' ),
236 'author_email' => $this->get_tag( $a, 'wp:author_email' ),
237 'author_display_name' => $this->get_tag( $a, 'wp:author_display_name' ),
238 'author_first_name' => $this->get_tag( $a, 'wp:author_first_name' ),
239 'author_last_name' => $this->get_tag( $a, 'wp:author_last_name' ),
240 ];
241 }
242
243 private function process_post( $post ) {
244 $normalize_tag_callback = function ( $matches ) {
245 return $this->normalize_tag( $matches );
246 };
247
248 $post_id = $this->get_tag( $post, 'wp:post_id' );
249 $post_title = $this->get_tag( $post, 'title' );
250 $post_date = $this->get_tag( $post, 'wp:post_date' );
251 $post_date_gmt = $this->get_tag( $post, 'wp:post_date_gmt' );
252 $comment_status = $this->get_tag( $post, 'wp:comment_status' );
253 $ping_status = $this->get_tag( $post, 'wp:ping_status' );
254 $status = $this->get_tag( $post, 'wp:status' );
255 $post_name = $this->get_tag( $post, 'wp:post_name' );
256 $post_parent = $this->get_tag( $post, 'wp:post_parent' );
257 $menu_order = $this->get_tag( $post, 'wp:menu_order' );
258 $post_type = $this->get_tag( $post, 'wp:post_type' );
259 $post_password = $this->get_tag( $post, 'wp:post_password' );
260 $is_sticky = $this->get_tag( $post, 'wp:is_sticky' );
261 $guid = $this->get_tag( $post, 'guid' );
262 $post_author = $this->get_tag( $post, 'dc:creator' );
263
264 $post_excerpt = $this->get_tag( $post, 'excerpt:encoded' );
265 $post_excerpt = preg_replace_callback( '|<(/?[A-Z]+)|', $normalize_tag_callback, $post_excerpt );
266 $post_excerpt = str_replace( '<br>', '<br />', $post_excerpt );
267 $post_excerpt = str_replace( '<hr>', '<hr />', $post_excerpt );
268
269 $post_content = $this->get_tag( $post, 'content:encoded' );
270 $post_content = preg_replace_callback( '|<(/?[A-Z]+)|', $normalize_tag_callback, $post_content );
271 $post_content = str_replace( '<br>', '<br />', $post_content );
272 $post_content = str_replace( '<hr>', '<hr />', $post_content );
273
274 $postdata = compact( 'post_id', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_excerpt', 'post_title', 'status', 'post_name', 'comment_status', 'ping_status', 'guid', 'post_parent', 'menu_order', 'post_type', 'post_password', 'is_sticky' );
275
276 $attachment_url = $this->get_tag( $post, 'wp:attachment_url' );
277 if ( $attachment_url ) {
278 $postdata['attachment_url'] = $attachment_url;
279 }
280
281 preg_match_all( '|<category domain="([^"]+?)" nicename="([^"]+?)">(.+?)</category>|is', $post, $terms, PREG_SET_ORDER );
282 foreach ( $terms as $t ) {
283 $post_terms[] = [
284 'slug' => $t[2],
285 'domain' => $t[1],
286 'name' => str_replace( [ '<![CDATA[', ']]>' ], '', $t[3] ),
287 ];
288 }
289 if ( ! empty( $post_terms ) ) {
290 $postdata['terms'] = $post_terms;
291 }
292
293 preg_match_all( '|<wp:comment>(.+?)</wp:comment>|is', $post, $comments );
294 $comments = $comments[1];
295 if ( $comments ) {
296 foreach ( $comments as $comment ) {
297 $post_comments[] = [
298 'comment_id' => $this->get_tag( $comment, 'wp:comment_id' ),
299 'comment_author' => $this->get_tag( $comment, 'wp:comment_author' ),
300 'comment_author_email' => $this->get_tag( $comment, 'wp:comment_author_email' ),
301 'comment_author_IP' => $this->get_tag( $comment, 'wp:comment_author_IP' ),
302 'comment_author_url' => $this->get_tag( $comment, 'wp:comment_author_url' ),
303 'comment_date' => $this->get_tag( $comment, 'wp:comment_date' ),
304 'comment_date_gmt' => $this->get_tag( $comment, 'wp:comment_date_gmt' ),
305 'comment_content' => $this->get_tag( $comment, 'wp:comment_content' ),
306 'comment_approved' => $this->get_tag( $comment, 'wp:comment_approved' ),
307 'comment_type' => $this->get_tag( $comment, 'wp:comment_type' ),
308 'comment_parent' => $this->get_tag( $comment, 'wp:comment_parent' ),
309 'comment_user_id' => $this->get_tag( $comment, 'wp:comment_user_id' ),
310 'commentmeta' => $this->process_meta( $comment, 'wp:commentmeta' ),
311 ];
312 }
313 }
314 if ( ! empty( $post_comments ) ) {
315 $postdata['comments'] = $post_comments;
316 }
317
318 $post_meta = $this->process_meta( $post, 'wp:postmeta' );
319 if ( ! empty( $post_meta ) ) {
320 $postdata['postmeta'] = $post_meta;
321 }
322
323 return $postdata;
324 }
325
326 private function get_tag( $string, $tag ) {
327 preg_match( "|<$tag.*?>(.*?)</$tag>|is", $string, $return );
328 if ( isset( $return[1] ) ) {
329 if ( substr( $return[1], 0, 9 ) == '<![CDATA[' ) {
330 if ( strpos( $return[1], ']]]]><![CDATA[>' ) !== false ) {
331 preg_match_all( '|<!\[CDATA\[(.*?)\]\]>|s', $return[1], $matches );
332 $return = '';
333 foreach ( $matches[1] as $match ) {
334 $return .= $match;
335 }
336 } else {
337 $return = preg_replace( '|^<!\[CDATA\[(.*)\]\]>$|s', '$1', $return[1] );
338 }
339 } else {
340 $return = $return[1];
341 }
342 } else {
343 $return = '';
344 }
345
346 return $return;
347 }
348
349 private function normalize_tag( $matches ) {
350 return '<' . strtolower( $matches[1] );
351 }
352
353 private function fopen( $filename, $mode = 'r' ) {
354 if ( $this->has_gzip ) {
355 return gzopen( $filename, $mode );
356 }
357
358 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- streaming read; WP_Filesystem cannot replace this.
359 return fopen( $filename, $mode );
360 }
361
362 private function feof( $fp ) {
363 if ( $this->has_gzip ) {
364 return gzeof( $fp );
365 }
366
367 return feof( $fp );
368 }
369
370 private function fgets( $fp, $len = 8192 ) {
371 if ( $this->has_gzip ) {
372 return gzgets( $fp, $len );
373 }
374
375 return fgets( $fp, $len );
376 }
377
378 private function fclose( $fp ) {
379 if ( $this->has_gzip ) {
380 return gzclose( $fp );
381 }
382
383 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- streaming close; WP_Filesystem cannot replace this.
384 return fclose( $fp );
385 }
386
387 public function __construct() {
388 $this->has_gzip = is_callable( 'gzopen' );
389 }
390 }
391