Вирішіть проблеми зі зміною масштабування зображення (округленням) в 4.1 (WP Ticket # 18532)


17

На даний момент я переношу вміст сайту зі старого веб-сайту до 4.1 до нового налаштування, і виникає проблема з проблемою помилки округлення № 18532 та відповідним виправленням .

Підсумовуючи це виправлене тривале неправильне поводження з боку WordPress:

Уявіть, що ми завантажуємо зображення з розміром 693x173 і масштабуємо його до ширини 300:

  • до 4.1: 300x74
  • пост 4.1: 300х75

Питання

Як правило, це не викликає жодних проблем, оскільки існуючі файли <img>не торкаються.

Але коли ви регенерує пальці або імпортують вкладення з файлу WXR вони отримують генеруватися по- різному в файлової системі , залишаючи все <img>в post_contentмертвих.

Шукаєте рішення

Я думав про різні рішення:

Повертаючись до поганих старих часів

Changeset 30660 представив новий фільтр, wp_constrain_dimensionsякий можна використовувати лише для підключення старої поведінки до 4.1 року. Це вирішує проблему. Але мені цікаво, чи це може викликати проблеми пізніше, і, як правило, я б хотів виправити це питання, хоча це працює, я вважаю його не ідеальним.

Часи, які вони - Чангін

Отже, це ставить перед нами ще одну мету: очистити БД і замінити всі посилання на старі файли посиланнями на нові файли. Питання, яке я зараз задаю тут, - як це зробити. Я шукаю ефективне і загальноприйнятне рішення, тому що я підозрюю, що це питання дійсно вплине на багатьох людей

Моя нинішня ідея така:

  1. Імпортуйте, відновіть або все, що залишає нас із новими файлами та зламаними тегами.
  2. Створіть список A з усіх розмірів файлів у файловій системі або, альтернативно, отримавши цю інформацію з бази даних
  3. Проаналізуйте цей список та створіть другий список B з назви файлів, усі зміщені на один піксель, як це виглядало б перед 4.1
  4. Виконайте пошук і замініть всю базу даних, замінивши всі випадки B на відповідний запис у A

Я просто не впевнений, чи це найбільш розумний та ефективний спосіб вирішити цю ситуацію. Це також відчуває себе занадто жорстокою силою. Тому перед його реалізацією я просто хотів перевірити з безмежною мудрістю натовпу WPSE;)

[редагувати] Прочитавши відповідь ck-macleod (спасибі!), я думаю, що виправлення має вирішити це раз і назавжди, тому вам не потрібно постійно тримати цю проблему в задній частині голови. [/ редагувати]

[edit2] Щойно я знайшов відповідний квиток на Trac . Додавання для довідки. [/ edit2]


де ти це error issue of #13852мав на увазі #18532? :)
Аравона

2
Ой, так, виправлено. :)
kraftner

Відповіді:


4

Це інший підхід, ніж інша відповідь, який працює при імпорті вмісту з імпортером і фіксує URL-адреси раз і назавжди. Знову ж таки: це не бойове випробування, але це рішення, на якому я вирішив, і воно спрацювало.

Я вважаю за краще це, оскільки це вирішує питання раз і назавжди, і якщо воно працює, воно працює. Оскільки ви не залишаєте пошкоджені речі в БД і виправляєте їх на дисплеї, вам не потрібно буде турбуватися про те, що вони будуть зламані згодом.

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

Приємно працювати +1.
gmazzap

1

Вирішити проблему в усьому світі та ідеально для ВСІХ файлів зображень (та посилань) на великому веб-сайті - враховуючи можливість, наприклад, що люди можуть періодично перейменовувати файли зображень вручну, що імітують стиль WP - та інші незвичайні варіанти - можуть бути складними. Операції пошуку та заміни бази даних також мають складності (та ризики!).

Чи можете ви впоратися з переважною більшістю помилок - пошкодженими зображеннями та порушеними посиланнями на зображення, і досягти бажаного кінцевого результату чи розумного факсимільного зв’язку наступним методом?

  1. Визначте дату, перед якою всі розміри зображення змінюються за старим методом "intval", а не новим методом "круглого". (Можна також створити різний вид відсікання, але дата здається найпростішою.)

  2. Для всіх публікацій, розміщених <= гранична дата, запустіть preg_replace на the_content () під час завантаження / візуалізації, захопивши всі файли зображень із проблемним малюнком або шаблонами та замінивши їх потрібним шаблоном. База даних залишатиметься незмінною, але вихід у більшості випадків буде помилковим. Я не впевнений, чи потрібно буде рішення застосовувати як до вмісту "єдиного" розміщення сторінки, так і до архіву сторінок та інших процесів.

Якщо рішення такого типу було б корисним, то наступним питанням буде те, чи можна було адекватно визначити шаблони проблем та заміни. Зі списку запропонованих рішень видно, що, можливо, можна було б виділити кілька типових зразків (можливо, взятих із попередніх налаштувань медіа, що створюють ескізи та деякі інші зображення).

Я вже написав більш просту функцію, яку я використовую (і перебуваю в процесі перетворення плагіна), яка глобально замінює всі файли зображень у визначених каталогах до певної дати на зображення за замовчуванням або на зображення-посилання, відповідно до вищеописаного способу. Це був веб-сайт, на якому, з обережністю щодо авторських прав, оператори просто видаляли всі свої зображення, не усвідомлюючи, що, окрім створення некрасивих результатів на старих сторінках, вони також виявляли тисячі помилок, по дві для кожної зображення.

Якщо ви зможете більш конкретно звузити схему проблеми, а також випадки, коли результат повинен бути змінений, я міг би бачити, як підключити його до свого формату - який не дуже складний і який для кращого RegExer, ніж я міг би навіть бути легким. З іншого боку, я б не хотів витрачати ваш чи мій час, якщо такий підхід не відповість на проблему для вас.


Дякуємо, що взяли на себе цю справу! Лише деякі думки: я думаю, що неправильні дані у БД та просто виправлення мавп на дисплеї не є дуже чистим та стійким рішенням. Це може порушити будь-який час і завдати шкоди продуктивності кожного перегляду. Це також може мати непередбачувані побічні ефекти, наприклад, для інших плагінів, які аналізують або змінюють вміст якось іншим чином. Залежно від того, як це робиться, зображення все ще ламаються в бекенді. У цьому випадку я думаю, що просто скинути масштаб через те wp_constrain_dimensions, що було зазначено у питанні, виконуючи імпорт та утримуватися від відновлення великих пальців, було б більш чисто.
kraftner

Вас цілком вітає Справа в тому, що дані в БД не є помилковими даними, це просто не ті дані, які ви хочете більше в новому режимі. Щодо хіта на продуктивність, я думаю, що це, ймовірно, буде мінімальним, тому що це теоретично застосовується лише до публікацій до дати X. Більш загально, не може бути найкращого рішення в розмірі: я думаю, що добре достатньо рішення може змінюватись в залежності від характеру сайту, минулих додатків та звичок для обробки зображень, розміру БД, практичних та часових обмежень тощо.
CK MacLeod

Ви, напевно, праві, що для нього не знайдеться рішення для одного розміру. В даний час я досліджую різні способи вирішення цього питання, серед яких підхід на рендері, подібний до вашого, і підхід під час імпорту, який я вважаю за краще, оскільки це вирішує це раз і назавжди. Ми побачимо, куди це веде. :)
kraftner

1

Гаразд, це базовий підхід щодо заміни зламаних зображень на льоту. Будьте в курсі, що це скоріше доказ концепції, ніж тестування битви. Він просто зачепить the_contentфільтр, що може (можливо, має) небажані побічні ефекти в деяких ситуаціях. Поводьтеся обережно. :)

Хоча в коді написано так, я також хочу зарахувати @Rarst за цю відповідь, що використовується в моєму коді.

/*
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'));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.