Gestire i problemi con il cambiamento del ridimensionamento delle immagini (arrotondamento) in 4.1 (WP Ticket # 18532)


17

Attualmente sto eseguendo la migrazione del contenuto del sito da un vecchio sito precedente alla 4.1 in una nuova configurazione e ho riscontrato un problema con il problema dell'errore di arrotondamento di # 18532 e la correzione corrispondente .

Per riassumere questo, risolto un malfunzionamento di arrotondamento di vecchia data sul lato di WordPress:

Immagina di caricare un'immagine con 693x173 e di ridimensionarla su una larghezza di 300:

  • pre 4.1: 300x74
  • post 4.1: 300x75

Il problema

In genere ciò non causa alcun problema perché i file esistenti <img>non vengono toccati.

Ma quando si rigenera il pollice o allegati importazione da un file WXR ottengono generati in modo diverso nel file system lasciando tutto <img>in post_contentmorti.

Alla ricerca di una soluzione

Ho pensato a varie soluzioni:

Tornando ai brutti vecchi tempi

Il changeset 30660 ha introdotto un nuovo filtro wp_constrain_dimensionsche può essere utilizzato solo per ricollegare il vecchio comportamento precedente alla 4.1. Questo risolve il problema. Ma mi chiedo se questo potrebbe causare problemi in seguito e in generale mi piacerebbe avere la correzione, quindi anche se funziona lo riterrei non ideale.

I tempi che stanno cambiando

Quindi questo ci lascia con un altro obiettivo: ripulire il DB e sostituire tutti i riferimenti ai vecchi file con riferimenti ai nuovi file. La domanda che in realtà sto ponendo qui ora è come farlo. Sto cercando una soluzione efficace e generalmente applicabile, poiché sospetto che questo problema lo faccia e influenzerà molte persone

La mia idea attuale è questa:

  1. Importa, rigenera o qualunque cosa ci lasci con i nuovi file e tag rotti.
  2. Creare un elenco A da tutti i file ridimensionati nel filesystem o in alternativa ottenere queste informazioni dal database
  3. Analizza questo elenco e crea un secondo elenco B con nomi di file tutti sfalsati di un pixel come apparirebbe prima 4.1
  4. Effettua una ricerca e sostituisci l'intero database sostituendo tutte le occorrenze di B con la relativa voce in A

Non sono sicuro che questo sia il modo più intelligente ed efficiente per gestire questa situazione. Sembra anche un po 'troppo bruta. Quindi prima di implementarlo volevo solo verificare con l'infinita saggezza della folla del WPSE;)

[modifica] Dopo aver letto la risposta di ck-macleod (grazie!) Penso che una soluzione dovrebbe risolverlo una volta per tutte, quindi non è necessario mantenere costantemente questo problema nella parte posteriore della testa. [/modificare]

[edit2] Ho appena trovato un biglietto correlato su Trac . Aggiunta per riferimento. [/ EDIT2]


dove error issue of #13852intendevi #18532? :)
Aravona,

2
Oops, sì, risolto. :)
Kraftner,

Risposte:


4

Questo è un altro approccio rispetto all'altra risposta che funziona durante l'importazione di contenuti con l'importatore e corregge gli URL una volta per tutte. Di nuovo: questa non è una prova di battaglia, ma è la soluzione su cui ho optato e ha funzionato.

Preferisco questo perché risolve il problema una volta per tutte e se funziona, funziona. Dal momento che non si lasciano oggetti rotti nel DB e li si risolve sul display non è necessario preoccuparsi che si rompano in seguito.

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

Bel lavoro un +1.
gmazzap

1

Risolvere il problema a livello globale e perfettamente per TUTTI i file di immagine (e collegamenti) in un sito di grandi dimensioni - data la possibilità, ad esempio, che gli individui possano aver occasionalmente rinominato file di immagine imitando manualmente lo stile WP - e altre strane variazioni - potrebbe essere difficile. Anche le operazioni di ricerca e sostituzione del database comporteranno complicazioni (e rischi!).

Potresti gestire la stragrande maggioranza degli errori - immagini interrotte e collegamenti di immagini interrotti, presumo - e ottenere il risultato finale desiderato o un facsimile ragionevole, con il seguente metodo?

  1. Identificare la data prima della quale tutte le immagini ridimensionate sono state ridimensionate con il vecchio metodo "intval" anziché con il nuovo metodo "round". (Potrebbe anche essere creato un diverso tipo di cut-off, ma la data sembra più semplice.)

  2. Per tutti i post pubblicati <= la data limite, esegui preg_replace su the_content () al momento del caricamento / rendering, acquisendo tutti i file di immagine con lo schema o gli schemi del problema e sostituendoli con lo schema desiderato. Il database rimarrebbe inalterato, ma l'output sarebbe privo di errori nella maggior parte dei casi. Non sono sicuro che la soluzione debba essere applicata sia al contenuto "singolare" dei post delle pagine sia all'archiviazione di pagine e altri processi.

Se una soluzione di questo tipo fosse utile, la domanda successiva sarebbe se i modelli di problema e le sostituzioni potessero essere adeguatamente definiti. Dal tuo elenco di soluzioni proposte sembra che probabilmente alcuni schemi tipici potrebbero essere effettivamente isolati (forse presi da precedenti impostazioni multimediali che producono miniature e alcune altre immagini).

Ho già scritto una funzione più semplice che uso (e sto per trasformare in un plug-in), che sostituisce globalmente tutti i file di immagine in directory designate, fino a una certa data, con un'immagine predefinita o un collegamento immagine, secondo il metodo sopra descritto. Era per un sito in cui, in eccesso di cautela sul copyright, gli operatori semplicemente cancellavano tutte le loro immagini, ignari del fatto che, oltre a produrre brutti risultati su vecchie pagine, stavano anche scoprendo migliaia di errori, due ciascuno per ciascuno Immagine.

Se riesci a restringere il modello del problema in modo più specifico e le istanze in cui l'output dovrebbe essere modificato, allora potrei vedere come collegarlo al mio formato - che non è molto complicato e che per un RegExer migliore di quanto potrei anche essere facile. D'altra parte, non vorrei perdere tempo o il mio tempo se questo approccio non rispondesse al problema per te.


Grazie per la tua opinione su questo! Solo alcuni pensieri: penso che avere dati errati nel DB e semplicemente correggerli con le scimmie in mostra non sia una soluzione molto pulita e sostenibile. Potrebbe interrompersi in qualsiasi momento e danneggiare le prestazioni su ciascuna vista. Potrebbe anche avere effetti collaterali imprevedibili, ad esempio per altri plugin che analizzano o alterano il contenuto in qualche altro modo. A seconda di come viene eseguita, le immagini vengono comunque interrotte nel backend. In tal caso, penso che solo ripristinare il ridimensionamento tramite wp_constrain_dimensionscome menzionato nella domanda durante l'importazione e astenersi dalla ricostruzione dei pollici sarebbe più pulito.
Kraftner,

Sei il benvenuto. Il fatto è che i dati nel DB non sono dati errati, non sono più i dati desiderati con il nuovo regime. Per quanto riguarda le prestazioni, penso che sarebbe probabilmente minimo, soprattutto perché si applica solo, in teoria, ai post precedenti alla data X. Più in generale, potrebbe non esserci una soluzione unica adatta a tutti: penso che il bene una soluzione sufficiente può variare in base al carattere del sito, alle applicazioni e alle abitudini di gestione delle immagini passate, alle dimensioni del DB, ai limiti pratici e temporali e così via.
CK MacLeod,

Probabilmente hai ragione sul fatto che non ci sarà una soluzione unica per tutti. Attualmente sto esplorando vari modi di gestire questo tra cui un approccio sul rendering simile al tuo e un approccio sull'importazione che preferirei poiché risolve questo una volta per tutte. Vedremo dove questo porta. :)
Kraftner,

1

Va bene, questo è un approccio di base per la sostituzione al volo di immagini rotte. Ricorda che questa è più una prova di concetto che una soluzione testata in battaglia. Si aggancia semplicemente al the_contentfiltro che potrebbe (probabilmente ha) alcuni effetti collaterali indesiderati in alcune situazioni. Maneggiare con cura. :)

Anche se lo dice anche nel codice, voglio anche accreditare @Rarst per questa risposta usata nel mio codice.

/*
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'));
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.