[1] : 'auto'; // Handle resize. $sizes2crop = self::size_to_crop(); $resize = apply_filters( 'optml_default_crop', [] ); $sizes = image_get_intermediate_size( $attachment_id, $size ); if ( false !== $sizes ) { if ( isset( $sizes2crop[ $width . $height ] ) ) { $resize = $this->to_optml_crop( $sizes2crop[ $width . $height ] ); } } // Build the optimized URL. $optimized_url = $this->get_optimized_image_url( self::KEYS['not_processed_flag'] . $attachment_id . '/' . ltrim( $this->get_offloaded_attachment_url( $attachment_id, $url ), '/' ), $width, $height, $resize ); // Drop any image size from the URL. $optimized_url = str_replace( '-' . $width . 'x' . $height, '', $optimized_url ); $to_replace[ $url ] = $optimized_url; } return str_replace( array_keys( $to_replace ), array_values( $to_replace ), $content ); } /** * Replaces the post content URLs to use Offloaded ones on editor fetch. * * @param \WP_REST_Response $response The response object. * @param \WP_Post $post The post object. * @param \WP_REST_Request $request The request object. * * @return \WP_REST_Response */ public function pre_filter_rest_content( \WP_REST_Response $response, \WP_Post $post, \WP_REST_Request $request ) { $context = $request->get_param( 'context' ); if ( $context !== 'edit' ) { return $response; } $data = $response->get_data(); // Actually replace all URLs. $data['content']['raw'] = $this->replace_urls_in_editor_content( $data['content']['raw'] ); $response->set_data( $data ); return $response; } /** * Legacy function to be used for WordPress versions under 6.0.0. * * @param array $post_data Slashed, sanitized, processed post data. * @param array $postarr Slashed sanitized post data. * @param array $unsanitized_postarr Un-sanitized post data. * * @return array */ public function legacy_filter_saved_data( $post_data, $postarr, $unsanitized_postarr ) { return $this->filter_saved_data( $post_data, $postarr, $unsanitized_postarr, true ); } /** * Filter post content to use local attachments when saving offloaded images. * * @param array $post_data Slashed, sanitized, processed post data. * @param array $postarr Slashed sanitized post data. * @param array $unsanitized_postarr Un-sanitized post data. * @param bool $update Whether this is an existing post being updated or not. * * @return array */ public function filter_saved_data( $post_data, $postarr, $unsanitized_postarr, $update ) { if ( $postarr['post_status'] === 'trash' ) { return $post_data; } $content = $post_data['post_content']; $extracted = Optml_Main::instance()->manager->extract_urls_from_content( $content ); $replace = []; foreach ( $extracted as $idx => $url ) { $id = self::get_attachment_id_from_url( $url ); if ( $id === false ) { continue; } $id = (int) $id; if ( $this->is_legacy_offloaded_attachment( $id ) ) { continue; } $original = self::get_original_url( $id ); if ( $original === false ) { continue; } $replace[ $url ] = $original; $size = $this->parse_dimension_from_optimized_url( $url ); if ( $size[0] === false || $size[1] === false ) { continue; } if ( $size[0] === 'auto' || $size[1] === 'auto' ) { continue; } $extension = $this->get_ext( $url ); $metadata = wp_get_attachment_metadata( $id ); // Is this the full URL. if ( $metadata['width'] === (int) $size[0] && $metadata['height'] === (int) $size[1] ) { continue; } $size_crop_map = self::size_to_crop(); $crop = false; if ( isset( $size_crop_map[ $size[0] . $size[1] ] ) ) { $crop = $size_crop_map[ $size[0] . $size[1] ]; } if ( $crop ) { $width = $size[0]; $height = $size[1]; } else { // In case of an image size, we need to calculate the new dimensions for the proper file path. $constrained = wp_constrain_dimensions( $metadata['width'], $metadata['height'], $size[0], $size[1] ); $width = $constrained[0]; $height = $constrained[1]; } $replace[ $url ] = $this->maybe_strip_scaled( $replace[ $url ] ); $suffix = sprintf( '-%sx%s.%s', $width, $height, $extension ); $replace[ $url ] = str_replace( '.' . $extension, $suffix, $replace[ $url ] ); } $post_data['post_content'] = str_replace( array_keys( $replace ), array_values( $replace ), $content ); return $post_data; } /** * Alter the image size for the image widget. * * @param string $html the attachment image HTML string. * @param array $settings Control settings. * @param string $image_size_key Optional. Settings key for image size. * Default is `image`. * @param string $image_key Optional. Settings key for image. Default * is null. If not defined uses image size key * as the image key. * * @return string */ public function alter_elementor_image_size( $html, $settings, $image_size_key, $image_key ) { if ( ! isset( $settings['image'] ) ) { return $html; } $image = $settings['image']; if ( ! isset( $image['id'] ) ) { return $html; } if ( ! $this->is_new_offloaded_attachment( $image['id'] ) ) { return $html; } if ( ! isset( $settings['image_size'] ) ) { return $html; } if ( $settings['image_size'] === 'custom' ) { if ( ! isset( $settings['image_custom_dimension'] ) ) { return $html; } $custom_dimensions = $settings['image_custom_dimension']; if ( ! isset( $custom_dimensions['width'] ) || ! isset( $custom_dimensions['height'] ) ) { return $html; } $new_args = [ 'width' => $custom_dimensions['width'], 'height' => $custom_dimensions['height'], 'resize' => $this->to_optml_crop( true ), ]; $new_url = $this->get_new_offloaded_attachment_url( $image['url'], $image['id'], $new_args ); return str_replace( $image['url'], $new_url, $html ); } return $html; } /** * Adds new actions for new offloads. * * @return void */ public function add_new_actions() { add_filter( 'wp_prepare_attachment_for_js', [ self::$instance, 'alter_attachment_for_js' ], 999, 3 ); add_filter( 'wp_get_attachment_metadata', [ self::$instance, 'alter_attachment_metadata' ], 10, 2 ); add_filter( 'wp_get_attachment_image_src', [ self::$instance, 'alter_attachment_image_src' ], 10, 4 ); // Needed for rendering beaver builder css properly. add_filter( 'fl_builder_render_css', [ self::$instance, 'replace_urls_in_editor_content' ], 10, 1 ); // Filter saved data on insert to use local attachments. // Backwards compatibility for older versions of WordPress < 6.0.0 requiring 3 parameters for this specific filter. $below_6_0_0 = version_compare( get_bloginfo( 'version' ), '6.0.0', '<' ); if ( $below_6_0_0 ) { add_filter( 'wp_insert_post_data', [ self::$instance, 'legacy_filter_saved_data' ], 10, 3 ); } else { add_filter( 'wp_insert_post_data', [ self::$instance, 'filter_saved_data' ], 10, 4 ); } // Filter loaded data in the editors to use local attachments. add_filter( 'content_edit_pre', [ self::$instance, 'replace_urls_in_editor_content' ], 10, 1 ); add_action( 'init', function () { $types = get_post_types_by_support( 'editor' ); foreach ( $types as $type ) { $post_type = get_post_type_object( $type ); if ( property_exists( $post_type, 'show_in_rest' ) && true === $post_type->show_in_rest ) { add_filter( 'rest_prepare_' . $type, [ self::$instance, 'pre_filter_rest_content' ], 10, 3 ); } } }, PHP_INT_MAX ); add_filter( 'get_attached_file', [ $this, 'alter_attached_file_response' ], 10, 2 ); add_filter( 'elementor/image_size/get_attachment_image_html', [ $this, 'alter_elementor_image_size', ], 10, 4 ); } /** * Elementor checks if the file exists before requesting a specific image size. * * Needed because otherwise there won't be any width/height on the `img` tags, breaking lazyload. * * Also needed because some * * @param string $file The file path. * @param int $id The attachment ID. * * @return bool|string */ public function alter_attached_file_response( $file, $id ) { if ( ! $this->is_new_offloaded_attachment( $id ) ) { return $file; } $metadata = wp_get_attachment_metadata( $id ); if ( isset( $metadata['file'] ) ) { $uploads = wp_get_upload_dir(); return $uploads['basedir'] . '/' . $metadata['file']; } return true; } /** * Maybe strip the `-scaled` from the URL. * * @param string $url The url. * * @return string */ public function maybe_strip_scaled( $url ) { $ext = $this->get_ext( $url ); return str_replace( '-scaled.' . $ext, '.' . $ext, $url ); } /** * Is it a PHPUnit test run. * * @return bool */ public static function is_phpunit_test() { return defined( 'OPTML_PHPUNIT_TESTING' ) && OPTML_PHPUNIT_TESTING === true; } /** * Get offloaded image attachment URL based on the given attachment ID and URL. * * @param mixed $attachment_id The attachment ID. * @param string $url The attachment URL. * * @return string */ private function get_offloaded_attachment_url( $attachment_id, $url ) { if ( ! $this->settings->is_offload_enabled() || ! is_numeric( $attachment_id ) ) { return $url; } elseif ( empty( $attachment_id ) && strpos( $url, self::KEYS['not_processed_flag'] ) !== false ) { $attachment_id = (int) self::get_attachment_id_from_url( $url ); } elseif ( empty( $attachment_id ) ) { $attachment_id = $this->attachment_url_to_post_id( $url ); } if ( $attachment_id > 0 || ! empty( get_post_meta( $attachment_id, self::OM_OFFLOADED_FLAG, true ) ) ) { $url = wp_get_attachment_metadata( $attachment_id )['file']; } return $url; } /** * Cleanup the offload errors meta. */ public static function clear_offload_errors_meta() { global $wpdb; return $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->postmeta} WHERE meta_key = %s", self::META_KEYS['offload_error'] ) ); } }