web-dev-qa-db-ja.com

4.1における画像の拡大縮小(四捨五入)の変更に関する問題を処理しました。(WP Ticket#18532)

現在、4.1以前のサイトから新しい設定にサイトコンテンツを移行し、丸め誤差の問題である{ #18532対応する修正 の問題に直面しています。

これを要約すると、WordPress側での長年の丸めの誤動作が修正されました。

693x173の画像をアップロードし、幅300に拡大するとします。

  • プレ4.1:300x74
  • ポスト4.1:300x75

問題

既存のファイルと<img>は変更されていないので、通常これは問題を引き起こしません。

しかし、WXRファイルからサムを再生成したり、添付ファイルをインポートしたりすると、ファイルシステム内で生成されるファイルの生成方法が異なり、すべての<img>post_content deadのままになります。

解決策を探す

私はさまざまな解決策を考えてきました:

悪い昔に戻る

Changeset 30660 は、4.1以前の古い動作をプラグインするために使用できる新しいフィルタwp_constrain_dimensionsを導入しました。これは問題を解決します。しかし、これが後で問題を引き起こす可能性があるかどうかと私は思っています、そして、私はそれがうまくいかないけれども、私はそれが理想的ではないと思うけれども.

時は彼らがa-Changin 'である

ですから、これは私たちに別の目標を残します。DBをクリーンアップし、古いファイルへのすべての参照を新しいファイルへの参照に置き換えます。私が今ここで実際に質問しているのは、これをどうやって行うかです。私はこの問題が多くの人々に影響を及ぼし、影響を与えると疑うので、私は効果的で一般的に適用可能な解決策を探しています。

私の現在の考えはこれです:

  1. 新しいファイルと壊れたタグを使って、インポート、再生成などを行います。
  2. ファイルシステム内のすべてのサイズ変更されたファイルからリストAを作成するか、あるいはデータベースからこの情報を取得します。
  3. このリストを解析して、4.1の前と同じように、ファイル名がすべて1ピクセルオフセットされた2番目のリストBを作成します。
  4. データベース全体を検索して置換し、Bのすべての出現箇所をAの関連する項目に置き換えます。

これが、この状況に対処するための最も賢く効率的な方法であるかどうか、私にはわかりません。それはまた少し強引な力を感じます。それでそれを実行する前に、私はWPSE群衆の無限の知恵をチェックしたいだけでした;)

[編集] ck-macleod sの答えを読んだことがある(ありがとう!)この問題を解決するには修正が必要だと思います。 [/編集]

[edit2]私はちょうど Tracの関連チケット を見つけました。参照用に追加しています。 [/ edit2]

17
kraftner

これは、 他の答え とは別のアプローチです。/ - インポーターでコンテンツをインポートするときに機能し、URLを一度に修正します。繰り返しますが、これはバトルテストされていませんが、私が解決した解決策であり、うまくいきました。

私はこれが問題を一度にそしてすべてのために解決し、それがうまくいけばそれがうまくいくので好む。壊れたものをDBに残して表示に修正しないので、後で壊れるものについて心配する必要はありません。

/*
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'));
4
kraftner

さて、これは壊れた画像をその場で置き換えるための基本的なアプローチです。これは、戦闘で検証されたソリューションよりも概念実証であることに注意してください。それはthe_contentフィルタをフックするだけで、状況によっては望ましくない副作用を引き起こす可能性があります。取扱注意。 :)

それはコードでもそう言っていますが、私もクレジットしたいと思います @Rarst for この答え 私のコードで使用されています。

/*
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'));
1
kraftner

大規模サイトのすべての画像ファイル(およびリンク)について問題をグローバルに、そして完全に解決する - たとえば、個人が時々WPスタイルを模倣して手動で画像ファイルの名前を変更した場合など - 難しいかもしれません。データベースの検索と置換操作も複雑になります(そしてリスクも伴います)。

壊れた画像や壊れた画像のリンクなど、大部分のエラーを処理して、次の方法で目的の結果または妥当なファクシミリを達成することができますか。

  1. すべてのサイズ変更された画像が新しい "round"メソッドではなく古い "intval"メソッドによってサイズ変更される前の日付を特定します。 (別の種類のカットオフも作成できますが、日付は最も簡単に見えます。)

  2. 締め切り日以下に公開されたすべての投稿について、ロード/レンダリング時にthe_content()でpreg_replaceを実行し、問題のあるパターンを持つすべての画像ファイルをキャプチャし、それらを目的のパターンに置き換えます。データベースは変更されないままですが、ほとんどの場合、出力にエラーはありません。解決策が「単一の」ページ投稿コンテンツと、ページのアーカイブおよびその他のプロセスの両方にも適用する必要があるかどうかわかりません。

このタイプの解決策が役に立つなら、次の問題は問題パターンと置き換えが適切に定義されることができるかどうかということでしょう。提案された解決策のリストから、実際にはいくつかの典型的なパターンが実際には分離される可能性があることがわかります(おそらく、以前のメディア設定からサムネイルやその他の画像を作成したものと思われます)。

指定したディレクトリ内のすべての画像ファイルを特定の日付まで、デフォルトの画像または画像リンクでグローバルに置き換える、私が使用した(そしてプラグイン化する過程の)単純な関数をすでに書いています。上記の方法による。それは、古いページで醜い結果を出すことに加えて、それぞれが2つずつ、何千ものエラーを出していたことに気づいていない、著作権侵害の注意を超えて、オペレータがすべての画像を単に削除したサイトのためのものでした画像。

問題のパターンと、出力を変更する必要があるインスタンスをさらに絞り込むことができれば、それを自分のフォーマットにプラグインすることができます。これはそれほど複雑ではなく、RegExerとしては私よりも優れています。でも簡単です。一方、この方法で問題が解決されない場合は、あなたや私の時間を無駄にしたくはありません。

1
CK MacLeod