Hosts that refused or timed out during this request. */ private static $failed_hosts = []; /** @var float Seconds spent downloading during this request. */ private static $time_spent = 0.0; /** * @param string $url Image URL taken from the export file. * @param int $parent_id Post the new attachment is attached to. * @return int Attachment ID, or 0 when the image could not be imported. */ public static function import_image_from_url( $url, $parent_id = 0 ) { $url = esc_url_raw( trim( (string) $url ), [ 'http', 'https' ] ); if ( empty( $url ) ) { return 0; } // Same site (or already imported once): reuse the existing attachment. $attachment_id = attachment_url_to_postid( $url ); if ( $attachment_id && wp_attachment_is_image( $attachment_id ) ) { return (int) $attachment_id; } $attachment_id = self::get_previously_imported( $url ); if ( $attachment_id ) { return $attachment_id; } $host = strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) ); $budget = (float) apply_filters( 'academy/import/media_time_budget', self::TIME_BUDGET ); if ( isset( self::$failed_hosts[ $host ] ) || self::$time_spent >= $budget ) { return 0; } if ( ! function_exists( 'media_sideload_image' ) ) { require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/image.php'; } $cap_timeout = function ( $args ) { $args['timeout'] = min( (float) ( $args['timeout'] ?? self::HTTP_TIMEOUT ), self::HTTP_TIMEOUT ); return $args; }; add_filter( 'http_request_args', $cap_timeout ); $started = microtime( true ); $attachment_id = media_sideload_image( $url, absint( $parent_id ), null, 'id' ); self::$time_spent += microtime( true ) - $started; remove_filter( 'http_request_args', $cap_timeout ); if ( is_wp_error( $attachment_id ) || ! $attachment_id ) { // Connection-level failure: the host is down, skip its other images. A 404 only loses this one. if ( is_wp_error( $attachment_id ) && 'http_request_failed' === $attachment_id->get_error_code() ) { self::$failed_hosts[ $host ] = true; } return 0; } update_post_meta( $attachment_id, self::SOURCE_URL_META, $url ); return (int) $attachment_id; } /** * @param int $post_id * @param string $url * @return int Attachment ID set as the thumbnail, or 0. */ public static function set_post_thumbnail_from_url( $post_id, $url ) { $attachment_id = self::import_image_from_url( $url, $post_id ); if ( $attachment_id && set_post_thumbnail( $post_id, $attachment_id ) ) { return $attachment_id; } return 0; } /** * Imports images that an HTML body hotlinks from another site, and points * the tags (and image block ids) at the local copies. * Images that are already local or cannot be imported are left untouched. * * @param string $content Sanitized HTML. * @param int $parent_id Post the new attachments are attached to. * @return string */ public static function import_content_images( $content, $parent_id = 0 ) { if ( ! is_string( $content ) || false === stripos( $content, 'next_tag( 'img' ) ) { $src = $processor->get_attribute( 'src' ); if ( ! is_string( $src ) || self::is_local_url( $src ) ) { continue; } $attachment_id = self::import_image_from_url( $src, $parent_id ); if ( ! $attachment_id ) { continue; } $processor->set_attribute( 'src', wp_get_attachment_url( $attachment_id ) ); // srcset/sizes still list the source site's resized copies. $processor->remove_attribute( 'srcset' ); $processor->remove_attribute( 'sizes' ); $class = $processor->get_attribute( 'class' ); if ( is_string( $class ) && preg_match( '/\bwp-image-(\d+)\b/', $class, $matches ) ) { $id_map[ (int) $matches[1] ] = $attachment_id; $processor->set_attribute( 'class', preg_replace( '/\bwp-image-\d+\b/', 'wp-image-' . $attachment_id, $class ) ); } }//end while $content = $processor->get_updated_html(); if ( $id_map ) { // Keep `` in sync so the block editor doesn't flag the block. $content = preg_replace_callback( '/(