Lidar com problemas com a mudança de escala de imagem (arredondamento) em 4.1 (WP Ticket # 18532)

16

No momento, estou migrando o conteúdo do site de um antigo site anterior à 4.1 para uma nova configuração e estou tendo um problema com a questão do erro de arredondamento de # 18532 e a correção correspondente .

Para resumir isso, corrigimos um mau comportamento de longo prazo do lado do WordPress:

Imagine que você envie uma imagem com 693x173 e dimensione-a para uma largura de 300:

  • pré 4.1: 300 x 74
  • postar 4.1: 300 x 75

O problema

Geralmente, isso não causa problemas porque os arquivos existentes e <img> não são tocados.

Mas quando você regenera os polegares ou importa anexos de um arquivo WXR, eles são gerados de forma diferente no sistema de arquivos, deixando todos os <img> em post_content mortos.

Procurando por uma solução

Tenho pensado em várias soluções:

Voltando aos velhos e maus tempos

O changeset 30660 introduziu um novo filtro wp_constrain_dimensions , que pode ser usado para apenas ligar o antigo comportamento de antes 4.1 de volta. Isso corrige o problema. Mas eu estou querendo saber se isso pode causar problemas mais tarde e, geralmente, eu gostaria de ter a correção, embora isso funcione eu considero não-ideal.

Os tempos estão mudando

Isso nos deixa com outro objetivo: limpar o banco de dados e substituir todas as referências aos arquivos antigos por referências aos novos arquivos. A pergunta que estou fazendo aqui agora é como fazer isso. Eu estou procurando uma solução eficaz e geralmente aplicável, como eu suspeito que esse problema afeta e afetará muitas pessoas

Minha ideia atual é esta:

  1. Importe, regenere ou qualquer coisa que nos deixe com os novos arquivos e tags quebrados.
  2. Crie uma lista A de todos os arquivos redimensionados no sistema de arquivos ou, como alternativa, obtenha essas informações no banco de dados
  3. Analise essa lista e crie uma segunda lista B com nomes de arquivos, todos compensados por um pixel, como seria antes de 4.1
  4. Faça uma pesquisa e substitua o banco de dados inteiro, substituindo todas as ocorrências de B pela entrada relacionada em A

Não tenho certeza se essa é a maneira mais inteligente e eficiente de lidar com essa situação. Também parece um pouco força bruta. Então, antes de implementá-lo, eu só queria verificar com a infinita sabedoria da multidão WPSE;)

[edit] Depois de ler resposta do ck-macleod (obrigado!) eu acho uma correção deve resolver isso de uma vez por todas, para que você não precise manter esse problema constantemente na parte de trás da sua cabeça. [/ edit]

[edit2] Acabei de encontrar um tíquete relacionado no Trac . Adicionando para referência. [/ edit2]

    
por kraftner 14.09.2015 / 11:30

3 respostas

4

Essa é outra abordagem que a outra resposta que funciona ao importar conteúdo com o importador e corrige as URLs uma vez e para todos. Novamente: Isso não é testado em batalha, mas é a solução que eu decidi e funcionou.

Eu prefiro isso, pois resolve o problema de uma vez por todas e, se funciona, funciona. Como você não está deixando coisas quebradas no banco de dados e consertá-lo em exibição, você não precisa se preocupar com a quebra de coisas mais tarde.

/*
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'));
    
por kraftner 29.10.2015 / 11:31
1

Resolver o problema global e perfeitamente para TODOS os arquivos de imagem (e links) em um site grande - dada a possibilidade, por exemplo, de que indivíduos ocasionalmente renomearem arquivos de imagem manualmente imitando o estilo WP - e outras variações difícil. As operações de pesquisa e substituição de banco de dados também envolvem complicações (e riscos!).

Você poderia lidar com a grande maioria dos erros - imagens quebradas e links de imagem quebrados, presumo - e alcançar o resultado final desejado ou fac-símile razoável, pelo seguinte método?

  1. Identifique a data antes da qual todas as imagens redimensionadas foram redimensionadas pelo método antigo "intval" em vez do novo método "round". (Um tipo diferente de corte também pode ser criado, mas a data parece mais fácil.)

  2. Para todas as postagens publicadas < = a data-limite, execute preg_replace em the_content () no tempo de carregamento / renderização, capturando todos os arquivos de imagem com o padrão ou padrões problemáticos e substituindo-os pelo padrão desejado. O banco de dados permaneceria inalterado, mas a saída ficaria livre de erros na maioria das instâncias. Não tenho certeza se a solução precisaria aplicar-se tanto ao conteúdo de postagem de página "singular" quanto ao arquivamento de páginas e outros processos também.

Se uma solução desse tipo fosse útil, a próxima pergunta seria se os padrões e substituições do problema poderiam ser adequadamente definidos. Parece da sua lista de soluções propostas que possivelmente alguns padrões típicos poderiam de fato ser isolados (talvez retirados de configurações de mídia anteriores produzindo miniaturas e algumas outras imagens).

Eu já escrevi uma função mais simples que eu uso (e estou no processo de transformar em um plug-in), que globalmente substitui todos os arquivos de imagem em diretórios designados, até uma certa data, por uma imagem ou imagem padrão. -link, de acordo com o método acima descrito. Era para um site onde, em um excesso de cautela com os direitos autorais, os operadores simplesmente excluíam todas as suas imagens, sem saber que, além de produzir resultados desagradáveis em páginas antigas, também estavam gerando milhares de erros, dois para cada um. imagem.

Se você puder refinar o padrão do problema mais especificamente, e as instâncias em que a saída precisaria ser alterada, então eu poderia ver sobre conectá-lo ao meu formato - o que não é muito complicado, e que para um melhor RegExer do que eu poderia até ser fácil. Por outro lado, eu não gostaria de perder seu tempo se essa abordagem não responder ao problema para você.

    
por CK MacLeod 16.09.2015 / 23:38
1

Ok, esta é uma abordagem básica para substituir instantaneamente imagens quebradas. Esteja ciente de que isso é mais uma prova de conceito do que uma solução testada em batalha. Ele apenas conecta o filtro the_content , que pode (provavelmente tem) alguns efeitos colaterais indesejados em algumas situações. Manuseie com cuidado. :)

Embora seja dito também no código, também quero creditar @Rarst por esta resposta usada no meu código.

/*
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'));
    
por kraftner 29.10.2015 / 11:17