Ticket #42663: imagick.5.diff
File imagick.5.diff, 12.9 KB (added by , 5 years ago) |
---|
-
src/wp-includes/class-wp-image-editor-imagick.php
diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 9f6a0f3b52..9d2115c391 100644
a b class WP_Image_Editor_Imagick extends WP_Image_Editor { 21 21 */ 22 22 protected $image; 23 23 24 /** 25 * Generated temporary files 26 * 27 * @var array 28 */ 29 private $_tmp; 30 24 31 public function __destruct() { 25 32 if ( $this->image instanceof Imagick ) { 26 33 // we don't need the original in memory anymore 27 34 $this->image->clear(); 28 35 $this->image->destroy(); 29 36 } 37 // if we have processed files from stream wrappers remove the local copies 38 if ( is_array( $this->_tmp ) ) { 39 foreach ( $this->_tmp as $tmpfile ) { 40 @unlink( $tmpfile ); 41 } 42 } 30 43 } 31 44 32 45 /** … … class WP_Image_Editor_Imagick extends WP_Image_Editor { 133 146 if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) ) 134 147 return new WP_Error( 'error_loading_image', __('File doesn’t exist?'), $this->file ); 135 148 149 $local_file = $this->file; 150 if ( wp_is_stream( $this->file ) ) { 151 $local_file = wp_tempnam(); 152 153 if ( empty( $this->_tmp ) ) { 154 $this->_tmp = array(); 155 } 156 $this->_tmp[] = $local_file; 157 158 if ( false === @copy( $this->file, $local_file ) ) { 159 return new WP_Error( 'error_loading_image', __( 'File doesn’t exist?' ), error_get_last() ); 160 } 161 } 162 136 163 /* 137 164 * Even though Imagick uses less PHP memory than GD, set higher limit 138 165 * for users that have low PHP.ini limits. … … class WP_Image_Editor_Imagick extends WP_Image_Editor { 142 169 try { 143 170 $this->image = new Imagick(); 144 171 $file_extension = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) ); 145 $filename = $ this->file;172 $filename = $local_file; 146 173 147 174 if ( 'pdf' == $file_extension ) { 148 $filename = $this->pdf_setup( );175 $filename = $this->pdf_setup( $local_file ); 149 176 } 150 177 151 178 // Reading image after Imagick instantiation because `setResolution` … … class WP_Image_Editor_Imagick extends WP_Image_Editor { 735 762 * 736 763 * @return string|WP_Error File to load or WP_Error on failure. 737 764 */ 738 protected function pdf_setup( ) {765 protected function pdf_setup( $local_file ) { 739 766 try { 740 767 // By default, PDFs are rendered in a very low resolution. 741 768 // We want the thumbnail to be readable, so increase the rendering DPI. 742 769 $this->image->setResolution( 128, 128 ); 743 770 744 771 // Only load the first page. 745 return $ this->file . '[0]';772 return $local_file . '[0]'; 746 773 } 747 774 catch ( Exception $e ) { 748 775 return new WP_Error( 'pdf_setup_failed', $e->getMessage(), $this->file ); 749 776 } 750 777 } 751 778 779 /** 780 * Either calls editor's save function or handles file as a stream. 781 * 782 * @param string|stream $filename 783 * @param callable $function 784 * @param array $arguments 785 * @return bool 786 */ 787 protected function make_image( $filename, $function, $arguments ) { 788 if ( wp_is_stream( $filename ) ) { 789 $tmp = wp_tempnam(); 790 if ( empty( $this->_tmp ) ) { 791 $this->_tmp = array(); 792 } 793 $this->_tmp[] = $tmp; 794 795 $arguments[0] = $tmp; 796 } 797 798 if ( ! parent::make_image( $filename, $function, $arguments ) ) { 799 return false; 800 } 801 802 if ( wp_is_stream( $filename ) ) { 803 return copy( $tmp, $filename ); 804 } 805 } 752 806 } -
new file tests/phpunit/includes/class-wp-test-stream.php
diff --git a/tests/phpunit/includes/class-wp-test-stream.php b/tests/phpunit/includes/class-wp-test-stream.php new file mode 100644 index 0000000000..4eb0f4dcfc
- + 1 <?php 2 class WP_Test_Stream { 3 const FILE_MODE = 33206; // 100666 in octal 4 const DIRECTORY_MODE = 16895; // 40777 in octal 5 6 static $data = array(); 7 8 var $position; 9 var $file; 10 var $bucket; 11 12 private function open( $path ) { 13 $url = array_merge( 14 array( 15 'host' => '', 16 'path' => '', 17 ), 18 parse_url( $path ) 19 ); 20 21 $this->bucket = $url['host']; 22 $this->file = $url['path'] ? $url['path'] : '/'; 23 24 if ( empty( $this->bucket ) ) { 25 trigger_error( 'Cannot use an empty bucket name', E_USER_ERROR ); 26 } 27 28 if ( ! isset( WP_Test_Stream::$data[ $this->bucket ] ) ) { 29 WP_Test_Stream::$data[ $this->bucket ] = array(); 30 } 31 32 $this->position = 0; 33 } 34 35 function stream_open( $path, $mode, $options, &$opened_path ) { 36 $this->open( $path ); 37 return true; 38 } 39 40 function stream_read( $count ) { 41 if ( ! isset( WP_Test_Stream::$data[ $this->bucket ][ $this->file ] ) ) { 42 return ''; 43 } 44 45 $ret = substr( WP_Test_Stream::$data[ $this->bucket ][ $this->file ], $this->position, $count ); 46 $this->position += strlen( $ret ); 47 return $ret; 48 } 49 50 function stream_write( $data ) { 51 if ( ! isset( WP_Test_Stream::$data[ $this->bucket ][ $this->file ] ) ) { 52 WP_Test_Stream::$data[ $this->bucket ][ $this->file ] = ''; 53 } 54 55 $left = substr( WP_Test_Stream::$data[ $this->bucket ][ $this->file ], 0, $this->position ); 56 $right = substr( WP_Test_Stream::$data[ $this->bucket ][ $this->file ], $this->position + strlen( $data ) ); 57 WP_Test_Stream::$data[ $this->bucket ][ $this->file ] = $left . $data . $right; 58 $this->position += strlen( $data ); 59 return strlen( $data ); 60 } 61 62 function stream_tell() { 63 return $this->position; 64 } 65 66 function stream_eof() { 67 if ( ! isset( WP_Test_Stream::$data[ $this->bucket ][ $this->file ] ) ) { 68 return true; 69 } 70 71 return $this->position >= strlen( WP_Test_Stream::$data[ $this->bucket ][ $this->file ] ); 72 } 73 74 function stream_seek( $offset, $whence ) { 75 switch ( $whence ) { 76 case SEEK_SET: 77 if ( $offset < strlen( WP_Test_Stream::$data[ $this->bucket ][ $this->file ] ) && $offset >= 0 ) { 78 $this->position = $offset; 79 return true; 80 } else { 81 return false; 82 } 83 break; 84 85 case SEEK_CUR: 86 if ( $offset >= 0 ) { 87 $this->position += $offset; 88 return true; 89 } else { 90 return false; 91 } 92 break; 93 94 case SEEK_END: 95 if ( strlen( WP_Test_Stream::$data[ $this->bucket ][ $this->file ] ) + $offset >= 0 ) { 96 $this->position = strlen( WP_Test_Stream::$data[ $this->bucket ][ $this->file ] ) + $offset; 97 return true; 98 } else { 99 return false; 100 } 101 break; 102 103 default: 104 return false; 105 } 106 } 107 108 function stream_metadata( $path, $option, $var ) { 109 $this->open( $path ); 110 if ( STREAM_META_TOUCH == $option ) { 111 if ( ! isset( WP_Test_Stream::$data[ $this->bucket ][ $this->file ] ) ) { 112 WP_Test_Stream::$data[ $this->bucket ][ $this->file ] = ''; 113 } 114 return true; 115 } 116 return false; 117 } 118 119 function mkdir( $path, $mode, $options ) { 120 $this->open( $path ); 121 if ( isset( WP_Test_Stream::$data[ $this->bucket ][ rtrim( $this->file, '/' ) ] ) ) { 122 return false; 123 } 124 WP_Test_Stream::$data[ $this->bucket ][ rtrim( $this->file, '/' ) . '/' ] = 'DIRECTORY'; 125 return true; 126 } 127 128 private function make_stat( $stats ) { 129 $defaults = array( 130 'dev' => 0, 131 'ino' => 0, 132 'mode' => 0, 133 'nlink' => 0, 134 'uid' => 0, 135 'gid' => 0, 136 'rdev' => 0, 137 'size' => 0, 138 'atime' => 0, 139 'mtime' => 0, 140 'ctime' => 0, 141 'blksize' => 0, 142 'blocks' => 0, 143 ); 144 145 return array_merge( $defaults, $stats ); 146 } 147 148 public function stream_stat() { 149 if ( substr( $this->file, -1 ) == '/' || isset( WP_Test_Stream::$data[ $this->bucket ][ rtrim( $this->file, '/' ) . '/' ] ) ) { 150 return $this->make_stat( 151 array( 152 'mode' => WP_Test_Stream::DIRECTORY_MODE, 153 ) 154 ); 155 } 156 157 if ( ! isset( WP_Test_Stream::$data[ $this->bucket ][ $this->file ] ) ) { 158 return false; 159 } 160 161 return $this->make_stat( 162 array( 163 'size' => strlen( WP_Test_Stream::$data[ $this->bucket ][ $this->file ] ), 164 'mode' => WP_Test_Stream::FILE_MODE, 165 ) 166 ); 167 } 168 169 public function url_stat( $path, $flags ) { 170 $this->open( $path ); 171 return $this->stream_stat(); 172 } 173 } -
new file tests/phpunit/tests/image/streams.php
diff --git a/tests/phpunit/tests/image/streams.php b/tests/phpunit/tests/image/streams.php new file mode 100644 index 0000000000..5e3719b739
- + 1 <?php 2 3 /** 4 * @group image 5 * @group media 6 * @group upload 7 */ 8 class Tests_Image_Stream_Wrappers extends WP_UnitTestCase { 9 10 /** 11 * Setup test fixture 12 */ 13 public function setUp() { 14 parent::setUp(); 15 16 require_once( ABSPATH . WPINC . '/class-wp-image-editor.php' ); 17 require_once( ABSPATH . WPINC . '/class-wp-image-editor-gd.php' ); 18 require_once( ABSPATH . WPINC . '/class-wp-image-editor-imagick.php' ); 19 20 require_once( DIR_TESTDATA . '/../includes/class-wp-test-stream.php' ); 21 include_once( DIR_TESTDATA . '/../includes/mock-image-editor.php' ); 22 23 if ( in_array( 'dummy', stream_get_wrappers() ) ) { 24 stream_wrapper_unregister( 'dummy' ); 25 } 26 stream_wrapper_register( 'dummy', 'WP_Test_Stream' ); 27 WP_Test_Stream::$data = array(); 28 29 // Ensure no legacy / failed tests detritus. 30 $folder = '/tmp/wordpress-gsoc-flyer*.{jpg,pdf}'; 31 32 foreach ( glob( $folder, GLOB_BRACE ) as $file ) { 33 unlink( $file ); 34 } 35 } 36 37 public function image_editor_class() { 38 return array( 39 array( 'WP_Image_Editor_GD' ), 40 array( 'WP_Image_Editor_Imagick' ), 41 ); 42 } 43 44 /** 45 * @dataProvider image_editor_class 46 */ 47 public function test_save_to_stream_wrappers( $class ) { 48 // If the image editor isn't available, skip it 49 if ( ! call_user_func( array( $class, 'test' ) ) ) { 50 $this->markTestSkipped( "$class is not available" ); 51 } 52 53 $img = new $class( DIR_TESTDATA . '/images/canola.jpg' ); 54 $this->assertNotInstanceOf( 'WP_Error', $img ); 55 $loaded = $img->load(); 56 $this->assertTrue( $loaded ); 57 58 $new_filename = 'dummy://test/canola.jpg'; 59 $this->assertTrue( wp_is_stream( $new_filename ) ); 60 61 $ret = $img->save( $new_filename ); 62 $this->assertNotInstanceOf( 'WP_Error', $ret ); 63 $this->assertArrayHasKey( '/canola.jpg', WP_Test_Stream::$data['test'] ); 64 65 // Clean up 66 unset( $img ); 67 } 68 69 /** 70 * @dataProvider image_editor_class 71 */ 72 public function test_load_from_stream_wrappers( $class ) { 73 // If the image editor isn't available, skip it 74 if ( ! call_user_func( array( $class, 'test' ) ) ) { 75 $this->markTestSkipped( "$class is not available" ); 76 } 77 78 $filename = 'dummy://test/canola.jpg'; 79 $this->assertTrue( copy( DIR_TESTDATA . '/images/canola.jpg', $filename ) ); 80 $this->assertArrayHasKey( '/canola.jpg', WP_Test_Stream::$data['test'] ); 81 82 $img = new $class( $filename ); 83 $this->assertNotInstanceOf( 'WP_Error', $img ); 84 $loaded = $img->load(); 85 $this->assertTrue( $loaded ); 86 87 // Clean up 88 unset( $img ); 89 } 90 91 /** 92 * @dataProvider image_editor_class 93 */ 94 public function test_image_manipulation( $class ) { 95 // If the image editor isn't available, skip it 96 if ( ! call_user_func( array( $class, 'test' ) ) ) { 97 $this->markTestSkipped( "$class is not available" ); 98 } 99 100 $filename = 'dummy://test/canola.jpg'; 101 $this->assertTrue( copy( DIR_TESTDATA . '/images/canola.jpg', $filename ) ); 102 $this->assertArrayHasKey( '/canola.jpg', WP_Test_Stream::$data['test'] ); 103 104 $img = new $class( $filename ); 105 $this->assertNotInstanceOf( 'WP_Error', $img ); 106 $loaded = $img->load(); 107 $this->assertTrue( $loaded ); 108 109 $cropped = $img->crop( 0, 0, 100, 100 ); 110 $this->assertNotInstanceOf( 'WP_Error', $cropped ); 111 112 $ret = $img->save( 'dummy://test/canola-100x100.jpg' ); 113 $this->assertNotInstanceOf( 'WP_Error', $ret ); 114 $this->assertArrayHasKey( '/canola-100x100.jpg', WP_Test_Stream::$data['test'] ); 115 116 $orig_content = WP_Test_Stream::$data['test']['/canola.jpg']; 117 $resized_content = WP_Test_Stream::$data['test']['/canola-100x100.jpg']; 118 119 $this->assertFalse( $orig_content == $resized_content ); 120 121 // Clean up 122 unset( $img ); 123 } 124 125 /** 126 * @dataProvider image_editor_class 127 */ 128 129 public function test_image_upload( $class ) { 130 // If the image editor isn't available, skip it 131 if ( ! call_user_func( array( $class, 'test' ) ) ) { 132 $this->markTestSkipped( "$class is not available" ); 133 } 134 135 add_filter( 136 'wp_image_editors', 137 function ( $editors ) use ( $class ) { 138 return array( $class ); 139 } 140 ); 141 142 add_filter( 143 'upload_dir', 144 function ( $dir ) { 145 $dir['base_dir'] = 'dummy://test/uploads'; 146 $dir['path'] = 'dummy://test/uploads' . $dir['subdir']; 147 return $dir; 148 } 149 ); 150 151 $filename = DIR_TESTDATA . '/images/canola.jpg'; 152 $contents = file_get_contents( $filename ); 153 $upload = wp_upload_bits( basename( $filename ), null, $contents ); 154 $this->assertTrue( empty( $upload['error'] ), print_r( $upload, true ) ); 155 $id = $this->_make_attachment( $upload ); 156 157 $path = wp_upload_dir()['path']; 158 $this->assertEquals( substr( $path, 0, 13 ), 'dummy://test/' ); 159 160 $this->assertFileExists( sprintf( '%s/%s', $path, 'canola.jpg' ) ); 161 $this->assertFileExists( sprintf( '%s/%s', $path, 'canola-150x150.jpg' ) ); 162 $this->assertFileExists( sprintf( '%s/%s', $path, 'canola-300x225.jpg' ) ); 163 } 164 165 } 166 167 /* vim: set ft=php.wp: */