PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.2
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 3.5.1 All 200 releases
← All changes | includes/Admin/Importer/WPImport.php +67 -14 4.6.24.9.2 View file →
@@ -299,13 +299,12 @@
299 299 $action = $this->args['action'];
300 300
301 301 $import_data = $this->parse( $file );
302 302
303 - if ( isset( $import_data['type'] ) && $import_data['type'] === 'sample/csv' ) {
304 - $this->import_sample_data( $import_data['posts'], $action );
305 - return true;
306 - }
307 -
303 + // Check for a parser error first: on an invalid/unsupported file the
304 + // parser returns a WP_Error, and reading $import_data['type'] on it
305 + // would fatal ("Cannot use object of type WP_Error as array"). Report
306 + // it cleanly instead.
308 307 if ( is_wp_error( $import_data ) ) {
309 308 /**
310 309 * @var WP_Error $import_data ;
311 310 */
@@ -313,8 +312,13 @@
313 312
314 313 return false;
315 314 }
316 315
316 + if ( isset( $import_data['type'] ) && $import_data['type'] === 'sample/csv' ) {
317 + $this->import_sample_data( $import_data['posts'], $action );
318 + return true;
319 + }
320 +
317 321 $posts = $import_data['posts'];
318 322 // Use array_map to apply the callback function to each item in the array
319 323 $posts = array_map( [ $this, 'modify_post_type' ], $posts );
320 324
@@ -319,9 +323,9 @@
319 323 $posts = array_map( [ $this, 'modify_post_type' ], $posts );
320 324
321 325 if ( ! empty( $action ) ) {
322 326 $existing_posts = $this->args['existing_slug'];
323 - $existing_posts_array = explode( ',', $existing_posts );
327 + $existing_posts_array = is_array( $existing_posts ) ? $existing_posts : explode( ',', (string) $existing_posts );
324 328
325 329 if ( $existing_posts && $action == 'ignore' ) {
326 330 // Filter out posts with slugs in $existing_slugs_array
327 331 $filtered_posts = array_filter(
@@ -368,8 +372,16 @@
368 372
369 373 wp_defer_term_counting( true );
370 374 wp_defer_comment_counting( true );
371 375
376 + // Safety net for an aborted run: a PHP timeout, memory limit, fatal, or a
377 + // cut-off request can end the import before import_end() runs, which would
378 + // strand wp_defer_term_counting(true) and a stale {taxonomy}_children cache
379 + // and silently drop nested-category counts site-wide. A shutdown handler
380 + // guarantees the restore runs on the way out; it is idempotent, so the normal
381 + // import_end() path (which also calls it) makes this a no-op. (#167)
382 + register_shutdown_function( array( $this, 'restore_counting_and_hierarchy' ) );
383 +
372 384 do_action( 'import_start', $this );
373 385
374 386 return true;
375 387 }
@@ -381,8 +393,29 @@
381 393 wp_import_cleanup( $this->id );
382 394
383 395 wp_cache_flush();
384 396
397 + $this->restore_counting_and_hierarchy();
398 +
399 + do_action( 'import_end' );
400 + }
401 +
402 + /**
403 + * Turn term/comment counting back on and rebuild the term hierarchy.
404 + *
405 + * import_start() registers this as a shutdown handler so an aborted run (timeout,
406 + * memory, fatal, cut-off request) that never reaches import_end() cannot strand
407 + * wp_defer_term_counting(true) or a stale {taxonomy}_children cache. Idempotent,
408 + * so it is safe to call from both import_end() and the shutdown handler. (#167)
409 + */
410 + public function restore_counting_and_hierarchy() {
411 + static $restored = false;
412 +
413 + if ( $restored ) {
414 + return;
415 + }
416 + $restored = true;
417 +
385 418 foreach ( get_taxonomies() as $tax ) {
386 419 delete_option( "{$tax}_children" );
387 420 _get_term_hierarchy( $tax );
388 421 }
@@ -388,10 +421,8 @@
388 421 }
389 422
390 423 wp_defer_term_counting( false );
391 424 wp_defer_comment_counting( false );
392 -
393 - do_action( 'import_end' );
394 425 }
395 426
396 427 /**
397 428 * Retrieve authors from parsed WXR data and set it to `$this->>authors`.
@@ -647,9 +678,9 @@
647 678 }
648 679
649 680 public function existing_slug_action( $posts, $action ) {
650 681 $existing_posts = $this->args['existing_slug'];
651 - $existing_posts_array = explode( ',', $existing_posts );
682 + $existing_posts_array = is_array( $existing_posts ) ? $existing_posts : explode( ',', (string) $existing_posts );
652 683
653 684 if ( $existing_posts && $action == 'ignore' ) {
654 685 // Filter out posts with slugs in $existing_slugs_array
655 686 $filtered_posts = array_filter(
@@ -1590,14 +1621,29 @@
1590 1621 * @param string $file Path to WXR file for parsing
1591 1622 *
1592 1623 * @return array Information gathered from the WXR file
1593 1624 */
1594 - private function parse( $file ): array {
1595 - if ( $this->file_type == 'text/xml' ) {
1625 + private function parse( $file ) {
1626 + $type = strtolower( (string) $this->file_type );
1627 +
1628 + // The uploaded temp file has no extension, and browsers report a .xml
1629 + // upload inconsistently (text/xml, application/xml, text/plain, or
1630 + // nothing) — so prefer the original file name's extension when it was
1631 + // passed through, fall back to the MIME, and default anything that is
1632 + // not clearly CSV to the WordPress export parser. WXR_Parser validates
1633 + // the file and returns a WP_Error for a non-WXR file, which
1634 + // import_start() already reports cleanly; previously an unrecognised
1635 + // MIME left $parser null and fataled with "Call to a member function
1636 + // parse() on null".
1637 + $name = isset( $this->args['file_name'] ) ? (string) $this->args['file_name'] : (string) $this->requested_file_path;
1638 + $ext = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );
1639 +
1640 + if ( 'csv' === $ext || ( '' === $ext && strpos( $type, 'csv' ) !== false ) ) {
1641 + $parser = new CSV_Parser();
1642 + } else {
1596 1643 $parser = new WXR_Parser();
1597 - } elseif ( $this->file_type == 'text/csv' ) {
1598 - $parser = new CSV_Parser();
1599 1644 }
1645 +
1600 1646 return $parser->parse( $file );
1601 1647 }
1602 1648
1603 1649 /**
@@ -1675,9 +1721,16 @@
1675 1721 * @param $file
1676 1722 * @param array $args
1677 1723 */
1678 1724 public function __construct( $file, array $args = [] ) {
1679 - parent::__construct();
1725 + // WP core's WP_Importer has no constructor, and on PHP 8 calling
1726 + // parent::__construct() on a constructor-less parent throws
1727 + // "Error: Cannot call constructor" — which aborted every import. Only
1728 + // call the parent when it actually defines one (future-proof if WP adds
1729 + // it back).
1730 + if ( method_exists( get_parent_class( $this ), '__construct' ) ) {
1731 + parent::__construct();
1732 + }
1680 1733
1681 1734 $this->requested_file_path = $file;
1682 1735 $this->args = $args;
1683 1736