Manejar problemas con el cambio de escala de imagen (redondeo) en 4.1 (WP Ticket # 18532)

17

Actualmente estoy en el proceso de migrar el contenido del sitio de un sitio anterior a 4.1 a una nueva configuración y estoy resolviendo un problema con el problema de error de redondeo de # 18532 y la corrección correspondiente .

Para resumir esto, se corrigió un mal comportamiento de redondeo de larga data en el lado de WordPress:

Imagina que cargamos una imagen con 693x173 y la escalamos a un ancho de 300:

  • pre 4.1: 300x74
  • publicación 4.1: 300x75

La cuestión

En general, esto no causa ningún problema porque los archivos existentes <img>no se tocan.

Pero cuando la regeneración de los pulgares o los accesorios de importación de un archivo WXR consiguen generan de manera diferente en el sistema de ficheros dejando todo <img>en el post_contentmuerto.

Buscando una solución

He estado pensando en varias soluciones:

Volviendo a los viejos tiempos malos

Changeset 30660 introdujo un nuevo filtro wp_constrain_dimensionsque se puede usar para conectar el comportamiento anterior antes de 4.1 nuevamente. Esto soluciona el problema. Pero me pregunto si esto podría causar problemas más adelante y, en general, me gustaría tener la solución, por lo que, aunque funciona, lo consideraría no ideal.

Los tiempos están cambiando'

Esto nos deja con otro objetivo: limpiar la base de datos y reemplazar todas las referencias a los archivos antiguos con referencias a los archivos nuevos. La pregunta que realmente estoy haciendo aquí ahora es cómo hacer esto. Estoy buscando una solución efectiva y generalmente aplicable, ya que sospecho que este problema afecta y afectará a muchas personas.

Mi idea actual es esta:

  1. Importar, regenerar o lo que sea que nos deje con los nuevos archivos y etiquetas rotas.
  2. Cree una lista A de todos los archivos redimensionados en el sistema de archivos u, alternativamente, obtenga esta información de la base de datos
  3. Analice esta lista y cree una segunda lista B con nombres de archivo todos desplazados por un píxel como se vería antes de 4.1
  4. Haga una búsqueda y reemplazo en toda la base de datos reemplazando todas las ocurrencias de B con la entrada correspondiente en A

No estoy seguro de si esta es la forma más inteligente y eficiente de manejar esta situación. También se siente un poco demasiado fuerza bruta. Entonces, antes de implementarlo, solo quería verificar con la sabiduría infinita de la multitud de WPSE;)

[editar] Después de leer la respuesta de ck-macleod (¡gracias!) Creo que una solución debería resolver esto de una vez por todas para que no tenga que mantener constantemente este problema en la parte posterior de su cabeza. [/editar]

[edit2] Acabo de encontrar un ticket relacionado en Trac . Agregando para referencia. [/ edit2]

Kraftner
fuente
a donde error issue of #13852te refieres #18532? :)
Aravona
2
Vaya, sí, arreglado. :)
kraftner

Respuestas:

4

Este es otro enfoque que la otra respuesta que funciona al importar contenido con el importador y corrige las URL de una vez por todas. Nuevamente: esto no está probado en batalla, pero es la solución que decidí y funcionó.

Prefiero esto, ya que resuelve el problema de una vez por todas y si funciona, funciona. Como no va a dejar cosas rotas en la base de datos y arreglarlas en la pantalla, no necesita preocuparse por las cosas que se rompan más adelante.

/*
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'));
Kraftner
fuente
Buen trabajo un +1.
gmazzap
1

Resolver el problema de manera global y perfecta para TODOS los archivos de imagen (y enlaces) en un sitio grande, dada la posibilidad, por ejemplo, de que las personas ocasionalmente hayan cambiado el nombre de los archivos de imagen manualmente imitando el estilo WP, y otras variaciones extrañas, podrían ser difíciles. Las operaciones de búsqueda y reemplazo de bases de datos también implican complicaciones (¡y riesgos!).

¿Podría manejar la gran mayoría de los errores (imágenes rotas y enlaces de imágenes rotas, supongo) y lograr el resultado final deseado o facsímil razonable, mediante el siguiente método?

  1. Identifique la fecha antes de la cual todas las imágenes redimensionadas fueron redimensionadas por el antiguo método "intval" en lugar del nuevo método "redondo". (También se podría crear un tipo de corte diferente, pero la fecha parece más fácil).

  2. Para todas las publicaciones publicadas <= la fecha límite, ejecute preg_replace en the_content () en el momento de carga / representación, capturando todos los archivos de imagen con el patrón o patrones problemáticos y reemplazándolos con el patrón deseado. La base de datos permanecería inalterada, pero la salida estaría libre de errores en la mayoría de los casos. No estoy seguro de si la solución necesitaría aplicarse tanto al contenido de publicación de página "singular" como a páginas de archivo y otros procesos también.

Si una solución de este tipo fuera útil, la siguiente pregunta sería si los patrones del problema y los reemplazos podrían definirse adecuadamente. Según su lista de soluciones propuestas, posiblemente algunos patrones típicos podrían de hecho estar aislados (quizás tomados de configuraciones de medios anteriores que producen miniaturas y algunas otras imágenes).

Ya he escrito una función más simple que uso (y estoy en proceso de convertirme en un complemento), que reemplaza globalmente todos los archivos de imagen en directorios designados, hasta una fecha determinada, con una imagen o enlace de imagen predeterminado, según el método descrito anteriormente. Era para un sitio donde, en exceso de precaución de derechos de autor, los operadores simplemente borraron todas sus imágenes, sin saber que, además de producir resultados feos en páginas antiguas, también estaban produciendo miles de errores, dos por cada uno. imagen.

Si puede reducir el patrón del problema más específicamente, y las instancias en las que la salida necesitaría ser alterada, entonces podría ver cómo conectarlo a mi formato, lo cual no es muy complicado y para un mejor RegExer del que podría. incluso ser fácil Por otro lado, no quisiera perder su tiempo o el mío si este enfoque no respondiera el problema por usted.

CK MacLeod
fuente
¡Gracias por tu opinión sobre esto! Solo algunas ideas: creo que tener datos incorrectos en la base de datos y simplemente parchearlos en la pantalla no es una solución muy limpia y sostenible. Puede romperse en cualquier momento y dañar el rendimiento en cada vista. También puede tener efectos secundarios impredecibles, por ejemplo, para otros complementos que analizan o alteran el contenido de alguna otra manera. Dependiendo de cómo se haga, las imágenes todavía están rotas en el backend. En ese caso, creo que simplemente restablecer la escala a través de wp_constrain_dimensionslo mencionado en la pregunta mientras se realiza la importación y abstenerse de reconstruir los pulgares sería más limpio.
kraftner
Eres muy bienvenido. La cosa es que los datos en el DB no son datos incorrectos, simplemente ya no son los datos que desea bajo el nuevo régimen. En cuanto al rendimiento, creo que probablemente sea mínimo, especialmente porque solo se aplica, en teoría, a las publicaciones anteriores a la fecha X. En términos más generales, puede que no haya una mejor solución única para todos: creo que lo bueno una solución suficiente puede variar con el carácter del sitio, las aplicaciones y hábitos de manejo de imágenes anteriores, el tamaño de la base de datos, las limitaciones prácticas y de tiempo, etc.
CK MacLeod
Probablemente tenga razón en que no habrá una solución única para todos. Actualmente estoy explorando varias formas de manejar esto, entre las que se encuentran un enfoque de representación similar al suyo y un enfoque de importación que preferiría, ya que resuelve esto de una vez por todas. Veremos a dónde lleva esto. :)
kraftner
1

Bien, este es un enfoque básico para reemplazar imágenes rotas sobre la marcha. Tenga en cuenta que esto es más una prueba de concepto que una solución probada en batalla. Simplemente se engancha en el the_contentfiltro que podría (probablemente tenga) algunos efectos secundarios no deseados en algunas situaciones. Tratar con cuidado. :)

Aunque también lo dice en el código, también quiero acreditar a @Rarst por esta respuesta utilizada en mi código.

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