Beheben von Problemen mit der Änderung der Bildskalierung (Rundung) in 4.1 (WP Ticket # 18532)

17

Ich bin gerade dabei, Websiteinhalte von einer alten Site vor 4.1 auf eine neue zu migrieren und stoße auf ein Problem mit dem Rundungsfehler # 18532 und dem entsprechenden Fix .

Zusammenfassend lässt sich sagen, dass ein langjähriges Rundungsproblem auf der Seite von WordPress behoben wurde:

Stellen Sie sich vor, wir laden ein Bild mit einer Größe von 693 x 173 hoch und skalieren es auf eine Breite von 300:

  • vor 4,1: 300 x 74
  • Post 4.1: 300x75

Die Angelegenheit

Im Allgemeinen verursacht dies keine Probleme, da vorhandene Dateien und <img>nicht berührt werden.

Aber wenn man den Daumen oder den Import - Anhänge von einem WXR Datei regenerieren sie erhalten erzeugt anders im Dateisystem alle verlassen <img>in post_contenttot.

Auf der Suche nach einer Lösung

Ich habe über verschiedene Lösungen nachgedacht:

Zurück zu den schlechten alten Zeiten

Mit Changeset 30660 wurde ein neuer Filter eingeführt, mit wp_constrain_dimensionsdem das alte Verhalten von vor 4.1 einfach wieder hergestellt werden kann. Dies behebt das Problem. Aber ich frage mich, ob dies später zu Problemen führen könnte, und im Allgemeinen möchte ich das Problem beheben, obwohl dies funktioniert, würde ich es für nicht ideal halten.

Die Zeiten ändern sich'

Damit bleibt uns ein weiteres Ziel: Bereinigen Sie die Datenbank und ersetzen Sie alle Verweise auf die alten Dateien durch Verweise auf die neuen Dateien. Die Frage, die ich hier jetzt stelle, ist, wie das geht. Ich bin auf der Suche nach einer effektiven und allgemein anwendbaren Lösung, da ich vermute, dass dieses Problem viele Menschen betrifft und betreffen wird

Meine aktuelle Idee ist:

  1. Importieren, regenerieren oder was auch immer, was uns mit den neuen Dateien und defekten Tags zurücklässt.
  2. Erstellen Sie eine Liste A aus allen Dateien mit geänderter Größe im Dateisystem oder rufen Sie diese Informationen alternativ aus der Datenbank ab
  3. Analysieren Sie diese Liste und erstellen Sie eine zweite Liste B mit Dateinamen, die alle um ein Pixel versetzt sind, wie es vor 4.1 aussehen würde
  4. Führen Sie ein Suchen & Ersetzen in der gesamten Datenbank durch, indem Sie alle Vorkommen von B durch den entsprechenden Eintrag in A ersetzen

Ich bin mir nur nicht sicher, ob dies der intelligenteste und effizienteste Weg ist, um mit dieser Situation umzugehen. Es fühlt sich auch ein bisschen zu brachial an. Bevor ich es implementierte, wollte ich nur die unendliche Weisheit der WPSE-Menge überprüfen;)

[edit] Nachdem Sie die Antwort von ck-macleod gelesen haben (danke!), denke ich, dass ein Fix dies ein für alle Mal lösen sollte, damit Sie dieses Problem nicht ständig im Hinterkopf behalten müssen. [/bearbeiten]

[edit2] Ich habe gerade ein passendes Ticket für Trac gefunden . Hinzufügen als Referenz. [/ edit2]

Kraftner
quelle
wo Sie error issue of #13852haben meinen Sie #18532? :)
Aravona
2
Hoppla, ja, behoben. :)
Kraftner

Antworten:

4

Dies ist ein anderer Ansatz als die andere Antwort , die beim Importieren von Inhalten mit dem Importprogramm funktioniert und die URLs ein für alle Mal repariert. Nochmals: Dies ist keine kampferprobte Lösung, sondern die Lösung, auf die ich mich festgelegt habe und die funktioniert hat.

Ich bevorzuge dies, da es das Problem ein für alle Mal löst und wenn es funktioniert, funktioniert es. Da Sie defekte Daten nicht in der Datenbank belassen und auf dem Display reparieren, müssen Sie sich später keine Gedanken mehr über defekte Daten machen.

/*
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
quelle
Gute Arbeit und +1.
gmazzap
1

Das Problem global und perfekt für ALLE Bilddateien (und Links) auf einer großen Site zu lösen - zum Beispiel angesichts der Möglichkeit, dass Einzelpersonen gelegentlich Bilddateien manuell umbenannt haben, um den WP-Stil zu imitieren - und anderer merkwürdiger Variationen - könnte schwierig sein. Operationen zum Suchen und Ersetzen von Datenbanken sind ebenfalls mit Komplikationen (und Risiken!) Verbunden.

Könnten Sie die große Mehrheit der Fehler - vermutlich fehlerhafte Bilder und fehlerhafte Bildverknüpfungen - bewältigen und mit der folgenden Methode das gewünschte Endergebnis oder ein angemessenes Faksimile erzielen?

  1. Geben Sie das Datum an, vor dem die Größe aller Bilder nach der alten Methode "intval" und nicht nach der neuen Methode "round" geändert wurde. (Es könnte auch eine andere Art von Cut-Off erstellt werden, das Datum scheint jedoch am einfachsten zu sein.)

  2. Führen Sie für alle veröffentlichten Beiträge <= zum Stichtag zum Lade- / Renderzeitpunkt preg_replace auf the_content () aus, erfassen Sie alle Bilddateien mit dem Problemmuster oder den Problemmustern und ersetzen Sie sie durch das gewünschte Muster. Die Datenbank würde unverändert bleiben, aber die Ausgabe wäre in den meisten Fällen fehlerfrei. Ich bin nicht sicher, ob die Lösung sowohl für "einzelne" Inhalte von Seiten als auch für das Archivieren von Seiten und anderen Prozessen gelten müsste.

Wenn eine Lösung dieser Art hilfreich wäre, wäre die nächste Frage, ob die Problemmuster und -ersetzungen angemessen definiert werden könnten. Aus Ihrer Liste der vorgeschlagenen Lösungen geht hervor, dass möglicherweise einige typische Muster tatsächlich isoliert werden könnten (möglicherweise aus früheren Medieneinstellungen entnommen, die Miniaturansichten und einige andere Bilder produzieren).

Ich habe bereits eine einfachere Funktion geschrieben, die ich verwende (und die sich in ein Plug-In verwandelt) und die alle Bilddateien in bestimmten Verzeichnissen bis zu einem bestimmten Datum global durch ein Standardbild oder einen Standard-Image-Link ersetzt. gemäß dem oben beschriebenen Verfahren. Es war eine Website, auf der die Betreiber mit übertriebener Vorsicht vor Urheberrechten einfach alle Bilder löschten, ohne zu wissen, dass sie nicht nur hässliche Ergebnisse auf alten Seiten erzielten, sondern auch Tausende von Fehlern verursachten, von denen jeder zwei Fehler aufwies Bild.

Wenn Sie das Problemmuster genauer eingrenzen können und die Fälle, in denen die Ausgabe geändert werden müsste, könnten Sie es in mein Format einfügen - das ist nicht sehr kompliziert und für einen besseren RegExer als ich sei sogar einfach. Andererseits würde ich Ihre oder meine Zeit nicht verschwenden wollen, wenn dieser Ansatz das Problem für Sie nicht lösen würde.

CK MacLeod
quelle
Vielen Dank für Ihre Meinung! Nur ein paar Gedanken: Ich denke, dass es keine sehr saubere und nachhaltige Lösung ist, falsche Daten in der Datenbank zu haben und sie nur mit Affen zu patchen. Es kann jederzeit zu Unterbrechungen kommen und die Leistung bei jeder Ansicht beeinträchtigen. Es kann auch unvorhersehbare Nebenwirkungen haben, z. B. für andere Plugins, die den Inhalt analysieren oder auf andere Weise ändern. Je nachdem, wie es gemacht wird, sind die Bilder im Backend noch defekt. In diesem Fall denke ich, dass das Zurücksetzen der Skalierung über, wp_constrain_dimensionswie in der Frage erwähnt, während des Imports und der Verzicht auf die Neuerstellung der Daumen sauberer wäre.
Kraftner
Du bist immer willkommen. Tatsache ist, dass die Daten in der Datenbank keine falschen Daten sind, sondern nur die Daten, die Sie unter dem neuen Regime nicht mehr benötigen. Was die Leistung angeht, denke ich, dass dies wahrscheinlich nur minimal ist, zumal es theoretisch nur für Posts vor dem Datum X gilt. Im Allgemeinen gibt es möglicherweise keine allgemein gültige Lösung: Ich denke, das ist gut Die zu erwartende Lösung kann je nach Standortcharakter, früheren Bildbearbeitungsanwendungen und -gewohnheiten, Größe der Datenbank, praktischen und zeitlichen Einschränkungen usw. variieren.
CK MacLeod,
Sie haben wahrscheinlich Recht, dass es keine einheitliche Lösung dafür gibt. Momentan prüfe ich verschiedene Möglichkeiten, um damit umzugehen, darunter einen On-Render-Ansatz, der Ihrem ähnelt, und einen On-Import-Ansatz, den ich vorziehen würde, da er dieses Problem ein für alle Mal löst. Wir werden sehen, wohin das führt. :)
Kraftner
1

Okay, dies ist ein grundlegender Ansatz zum schnellen Ersetzen defekter Bilder. Beachten Sie, dass dies eher ein Proof-of-Concept als eine kampferprobte Lösung ist. Es hängt einfach am the_contentFilter, der in manchen Situationen (wahrscheinlich) einige unerwünschte Nebenwirkungen haben kann. Mit Vorsicht behandeln. :)

Obwohl es auch im Code so steht, möchte ich @Rarst auch für diese in meinem Code verwendete Antwort schreiben .

/*
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
quelle