Lidar com problemas com a alteração do dimensionamento da imagem (arredondamento) em 4.1 (WP Ticket # 18532)

17

Atualmente, estou migrando o conteúdo do site de um site antigo anterior ao 4.1 para uma nova instalação e encontrando um problema com o problema de erro de arredondamento no # 18532 e a correção correspondente .

Para resumir isso, foi corrigido um mau comportamento de arredondamento no lado do WordPress:

Imagine que carregamos uma imagem com 693x173 e a dimensionamos para uma largura de 300:

  • pré 4.1: 300 x 74
  • post 4.1: 300x75

O problema

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

Mas quando você regenerar polegares ou anexos importando de um arquivo WXR eles são gerados de forma diferente no sistema de arquivos deixando todos <img>em post_contentmortos.

Procurando uma solução

Eu tenho pensado em várias soluções:

Voltando aos maus velhos tempos

O Changeset 30660 introduziu um novo filtro wp_constrain_dimensionsque pode ser usado para conectar o comportamento antigo anterior ao 4.1 novamente. 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, considero não ideal.

Os tempos estão mudando'

Portanto, 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 realmente fazendo aqui agora é como fazer isso. Estou procurando uma solução eficaz e geralmente aplicável, pois suspeito que esse problema afeta e afetará muitas pessoas

Minha idéia atual é esta:

  1. Importe, regenere ou o que nos deixar com os novos arquivos e tags quebradas.
  2. Crie uma lista A de todos os arquivos redimensionados no sistema de arquivos ou, alternativamente, obtenha essas informações do banco de dados
  3. Analise esta lista e crie uma segunda lista B com os nomes de arquivos todos deslocados em um pixel, como seria antes do 4.1
  4. Faça uma pesquisa e substitua em todo o banco de dados, substituindo todas as ocorrências de B pela entrada relacionada em A

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

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

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

kraftner
fonte
onde você error issue of #13852quis dizer #18532? :)
Aravona
2
Opa, sim, consertado. :)
kraftner 14/09

Respostas:

4

Essa é outra abordagem que não a outra resposta que funciona ao importar conteúdo com o importador e corrige os URLs de uma vez por todas. Novamente: Isso não foi testado em batalha, mas é a solução em que me decidi e funcionou.

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

/*
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
fonte
Bom trabalho, um +1.
gmazzap
1

Resolver o problema de maneira global e perfeita para TODOS os arquivos de imagem (e links) em um site grande - dada a possibilidade, por exemplo, de que indivíduos possam ocasionalmente renomear arquivos de imagem manualmente imitando o estilo WP - e outras variações estranhas - pode ser difícil. As operações de busca 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 imagens quebradas, presumo - e alcançar o resultado final desejado ou um fax razoável, pelo método a seguir?

  1. Identifique a data anterior à qual todas as imagens redimensionadas foram redimensionadas pelo método "intval" antigo, e não pelo novo método "redondo". (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 no the_content () no momento do carregamento / renderização, capturando todos os arquivos de imagem com o padrão ou padrões de problemas e substituindo-os pelo padrão desejado. O banco de dados permaneceria inalterado, mas a saída estaria livre de erros na maioria dos casos. Não tenho certeza se a solução precisaria se aplicar ao conteúdo de postagem da página "singular" e ao arquivamento de páginas e outros processos também.

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

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

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

CK MacLeod
fonte
Obrigado pela sua opinião sobre isso! Apenas algumas considerações: acho que ter dados errados no banco de dados e apenas corrigi-los em exibição não é uma solução muito limpa e sustentável. Pode interromper a qualquer momento e prejudicar o desempenho em cada exibição. Também pode ter efeitos colaterais imprevisíveis, por exemplo, para outros plugins que analisam ou alteram o conteúdo de alguma outra maneira. Dependendo de como é feito, as imagens ainda são quebradas no back-end. Nesse caso, acho que apenas redefinir a escala via wp_constrain_dimensionscomo mencionado na pergunta ao fazer a importação e evitar reconstruir os polegares seria mais limpo.
Kraftner 17/09/2015
Você é muito bem-vindo. O problema é que os dados no banco de dados não são dados errados, apenas não são os dados que você deseja mais sob o novo regime. No que diz respeito ao desempenho, acho que provavelmente seria mínimo, especialmente porque, em teoria, só se aplica a postagens anteriores à data X. De maneira geral, pode não haver uma solução melhor para o tamanho único: acho que o bom solução suficiente pode variar de acordo com o caráter do site, aplicativos e hábitos anteriores de manipulação de imagens, tamanho do banco de dados, restrições práticas e de tempo, etc.
CK MacLeod
Você provavelmente está certo de que não haverá uma solução única para isso. Atualmente, estou explorando várias maneiras de lidar com isso, entre as quais uma abordagem de renderização semelhante à sua e uma abordagem de importação que eu preferiria, pois resolve isso de uma vez por todas. Vamos ver aonde isso leva. :)
kraftner 19/09/15
1

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

Embora esteja indicado no código, também quero creditar @Rarst por esta resposta usada no meu 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
fonte