Menangani masalah dengan perubahan penskalaan gambar (pembulatan) di 4.1 (WP Ticket # 18532)

17

Saat ini saya dalam perjalanan migrasi konten situs dari pra 4,1 situs lama ke setup baru dan memukul masalah dengan masalah kesalahan pembulatan dari # 18.532 dan sesuai memperbaiki .

Untuk meringkas ini memperbaiki perilaku pembulatan berdiri lama di sisi WordPress:

Bayangkan kami mengunggah gambar dengan 693x173 dan skala ke lebar 300:

  • pra 4.1: 300x74
  • posting 4.1: 300x75

Masalah

Umumnya ini tidak menyebabkan masalah apapun karena file yang ada dan <img>tidak tersentuh.

Tetapi ketika Anda regenerasi jempol atau mengimpor lampiran dari file WXR mereka mendapatkan dihasilkan berbeda di filesystem meninggalkan semua <img>di post_contentmati.

Mencari solusi

Saya telah memikirkan berbagai solusi:

Kembali ke masa lalu yang buruk

Changeset 30660 memperkenalkan filter baru wp_constrain_dimensionsyang dapat digunakan untuk cukup menyambungkan perilaku lama dari sebelum 4,1 kembali. Ini memperbaiki masalah. Tetapi saya bertanya-tanya apakah ini dapat menyebabkan masalah di kemudian hari dan secara umum saya ingin memperbaikinya meskipun ini berfungsi saya anggap tidak ideal.

Masa Mereka Berubah

Jadi ini meninggalkan kita dengan tujuan lain: Bersihkan DB dan ganti semua referensi ke file lama dengan referensi ke file baru. Pertanyaan saya sebenarnya bertanya di sini sekarang adalah bagaimana melakukan ini. Saya mencari solusi yang efektif dan berlaku umum karena saya menduga masalah ini mempengaruhi dan akan mempengaruhi banyak orang

Ide saya saat ini adalah:

  1. Impor, buat ulang, atau apa pun yang meninggalkan kita dengan file baru dan tag yang rusak.
  2. Buat daftar A dari semua file yang diubah ukurannya di sistem file atau sebagai alternatif dapatkan informasi ini dari database
  3. Parsing daftar ini dan buat daftar kedua B dengan nama file semuanya diimbangi oleh satu piksel karena akan terlihat sebelum 4.1
  4. Lakukan pencarian & ganti seluruh database yang menggantikan semua kejadian B dengan entri terkait di A

Saya hanya tidak yakin apakah ini adalah cara paling cerdas dan efisien untuk menangani situasi ini. Rasanya juga agak terlalu kasar. Jadi sebelum menerapkannya, saya hanya ingin memeriksa dengan kebijaksanaan tak terbatas dari kerumunan WPSE;)

Setelah membaca jawaban ck-macleod (terima kasih!) Saya pikir perbaikan harus menyelesaikan ini sekali dan untuk semua sehingga Anda tidak perlu terus-menerus menyimpan masalah ini di belakang kepala Anda. [/ edit]

[edit2] Saya baru saja menemukan tiket terkait di Trac . Menambahkan untuk referensi. [/ edit2]

pembuat kraftner
sumber
di mana error issue of #13852Anda maksudnya #18532? :)
Aravona
2
Ups, ya, sudah diperbaiki. :)
kraftner

Jawaban:

4

Ini adalah pendekatan lain daripada jawaban lain yang berfungsi saat mengimpor konten dengan importir dan memperbaiki URL sekali dan untuk semua. Sekali lagi: Ini bukan pertempuran-diuji tetapi merupakan solusi yang saya pilih dan itu berhasil.

Saya lebih suka ini karena ini menyelesaikan masalah sekali dan untuk semua dan jika itu berhasil, itu berhasil. Karena Anda tidak meninggalkan barang rusak di DB dan memperbaikinya pada layar Anda tidak perlu khawatir tentang hal-hal yang melanggar nanti.

/*
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'));
pembuat kraftner
sumber
Kerja bagus menjadi +1.
gmazzap
1

Memecahkan masalah secara global dan sempurna untuk SEMUA file gambar (dan tautan) di situs besar - mengingat kemungkinan, misalnya, bahwa individu mungkin sesekali mengganti nama file gambar secara manual dengan meniru gaya WP - dan variasi aneh lainnya - mungkin sulit. Pencarian basis data dan operasi penggantian juga akan melibatkan komplikasi (dan risiko!).

Bisakah Anda menangani sebagian besar kesalahan - gambar rusak dan tautan gambar rusak, saya kira - dan mencapai hasil akhir yang diinginkan atau faksimili yang wajar, dengan metode berikut?

  1. Identifikasi tanggal sebelum semua ukuran gambar di mana diubah ukurannya dengan metode "intval" lama daripada metode "putaran" yang baru. (Jenis cut-off yang berbeda juga bisa dibuat, tetapi tanggal sepertinya paling mudah.)

  2. Untuk semua posting yang diterbitkan <= tanggal batas, jalankan preg_replace pada the_content () pada waktu load / render, ambil semua file gambar dengan pola atau pola masalah dan gantikan dengan pola yang diinginkan. Basis data akan tetap tidak berubah, tetapi hasilnya akan terbebas dari kesalahan dalam banyak kasus. Saya tidak yakin apakah solusinya perlu diterapkan untuk konten posting halaman "tunggal" dan untuk mengarsipkan halaman dan proses lainnya juga.

Jika solusi jenis ini akan membantu, maka pertanyaan selanjutnya adalah apakah pola masalah dan penggantiannya dapat didefinisikan secara memadai. Itu terlihat dari daftar solusi yang diajukan yang mungkin beberapa pola tipikal sebenarnya dapat diisolasi (mungkin diambil dari pengaturan media sebelumnya yang menghasilkan gambar kecil dan beberapa gambar lainnya).

Saya telah menulis fungsi sederhana yang saya gunakan (dan sedang dalam proses berubah menjadi plug-in), yang secara global mengganti semua file gambar dalam direktori yang ditunjuk, hingga tanggal tertentu, dengan gambar atau tautan gambar default, sesuai metode yang dijelaskan di atas. Itu adalah untuk situs di mana, di luar kewaspadaan hak cipta, operator hanya menghapus semua gambar mereka, tidak menyadari bahwa, selain menghasilkan hasil yang buruk di halaman lama, mereka juga menghasilkan ribuan kesalahan, masing-masing dua untuk masing-masing gambar.

Jika Anda dapat mempersempit pola masalah lebih khusus, dan contoh-contoh di mana output perlu diubah, maka saya bisa melihat tentang memasukkannya ke dalam format saya - yang tidak terlalu rumit, dan yang untuk RegExer yang lebih baik daripada yang saya bisa bahkan menjadi mudah. Di sisi lain, saya tidak ingin membuang waktu Anda atau saya jika pendekatan ini tidak akan menjawab masalah untuk Anda.

CK MacLeod
sumber
Terima kasih atas pendapat Anda! Hanya beberapa pemikiran: Saya pikir memiliki data yang salah dalam DB dan hanya menambal monyet pada layar bukanlah solusi yang sangat bersih dan berkelanjutan. Ini dapat merusak waktu dan merusak kinerja pada setiap tampilan. Ini juga mungkin memiliki efek samping yang tidak terduga misalnya untuk plugin lain yang menguraikan atau mengubah konten dengan cara lain. Tergantung pada bagaimana hal itu dilakukan, gambar masih rusak di backend. Dalam hal ini saya pikir hanya mengatur ulang skala wp_constrain_dimensionsseperti yang disebutkan dalam pertanyaan saat melakukan impor dan menahan diri dari membangun kembali ibu jari akan lebih bersih.
kraftner
Anda cukup diterima. Masalahnya, data dalam DB bukan data yang salah, hanya saja bukan data yang Anda inginkan lagi di bawah rezim baru. Pada hit kinerja, saya pikir itu akan menjadi minimal, terutama karena itu hanya berlaku, secara teori, untuk posting sebelum tanggal X. Secara umum, mungkin tidak ada solusi terbaik satu-ukuran-cocok-untuk-semua: Saya pikir yang baik solusi yang cukup dapat bervariasi sesuai dengan karakter situs, aplikasi dan kebiasaan penanganan gambar sebelumnya, ukuran DB, batasan praktis dan waktu, dan sebagainya.
CK MacLeod
Anda mungkin benar bahwa tidak akan ada solusi satu ukuran untuk semua. Saat ini saya sedang mengeksplorasi berbagai cara untuk menangani hal ini di antaranya adalah pendekatan on-render yang serupa dengan Anda dan pendekatan on-import yang saya lebih suka karena ini menyelesaikan ini sekali dan untuk semua. Kami akan melihat di mana ini mengarah. :)
kraftner
1

Oke ini adalah pendekatan dasar untuk mengganti gambar yang rusak dengan cepat. Ketahuilah bahwa ini lebih merupakan bukti konsep daripada solusi teruji pertempuran. Itu hanya mengait pada the_contentfilter yang mungkin (mungkin memiliki) beberapa efek samping yang tidak diinginkan dalam beberapa situasi. Tangani dengan hati-hati. :)

Walaupun dikatakan demikian dalam kode juga saya juga ingin kredit @Rarst untuk jawaban ini digunakan dalam kode saya.

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