WP_Image_Editor_Imagick::load(): true|WP_Error

In this article

Loads image from $this->file into new Imagick Object.

Return

true|WP_Error True if loaded; WP_Error on failure.

Source

public function load() {
	if ( $this->image instanceof Imagick ) {
		return true;
	}

	$is_stream = wp_is_stream( $this->file );
	$is_file   = ! $is_stream && is_file( $this->file );

	// Only allow loading files or streams.
	if ( ! $is_file && ! $is_stream ) {
		return new WP_Error( 'error_loading_image', __( 'File does not exist?' ), $this->file );
	}

	// Establish the provided filename based on the kind of resource being loaded.
	$given_filename = $this->file;
	if ( 0 === strncasecmp( $given_filename, 'file://', 7 ) ) {
		$given_filename = basename( substr( $given_filename, 7 ) ); // 7 is the strlen of 'file://'.
	} elseif ( 1 === preg_match( '~^https?://~i', $this->file ) ) {
		/*
		 * For URLs, it will be the final path segment.
		 *
		 * Example:
		 *
		 *     https://wordpress.org/i/happy.png?size=40px
		 *                             ╰───────╯
		 *                                this is the given filename
		 *
		 * If the stream returns a `Content-Disposition` header it would
		 * provide an alternative name, but this is used as a reasonable
		 * proxy to avoid adding the additional complexity of reading and
		 * parsing the returned HTTP headers.
		 */
		$url_path = wp_parse_url( $this->file, PHP_URL_PATH );

		// This URL can not be parsed, so it is not a valid image resource.
		if ( false === $url_path ) {
			return new WP_Error( 'error_loading_image', __( 'File is not an image.' ), $this->file );
		}

		/**
		 * The URL has an empty path, so continue with an empty string.
		 *
		 * This is the case with a URL such as `https://example.com?file_id=123`
		 */
		if ( null === $url_path ) {
			$url_path = '';
		}

		$last_path_at   = strrpos( $url_path, '/' );
		$given_filename = is_int( $last_path_at ) ? substr( $url_path, $last_path_at + 1 ) : $url_path;
		$given_filename = rawurldecode( $given_filename );
	}

	/*
	 * Strip off any potential `Imagick` format specifiers.
	 *
	 * If a real file exists with the identified format specifier, then
	 * `Imagick` may not treat it as a format, but WordPress will reject
	 * it anyway to avoid adding more complexity into this detection.
	 *
	 * `Imagick` reads only the first `FORMAT:` specifier on a name, but
	 * stripping a segment would promote a second specifier to the front
	 * of the name handed to `Imagick`, which would then honor it.
	 *
	 * Loop to capture all format specifiers for comparison.
	 *
	 * Exclude Windows drive-letter prefixes from here.
	 */
	$imagick_formats = array();
	while (
		false !== ( $format_ends_at = strpos( $given_filename, ':' ) ) &&
		1 !== preg_match( '~^[a-z]:~i', $given_filename )
	) {
		$imagick_formats[] = strtoupper( substr( $given_filename, 0, $format_ends_at ) );
		$given_filename    = substr( $given_filename, $format_ends_at + 1 );
	}

	$file_extension = strtolower( pathinfo( $given_filename, PATHINFO_EXTENSION ) );

	/*
	 * Even though Imagick uses less PHP memory than GD, set higher limit
	 * for users that have low PHP.ini limits.
	 */
	wp_raise_memory_limit( 'image' );

	/**
	 * Read the resource header for MIME sniffing.
	 *
	 * For files, which will be passed into Imagick by their file names, avoid
	 * eagerly loading the entire contents into PHP memory. For streams, however,
	 * it’s more important to avoid validating a separate copy of the file data
	 * than is later fetched by Imagick, so go ahead and load the entire payload,
	 * then pass it to Imagick as the data blob itself.
	 *
	 * @link https://mimesniff.spec.whatwg.org/#reading-the-resource-header
	 */
	try {
		if ( $is_file ) {
			$file_data = file_get_contents( $this->file, false, null, 0, 1445 );
		} else {
			$file_data = file_get_contents( $this->file );
		}
	} catch ( Exception $e ) {
		$file_data = false;
	}
	if ( false === $file_data ) {
		return new WP_Error( 'error_loading_image', __( 'File does not exist?' ), $this->file );
	}

	$pdf_extensions = array(
		'ai',
		'epdf',
		'pdf',
		'pdfa',
		'pocketmod',
	);

	// Reject files claiming to be PDFs which lack the required signature.
	$has_pdf_extension = in_array( $file_extension, $pdf_extensions, true );
	$has_pdf_signature = str_starts_with( $file_data, '%PDF-' );
	if ( $has_pdf_extension && ! $has_pdf_signature ) {
		return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file );
	}

	$ps_formats = array(
		'DPS',
		'EPI',
		'EPS',
		'EPSF',
		'EPSI',
		'PS',
		'WPG',
	);

	$ps_extensions = array(
		'dps',
		'epi',
		'eps',
		'eps2',
		'eps3',
		'epsf',
		'epsi',
		'ept',
		'ept2',
		'ept3',
		'ps',
		'ps2',
		'ps3',
		'wpg',
	);

	// Reject files which Imagick will parse as PostScript.
	if (
		array() !== array_intersect( $imagick_formats, $ps_formats ) ||
		in_array( $file_extension, $ps_extensions, true ) ||
		str_starts_with( $file_data, '%!' ) ||
		str_starts_with( $file_data, "\x04%!" ) ||
		str_starts_with( $file_data, "\xC5\xD0\xD3\xC6" ) ||
		str_starts_with( $file_data, "\xFFWPC" )
	) {
		return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file );
	}

	$compressed_extensions = array(
		'gz',
		'bz2',
		'svgz',
		'z',
		'wmz',
	);

	/*
	 * Reject compressed archives that Imagick will transparently decompress.
	 * Unfortunately this rejects `.svgz` because there’s no intermediate step
	 * in the loading process. `Imagick` would decompress the file, then look
	 * to see what kind of content was decompressed instead of asserting SVG.
	 */
	if (
		in_array( $file_extension, $compressed_extensions, true ) ||
		str_starts_with( $file_data, "\x1F\x8B\x08" ) || // gzip
		str_starts_with( $file_data, 'BZh' ) || // bzip2
		str_starts_with( $file_data, "\x1F\x9D" ) // compress
	) {
		return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file );
	}

	try {
		$this->image = new Imagick();

		if ( $has_pdf_signature ) {
			/*
			 * Load these values for use in the helper method without forcing a change
			 * of its expected arguments, but then free them after calling to prevent
			 * keeping them around in memory and bloating the app.
			 */
			$this->stream_file_data = $is_stream ? $file_data : null;
			$this->image_given_name = $given_filename;
			$pdf_loaded             = $this->pdf_load_source();
			$this->stream_file_data = null;
			$this->image_given_name = null;

			if ( is_wp_error( $pdf_loaded ) ) {
				return $pdf_loaded;
			}
		} else {
			if ( $is_stream ) {
				$this->image->readImageBlob( $file_data, $given_filename );
			} else {
				$this->image->readImage( $this->file );
			}
		}

		if ( ! $this->image->valid() ) {
			return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file );
		}

		// Select the first frame to handle animated images properly.
		if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) ) {
			$this->image->setIteratorIndex( 0 );
		}

		if ( $has_pdf_signature ) {
			$this->remove_pdf_alpha_channel();
		}

		$this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
	} catch ( Exception $e ) {
		return new WP_Error( 'invalid_image', $e->getMessage(), $this->file );
	}

	$updated_size = $this->update_size();

	if ( is_wp_error( $updated_size ) ) {
		return $updated_size;
	}

	return $this->set_quality();
}

Changelog

VersionDescription
3.5.0Introduced.

User Contributed Notes

You must log in before being able to contribute a note or feedback.