web-dev-qa-db-fra.com

en utilisant un outil de téléchargement de média pour sélectionner une image de taille spécifique, appliquer le cropper

J'ai suivi cet article utiliser Media Uploader dans mon code. Lorsque je clique sur le bouton Parcourir de mon formulaire, l'utilitaire de téléchargement de média s'ouvre et, après avoir sélectionné l'image, l'URL de cette image est ajoutée dans le champ de mon formulaire. Aucun problème jusqu'à présent.

Mais je veux imposer à l'image une taille spécifique. Si la taille est différente, l'utilisateur doit la recadrer pour qu'elle corresponde à la taille requise (ou le rapport de format) avant il peut sélectionner l'image (et ma mediaUploader.on('select', function() { est exécutée).

Veuillez noter que je ne souhaite pas utiliser add_image_size car cela créera une vignette de la taille donnée pour chaque image téléchargée (AFAIK ... corrigez-moi si je me trompe). Dans un scénario spécifique, je souhaite que l’utilisateur recadre l’image manuellement lors du téléchargement.

En gros, mon exigence est bien décrite dans une question sur SO: wordpress-media-library-crop-image-sur-insert-like-header-image-cropper

Mais il n'y a pas de solution affichée ici. Donc, je le mets ici pour que les WP experts me guident.

J'ai essayé de rechercher 'WordPress Media Uploader (appliquer une taille d'image | un rogneur d'image | une image de taille spécifique)' ou non. Mais en quelque sorte, aucun lien ne décrit les étapes à suivre pour appliquer une taille d'image donnée. Je n'ai pas non plus trouvé de documentation pour wp.media qui décrit toutes les options pouvant être passées.

J'ai déjà vu les questions suivantes mais cela ne semble pas faire ce que je cherche:

Modifier

Je pourrais trouver l’utilisation de WP_Customize_Cropped_Image_Control @ https://www.sitepoint.com/using-the-wordpress-customizer-media-controls/ Mais c’est dans le contexte de Customizer. Y at-il un contrôle similaire qui peut être utilisé dans un plugin standard?

Et il y a ce wp.media.controller.Cropper défini à l'intérieur de wp-includes/js/media-views.js mais comment l'utiliser?

2
Vivek Athalye

Il semble que la fonctionnalité de sélection et de recadrage ne soit actuellement utilisée que dans WordPress Customizer.

Cependant, après avoir examiné le code dans le dossier Admin de WordPress, je suis parvenu à le faire fonctionner sur la page d'options de mes thèmes.

Voici mon réglage pour une image d'en-tête:

function setting_heading_picture() { 
  $heading_picture = esc_attr(get_option( 'heading_picture' )); ?>
  <input type="hidden" name="heading_picture" id="heading_picture" value="<?php echo $heading_picture; ?>" />
  <img id="heading_picture_preview" class="heading-picture" src="<?php echo $heading_picture; ?>" />
  <button id="btn_heading_picture" name="btn_heading_picture" class="button default">Choose Picture</button>
}

Maintenant, voici le javascript de l'administrateur requis:

$(function() {

function myTheme_calculateImageSelectOptions(attachment, controller) {

    var control = controller.get( 'control' );

    var flexWidth = !! parseInt( control.params.flex_width, 10 );
    var flexHeight = !! parseInt( control.params.flex_height, 10 );

    var realWidth = attachment.get( 'width' );
    var realHeight = attachment.get( 'height' );

    var xInit = parseInt(control.params.width, 10);
    var yInit = parseInt(control.params.height, 10);

    var ratio = xInit / yInit;

    controller.set( 'canSkipCrop', ! control.mustBeCropped( flexWidth, flexHeight, xInit, yInit, realWidth, realHeight ) );

    var xImg = xInit;
    var yImg = yInit;

    if ( realWidth / realHeight > ratio ) {
        yInit = realHeight;
        xInit = yInit * ratio;
    } else {
        xInit = realWidth;
        yInit = xInit / ratio;
    }        

    var x1 = ( realWidth - xInit ) / 2;
    var y1 = ( realHeight - yInit ) / 2;        

    var imgSelectOptions = {
        handles: true,
        keys: true,
        instance: true,
        persistent: true,
        imageWidth: realWidth,
        imageHeight: realHeight,
        minWidth: xImg > xInit ? xInit : xImg,
        minHeight: yImg > yInit ? yInit : yImg,            
        x1: x1,
        y1: y1,
        x2: xInit + x1,
        y2: yInit + y1
    };

    return imgSelectOptions;
}  

function myTheme_setImageFromURL(url, attachmentId, width, height) {
    var choice, data = {};

    data.url = url;
    data.thumbnail_url = url;
    data.timestamp = _.now();

    if (attachmentId) {
        data.attachment_id = attachmentId;
    }

    if (width) {
        data.width = width;
    }

    if (height) {
        data.height = height;
    }

    $("#heading_picture").val( url );
    $("#heading_picture_preview").prop("src", url);        

}

function myTheme_setImageFromAttachment(attachment) {

    $("#heading_picture").val( attachment.url );
    $("#heading_picture_preview").prop("src", attachment.url);             

}

var mediaUploader;

$("#btn_heading_picture").on("click", function(e) {

    e.preventDefault(); 

    /* We need to setup a Crop control that contains a few parameters
       and a method to indicate if the CropController can skip cropping the image.
       In this example I am just creating a control on the fly with the expected properties.
       However, the controls used by WordPress Admin are api.CroppedImageControl and api.SiteIconControl
    */

   var cropControl = {
       id: "control-id",
       params : {
         flex_width : false,  // set to true if the width of the cropped image can be different to the width defined here
         flex_height : true, // set to true if the height of the cropped image can be different to the height defined here
         width : 300,  // set the desired width of the destination image here
         height : 200, // set the desired height of the destination image here
       }
   };

   cropControl.mustBeCropped = function(flexW, flexH, dstW, dstH, imgW, imgH) {

    // If the width and height are both flexible
    // then the user does not need to crop the image.

    if ( true === flexW && true === flexH ) {
        return false;
    }

    // If the width is flexible and the cropped image height matches the current image height, 
    // then the user does not need to crop the image.
    if ( true === flexW && dstH === imgH ) {
        return false;
    }

    // If the height is flexible and the cropped image width matches the current image width, 
    // then the user does not need to crop the image.        
    if ( true === flexH && dstW === imgW ) {
        return false;
    }

    // If the cropped image width matches the current image width, 
    // and the cropped image height matches the current image height
    // then the user does not need to crop the image.               
    if ( dstW === imgW && dstH === imgH ) {
        return false;
    }

    // If the destination width is equal to or greater than the cropped image width
    // then the user does not need to crop the image...
    if ( imgW <= dstW ) {
        return false;
    }

    return true;        

   };      

    /* NOTE: Need to set this up every time instead of reusing if already there
             as the toolbar button does not get reset when doing the following:

            mediaUploader.setState('library');
            mediaUploader.open();

    */       

    mediaUploader = wp.media({
        button: {
            text: 'Select and Crop', // l10n.selectAndCrop,
            close: false
        },
        states: [
            new wp.media.controller.Library({
                title:     'Select and Crop', // l10n.chooseImage,
                library:   wp.media.query({ type: 'image' }),
                multiple:  false,
                date:      false,
                priority:  20,
                suggestedWidth: 300,
                suggestedHeight: 200
            }),
            new wp.media.controller.CustomizeImageCropper({ 
                imgSelectOptions: myTheme_calculateImageSelectOptions,
                control: cropControl
            })
        ]
    });

    mediaUploader.on('cropped', function(croppedImage) {

        var url = croppedImage.url,
            attachmentId = croppedImage.attachment_id,
            w = croppedImage.width,
            h = croppedImage.height;

            myTheme_setImageFromURL(url, attachmentId, w, h);            

    });

    mediaUploader.on('skippedcrop', function(selection) {

        var url = selection.get('url'),
            w = selection.get('width'),
            h = selection.get('height');

            myTheme_setImageFromURL(url, selection.id, w, h);            

    });        

    mediaUploader.on("select", function() {

        var attachment = mediaUploader.state().get( 'selection' ).first().toJSON();

        if (     cropControl.params.width  === attachment.width 
            &&   cropControl.params.height === attachment.height 
            && ! cropControl.params.flex_width 
            && ! cropControl.params.flex_height ) {
                myTheme_setImageFromAttachment( attachment );
            mediaUploader.close();
        } else {
            mediaUploader.setState( 'cropper' );
        }

    });

    mediaUploader.open();

});
});

N'oubliez pas de régler la largeur/hauteur souhaitée dans le code ci-dessus.

Code pour enregistrer l'image recadrée en tant que pièce jointe a été omis. Les images recadrées seront toujours à l'emplacement habituel dans wp-content/uploads mais vous ne verrez pas les images recadrées dans la médiathèque.

Je ne sais pas comment imposer une taille exacte dans le Cropper. Espérons que quelqu'un d'autre puisse venir et aider à répondre à cela.

2
Darren

Commencez par utiliser le code donné par @Darren sur n’importe quelle page. Par souci de simplicité, j'ai combiné PHP et le code JS en un seul bloc:

  <?php $heading_picture = esc_attr(get_option( 'heading_picture' )); ?>
  <input type="hidden" name="heading_picture" id="heading_picture" value="<?php echo $heading_picture; ?>" />
  <img id="heading_picture_preview" class="heading-picture" src="<?php echo $heading_picture; ?>" />
  <button id="btn_heading_picture" name="btn_heading_picture" class="button default">Choose Picture</button>
<script>
jQuery(function($) {

function myTheme_calculateImageSelectOptions(attachment, controller) {

    var control = controller.get( 'control' );

    var flexWidth = !! parseInt( control.params.flex_width, 10 );
    var flexHeight = !! parseInt( control.params.flex_height, 10 );

    var realWidth = attachment.get( 'width' );
    var realHeight = attachment.get( 'height' );

    var xInit = parseInt(control.params.width, 10);
    var yInit = parseInt(control.params.height, 10);

    var ratio = xInit / yInit;

    controller.set( 'canSkipCrop', ! control.mustBeCropped( flexWidth, flexHeight, xInit, yInit, realWidth, realHeight ) );

    var xImg = xInit;
    var yImg = yInit;

    if ( realWidth / realHeight > ratio ) {
        yInit = realHeight;
        xInit = yInit * ratio;
    } else {
        xInit = realWidth;
        yInit = xInit / ratio;
    }        

    var x1 = ( realWidth - xInit ) / 2;
    var y1 = ( realHeight - yInit ) / 2;        

    var imgSelectOptions = {
        handles: true,
        keys: true,
        instance: true,
        persistent: true,
        imageWidth: realWidth,
        imageHeight: realHeight,
        minWidth: xImg > xInit ? xInit : xImg,
        minHeight: yImg > yInit ? yInit : yImg,            
        x1: x1,
        y1: y1,
        x2: xInit + x1,
        y2: yInit + y1
    };

    return imgSelectOptions;
}  

function myTheme_setImageFromURL(url, attachmentId, width, height) {
    var choice, data = {};

    data.url = url;
    data.thumbnail_url = url;
    data.timestamp = _.now();

    if (attachmentId) {
        data.attachment_id = attachmentId;
    }

    if (width) {
        data.width = width;
    }

    if (height) {
        data.height = height;
    }

    $("#heading_picture").val( url );
    $("#heading_picture_preview").prop("src", url);        

}

function myTheme_setImageFromAttachment(attachment) {

    $("#heading_picture").val( attachment.url );
    $("#heading_picture_preview").prop("src", attachment.url);             

}

var mediaUploader;

$("#btn_heading_picture").on("click", function(e) {

    e.preventDefault(); 

    /* We need to setup a Crop control that contains a few parameters
       and a method to indicate if the CropController can skip cropping the image.
       In this example I am just creating a control on the fly with the expected properties.
       However, the controls used by WordPress Admin are api.CroppedImageControl and api.SiteIconControl
    */

   var cropControl = {
       id: "control-id",
       params : {
         flex_width : false,  // set to true if the width of the cropped image can be different to the width defined here
         flex_height : true, // set to true if the height of the cropped image can be different to the height defined here
         width : 300,  // set the desired width of the destination image here
         height : 200, // set the desired height of the destination image here
       }
   };

   cropControl.mustBeCropped = function(flexW, flexH, dstW, dstH, imgW, imgH) {

    // If the width and height are both flexible
    // then the user does not need to crop the image.

    if ( true === flexW && true === flexH ) {
        return false;
    }

    // If the width is flexible and the cropped image height matches the current image height, 
    // then the user does not need to crop the image.
    if ( true === flexW && dstH === imgH ) {
        return false;
    }

    // If the height is flexible and the cropped image width matches the current image width, 
    // then the user does not need to crop the image.        
    if ( true === flexH && dstW === imgW ) {
        return false;
    }

    // If the cropped image width matches the current image width, 
    // and the cropped image height matches the current image height
    // then the user does not need to crop the image.               
    if ( dstW === imgW && dstH === imgH ) {
        return false;
    }

    // If the destination width is equal to or greater than the cropped image width
    // then the user does not need to crop the image...
    if ( imgW <= dstW ) {
        return false;
    }

    return true;        

   };      

    /* NOTE: Need to set this up every time instead of reusing if already there
             as the toolbar button does not get reset when doing the following:

            mediaUploader.setState('library');
            mediaUploader.open();

    */       

    mediaUploader = wp.media({
        button: {
            text: 'Select and Crop', // l10n.selectAndCrop,
            close: false
        },
        states: [
            new wp.media.controller.Library({
                title:     'Select and Crop', // l10n.chooseImage,
                library:   wp.media.query({ type: 'image' }),
                multiple:  false,
                date:      false,
                priority:  20,
                suggestedWidth: 300,
                suggestedHeight: 200
            }),
            new wp.media.controller.CustomizeImageCropper({ 
                imgSelectOptions: myTheme_calculateImageSelectOptions,
                control: cropControl
            })
        ]
    });

    mediaUploader.on('cropped', function(croppedImage) {

        var url = croppedImage.url,
            attachmentId = croppedImage.attachment_id,
            w = croppedImage.width,
            h = croppedImage.height;

            myTheme_setImageFromURL(url, attachmentId, w, h);            

    });

    mediaUploader.on('skippedcrop', function(selection) {

        var url = selection.get('url'),
            w = selection.get('width'),
            h = selection.get('height');

            myTheme_setImageFromURL(url, selection.id, w, h);            

    });        

    mediaUploader.on("select", function() {

        var attachment = mediaUploader.state().get( 'selection' ).first().toJSON();

        if (     cropControl.params.width  === attachment.width 
            &&   cropControl.params.height === attachment.height 
            && ! cropControl.params.flex_width 
            && ! cropControl.params.flex_height ) {
                myTheme_setImageFromAttachment( attachment );
            mediaUploader.close();
        } else {
            mediaUploader.setState( 'cropper' );
        }

    });

    mediaUploader.open();

});
});
</script>

Ensuite, dans certains plugin , utilisez le code suivant:

add_action( 'wp_enqueue_scripts', 'vna_wp_enqueue_scripts' );
function vna_wp_enqueue_scripts() {
    wp_enqueue_media();
    wp_enqueue_script( 'imgareaselect', get_bloginfo('url') . '/wp-includes/js/imgareaselect/jquery.imgareaselect.js', array( 'jquery' ), '1', true );
    wp_enqueue_style( 'imgareaselect', get_bloginfo('url') . '/wp-includes/js/imgareaselect/imgareaselect.css', array(), '0.9.8' );
}
add_action( 'setup_theme','vna_wp_ajax_crop_image', 1 );
function vna_wp_ajax_crop_image() {
    global $wp_customize;
    if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'crop-image') {
        $post = new WP_Post(new stdClass());
        $post->ID = $_REQUEST['id'];
        $post->post_type = 'customize_changeset';
        $post->filter = 'raw';

        wp_cache_set($wp_customize->changeset_uuid(), $post, 'customize_changeset_post');
    }
}

Notes:

  1. Il y a beaucoup de dépendance sur les fichiers JS admin. Auparavant, j'essayais d'ajouter ces fichiers l'un après l'autre en fonction des erreurs JS que j'avais lors des tests. Ensuite, j'ai trouvé wp_enqueue_media() qui fait la plupart du travail. Il ne charge toujours pas imgareaselect JS et CSS, nous devons donc les charger explicitement.
  2. Le code ci-dessus doit être dans un plugin . J'essayais le code dans un thème et cela ne fonctionnait pas car l'action setup_theme dans le thème est appelée après l'événement attendu (expliqué ci-dessous). Pour l'éviter, je devais le mettre en plugin.
  3. La fonctionnalité de recadrage ne fonctionne que lorsqu'un utilisateur est connecté et dispose de edit_post permission sur l'image. Sinon, vous obtiendrez 0 comme réponse de admin-ajax.php lorsque l'action sera crop-image.
  4. La manière de hacky} _: Exécute le code ci-dessus pour l'action setup_theme avec la priorité définie sur 1. Ceci est dû au fait que class WP_Customize_Manager a une méthode setup_theme qui est exécutée et vérifie la présence d'un post id/post de type customize_changeset. De plus, le filtre de publication doit être raw sinon il essaie de charger la publication à partir de la base de données pour l'ID de publication donné (dans ce cas, nous définissons l'ID de l'image comme ID de publication), ce qui entraîne la définition du type de publication sur attachment. Si la vérification échoue (il ne trouve pas de publication pour un ID donné de type customize_changeset), vous obtenez -1 comme réponse de admin-ajax.php lorsque l'action est crop-image. Au lieu de créer un article de jeu de modifications valide dans la base de données, je viens de créer un objet d'article factice et de l'ajouter au cache.

Le code n'a pas l'air beaucoup mais comme je l'ai mentionné plus tôt dans mon commentaire c'était vraiment pénible de découvrir toutes ces conditions. Je suis content d'avoir réussi après tout.

2
Vivek Athalye