处理4.1中图像缩放(四舍五入)更改的问题(WP票据#18532)

时间:2015-09-14 作者:kraftner

我目前正在将网站内容从旧的4.1版之前的网站迁移到新的设置,并遇到了舍入错误问题#18532 以及corresponding fix.

总结一下,WordPress修复了一个长期存在的舍入错误行为:

想象一下,我们上传一幅693x173的图像,并将其缩放到300的宽度:

4.1版本前:300x74版本后4.1版本后:300x75版本问题通常不会导致任何问题,因为现有文件和<img> 没有被碰过。

但是,当您重新生成拇指或从WXR文件导入附件时,它们在文件系统中的生成方式不同<img> 在里面post_content 死去的

寻找解决方案我一直在考虑各种解决方案:

回到糟糕的旧时代

Changeset 30660 引入了新过滤器wp_constrain_dimensions 它可以用来插入4.1之前的旧行为。这确实解决了问题。但我想知道这是否会在以后引起问题,一般来说,我希望得到修复,因此,虽然这是可行的,但我认为它不理想。

《时代》杂志改变了我们的目标:清理数据库,用新文件的引用替换所有旧文件的引用。我现在要问的问题是如何做到这一点。我正在寻找一个有效且普遍适用的解决方案,因为我怀疑这个问题确实会影响很多人

我目前的想法是:

导入、重新生成或任何会给我们留下新文件和坏标签的东西

  • 从文件系统中所有调整大小的文件创建一个列表a,或者从数据库获取此信息,解析此列表并创建第二个列表B,其中所有文件名都偏移一个像素,就像4.1之前一样进行搜索;用A中的相关条目替换整个数据库中出现的所有B,我只是不确定这是否是处理这种情况的最聪明和最有效的方法。这也让人觉得有点太暴力了。因此,在实现它之前,我只想用WPSE人群的无限智慧来验证;)

    [编辑]已阅读ck-macleods答案(谢谢!)我认为修复应该一劳永逸地解决这个问题,这样你就不必一直把这个问题放在脑后。[/编辑]

    我刚找到一个related ticket on Trac. 添加以供参考。[/编辑2]

  • 3 个回复
    SO网友:kraftner

    这是另一种方法the other answer 这在使用导入器导入内容时有效,并一次性修复URL。再次强调:这不是经过战斗考验的解决方案,但这是我确定的解决方案,它确实奏效了。

    我更喜欢这种方法,因为它可以一劳永逸地解决问题,如果它奏效,它也会奏效。由于您不会将损坏的东西留在数据库中并在显示器上进行修复,因此您无需担心以后的东西损坏。

    /*
    Plugin Name:  Bugfix Ticket 31581 for WP_Importer
    Plugin URI:   https://wordpress.stackexchange.com/a/206992/47733
    Description:  Fixes image references after post WP 4.1 image scaling change in post content when using the WP_Importer  (see https://core.trac.wordpress.org/ticket/31581)
    Version:      0.0.1
    */
    
    class Bugfix31581WPImporter {
    
        protected $remaps;
    
        /**
         * Initialize class, mainly setting up hooks
         */
        public function init(){
    
            if (WP_IMPORTING === true){
    
                $this->remaps = array();
    
                //This hook is chosen because it is pretty close to where the actual attachment import is happening.
                //TODO: May be reconsidered.
                add_filter(\'wp_update_attachment_metadata\', array($this, \'collectRemaps\'), 10 , 2);
    
                add_action(\'import_end\', array($this, \'remap\'));
                add_action(\'import_end\', array($this, \'importEnded\'));
            }
    
        }
    
        /**
         * Cleans up hooks after the import has ended.
         */
        public function importEnded(){
            remove_filter(\'wp_update_attachment_metadata\', array($this, \'collectRemaps\'), 10);
    
            remove_action(\'import_end\', array($this, \'remap\'), 10);
            remove_action(\'import_end\', array($this, \'importEnded\'), 10);
        }
    
        /**
         * When an attachment is added compare the resulting sizes with the sizes from the legacy algorithm and setup remap.
         *
         * @param $data
         * @param $post_id
         *
         * @return mixed
         */
        public function collectRemaps($data, $post_id ){
    
            $intermediate_sizes = $this->getIntermediateSizes();
    
            if(empty($data) || !array_key_exists(\'sizes\', $data)){
                return $data;
            }
    
            foreach($data[\'sizes\'] as $key => $size){
    
                $size_new_algorithm = array($size[\'width\'], $size[\'height\']);
    
                $dest_w = $intermediate_sizes[$key][\'width\'];
                $dest_h = $intermediate_sizes[$key][\'height\'];
                $crop = $intermediate_sizes[$key][\'crop\'];
    
                add_filter(\'wp_constrain_dimensions\', array($this, \'legacy_wp_constrain_dimensions\'), 10, 5);
    
                $size_old_algorithm = image_resize_dimensions($data[\'width\'], $data[\'height\'], $dest_w, $dest_h, $crop);
    
                //Bail out in the rare case of `image_resize_dimensions` returning false
                if($size_old_algorithm===false){
                    continue;
                }
    
                $size_old_algorithm = array($size_old_algorithm[4], $size_old_algorithm[5]);
    
                remove_filter(\'wp_constrain_dimensions\', array($this, \'legacy_wp_constrain_dimensions\'), 10);
    
                // Compare the current size with the calculation of the old algorithm...
                $diff = array_diff($size_new_algorithm, $size_old_algorithm);
    
                // ...to detect any mismatches
                if(!empty($diff)){
    
                    $oldFilename = $this->getOldFilename($size[\'file\'], $size_old_algorithm);
    
                    // If getting the old filename didn\'t work for some reason (probably other filename-structure) bail out.
                    if($oldFilename===false){
                        continue;
                    }
    
                    if(!array_key_exists($post_id, $this->remaps)){
                        $this->remaps[$post_id] = array();
                    }
    
                    $this->remaps[$post_id][$size[\'file\']] = array(
                        \'file\' => $oldFilename,
                        \'size\' => $key
                    );
                }
    
            }
    
            return $data;
        }
    
        /**
         * Get resize settings for all image sizes
         *
         * Taken from wp_generate_attachment_metadata() in includes/image.php
         *
         * @return array
         */
        public function getIntermediateSizes(){
    
            global $_wp_additional_image_sizes;
    
            $sizes = array();
            foreach ( get_intermediate_image_sizes() as $s ) {
                $sizes[$s] = array( \'width\' => \'\', \'height\' => \'\', \'crop\' => false );
                if ( isset( $_wp_additional_image_sizes[$s][\'width\'] ) )
                    $sizes[$s][\'width\'] = intval( $_wp_additional_image_sizes[$s][\'width\'] ); // For theme-added sizes
                else
                    $sizes[$s][\'width\'] = get_option( "{$s}_size_w" ); // For default sizes set in options
                if ( isset( $_wp_additional_image_sizes[$s][\'height\'] ) )
                    $sizes[$s][\'height\'] = intval( $_wp_additional_image_sizes[$s][\'height\'] ); // For theme-added sizes
                else
                    $sizes[$s][\'height\'] = get_option( "{$s}_size_h" ); // For default sizes set in options
                if ( isset( $_wp_additional_image_sizes[$s][\'crop\'] ) )
                    $sizes[$s][\'crop\'] = $_wp_additional_image_sizes[$s][\'crop\']; // For theme-added sizes
                else
                    $sizes[$s][\'crop\'] = get_option( "{$s}_crop" ); // For default sizes set in options
            }
    
            return $sizes;
        }
    
        /**
         * Turn the new filename into the old filename reducing the height by one
         *
         * @param $newFilename
         * @param $size
         *
         * @return mixed
         */
        public function getOldFilename($newFilename, $size){
    
            $dimensions = array();
    
            $filetypes = $this->getAllowedImageExtentions();
    
            // TODO: This pattern can be different. See `image_make_intermediate_size` in image editor implementation.
            $matchFiles = \'/([0-9]{1,5})x([0-9]{1,5}).(\' . $filetypes . \')$/\';
    
            // Extract the dimensions
            preg_match($matchFiles,$newFilename,$dimensions);
    
            // If the file URL doesn\'t allow guessing the dimensions bail out.
            if(empty($dimensions)){
                return $newFilename;
            }
    
            $newStub = $dimensions[1] . \'x\' . $dimensions[2] . \'.\' . $dimensions[3];
    
            $oldStub = $size[0] . \'x\' . $size[1] . \'.\' . $dimensions[3];
    
            $oldFilename = str_replace($newStub,$oldStub,$newFilename);
    
            return $oldFilename;
        }
    
        /**
         * Extract all file extensions that match an image/* mime type
         *
         * @return string
         */
        protected function getAllowedImageExtentions(){
            $allowed_filetypes = get_allowed_mime_types();
    
            $allowed_images = array();
    
            foreach($allowed_filetypes as $extensions => $mimetype){
                if( substr($mimetype,0,6) == \'image/\' ){
                    $allowed_images[] = $extensions;
                }
            }
    
            return implode(\'|\',$allowed_images);
        }
    
    
        /**
         * This is the heart of this class. Based on the collected remaps from earlier it does a S&R on the DB.
         */
        public function remap(){
    
            global $wpdb;
    
            foreach($this->remaps as $attachment_id => $replaces){
    
                foreach($replaces as $new_url => $old_data){
    
                    $to_url = wp_get_attachment_image_src($attachment_id,$old_data[\'size\']);
                    $to_url = $to_url[0];
    
                    $from_url = str_replace($new_url, $old_data[\'file\'], $to_url);
    
                    // remap urls in post_content
                    $wpdb->query( $wpdb->prepare("UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, %s, %s)", $from_url, $to_url) );
    
                    //TODO: This is disabled as enclosures can\'t be images, right?
                    // remap enclosure urls
                    //$result = $wpdb->query( $wpdb->prepare("UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_key=\'enclosure\'", $from_url, $to_url) );
    
                }
    
            }
    
        }
    
        /**
         * This is a copy of the legacy pre 4.1 wp_constrain_dimensions()
         *
         * @param $dimensions
         * @param $current_width
         * @param $current_height
         * @param $max_width
         * @param $max_height
         *
         * @return array
         */
        public function legacy_wp_constrain_dimensions($dimensions, $current_width, $current_height, $max_width, $max_height){
            if ( !$max_width and !$max_height )
                return array( $current_width, $current_height );
    
            $width_ratio = $height_ratio = 1.0;
            $did_width = $did_height = false;
    
            if ( $max_width > 0 && $current_width > 0 && $current_width > $max_width ) {
                $width_ratio = $max_width / $current_width;
                $did_width = true;
            }
    
            if ( $max_height > 0 && $current_height > 0 && $current_height > $max_height ) {
                $height_ratio = $max_height / $current_height;
                $did_height = true;
            }
    
            // Calculate the larger/smaller ratios
            $smaller_ratio = min( $width_ratio, $height_ratio );
            $larger_ratio  = max( $width_ratio, $height_ratio );
    
            if ( intval( $current_width * $larger_ratio ) > $max_width || intval( $current_height * $larger_ratio ) > $max_height )
                // The larger ratio is too big. It would result in an overflow.
                $ratio = $smaller_ratio;
            else
                // The larger ratio fits, and is likely to be a more "snug" fit.
                $ratio = $larger_ratio;
    
            // Very small dimensions may result in 0, 1 should be the minimum.
            $w = max ( 1, intval( $current_width  * $ratio ) );
            $h = max ( 1, intval( $current_height * $ratio ) );
    
            // Sometimes, due to rounding, we\'ll end up with a result like this: 465x700 in a 177x177 box is 117x176... a pixel short
            // We also have issues with recursive calls resulting in an ever-changing result. Constraining to the result of a constraint should yield the original result.
            // Thus we look for dimensions that are one pixel shy of the max value and bump them up
            if ( $did_width && $w == $max_width - 1 )
                $w = $max_width; // Round it up
            if ( $did_height && $h == $max_height - 1 )
                $h = $max_height; // Round it up
    
            return array( $w, $h );
        }
    
    }
    
    add_filter(\'import_start\',array(new Bugfix31581WPImporter(),\'init\'));
    

    SO网友:CK MacLeod

    对于大型站点中的所有图像文件(和链接)来说,全面、完美地解决这个问题——例如,考虑到个人可能偶尔会模仿WP样式手动重命名图像文件,以及其他奇怪的变体——可能很难。数据库搜索和替换操作也将涉及复杂性(和风险!)。

    您是否可以通过以下方法处理绝大多数错误(我想是损坏的图像和损坏的图像链接),并实现预期的最终结果或合理的传真?

    识别所有已调整大小的图像之前的日期,在该日期之前,使用旧的“intval”方法而不是新的“round”方法调整大小。(也可以创建一种不同的截止日期,但日期似乎最简单。)

  • 对于所有已发布的帖子<;=截止日期,在加载/渲染时对\\u content()运行preg\\u replace,捕获具有一个或多个问题模式的所有图像文件,并将其替换为所需的模式。数据库将保持不变,但输出在大多数情况下是无错误的。我不确定该解决方案是否需要同时应用于“单一”页面帖子内容以及归档页面和其他流程。

    如果这种类型的解决方案有帮助,那么下一个问题就是是否可以充分定义问题模式和替换。从您提出的解决方案列表中可以看出,事实上可能有一些典型的模式是可以隔离的(可能是从以前生成缩略图和其他一些图像的媒体设置中提取的)。

    我已经编写了一个我使用的更简单的函数(正在变成插件),该函数按照上述方法使用默认图像或图像链接全局替换指定目录中截至某一日期的所有图像文件。对于一个网站来说,这是一种过度的版权保护,运营商只是删除了他们所有的图片,而没有意识到,除了在旧页面上产生难看的结果外,他们还产生了数千个错误,每个图片两个错误。

    如果您可以更具体地缩小问题模式的范围,以及需要更改输出的实例,那么我可以考虑将其插入到我的格式中,这并不复杂,对于一个比我更好的RegExer来说,这甚至可能很容易。另一方面,如果这种方法不能为您解决问题,我也不想浪费您或我的时间。

  • SO网友:kraftner

    好的,这是一种在运行中替换损坏图像的基本方法。请注意,这与其说是经过战斗测试的解决方案,不如说是概念验证。它只是挂在the_content 在某些情况下可能(可能)有一些不必要的副作用的过滤器。小心轻放。:)

    虽然代码中也这么说,但我也希望@Rarst 对于this answer 在我的代码中使用。

    /*
    Plugin Name:  Bugfix 31581 Live
    Plugin URI:   https://wordpress.stackexchange.com/a/206986/47733
    Description:  Fixes image references in post content after post 4.1 image scaling change (see https://core.trac.wordpress.org/ticket/31581)
    Version:      0.0.1
    */
    
    class Bugfix31581Live {
    
        protected $matchURLs;
        protected $matchFiles;
    
        protected $remaps;
    
        public function init(){
    
            $filetypes = $this->get_allowed_image_extentions();
    
            $baseurl = wp_upload_dir();
            $baseurl = preg_quote($baseurl[\'baseurl\'], \'/\');
    
            $this->matchURLs = \'/\' . $baseurl . \'\\/.??([a-zA-Z0-9_-]*?\\.(?:\' . $filetypes . \'))/\';
    
            //TODO: This pattern can be different. See `image_make_intermediate_size` in image editor implementation
            $this->matchFiles = \'/([0-9]{1,4})x([0-9]{1,4}).(\' . $filetypes . \')$/\';
    
            add_filter(\'the_content\', array($this, \'update_urls\') );
        }
    
        public function update_urls($content){
    
            $urls = array();
    
            preg_match_all($this->matchURLs,$content,$urls);
    
            // Bail out early if we don\'t have any match.
            if($urls === false || empty($urls[0])){
                return $content;
            }
    
            // Loop through all matches
            foreach($urls[0] as $url){
    
                // Try to resolve this URL to an attachment ID
                $id = $this->get_attachment_id($url);
    
                // If not  let\'s see if this might be a URL that has been broken by our beloved Changeset 30660
                if( $id === false ){
    
                    $dimensions = array();
    
                    // Extract the dimensions
                    preg_match($this->matchFiles,$url,$dimensions);
    
                    // If the file URL doesn\'t allow guessing the dimensions bail out.
                    if(empty($dimensions)){
                        continue;
                    }
    
                    // Old filename
                    $old = $dimensions[1] . \'x\' . $dimensions[2] . \'.\' . $dimensions[3];
    
                    // New filename (not sure if this exists yet)
                    $new = $dimensions[1] . \'x\' . ($dimensions[2]+1) . \'.\' . $dimensions[3];
    
                    // Build the new URL (not sure if this exists yet)
                    $new_url = str_replace($old,$new,$url);
    
                    // Try to get the attachment with the new url
                    $id = $this->get_attachment_id($new_url);
    
                    // Bad luck. This also doesn\'t exist.
                    if( $id === false ) {
                        continue;
                    }
    
                    // Just to be sure everything is in sync we get the URL built from id and size.
                    $db_url = wp_get_attachment_image_src($id,array($dimensions[1], $dimensions[2]+1));
    
                    // Check if the URL we created and the one wp_get_attachment_image_src builds are the same.
                    if($new_url === $db_url[0]){
    
                        // Awesome let\'s replace the broken URL.
                        $content = str_replace($url,$new_url,$content);
                    }
    
                }
    
            }
    
            return $content;
        }
    
        /**
         * Get the Attachment ID for a given image URL.
         *
         * @link   https://wordpress.stackexchange.com/a/7094
         *
         * @param  string $url
         *
         * @return boolean|integer
         */
        protected function get_attachment_id( $url ) {
    
            $dir = wp_upload_dir();
    
            // baseurl never has a trailing slash
            if ( false === strpos( $url, $dir[\'baseurl\'] . \'/\' ) ) {
                // URL points to a place outside of upload directory
                return false;
            }
    
            $file  = basename( $url );
            $query = array(
                \'post_type\'  => \'attachment\',
                \'fields\'     => \'ids\',
                \'meta_query\' => array(
                    array(
                        \'value\'   => $file,
                        \'compare\' => \'LIKE\',
                    ),
                )
            );
    
            $query[\'meta_query\'][0][\'key\'] = \'_wp_attached_file\';
    
            // query attachments
            $ids = get_posts( $query );
    
            if ( ! empty( $ids ) ) {
    
                foreach ( $ids as $id ) {
    
                    $tmp = wp_get_attachment_image_src( $id, \'full\' );
    
                    // first entry of returned array is the URL
                    if ( $url === array_shift( $tmp ) )
                        return $id;
                }
            }
    
            $query[\'meta_query\'][0][\'key\'] = \'_wp_attachment_metadata\';
    
            // query attachments again
            $ids = get_posts( $query );
    
            if ( empty( $ids) )
                return false;
    
            foreach ( $ids as $id ) {
    
                $meta = wp_get_attachment_metadata( $id );
    
                foreach ( $meta[\'sizes\'] as $size => $values ) {
    
                    $tmp = wp_get_attachment_image_src( $id, $size );
    
                    if ( $values[\'file\'] === $file && $url === array_shift( $tmp ) )
                        return $id;
                }
            }
    
            return false;
        }
    
        protected function get_allowed_image_extentions(){
            $allowed_filetypes = get_allowed_mime_types();
    
            $allowed_images = array();
    
            foreach($allowed_filetypes as $extensions => $mimetype){
                if( substr($mimetype,0,6) == \'image/\' ){
                    $allowed_images[] = $extensions;
                }
            }
    
            return implode(\'|\',$allowed_images);
        }
    
    }
    
    add_filter(\'init\',array(new Bugfix31581Live(),\'init\'));
    

    相关推荐

    Host images only on CDN

    我开发了一个wordpress网站,该网站托管在AWS上。该网站运行良好,但由于我希望它成为一个电子商务网站,将有大量的图像。AWS的托管帐户仅提供30 GB的存储空间。所以我想host images completely on an external CDN off my server. 但最近对CDN的搜索导致我只缓存CDN,我希望只在其他服务器上托管图像。有线索吗?