处理4.1中图像缩放比例(取整)更改的问题(WP Ticket#18532)


17

我目前正在将网站内容从旧的4.1之前的站点迁移到新的设置,并且遇到#18532的舍入错误问题和相应的修复问题

总结一下,这解决了WordPress长期存在的舍入问题:

想象一下,我们以693x173上传图片并将其缩放为300的宽度:

  • 4.1以下:300x74
  • 文章4.1:300x75

问题

通常,这不会造成任何问题,因为现有文件和 <img>不会被触摸。

但是,当你再生拇指或从WXR文件导入附件,它们会在文件系统中生成的不同使所有<img>post_content死。

寻找解决方案

我一直在想各种解决方案:

回到糟糕的过去

Changeset 30660引入了一个新的筛选器wp_constrain_dimensions,该筛选器可用于仅插入4.1之前的旧行为。这确实解决了该问题。但是我想知道这是否会在以后引起问题,通常我想解决这个问题,因此尽管我认为这是不理想的。

他们是一个改变的时代

因此,这给了我们另一个目标:清理数据库,并用对新文件的引用替换对旧文件的所有引用。我现在在这里实际上要问的问题是如何做到这一点。我正在寻找一个有效且普遍适用的解决方案,因为我怀疑此问题确实会影响很多人

我目前的想法是这样的:

  1. 导入,重新生成或使用新文件和破损标签留下的任何内容。
  2. 从文件系统中所有调整大小的文件创建列表A,或者从数据库中获取此信息
  3. 解析此列表并创建第二个列表B,其文件名全部偏移一个像素,就像在4.1之前一样
  4. 在整个数据库中进行搜索和替换,将所有B的出现替换为A中的相关条目

我只是不确定这是否是处理这种情况的最聪明,最有效的方法。它也感觉有点太暴力了。因此,在实施它之前,我只想检查一下WPSE人群的无限智慧;)

[edit]阅读了ck-macleod的答案(谢谢!),我认为一个修复程序应该一劳永逸地解决此问题,因此您无需经常将这个问题放在脑后。[/编辑]

[edit2]我刚刚在Trac上找到了相关票证。添加以供参考。[/ edit2]


在这里你有error issue of #13852你的意思是#18532?:)
Aravona 2015年

2
糟糕,是的。:)
kraftner 2015年

Answers:


4

这是与其他方法结合使用另一种方法,方法在使用导入器导入内容并永久修复URL时起作用。再说一次:这不是经过考验的,但是我确定的解决方案确实有效。

我更喜欢这样做,因为它可以一劳永逸地解决此问题,并且如果可行,它将奏效。由于您不会在数据库中留下损坏的东西并将其修复,因此您无需担心以后会损坏东西。

/*
Plugin Name:  Bugfix Ticket 31581 for WP_Importer
Plugin URI:   /wordpress//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'));

做得好,+ 1。
gmazzap

1

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

您是否可以通过以下方法处理绝大多数错误-我想是损坏的图像和断开的图像链接-并达到所需的最终结果或合理的传真?

  1. 标识所有通过旧的“ intval”方法而不是新的“ round”方法调整图像大小的日期。(也可以创建其他截止日期,但日期似乎最简单。)

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

如果这种解决方案会有所帮助,那么下一个问题将是是否可以充分定义问题模式和替代方案。从建议的解决方案列表中可以看出,实际上可能可以隔离一些典型的模式(可能取自先前的媒体设置,产生缩略图和其他图像)。

我已经编写了一个更简单的函数(正在使用一个插件),该函数使用默认的图像或图像链接全局替换指定目录中的所有图像文件,直到某个日期为止,按照上述方法。这是一个网站,出于版权保护的考虑,操作员只是删除了所有图像,没有意识到除了在旧页面上产生难看的结果外,他们还发现了数千个错误,每个错误两个图片。

如果您可以更具体地缩小问题模式的范围,并且需要更改输出的实例,那么我可以看到将其插入到我的格式中的情况-这不是很复杂,并且可以提供比我更好的RegExer甚至容易。另一方面,如果这种方法不能为您解决问题,我就不会浪费您或我的时间。


感谢您对此的重视!只是有些想法:我认为数据库中的数据有误,只显示猴子的补丁并不是一个很干净且可持续的解决方案。它可能随时中断,并且会损害每个视图的性能。它还可能具有不可预测的副作用,例如,对于以其他方式解析或更改内容的其他插件。根据完成方式的不同,图像仍会在后端破碎。在那种情况下,我认为wp_constrain_dimensions在执行导入时仅通过按问题中所述的那样重置缩放比例,并避免重建指尖会更干净。
kraftner

你太客气了。事实是,数据库中的数据不是错误的数据,只是新体制下不再需要的数据。对于性能方面的影响,我认为它可能很小,尤其是因为从理论上讲,它仅适用于日期X之前的帖子。更一般地说,可能没有一种千篇一律的最佳解决方案:我认为足够的解决方案可能会因站点的特性,过去的图像处理应用程序和习惯,数据库的大小,实际和时间限制等因素而异。
CK MacLeod

您可能是对的,不会有一种千篇一律的解决方案。我目前正在探索各种处理方法,其中包括一种与您类似的渲染方法和一种导入方法,我更喜欢这种方法,因为它可以彻底解决此问题。我们将看到结果。:)
kraftner 2015年

1

好的,这是即时替换损坏图像的基本方法。请注意,这比经过考验的解决方案更能证明概念。它只是挂在the_content过滤器上,在某些情况下,过滤器可能(可能)具有一些有害的副作用。小心轻放。:)

虽然在代码中是这么说的也是,我也想给信贷@Rarst对于这个答案在我的代码中使用。

/*
Plugin Name:  Bugfix 31581 Live
Plugin URI:   /wordpress//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   /wordpress//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'));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.