Make WordPress Core

Ticket #21811: trac-21811-extensible-image-editor.patch

File trac-21811-extensible-image-editor.patch, 58.5 KB (added by tomauger, 11 years ago)

Patch that enables extending the image editor groups. See notes below.

  • wp-admin/edit-form-advanced.php

     
    135135if ( 'attachment' == $post_type ) {
    136136        wp_enqueue_script( 'image-edit' );
    137137        wp_enqueue_style( 'imgareaselect' );
     138
     139        /**
     140         * Allows plugin and theme developers to hook in their own enqueued scripts and styles to be used
     141         * exclusively in the Image Editor
     142         */
     143        do_action( 'enqueue_image_editor_scripts' );
     144
    138145        add_meta_box( 'submitdiv', __('Save'), 'attachment_submit_meta_box', null, 'side', 'core' );
    139146        add_action( 'edit_form_after_title', 'edit_form_image_editor' );
    140147} else {
  • wp-admin/includes/image-edit.php

     
    66 * @subpackage Administration
    77 */
    88
     9$image_editor_groups = array();
     10
     11/**
     12 * @uses do_action( 'add_image_editor_groups' ) to allow plugins / themes to register / unregister image editor groups
     13 * @uses apply_filters( 'image_editor_groups' ) to allow direct filtering of the $image_editor_groups global array
     14 *
     15 * @global array $image_editor_groups
     16 * @param type $post_id
     17 * @param type $msg
     18 */
    919function wp_image_editor($post_id, $msg = false) {
    1020        $nonce = wp_create_nonce("image_editor-$post_id");
    1121        $meta = wp_get_attachment_metadata($post_id);
     
    1323        $sub_sizes = isset($meta['sizes']) && is_array($meta['sizes']);
    1424        $note = '';
    1525
     26        if ( $thumb && $sub_sizes ) {
     27                $thumb_img = wp_constrain_dimensions( $thumb['width'], $thumb['height'], 160, 120 );
     28
     29                add_image_edit_group (
     30                        'thumbnail-settings',
     31                        __( 'Thumbnail Settings' ),
     32                        'imgedit_group_thumbnail_settings',
     33                        'default',
     34                        __('The thumbnail image can be cropped differently. For example it can be square or contain only a portion of the original image to showcase it better. Here you can select whether to apply changes to all image sizes or make the thumbnail different.'),
     35                        'imgedit-applyto',
     36                        compact( 'thumb', 'thumb_img', 'sub_sizes' )
     37                );
     38        }
     39
     40        add_image_edit_group(
     41                'image-scale',
     42                __( 'Scale Image' ),
     43                'imgedit_group_scale_image',
     44                'default',
     45                __( 'You can proportionally scale the original image. For best results the scaling should be done before performing any other operations on it like crop, rotate, etc. Note that images can only be scaled down, not up.' ),
     46                '',
     47                compact( 'meta' )
     48        );
     49
     50
     51        add_image_edit_group(
     52                'image-crop',
     53                __( 'Crop Image' ),
     54                'imgedit_group_crop_image',
     55                'default',
     56                sprintf(
     57                        '<p>%1$s</p><p><strong>%2$s</strong><br />%3$s</p><p><strong>%4s</strong><br />%5$s</p>',
     58                        __('The image can be cropped by clicking on it and dragging to select the desired part. While dragging the dimensions of the selection are displayed below.'),
     59                        __('Crop Aspect Ratio'),
     60                        __('You can specify the crop selection aspect ratio then hold down the Shift key while dragging to lock it. The values can be 1:1 (square), 4:3, 16:9, etc. If there is a selection, specifying aspect ratio will set it immediately.'),
     61                        __('Crop Selection'),
     62                        __('Once started, the selection can be adjusted by entering new values (in pixels). Note that these values are scaled to approximately match the original image dimensions. The minimum selection size equals the thumbnail size as set in the Media settings.')
     63                )
     64        );
     65
    1666        if ( isset( $meta['width'], $meta['height'] ) )
    1767                $big = max( $meta['width'], $meta['height'] );
    1868        else
     
    2575        if ( ! empty( $backup_sizes ) && isset( $backup_sizes['full-orig'], $meta['file'] ) )
    2676                $can_restore = $backup_sizes['full-orig']['file'] != basename( $meta['file'] );
    2777
     78        if ( $can_restore ) {
     79                add_image_edit_group(
     80                        'image-restore',
     81                        __( 'Restore Original Image'),
     82                        'imgedit_group_restore_image',
     83                        'default',
     84                        __('Discard any changes and restore the original image.'),
     85                        ''
     86                );
     87        }
     88
     89
    2890        if ( $msg ) {
    2991                if ( isset($msg->error) )
    3092                        $note = "<div class='error'><p>$msg->error</p></div>";
     
    3597        ?>
    3698        <div class="imgedit-wrap">
    3799        <?php echo $note; ?>
    38         <table id="imgedit-panel-<?php echo $post_id; ?>"><tbody>
    39         <tr><td>
    40         <div class="imgedit-menu">
    41                 <div onclick="imageEdit.crop(<?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-crop disabled" title="<?php esc_attr_e( 'Crop' ); ?>"></div><?php
     100                <table id="imgedit-panel-<?php echo $post_id; ?>"><tbody>
     101                        <tr><td>
     102                                <div class="imgedit-menu">
     103                                        <div onclick="imageEdit.crop(<?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-crop disabled" title="<?php esc_attr_e( 'Crop' ); ?>"></div><?php
    42104
    43         // On some setups GD library does not provide imagerotate() - Ticket #11536
    44         if ( wp_image_editor_supports( array( 'mime_type' => get_post_mime_type( $post_id ), 'methods' => array( 'rotate' ) ) ) ) { ?>
    45                 <div class="imgedit-rleft"  onclick="imageEdit.rotate( 90, <?php echo "$post_id, '$nonce'"; ?>, this)" title="<?php esc_attr_e( 'Rotate counter-clockwise' ); ?>"></div>
    46                 <div class="imgedit-rright" onclick="imageEdit.rotate(-90, <?php echo "$post_id, '$nonce'"; ?>, this)" title="<?php esc_attr_e( 'Rotate clockwise' ); ?>"></div>
    47 <?php } else {
    48                 $note_no_rotate = esc_attr__('Image rotation is not supported by your web host.');
    49 ?>
    50             <div class="imgedit-rleft disabled"  title="<?php echo $note_no_rotate; ?>"></div>
    51             <div class="imgedit-rright disabled" title="<?php echo $note_no_rotate; ?>"></div>
    52 <?php } ?>
     105                                // On some setups GD library does not provide imagerotate() - Ticket #11536
     106                                if ( wp_image_editor_supports( array( 'mime_type' => get_post_mime_type( $post_id ), 'methods' => array( 'rotate' ) ) ) ) { ?>
     107                                        <div class="imgedit-rleft"  onclick="imageEdit.rotate( 90, <?php echo "$post_id, '$nonce'"; ?>, this)" title="<?php esc_attr_e( 'Rotate counter-clockwise' ); ?>"></div>
     108                                        <div class="imgedit-rright" onclick="imageEdit.rotate(-90, <?php echo "$post_id, '$nonce'"; ?>, this)" title="<?php esc_attr_e( 'Rotate clockwise' ); ?>"></div>
     109                        <?php } else {
     110                                        $note_no_rotate = esc_attr__('Image rotation is not supported by your web host.');
     111                        ?>
     112                                        <div class="imgedit-rleft disabled"  title="<?php echo $note_no_rotate; ?>"></div>
     113                                        <div class="imgedit-rright disabled" title="<?php echo $note_no_rotate; ?>"></div>
     114                        <?php } ?>
    53115
    54                 <div onclick="imageEdit.flip(1, <?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-flipv" title="<?php esc_attr_e( 'Flip vertically' ); ?>"></div>
    55                 <div onclick="imageEdit.flip(2, <?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-fliph" title="<?php esc_attr_e( 'Flip horizontally' ); ?>"></div>
     116                                        <div onclick="imageEdit.flip(1, <?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-flipv" title="<?php esc_attr_e( 'Flip vertically' ); ?>"></div>
     117                                        <div onclick="imageEdit.flip(2, <?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-fliph" title="<?php esc_attr_e( 'Flip horizontally' ); ?>"></div>
    56118
    57                 <div id="image-undo-<?php echo $post_id; ?>" onclick="imageEdit.undo(<?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-undo disabled" title="<?php esc_attr_e( 'Undo' ); ?>"></div>
    58                 <div id="image-redo-<?php echo $post_id; ?>" onclick="imageEdit.redo(<?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-redo disabled" title="<?php esc_attr_e( 'Redo' ); ?>"></div>
    59                 <br class="clear" />
    60         </div>
     119                                        <div id="image-undo-<?php echo $post_id; ?>" onclick="imageEdit.undo(<?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-undo disabled" title="<?php esc_attr_e( 'Undo' ); ?>"></div>
     120                                        <div id="image-redo-<?php echo $post_id; ?>" onclick="imageEdit.redo(<?php echo "$post_id, '$nonce'"; ?>, this)" class="imgedit-redo disabled" title="<?php esc_attr_e( 'Redo' ); ?>"></div>
     121                                        <br class="clear" />
     122                                </div>
    61123
    62         <input type="hidden" id="imgedit-sizer-<?php echo $post_id; ?>" value="<?php echo $sizer; ?>" />
    63         <input type="hidden" id="imgedit-minthumb-<?php echo $post_id; ?>" value="<?php echo ( get_option('thumbnail_size_w') . ':' . get_option('thumbnail_size_h') ); ?>" />
    64         <input type="hidden" id="imgedit-history-<?php echo $post_id; ?>" value="" />
    65         <input type="hidden" id="imgedit-undone-<?php echo $post_id; ?>" value="0" />
    66         <input type="hidden" id="imgedit-selection-<?php echo $post_id; ?>" value="" />
    67         <input type="hidden" id="imgedit-x-<?php echo $post_id; ?>" value="<?php echo isset( $meta['width'] ) ? $meta['width'] : 0; ?>" />
    68         <input type="hidden" id="imgedit-y-<?php echo $post_id; ?>" value="<?php echo isset( $meta['height'] ) ? $meta['height'] : 0; ?>" />
     124                                <input type="hidden" id="imgedit-sizer-<?php echo $post_id; ?>" value="<?php echo $sizer; ?>" />
     125                                <input type="hidden" id="imgedit-minthumb-<?php echo $post_id; ?>" value="<?php echo ( get_option('thumbnail_size_w') . ':' . get_option('thumbnail_size_h') ); ?>" />
     126                                <input type="hidden" id="imgedit-history-<?php echo $post_id; ?>" value="" />
     127                                <input type="hidden" id="imgedit-undone-<?php echo $post_id; ?>" value="0" />
     128                                <input type="hidden" id="imgedit-selection-<?php echo $post_id; ?>" value="" />
     129                                <input type="hidden" id="imgedit-x-<?php echo $post_id; ?>" value="<?php echo isset( $meta['width'] ) ? $meta['width'] : 0; ?>" />
     130                                <input type="hidden" id="imgedit-y-<?php echo $post_id; ?>" value="<?php echo isset( $meta['height'] ) ? $meta['height'] : 0; ?>" />
    69131
    70         <div id="imgedit-crop-<?php echo $post_id; ?>" class="imgedit-crop-wrap">
    71         <img id="image-preview-<?php echo $post_id; ?>" onload="imageEdit.imgLoaded('<?php echo $post_id; ?>')" src="<?php echo admin_url( 'admin-ajax.php', 'relative' ); ?>?action=imgedit-preview&amp;_ajax_nonce=<?php echo $nonce; ?>&amp;postid=<?php echo $post_id; ?>&amp;rand=<?php echo rand(1, 99999); ?>" />
    72         </div>
     132                                <div id="imgedit-crop-<?php echo $post_id; ?>" class="imgedit-crop-wrap">
     133                                <img id="image-preview-<?php echo $post_id; ?>" onload="imageEdit.imgLoaded('<?php echo $post_id; ?>')" src="<?php echo admin_url( 'admin-ajax.php', 'relative' ); ?>?action=imgedit-preview&amp;_ajax_nonce=<?php echo $nonce; ?>&amp;postid=<?php echo $post_id; ?>&amp;rand=<?php echo rand(1, 99999); ?>" />
     134                                </div>
    73135
    74         <div class="imgedit-submit">
    75                 <input type="button" onclick="imageEdit.close(<?php echo $post_id; ?>, 1)" class="button" value="<?php esc_attr_e( 'Cancel' ); ?>" />
    76                 <input type="button" onclick="imageEdit.save(<?php echo "$post_id, '$nonce'"; ?>)" disabled="disabled" class="button-primary imgedit-submit-btn" value="<?php esc_attr_e( 'Save' ); ?>" />
    77         </div>
    78         </td>
     136                                <?php do_action( 'image_editor_below_image' ) ?>
     137                        </td>
    79138
    80         <td class="imgedit-settings">
    81         <div class="imgedit-group">
    82         <div class="imgedit-group-top">
    83                 <a class="imgedit-help-toggle" onclick="imageEdit.toggleHelp(this);return false;" href="#"><strong><?php _e('Scale Image'); ?></strong></a>
    84                 <div class="imgedit-help">
    85                 <p><?php _e('You can proportionally scale the original image. For best results the scaling should be done before performing any other operations on it like crop, rotate, etc. Note that images can only be scaled down, not up.'); ?></p>
    86                 <?php if ( isset( $meta['width'], $meta['height'] ) ): ?>
    87                 <p><?php printf( __('Original dimensions %s'), $meta['width'] . '&times;' . $meta['height'] ); ?></p>
    88                 <?php endif ?>
    89                 <div class="imgedit-submit">
    90                 <span class="nowrap"><input type="text" id="imgedit-scale-width-<?php echo $post_id; ?>" onkeyup="imageEdit.scaleChanged(<?php echo $post_id; ?>, 1)" onblur="imageEdit.scaleChanged(<?php echo $post_id; ?>, 1)" style="width:4em;" value="<?php echo isset( $meta['width'] ) ? $meta['width'] : 0; ?>" />&times;<input type="text" id="imgedit-scale-height-<?php echo $post_id; ?>" onkeyup="imageEdit.scaleChanged(<?php echo $post_id; ?>, 0)" onblur="imageEdit.scaleChanged(<?php echo $post_id; ?>, 0)" style="width:4em;" value="<?php echo isset( $meta['height'] ) ? $meta['height'] : 0; ?>" />
    91                 <span class="imgedit-scale-warn" id="imgedit-scale-warn-<?php echo $post_id; ?>">!</span></span>
    92                 <input type="button" onclick="imageEdit.action(<?php echo "$post_id, '$nonce'"; ?>, 'scale')" class="button-primary" value="<?php esc_attr_e( 'Scale' ); ?>" />
    93                 </div>
    94                 </div>
    95         </div>
     139                        <td class="imgedit-settings">
    96140
    97 <?php if ( $can_restore ) { ?>
     141                        <?php
     142                                /* Image Editor Groups */
     143                                do_action( 'add_image_editor_groups', $post_id );
    98144
    99         <div class="imgedit-group-top">
    100                 <a class="imgedit-help-toggle" onclick="imageEdit.toggleHelp(this);return false;" href="#"><strong><?php _e('Restore Original Image'); ?></strong></a>
    101                 <div class="imgedit-help">
    102                 <p><?php _e('Discard any changes and restore the original image.');
     145                                do_image_edit_groups( $post_id, $nonce );
     146                        ?>
    103147
    104                 if ( !defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE )
    105                         echo ' '.__('Previously edited copies of the image will not be deleted.');
     148                                <div class="imgedit-submit">
     149                                        <input type="button" onclick="imageEdit.close(<?php echo $post_id; ?>, 1)" class="button" value="<?php esc_attr_e( 'Cancel' ); ?>" />
     150                                        <input type="button" onclick="imageEdit.save(<?php echo "$post_id, '$nonce'"; ?>)" disabled="disabled" class="button-primary imgedit-submit-btn" value="<?php esc_attr_e( 'Save' ); ?>" />
     151                                </div>
    106152
    107                 ?></p>
    108                 <div class="imgedit-submit">
    109                 <input type="button" onclick="imageEdit.action(<?php echo "$post_id, '$nonce'"; ?>, 'restore')" class="button-primary" value="<?php esc_attr_e( 'Restore image' ); ?>" <?php echo $can_restore; ?> />
    110                 </div>
    111                 </div>
    112         </div>
    113153
    114 <?php } ?>
     154                        </td><!-- /.imgedit-settings --></tr>
    115155
    116         </div>
     156                </tbody></table>
    117157
    118         <div class="imgedit-group">
    119         <div class="imgedit-group-top">
    120                 <strong><?php _e('Image Crop'); ?></strong>
    121                 <a class="imgedit-help-toggle" onclick="imageEdit.toggleHelp(this);return false;" href="#"><?php _e('(help)'); ?></a>
    122                 <div class="imgedit-help">
    123                 <p><?php _e('The image can be cropped by clicking on it and dragging to select the desired part. While dragging the dimensions of the selection are displayed below.'); ?></p>
    124 
    125                 <p><strong><?php _e('Crop Aspect Ratio'); ?></strong><br />
    126                 <?php _e('You can specify the crop selection aspect ratio then hold down the Shift key while dragging to lock it. The values can be 1:1 (square), 4:3, 16:9, etc. If there is a selection, specifying aspect ratio will set it immediately.'); ?></p>
    127 
    128                 <p><strong><?php _e('Crop Selection'); ?></strong><br />
    129                 <?php _e('Once started, the selection can be adjusted by entering new values (in pixels). Note that these values are scaled to approximately match the original image dimensions. The minimum selection size equals the thumbnail size as set in the Media settings.'); ?></p>
    130                 </div>
     158                <div class="imgedit-wait" id="imgedit-wait-<?php echo $post_id; ?>"></div>
     159                <script type="text/javascript">jQuery( function() { imageEdit.init(<?php echo $post_id; ?>); });</script>
     160                <div class="hidden" id="imgedit-leaving-<?php echo $post_id; ?>"><?php _e("There are unsaved changes that will be lost. 'OK' to continue, 'Cancel' to return to the Image Editor."); ?></div>
    131161        </div>
    132 
    133         <p>
    134                 <?php _e('Aspect ratio:'); ?>
    135                 <span  class="nowrap">
    136                 <input type="text" id="imgedit-crop-width-<?php echo $post_id; ?>" onkeyup="imageEdit.setRatioSelection(<?php echo $post_id; ?>, 0, this)" style="width:3em;" />
    137                 :
    138                 <input type="text" id="imgedit-crop-height-<?php echo $post_id; ?>" onkeyup="imageEdit.setRatioSelection(<?php echo $post_id; ?>, 1, this)" style="width:3em;" />
    139                 </span>
    140         </p>
    141 
    142         <p id="imgedit-crop-sel-<?php echo $post_id; ?>">
    143                 <?php _e('Selection:'); ?>
    144                 <span  class="nowrap">
    145                 <input type="text" id="imgedit-sel-width-<?php echo $post_id; ?>" onkeyup="imageEdit.setNumSelection(<?php echo $post_id; ?>)" style="width:4em;" />
    146                 :
    147                 <input type="text" id="imgedit-sel-height-<?php echo $post_id; ?>" onkeyup="imageEdit.setNumSelection(<?php echo $post_id; ?>)" style="width:4em;" />
    148                 </span>
    149         </p>
    150         </div>
    151 
    152         <?php if ( $thumb && $sub_sizes ) {
    153                 $thumb_img = wp_constrain_dimensions( $thumb['width'], $thumb['height'], 160, 120 );
    154         ?>
    155 
    156         <div class="imgedit-group imgedit-applyto">
    157         <div class="imgedit-group-top">
    158                 <strong><?php _e('Thumbnail Settings'); ?></strong>
    159                 <a class="imgedit-help-toggle" onclick="imageEdit.toggleHelp(this);return false;" href="#"><?php _e('(help)'); ?></a>
    160                 <p class="imgedit-help"><?php _e('The thumbnail image can be cropped differently. For example it can be square or contain only a portion of the original image to showcase it better. Here you can select whether to apply changes to all image sizes or make the thumbnail different.'); ?></p>
    161         </div>
    162 
    163         <p>
    164                 <img src="<?php echo $thumb['url']; ?>" width="<?php echo $thumb_img[0]; ?>" height="<?php echo $thumb_img[1]; ?>" class="imgedit-size-preview" alt="" /><br /><?php _e('Current thumbnail'); ?>
    165         </p>
    166 
    167         <p id="imgedit-save-target-<?php echo $post_id; ?>">
    168                 <strong><?php _e('Apply changes to:'); ?></strong><br />
    169 
    170                 <label class="imgedit-label">
    171                 <input type="radio" name="imgedit-target-<?php echo $post_id; ?>" value="all" checked="checked" />
    172                 <?php _e('All image sizes'); ?></label>
    173 
    174                 <label class="imgedit-label">
    175                 <input type="radio" name="imgedit-target-<?php echo $post_id; ?>" value="thumbnail" />
    176                 <?php _e('Thumbnail'); ?></label>
    177 
    178                 <label class="imgedit-label">
    179                 <input type="radio" name="imgedit-target-<?php echo $post_id; ?>" value="nothumb" />
    180                 <?php _e('All sizes except thumbnail'); ?></label>
    181         </p>
    182         </div>
    183 
    184         <?php } ?>
    185 
    186         </td></tr>
    187         </tbody></table>
    188         <div class="imgedit-wait" id="imgedit-wait-<?php echo $post_id; ?>"></div>
    189         <script type="text/javascript">jQuery( function() { imageEdit.init(<?php echo $post_id; ?>); });</script>
    190         <div class="hidden" id="imgedit-leaving-<?php echo $post_id; ?>"><?php _e("There are unsaved changes that will be lost. 'OK' to continue, 'Cancel' to return to the Image Editor."); ?></div>
    191         </div>
    192162<?php
    193163}
    194164
     
    733703        $return->msg = esc_js( __('Image saved') );
    734704        return $return;
    735705}
     706
     707/**
     708 * Adds a new image editor group to the Edit Image view.
     709 *
     710 * Should be called before {@link do_image_edit_groups()}, preferably from a 'add_image_edit_group' action callback.
     711 *
     712 * @global array $image_editor_groups
     713 *
     714 * @param int $id Unique ID for this editor group
     715 * @param string $title Title that appears in the editor group box
     716 * @param string|array $callback Function to be called to render the editor group content. Function signature is `( $post_id, $imgedit_group, $args )`
     717 * @param string $tab Optional. Default "default". The name of the tab this group appears in
     718 * @param string $help_text Optional. Help text that appears in the editor box content
     719 * @param string $class Optional. CSS class to be added to the editor group DIV
     720 * @param array $callback_args Optional. Additional arguments to be passed to the callback function.
     721 */
     722function add_image_edit_group( $id, $title, $callback, $tab = "default", $help_text = "", $class = "", $callback_args = null ){
     723        global $image_editor_groups;
     724
     725        /** @TODO make sure $id is unique **/
     726        $image_editor_groups[$tab][$id] = array(
     727                'id' => $id,
     728                'title' => $title,
     729                'callback' => $callback,
     730                'class' => $class,
     731                'help' => $help_text,
     732                'callback_args' => $callback_args
     733        );
     734}
     735
     736function remove_image_edit_group( $remove_id ){
     737        global $image_editor_groups;
     738
     739        foreach ( $image_editor_groups as $tab_group => &$groups ){
     740                foreach ( $groups as $id => $group ){
     741                        if ( $id == $remove_id ){
     742                                unset ( $groups[$id] );
     743                                return true;
     744                        }
     745                }
     746        }
     747        return false;
     748}
     749
     750function imgedit_group_scale_image( $post_id, $imgedit_group, $args ){
     751        extract( $args );
     752        ?>
     753
     754                <?php if ( isset( $meta['width'], $meta['height'] ) ): ?>
     755                <p><?php printf( __('Original dimensions %s'), $meta['width'] . '&times;' . $meta['height'] ); ?></p>
     756                <?php endif ?>
     757                <div class="imgedit-submit">
     758                        <span class="nowrap"><input type="text" id="imgedit-scale-width-<?php echo $post_id; ?>" onkeyup="imageEdit.scaleChanged(<?php echo $post_id; ?>, 1)" onblur="imageEdit.scaleChanged(<?php echo $post_id; ?>, 1)" style="width:4em;" value="<?php echo isset( $meta['width'] ) ? $meta['width'] : 0; ?>" />&times;<input type="text" id="imgedit-scale-height-<?php echo $post_id; ?>" onkeyup="imageEdit.scaleChanged(<?php echo $post_id; ?>, 0)" onblur="imageEdit.scaleChanged(<?php echo $post_id; ?>, 0)" style="width:4em;" value="<?php echo isset( $meta['height'] ) ? $meta['height'] : 0; ?>" />
     759                        <span class="imgedit-scale-warn" id="imgedit-scale-warn-<?php echo $post_id; ?>">!</span></span>
     760                        <input type="button" onclick="imageEdit.action(<?php echo "$post_id, '$nonce'"; ?>, 'scale')" class="button-primary" value="<?php esc_attr_e( 'Scale' ); ?>" />
     761                </div>
     762                </div>
     763
     764        <?php
     765}
     766
     767function imgedit_group_crop_image( $post_id, $imgedit_group, $args ){
     768        extract( $args );
     769        ?>
     770                <p>
     771                        <?php _e('Aspect ratio:'); ?>
     772                        <span  class="nowrap">
     773                        <input type="text" id="imgedit-crop-width-<?php echo $post_id; ?>" onkeyup="imageEdit.setRatioSelection(<?php echo $post_id; ?>, 0, this)" style="width:3em;" />
     774                        :
     775                        <input type="text" id="imgedit-crop-height-<?php echo $post_id; ?>" onkeyup="imageEdit.setRatioSelection(<?php echo $post_id; ?>, 1, this)" style="width:3em;" />
     776                        </span>
     777                </p>
     778
     779                <p id="imgedit-crop-sel-<?php echo $post_id; ?>">
     780                        <?php _e('Selection:'); ?>
     781                        <span  class="nowrap">
     782                        <input type="text" id="imgedit-sel-width-<?php echo $post_id; ?>" onkeyup="imageEdit.setNumSelection(<?php echo $post_id; ?>)" style="width:4em;" />
     783                        &times;
     784                        <input type="text" id="imgedit-sel-height-<?php echo $post_id; ?>" onkeyup="imageEdit.setNumSelection(<?php echo $post_id; ?>)" style="width:4em;" />
     785                        </span>
     786                </p>
     787        <?php
     788}
     789
     790function imgedit_group_thumbnail_settings( $post_id, $imgedit_group, $args ){
     791        extract( $args );
     792        ?>
     793
     794        <?php if ( isset( $thumb ) ): ?>
     795        <p>
     796                <img src="<?php echo $thumb['url']; ?>" width="<?php echo $thumb_img[0]; ?>" height="<?php echo $thumb_img[1]; ?>" class="imgedit-size-preview" alt="" /><br /><?php _e('Current thumbnail'); ?>
     797        </p>
     798        <?php endif; ?>
     799
     800        <p id="imgedit-save-target-<?php echo $post_id; ?>">
     801                <strong><?php _e('Apply changes to:'); ?></strong><br />
     802
     803                <label class="imgedit-label">
     804                <input type="radio" name="imgedit-target-<?php echo $post_id; ?>" value="all" checked="checked" />
     805                <?php _e('All image sizes'); ?></label>
     806
     807                <label class="imgedit-label">
     808                <input type="radio" name="imgedit-target-<?php echo $post_id; ?>" value="thumbnail" />
     809                <?php _e('Thumbnail'); ?></label>
     810
     811                <label class="imgedit-label">
     812                <input type="radio" name="imgedit-target-<?php echo $post_id; ?>" value="nothumb" />
     813                <?php _e('All sizes except thumbnail'); ?></label>
     814        </p>
     815
     816        <?php
     817}
     818
     819function imgedit_group_restore_image( $post_id, $imgedit_group, $args ){
     820        extract( $args );
     821        ?>
     822
     823        <p>
     824        <?php
     825                if ( !defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE )
     826                echo ' '.__('Previously edited copies of the image will not be deleted.');
     827        ?>
     828        </p>
     829
     830        <div class="imgedit-submit">
     831                <input type="button" onclick="imageEdit.action(<?php echo "$post_id, '$nonce'"; ?>, 'restore')" class="button-primary" value="<?php esc_attr_e( 'Restore image' ); ?>" />
     832        </div>
     833
     834        <?php
     835}
     836
     837/**
     838 * @global array $image_editor_groups
     839 *
     840 * @param int $post_id
     841 * @param string $nonce
     842 *
     843 * @TODO Currently, tabs auto-create themselves just by defining them in add_image_edit_group(). Eventually we'll want a add_editor_tab() suite of functions so we can better control these tabs.
     844 */
     845function do_image_edit_groups( $post_id, $nonce ){
     846        global $image_editor_groups;
     847
     848        /**
     849         * Allow modification of image editor groups.
     850         *
     851         * @since 3.9
     852         *
     853         * @param int $post_id The ID of the WP_Post (attachment) currently being edited.
     854         */
     855        $editor_groups = apply_filters( 'image_editor_groups', $image_editor_groups, $post_id );
     856
     857        // Only create the tabs structure if needed
     858        $has_tabs = count( $editor_groups ) > 1;
     859
     860        if ( $has_tabs ){
     861                echo '<div id="image-editor-group-tabs"><ul>';
     862
     863                foreach ( $editor_groups as $tab => $tab_groups ){
     864                        if ( $has_tabs ){
     865                                $tab_title = str_replace( array( '-', '_' ), ' ', strtoupper( $tab ) );
     866                                echo '<li><a href="#imgedit-group-tab-' . $tab . '">' . $tab_title . '</a></li>';
     867                        }
     868                }
     869
     870                echo '</ul>';
     871        }
     872
     873        foreach ( $editor_groups as $tab => $tab_groups ){
     874                if ( $has_tabs ) echo '<div id="#imgedit-group-tab-' . $tab . '">';
     875
     876                foreach ( $tab_groups as $imgedit_group ){
     877                        ?>
     878                        <div class="imgedit-group <?php echo $imgedit_group['class']?>">
     879                                <div class="imgedit-group-top">
     880                                        <strong><?php echo $imgedit_group['title'] ?></strong>
     881                                        <?php if ( ! empty( $imgedit_group['help'] ) ): ?>
     882                                                <a class="imgedit-help-toggle" onclick="imageEdit.toggleHelp(this);return false;" href="#"><?php _e('(help)'); ?></a>
     883                                                <div class="imgedit-help"><?php echo $imgedit_group['help'] ?></div>
     884                                        <?php endif; ?>
     885                                </div>
     886
     887                                <?php
     888                                        $imgedit_group['callback_args']['nonce'] = $nonce; // Add the nonce to the callback args of each item, in case it has a submit button
     889                                        call_user_func( $imgedit_group['callback'], $post_id, $imgedit_group, $imgedit_group['callback_args'] );
     890                                ?>
     891
     892                        </div>
     893                        <?php
     894                }
     895
     896                if ( $has_tabs ) echo '</div><!-- /#imgedit-group-tab-' . $tab . ' -->';
     897        }
     898
     899        if ( $has_tabs ) {
     900                echo '</div><!-- /#image-editor-group-tabs -->';
     901                ?>
     902                <script>jQuery( '#image-editor-group-tabs' ).tabs();</script>
     903                <?php
     904        }
     905
     906}
  • wp-admin/js/image-edit.js

     
    11/* global imageEditL10n, ajaxurl, confirm */
    22
    3 (function($) {
    4 var imageEdit = window.imageEdit = {
    5         iasapi : {},
    6         hold : {},
    7         postid : '',
     3;(function($) {
     4        var imageEdit = window.imageEdit = {
     5                iasapi : {},
     6                hold : {},
     7                eventListeners : {},
     8                postid : '',
    89
    9         intval : function(f) {
    10                 return f | 0;
    11         },
     10                intval : function(f) {
     11                        return f | 0;
     12                },
    1213
    13         setDisabled : function(el, s) {
    14                 if ( s ) {
    15                         el.removeClass('disabled');
    16                         $('input', el).removeAttr('disabled');
    17                 } else {
    18                         el.addClass('disabled');
    19                         $('input', el).prop('disabled', true);
    20                 }
    21         },
     14                setDisabled : function(el, s) {
     15                        if ( s ) {
     16                                el.removeClass('disabled');
     17                                $('input', el).removeAttr('disabled');
     18                        } else {
     19                                el.addClass('disabled');
     20                                $('input', el).prop('disabled', true);
     21                        }
     22                },
    2223
    23         init : function(postid) {
    24                 var t = this, old = $('#image-editor-' + t.postid),
    25                         x = t.intval( $('#imgedit-x-' + postid).val() ),
    26                         y = t.intval( $('#imgedit-y-' + postid).val() );
     24                init : function(postid) {
     25                        var t = this, old = $('#image-editor-' + t.postid),
     26                                x = t.intval( $('#imgedit-x-' + postid).val() ),
     27                                y = t.intval( $('#imgedit-y-' + postid).val() );
    2728
    28                 if ( t.postid !== postid && old.length ) {
    29                         t.close(t.postid);
    30                 }
     29                        if ( t.postid !== postid && old.length ) {
     30                                t.close(t.postid);
     31                        }
    3132
    32                 t.hold.w = t.hold.ow = x;
    33                 t.hold.h = t.hold.oh = y;
    34                 t.hold.xy_ratio = x / y;
    35                 t.hold.sizer = parseFloat( $('#imgedit-sizer-' + postid).val() );
    36                 t.postid = postid;
    37                 $('#imgedit-response-' + postid).empty();
     33                        t.hold.w = t.hold.ow = x;
     34                        t.hold.h = t.hold.oh = y;
     35                        t.hold.xy_ratio = x / y;
     36                        t.hold.sizer = parseFloat( $('#imgedit-sizer-' + postid).val() );
     37                        t.postid = postid;
     38                        $('#imgedit-response-' + postid).empty();
    3839
    39                 $('input[type="text"]', '#imgedit-panel-' + postid).keypress(function(e) {
    40                         var k = e.keyCode;
     40                        $('input[type="text"]', '#imgedit-panel-' + postid).keypress(function(e) {
     41                                var k = e.keyCode;
    4142
    42                         if ( 36 < k && k < 41 ) {
    43                                 $(this).blur();
    44                         }
     43                                if ( 36 < k && k < 41 ) {
     44                                        $(this).blur();
     45                                }
    4546
    46                         if ( 13 === k ) {
    47                                 e.preventDefault();
    48                                 e.stopPropagation();
    49                                 return false;
    50                         }
    51                 });
    52         },
     47                                if ( 13 === k ) {
     48                                        e.preventDefault();
     49                                        e.stopPropagation();
     50                                        return false;
     51                                }
     52                        });
     53                },
    5354
    54         toggleEditor : function(postid, toggle) {
    55                 var wait = $('#imgedit-wait-' + postid);
     55                toggleEditor : function(postid, toggle) {
     56                        var wait = $('#imgedit-wait-' + postid);
    5657
    57                 if ( toggle ) {
    58                         wait.height( $('#imgedit-panel-' + postid).height() ).fadeIn('fast');
    59                 } else {
    60                         wait.fadeOut('fast');
    61                 }
    62         },
     58                        if ( toggle ) {
     59                                wait.height( $('#imgedit-panel-' + postid).height() ).fadeIn('fast');
     60                        } else {
     61                                wait.fadeOut('fast');
     62                                this.dispatchEvent( this.events.INIT_EVENT, this );
     63                        }
     64                },
    6365
    64         toggleHelp : function(el) {
    65                 $(el).siblings('.imgedit-help').slideToggle('fast');
    66                 return false;
    67         },
     66                toggleHelp : function(el) {
     67                        $(el).siblings('.imgedit-help').slideToggle('fast');
     68                        return false;
     69                },
    6870
    69         getTarget : function(postid) {
    70                 return $('input[name="imgedit-target-' + postid + '"]:checked', '#imgedit-save-target-' + postid).val() || 'full';
    71         },
     71                getTarget : function(postid) {
     72                        return $('input[name="imgedit-target-' + postid + '"]:checked', '#imgedit-save-target-' + postid).val() || 'full';
     73                },
    7274
    73         scaleChanged : function(postid, x) {
    74                 var w = $('#imgedit-scale-width-' + postid), h = $('#imgedit-scale-height-' + postid),
    75                 warn = $('#imgedit-scale-warn-' + postid), w1 = '', h1 = '';
     75                scaleChanged : function(postid, x) {
     76                        var w = $('#imgedit-scale-width-' + postid), h = $('#imgedit-scale-height-' + postid),
     77                        warn = $('#imgedit-scale-warn-' + postid), w1 = '', h1 = '';
    7678
    77                 if ( x ) {
    78                         h1 = ( w.val() !== '' ) ? Math.round( w.val() / this.hold.xy_ratio ) : '';
    79                         h.val( h1 );
    80                 } else {
    81                         w1 = ( h.val() !== '' ) ? Math.round( h.val() * this.hold.xy_ratio ) : '';
    82                         w.val( w1 );
    83                 }
     79                        if ( x ) {
     80                                h1 = ( w.val() !== '' ) ? Math.round( w.val() / this.hold.xy_ratio ) : '';
     81                                h.val( h1 );
     82                        } else {
     83                                w1 = ( h.val() !== '' ) ? Math.round( h.val() * this.hold.xy_ratio ) : '';
     84                                w.val( w1 );
     85                        }
    8486
    85                 if ( ( h1 && h1 > this.hold.oh ) || ( w1 && w1 > this.hold.ow ) ) {
    86                         warn.css('visibility', 'visible');
    87                 } else {
    88                         warn.css('visibility', 'hidden');
    89                 }
    90         },
     87                        if ( ( h1 && h1 > this.hold.oh ) || ( w1 && w1 > this.hold.ow ) ) {
     88                                warn.css('visibility', 'visible');
     89                        } else {
     90                                warn.css('visibility', 'hidden');
     91                        }
     92                },
    9193
    92         getSelRatio : function(postid) {
    93                 var x = this.hold.w, y = this.hold.h,
    94                         X = this.intval( $('#imgedit-crop-width-' + postid).val() ),
    95                         Y = this.intval( $('#imgedit-crop-height-' + postid).val() );
     94                getSelRatio : function(postid) {
     95                        var x = this.hold.w, y = this.hold.h,
     96                                X = this.intval( $('#imgedit-crop-width-' + postid).val() ),
     97                                Y = this.intval( $('#imgedit-crop-height-' + postid).val() );
    9698
    97                 if ( X && Y ) {
    98                         return X + ':' + Y;
    99                 }
     99                        if ( X && Y ) {
     100                                return X + ':' + Y;
     101                        }
    100102
    101                 if ( x && y ) {
    102                         return x + ':' + y;
    103                 }
     103                        if ( x && y ) {
     104                                return x + ':' + y;
     105                        }
    104106
    105                 return '1:1';
    106         },
     107                        return '1:1';
     108                },
    107109
    108         filterHistory : function(postid, setSize) {
    109                 // apply undo state to history
    110                 var history = $('#imgedit-history-' + postid).val(), pop, n, o, i, op = [];
     110                filterHistory : function(postid, setSize) {
     111                        // apply undo state to history
     112                        var history = $('#imgedit-history-' + postid).val(), pop, n, o, i, op = [];
    111113
    112                 if ( history !== '' ) {
    113                         history = JSON.parse(history);
    114                         pop = this.intval( $('#imgedit-undone-' + postid).val() );
    115                         if ( pop > 0 ) {
    116                                 while ( pop > 0 ) {
    117                                         history.pop();
    118                                         pop--;
     114                        if ( history !== '' ) {
     115                                history = JSON.parse(history);
     116                                pop = this.intval( $('#imgedit-undone-' + postid).val() );
     117                                if ( pop > 0 ) {
     118                                        while ( pop > 0 ) {
     119                                                history.pop();
     120                                                pop--;
     121                                        }
    119122                                }
    120                         }
    121123
    122                         if ( setSize ) {
    123                                 if ( !history.length ) {
    124                                         this.hold.w = this.hold.ow;
    125                                         this.hold.h = this.hold.oh;
    126                                         return '';
    127                                 }
     124                                if ( setSize ) {
     125                                        if ( !history.length ) {
     126                                                this.hold.w = this.hold.ow;
     127                                                this.hold.h = this.hold.oh;
     128                                                return '';
     129                                        }
    128130
    129                                 // restore
    130                                 o = history[history.length - 1];
    131                                 o = o.c || o.r || o.f || false;
     131                                        // restore
     132                                        o = history[history.length - 1];
     133                                        o = o.c || o.r || o.f || false;
    132134
    133                                 if ( o ) {
    134                                         this.hold.w = o.fw;
    135                                         this.hold.h = o.fh;
     135                                        if ( o ) {
     136                                                this.hold.w = o.fw;
     137                                                this.hold.h = o.fh;
     138                                        }
    136139                                }
    137                         }
    138140
    139                         // filter the values
    140                         for ( n in history ) {
    141                                 i = history[n];
    142                                 if ( i.hasOwnProperty('c') ) {
    143                                         op[n] = { 'c': { 'x': i.c.x, 'y': i.c.y, 'w': i.c.w, 'h': i.c.h } };
    144                                 } else if ( i.hasOwnProperty('r') ) {
    145                                         op[n] = { 'r': i.r.r };
    146                                 } else if ( i.hasOwnProperty('f') ) {
    147                                         op[n] = { 'f': i.f.f };
     141                                // filter the values
     142                                for ( n in history ) {
     143                                        i = history[n];
     144                                        if ( i.hasOwnProperty('c') ) {
     145                                                op[n] = { 'c': { 'x': i.c.x, 'y': i.c.y, 'w': i.c.w, 'h': i.c.h } };
     146                                        } else if ( i.hasOwnProperty('r') ) {
     147                                                op[n] = { 'r': i.r.r };
     148                                        } else if ( i.hasOwnProperty('f') ) {
     149                                                op[n] = { 'f': i.f.f };
     150                                        }
    148151                                }
     152                                return JSON.stringify(op);
    149153                        }
    150                         return JSON.stringify(op);
    151                 }
    152                 return '';
    153         },
     154                        return '';
     155                },
    154156
    155         refreshEditor : function(postid, nonce, callback) {
    156                 var t = this, data, img;
     157                refreshEditor : function(postid, nonce, callback) {
     158                        var t = this, data, img;
    157159
    158                 t.toggleEditor(postid, 1);
    159                 data = {
    160                         'action': 'imgedit-preview',
    161                         '_ajax_nonce': nonce,
    162                         'postid': postid,
    163                         'history': t.filterHistory(postid, 1),
    164                         'rand': t.intval(Math.random() * 1000000)
    165                 };
     160                        t.toggleEditor(postid, 1);
     161                        data = {
     162                                'action': 'imgedit-preview',
     163                                '_ajax_nonce': nonce,
     164                                'postid': postid,
     165                                'history': t.filterHistory(postid, 1),
     166                                'rand': t.intval(Math.random() * 1000000)
     167                        };
    166168
    167                 img = $('<img id="image-preview-' + postid + '" />')
    168                         .on('load', function() {
    169                                 var max1, max2, parent = $('#imgedit-crop-' + postid), t = imageEdit;
     169                        img = $('<img id="image-preview-' + postid + '" />')
     170                                .on('load', function() {
     171                                        var max1, max2, parent = $('#imgedit-crop-' + postid), t = imageEdit;
    170172
    171                                 parent.empty().append(img);
     173                                        parent.empty().append(img);
    172174
    173                                 // w, h are the new full size dims
    174                                 max1 = Math.max( t.hold.w, t.hold.h );
    175                                 max2 = Math.max( $(img).width(), $(img).height() );
    176                                 t.hold.sizer = max1 > max2 ? max2 / max1 : 1;
     175                                        // w, h are the new full size dims
     176                                        max1 = Math.max( t.hold.w, t.hold.h );
     177                                        max2 = Math.max( $(img).width(), $(img).height() );
     178                                        t.hold.sizer = max1 > max2 ? max2 / max1 : 1;
    177179
    178                                 t.initCrop(postid, img, parent);
    179                                 t.setCropSelection(postid, 0);
     180                                        t.initCrop(postid, img, parent);
     181                                        t.setCropSelection(postid, 0);
    180182
    181                                 if ( (typeof callback !== 'undefined') && callback !== null ) {
    182                                         callback();
    183                                 }
     183                                        if ( (typeof callback !== 'undefined') && callback !== null ) {
     184                                                callback();
     185                                        }
    184186
    185                                 if ( $('#imgedit-history-' + postid).val() && $('#imgedit-undone-' + postid).val() === '0' ) {
    186                                         $('input.imgedit-submit-btn', '#imgedit-panel-' + postid).removeAttr('disabled');
    187                                 } else {
    188                                         $('input.imgedit-submit-btn', '#imgedit-panel-' + postid).prop('disabled', true);
    189                                 }
     187                                        if ( $('#imgedit-history-' + postid).val() && $('#imgedit-undone-' + postid).val() === '0' ) {
     188                                                $('input.imgedit-submit-btn', '#imgedit-panel-' + postid).removeAttr('disabled');
     189                                        } else {
     190                                                $('input.imgedit-submit-btn', '#imgedit-panel-' + postid).prop('disabled', true);
     191                                        }
    190192
    191                                 t.toggleEditor(postid, 0);
    192                         })
    193                         .on('error', function() {
    194                                 $('#imgedit-crop-' + postid).empty().append('<div class="error"><p>' + imageEditL10n.error + '</p></div>');
    195                                 t.toggleEditor(postid, 0);
    196                         })
    197                         .attr('src', ajaxurl + '?' + $.param(data));
    198         },
     193                                        t.toggleEditor(postid, 0);
     194                                })
     195                                .on('error', function() {
     196                                        $('#imgedit-crop-' + postid).empty().append('<div class="error"><p>' + imageEditL10n.error + '</p></div>');
     197                                        t.toggleEditor(postid, 0);
     198                                })
     199                                .attr('src', ajaxurl + '?' + $.param(data));
     200                },
    199201
    200         action : function(postid, nonce, action) {
    201                 var t = this, data, w, h, fw, fh;
     202                action : function(postid, nonce, action) {
     203                        var t = this, data, w, h, fw, fh;
    202204
    203                 if ( t.notsaved(postid) ) {
    204                         return false;
    205                 }
     205                        if ( t.notsaved(postid) ) {
     206                                return false;
     207                        }
    206208
    207                 data = {
    208                         'action': 'image-editor',
    209                         '_ajax_nonce': nonce,
    210                         'postid': postid
    211                 };
     209                        data = {
     210                                'action': 'image-editor',
     211                                '_ajax_nonce': nonce,
     212                                'postid': postid
     213                        };
    212214
    213                 if ( 'scale' === action ) {
    214                         w = $('#imgedit-scale-width-' + postid),
    215                         h = $('#imgedit-scale-height-' + postid),
    216                         fw = t.intval(w.val()),
    217                         fh = t.intval(h.val());
     215                        if ( 'scale' === action ) {
     216                                w = $('#imgedit-scale-width-' + postid),
     217                                h = $('#imgedit-scale-height-' + postid),
     218                                fw = t.intval(w.val()),
     219                                fh = t.intval(h.val());
    218220
    219                         if ( fw < 1 ) {
    220                                 w.focus();
     221                                if ( fw < 1 ) {
     222                                        w.focus();
     223                                        return false;
     224                                } else if ( fh < 1 ) {
     225                                        h.focus();
     226                                        return false;
     227                                }
     228
     229                                if ( fw === t.hold.ow || fh === t.hold.oh ) {
     230                                        return false;
     231                                }
     232
     233                                data['do'] = 'scale';
     234                                data.fwidth = fw;
     235                                data.fheight = fh;
     236                        } else if ( 'restore' === action ) {
     237                                data['do'] = 'restore';
     238                        } else {
    221239                                return false;
    222                         } else if ( fh < 1 ) {
    223                                 h.focus();
    224                                 return false;
    225240                        }
    226241
    227                         if ( fw === t.hold.ow || fh === t.hold.oh ) {
     242                        t.toggleEditor(postid, 1);
     243                        $.post(ajaxurl, data, function(r) {
     244                                $('#image-editor-' + postid).empty().append(r);
     245                                t.toggleEditor(postid, 0);
     246                        });
     247                },
     248
     249                save : function(postid, nonce) {
     250                        var data, target = this.getTarget(postid), history = this.filterHistory(postid, 0);
     251
     252                        if ( '' === history ) {
    228253                                return false;
    229254                        }
    230255
    231                         data['do'] = 'scale';
    232                         data.fwidth = fw;
    233                         data.fheight = fh;
    234                 } else if ( 'restore' === action ) {
    235                         data['do'] = 'restore';
    236                 } else {
    237                         return false;
    238                 }
     256                        this.toggleEditor(postid, 1);
     257                        data = {
     258                                'action': 'image-editor',
     259                                '_ajax_nonce': nonce,
     260                                'postid': postid,
     261                                'history': history,
     262                                'target': target,
     263                                'context': $('#image-edit-context').length ? $('#image-edit-context').val() : null,
     264                                'do': 'save'
     265                        };
    239266
    240                 t.toggleEditor(postid, 1);
    241                 $.post(ajaxurl, data, function(r) {
    242                         $('#image-editor-' + postid).empty().append(r);
    243                         t.toggleEditor(postid, 0);
    244                 });
    245         },
     267                        $.post(ajaxurl, data, function(r) {
     268                                var ret = JSON.parse(r);
    246269
    247         save : function(postid, nonce) {
    248                 var data, target = this.getTarget(postid), history = this.filterHistory(postid, 0);
     270                                if ( ret.error ) {
     271                                        $('#imgedit-response-' + postid).html('<div class="error"><p>' + ret.error + '</p><div>');
     272                                        imageEdit.close(postid);
     273                                        return;
     274                                }
    249275
    250                 if ( '' === history ) {
    251                         return false;
    252                 }
     276                                if ( ret.fw && ret.fh ) {
     277                                        $('#media-dims-' + postid).html( ret.fw + ' &times; ' + ret.fh );
     278                                }
    253279
    254                 this.toggleEditor(postid, 1);
    255                 data = {
    256                         'action': 'image-editor',
    257                         '_ajax_nonce': nonce,
    258                         'postid': postid,
    259                         'history': history,
    260                         'target': target,
    261                         'context': $('#image-edit-context').length ? $('#image-edit-context').val() : null,
    262                         'do': 'save'
    263                 };
     280                                if ( ret.thumbnail ) {
     281                                        $('.thumbnail', '#thumbnail-head-' + postid).attr('src', ''+ret.thumbnail);
     282                                }
    264283
    265                 $.post(ajaxurl, data, function(r) {
    266                         var ret = JSON.parse(r);
     284                                if ( ret.msg ) {
     285                                        $('#imgedit-response-' + postid).html('<div class="updated"><p>' + ret.msg + '</p></div>');
     286                                }
    267287
    268                         if ( ret.error ) {
    269                                 $('#imgedit-response-' + postid).html('<div class="error"><p>' + ret.error + '</p><div>');
    270288                                imageEdit.close(postid);
    271                                 return;
    272                         }
     289                        });
     290                },
    273291
    274                         if ( ret.fw && ret.fh ) {
    275                                 $('#media-dims-' + postid).html( ret.fw + ' &times; ' + ret.fh );
    276                         }
     292                open : function(postid, nonce) {
     293                        var data,
     294                                elem = $('#image-editor-' + postid),
     295                                head = $('#media-head-' + postid),
     296                                btn = $('#imgedit-open-btn-' + postid),
     297                                spin = btn.siblings('.spinner'),
     298                                // Capture the arguments passed to open(). Used in dispatchEvent().
     299                                args = arguments;
    277300
    278                         if ( ret.thumbnail ) {
    279                                 $('.thumbnail', '#thumbnail-head-' + postid).attr('src', ''+ret.thumbnail);
    280                         }
     301                        btn.prop('disabled', true);
     302                        spin.show();
    281303
    282                         if ( ret.msg ) {
    283                                 $('#imgedit-response-' + postid).html('<div class="updated"><p>' + ret.msg + '</p></div>');
    284                         }
     304                        data = {
     305                                'action': 'image-editor',
     306                                '_ajax_nonce': nonce,
     307                                'postid': postid,
     308                                'do': 'open'
     309                        };
    285310
    286                         imageEdit.close(postid);
    287                 });
    288         },
     311                        elem.load(ajaxurl, data, function() {
     312                                elem.fadeIn('fast');
     313                                head.fadeOut('fast', function(){
     314                                        btn.removeAttr('disabled');
     315                                        spin.hide();
     316                                });
     317                                // Dispatch an event. Note that if you need to work with any of the editorGroup HTML, you should use the INIT_EVENT event instead.
     318                                imageEdit.dispatchEvent( imageEdit.events.OPEN_EVENT, this, { args: args } );
     319                        });
     320                },
    289321
    290         open : function(postid, nonce) {
    291                 var data, elem = $('#image-editor-' + postid), head = $('#media-head-' + postid),
    292                         btn = $('#imgedit-open-btn-' + postid), spin = btn.siblings('.spinner');
     322                imgLoaded : function(postid) {
     323                        var img = $('#image-preview-' + postid), parent = $('#imgedit-crop-' + postid);
    293324
    294                 btn.prop('disabled', true);
    295                 spin.show();
     325                        this.initCrop(postid, img, parent);
     326                        this.setCropSelection(postid, 0);
     327                        this.toggleEditor(postid, 0);
     328                },
    296329
    297                 data = {
    298                         'action': 'image-editor',
    299                         '_ajax_nonce': nonce,
    300                         'postid': postid,
    301                         'do': 'open'
    302                 };
     330                initCrop : function(postid, image, parent) {
     331                        var t = this, selW = $('#imgedit-sel-width-' + postid),
     332                                selH = $('#imgedit-sel-height-' + postid);
    303333
    304                 elem.load(ajaxurl, data, function() {
    305                         elem.fadeIn('fast');
    306                         head.fadeOut('fast', function(){
    307                                 btn.removeAttr('disabled');
    308                                 spin.hide();
    309                         });
    310                 });
    311         },
     334                        t.iasapi = $(image).imgAreaSelect({
     335                                parent: parent,
     336                                instance: true,
     337                                handles: true,
     338                                keys: true,
     339                                minWidth: 3,
     340                                minHeight: 3,
    312341
    313         imgLoaded : function(postid) {
    314                 var img = $('#image-preview-' + postid), parent = $('#imgedit-crop-' + postid);
     342                                onInit: function() {
     343                                        parent.children().mousedown(function(e){
     344                                                var ratio = false, sel, defRatio;
    315345
    316                 this.initCrop(postid, img, parent);
    317                 this.setCropSelection(postid, 0);
    318                 this.toggleEditor(postid, 0);
    319         },
     346                                                if ( e.shiftKey ) {
     347                                                        sel = t.iasapi.getSelection();
     348                                                        defRatio = t.getSelRatio(postid);
     349                                                        ratio = ( sel && sel.width && sel.height ) ? sel.width + ':' + sel.height : defRatio;
     350                                                }
    320351
    321         initCrop : function(postid, image, parent) {
    322                 var t = this, selW = $('#imgedit-sel-width-' + postid),
    323                         selH = $('#imgedit-sel-height-' + postid);
     352                                                t.iasapi.setOptions({
     353                                                        aspectRatio: ratio
     354                                                });
     355                                        });
     356                                },
    324357
    325                 t.iasapi = $(image).imgAreaSelect({
    326                         parent: parent,
    327                         instance: true,
    328                         handles: true,
    329                         keys: true,
    330                         minWidth: 3,
    331                         minHeight: 3,
     358                                onSelectStart: function() {
     359                                        imageEdit.setDisabled($('#imgedit-crop-sel-' + postid), 1);
     360                                },
    332361
    333                         onInit: function() {
    334                                 parent.children().mousedown(function(e){
    335                                         var ratio = false, sel, defRatio;
     362                                onSelectEnd: function(img, c) {
     363                                        imageEdit.setCropSelection(postid, c);
     364                                },
    336365
    337                                         if ( e.shiftKey ) {
    338                                                 sel = t.iasapi.getSelection();
    339                                                 defRatio = t.getSelRatio(postid);
    340                                                 ratio = ( sel && sel.width && sel.height ) ? sel.width + ':' + sel.height : defRatio;
    341                                         }
     366                                onSelectChange: function(img, c) {
     367                                        var sizer = imageEdit.hold.sizer;
     368                                        selW.val( imageEdit.round(c.width / sizer) );
     369                                        selH.val( imageEdit.round(c.height / sizer) );
     370                                }
     371                        });
     372                },
    342373
    343                                         t.iasapi.setOptions({
    344                                                 aspectRatio: ratio
    345                                         });
    346                                 });
    347                         },
     374                setCropSelection : function(postid, c) {
     375                        var sel, min = $('#imgedit-minthumb-' + postid).val() || '128:128',
     376                                sizer = this.hold.sizer;
     377                                min = min.split(':');
     378                                c = c || 0;
    348379
    349                         onSelectStart: function() {
    350                                 imageEdit.setDisabled($('#imgedit-crop-sel-' + postid), 1);
    351                         },
     380                        if ( !c || ( c.width < 3 && c.height < 3 ) ) {
     381                                this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 0);
     382                                this.setDisabled($('#imgedit-crop-sel-' + postid), 0);
     383                                $('#imgedit-sel-width-' + postid).val('');
     384                                $('#imgedit-sel-height-' + postid).val('');
     385                                $('#imgedit-selection-' + postid).val('');
     386                                return false;
     387                        }
    352388
    353                         onSelectEnd: function(img, c) {
    354                                 imageEdit.setCropSelection(postid, c);
    355                         },
     389                        if ( c.width < (min[0] * sizer) && c.height < (min[1] * sizer) ) {
     390                                this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 0);
     391                                $('#imgedit-selection-' + postid).val('');
     392                                return false;
     393                        }
    356394
    357                         onSelectChange: function(img, c) {
    358                                 var sizer = imageEdit.hold.sizer;
    359                                 selW.val( imageEdit.round(c.width / sizer) );
    360                                 selH.val( imageEdit.round(c.height / sizer) );
     395                        sel = { 'x': c.x1, 'y': c.y1, 'w': c.width, 'h': c.height };
     396                        this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 1);
     397                        $('#imgedit-selection-' + postid).val( JSON.stringify(sel) );
     398                },
     399
     400                close : function(postid, warn) {
     401                        warn = warn || false;
     402
     403                        if ( warn && this.notsaved(postid) ) {
     404                                return false;
    361405                        }
    362                 });
    363         },
    364406
    365         setCropSelection : function(postid, c) {
    366                 var sel, min = $('#imgedit-minthumb-' + postid).val() || '128:128',
    367                         sizer = this.hold.sizer;
    368                         min = min.split(':');
    369                         c = c || 0;
     407                        this.iasapi = {};
     408                        this.hold = {};
     409                        $('#image-editor-' + postid).fadeOut('fast', function() {
     410                                $('#media-head-' + postid).fadeIn('fast');
     411                                $(this).empty();
     412                        });
     413                },
    370414
    371                 if ( !c || ( c.width < 3 && c.height < 3 ) ) {
    372                         this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 0);
    373                         this.setDisabled($('#imgedit-crop-sel-' + postid), 0);
    374                         $('#imgedit-sel-width-' + postid).val('');
    375                         $('#imgedit-sel-height-' + postid).val('');
    376                         $('#imgedit-selection-' + postid).val('');
    377                         return false;
    378                 }
     415                notsaved : function(postid) {
     416                        var h = $('#imgedit-history-' + postid).val(),
     417                                history = ( h !== '' ) ? JSON.parse(h) : [],
     418                                pop = this.intval( $('#imgedit-undone-' + postid).val() );
    379419
    380                 if ( c.width < (min[0] * sizer) && c.height < (min[1] * sizer) ) {
    381                         this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 0);
    382                         $('#imgedit-selection-' + postid).val('');
     420                        if ( pop < history.length ) {
     421                                if ( confirm( $('#imgedit-leaving-' + postid).html() ) ) {
     422                                        return false;
     423                                }
     424                                return true;
     425                        }
    383426                        return false;
    384                 }
     427                },
    385428
    386                 sel = { 'x': c.x1, 'y': c.y1, 'w': c.width, 'h': c.height };
    387                 this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 1);
    388                 $('#imgedit-selection-' + postid).val( JSON.stringify(sel) );
    389         },
     429                addStep : function(op, postid, nonce) {
     430                        var t = this, elem = $('#imgedit-history-' + postid),
     431                        history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [],
     432                        undone = $('#imgedit-undone-' + postid),
     433                        pop = t.intval(undone.val());
    390434
    391         close : function(postid, warn) {
    392                 warn = warn || false;
     435                        while ( pop > 0 ) {
     436                                history.pop();
     437                                pop--;
     438                        }
     439                        undone.val(0); // reset
    393440
    394                 if ( warn && this.notsaved(postid) ) {
    395                         return false;
    396                 }
     441                        history.push(op);
     442                        elem.val( JSON.stringify(history) );
    397443
    398                 this.iasapi = {};
    399                 this.hold = {};
    400                 $('#image-editor-' + postid).fadeOut('fast', function() {
    401                         $('#media-head-' + postid).fadeIn('fast');
    402                         $(this).empty();
    403                 });
    404         },
     444                        t.refreshEditor(postid, nonce, function() {
     445                                t.setDisabled($('#image-undo-' + postid), true);
     446                                t.setDisabled($('#image-redo-' + postid), false);
     447                        });
     448                },
    405449
    406         notsaved : function(postid) {
    407                 var h = $('#imgedit-history-' + postid).val(),
    408                         history = ( h !== '' ) ? JSON.parse(h) : [],
    409                         pop = this.intval( $('#imgedit-undone-' + postid).val() );
     450                rotate : function(angle, postid, nonce, t) {
     451                        if ( $(t).hasClass('disabled') ) {
     452                                return false;
     453                        }
    410454
    411                 if ( pop < history.length ) {
    412                         if ( confirm( $('#imgedit-leaving-' + postid).html() ) ) {
     455                        this.addStep({ 'r': { 'r': angle, 'fw': this.hold.h, 'fh': this.hold.w }}, postid, nonce);
     456                },
     457
     458                flip : function (axis, postid, nonce, t) {
     459                        if ( $(t).hasClass('disabled') ) {
    413460                                return false;
    414461                        }
    415                         return true;
    416                 }
    417                 return false;
    418         },
    419462
    420         addStep : function(op, postid, nonce) {
    421                 var t = this, elem = $('#imgedit-history-' + postid),
    422                 history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [],
    423                 undone = $('#imgedit-undone-' + postid),
    424                 pop = t.intval(undone.val());
     463                        this.addStep({ 'f': { 'f': axis, 'fw': this.hold.w, 'fh': this.hold.h }}, postid, nonce);
     464                },
    425465
    426                 while ( pop > 0 ) {
    427                         history.pop();
    428                         pop--;
    429                 }
    430                 undone.val(0); // reset
     466                crop : function (postid, nonce, t) {
     467                        var sel = $('#imgedit-selection-' + postid).val(),
     468                                w = this.intval( $('#imgedit-sel-width-' + postid).val() ),
     469                                h = this.intval( $('#imgedit-sel-height-' + postid).val() );
    431470
    432                 history.push(op);
    433                 elem.val( JSON.stringify(history) );
     471                        if ( $(t).hasClass('disabled') || sel === '' ) {
     472                                return false;
     473                        }
    434474
    435                 t.refreshEditor(postid, nonce, function() {
    436                         t.setDisabled($('#image-undo-' + postid), true);
    437                         t.setDisabled($('#image-redo-' + postid), false);
    438                 });
    439         },
     475                        sel = JSON.parse(sel);
     476                        if ( sel.w > 0 && sel.h > 0 && w > 0 && h > 0 ) {
     477                                sel.fw = w;
     478                                sel.fh = h;
     479                                this.addStep({ 'c': sel }, postid, nonce);
     480                        }
     481                },
    440482
    441         rotate : function(angle, postid, nonce, t) {
    442                 if ( $(t).hasClass('disabled') ) {
    443                         return false;
    444                 }
     483                undo : function (postid, nonce) {
     484                        var t = this, button = $('#image-undo-' + postid), elem = $('#imgedit-undone-' + postid),
     485                                pop = t.intval( elem.val() ) + 1;
    445486
    446                 this.addStep({ 'r': { 'r': angle, 'fw': this.hold.h, 'fh': this.hold.w }}, postid, nonce);
    447         },
     487                        if ( button.hasClass('disabled') ) {
     488                                return;
     489                        }
    448490
    449         flip : function (axis, postid, nonce, t) {
    450                 if ( $(t).hasClass('disabled') ) {
    451                         return false;
    452                 }
     491                        elem.val(pop);
     492                        t.refreshEditor(postid, nonce, function() {
     493                                var elem = $('#imgedit-history-' + postid),
     494                                history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [];
    453495
    454                 this.addStep({ 'f': { 'f': axis, 'fw': this.hold.w, 'fh': this.hold.h }}, postid, nonce);
    455         },
     496                                t.setDisabled($('#image-redo-' + postid), true);
     497                                t.setDisabled(button, pop < history.length);
     498                        });
     499                },
    456500
    457         crop : function (postid, nonce, t) {
    458                 var sel = $('#imgedit-selection-' + postid).val(),
    459                         w = this.intval( $('#imgedit-sel-width-' + postid).val() ),
    460                         h = this.intval( $('#imgedit-sel-height-' + postid).val() );
     501                redo : function(postid, nonce) {
     502                        var t = this, button = $('#image-redo-' + postid), elem = $('#imgedit-undone-' + postid),
     503                                pop = t.intval( elem.val() ) - 1;
    461504
    462                 if ( $(t).hasClass('disabled') || sel === '' ) {
    463                         return false;
    464                 }
     505                        if ( button.hasClass('disabled') ) {
     506                                return;
     507                        }
    465508
    466                 sel = JSON.parse(sel);
    467                 if ( sel.w > 0 && sel.h > 0 && w > 0 && h > 0 ) {
    468                         sel.fw = w;
    469                         sel.fh = h;
    470                         this.addStep({ 'c': sel }, postid, nonce);
    471                 }
    472         },
     509                        elem.val(pop);
     510                        t.refreshEditor(postid, nonce, function() {
     511                                t.setDisabled($('#image-undo-' + postid), true);
     512                                t.setDisabled(button, pop > 0);
     513                        });
     514                },
    473515
    474         undo : function (postid, nonce) {
    475                 var t = this, button = $('#image-undo-' + postid), elem = $('#imgedit-undone-' + postid),
    476                         pop = t.intval( elem.val() ) + 1;
     516                setNumSelection : function(postid) {
     517                        var sel, elX = $('#imgedit-sel-width-' + postid), elY = $('#imgedit-sel-height-' + postid),
     518                                x = this.intval( elX.val() ), y = this.intval( elY.val() ),
     519                                img = $('#image-preview-' + postid), imgh = img.height(), imgw = img.width(),
     520                                sizer = this.hold.sizer, x1, y1, x2, y2, ias = this.iasapi;
    477521
    478                 if ( button.hasClass('disabled') ) {
    479                         return;
    480                 }
     522                        if ( x < 1 ) {
     523                                elX.val('');
     524                                return false;
     525                        }
    481526
    482                 elem.val(pop);
    483                 t.refreshEditor(postid, nonce, function() {
    484                         var elem = $('#imgedit-history-' + postid),
    485                         history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [];
     527                        if ( y < 1 ) {
     528                                elY.val('');
     529                                return false;
     530                        }
    486531
    487                         t.setDisabled($('#image-redo-' + postid), true);
    488                         t.setDisabled(button, pop < history.length);
    489                 });
    490         },
     532                        if ( x && y && ( sel = ias.getSelection() ) ) {
     533                                x2 = sel.x1 + Math.round( x * sizer );
     534                                y2 = sel.y1 + Math.round( y * sizer );
     535                                x1 = sel.x1;
     536                                y1 = sel.y1;
    491537
    492         redo : function(postid, nonce) {
    493                 var t = this, button = $('#image-redo-' + postid), elem = $('#imgedit-undone-' + postid),
    494                         pop = t.intval( elem.val() ) - 1;
     538                                if ( x2 > imgw ) {
     539                                        x1 = 0;
     540                                        x2 = imgw;
     541                                        elX.val( Math.round( x2 / sizer ) );
     542                                }
    495543
    496                 if ( button.hasClass('disabled') ) {
    497                         return;
    498                 }
     544                                if ( y2 > imgh ) {
     545                                        y1 = 0;
     546                                        y2 = imgh;
     547                                        elY.val( Math.round( y2 / sizer ) );
     548                                }
    499549
    500                 elem.val(pop);
    501                 t.refreshEditor(postid, nonce, function() {
    502                         t.setDisabled($('#image-undo-' + postid), true);
    503                         t.setDisabled(button, pop > 0);
    504                 });
    505         },
     550                                ias.setSelection( x1, y1, x2, y2 );
     551                                ias.update();
     552                                this.setCropSelection(postid, ias.getSelection());
     553                        }
     554                },
    506555
    507         setNumSelection : function(postid) {
    508                 var sel, elX = $('#imgedit-sel-width-' + postid), elY = $('#imgedit-sel-height-' + postid),
    509                         x = this.intval( elX.val() ), y = this.intval( elY.val() ),
    510                         img = $('#image-preview-' + postid), imgh = img.height(), imgw = img.width(),
    511                         sizer = this.hold.sizer, x1, y1, x2, y2, ias = this.iasapi;
     556                round : function(num) {
     557                        var s;
     558                        num = Math.round(num);
    512559
    513                 if ( x < 1 ) {
    514                         elX.val('');
    515                         return false;
    516                 }
     560                        if ( this.hold.sizer > 0.6 ) {
     561                                return num;
     562                        }
    517563
    518                 if ( y < 1 ) {
    519                         elY.val('');
    520                         return false;
    521                 }
     564                        s = num.toString().slice(-1);
    522565
    523                 if ( x && y && ( sel = ias.getSelection() ) ) {
    524                         x2 = sel.x1 + Math.round( x * sizer );
    525                         y2 = sel.y1 + Math.round( y * sizer );
    526                         x1 = sel.x1;
    527                         y1 = sel.y1;
    528 
    529                         if ( x2 > imgw ) {
    530                                 x1 = 0;
    531                                 x2 = imgw;
    532                                 elX.val( Math.round( x2 / sizer ) );
     566                        if ( '1' === s ) {
     567                                return num - 1;
     568                        } else if ( '9' === s ) {
     569                                return num + 1;
    533570                        }
    534571
    535                         if ( y2 > imgh ) {
    536                                 y1 = 0;
    537                                 y2 = imgh;
    538                                 elY.val( Math.round( y2 / sizer ) );
     572                        return num;
     573                },
     574
     575                setRatioSelection : function(postid, n, el) {
     576                        var sel, r, x = this.intval( $('#imgedit-crop-width-' + postid).val() ),
     577                                y = this.intval( $('#imgedit-crop-height-' + postid).val() ),
     578                                h = $('#image-preview-' + postid).height();
     579
     580                        if ( !this.intval( $(el).val() ) ) {
     581                                $(el).val('');
     582                                return;
    539583                        }
    540584
    541                         ias.setSelection( x1, y1, x2, y2 );
    542                         ias.update();
    543                         this.setCropSelection(postid, ias.getSelection());
    544                 }
    545         },
     585                        if ( x && y ) {
     586                                this.iasapi.setOptions({
     587                                        aspectRatio: x + ':' + y
     588                                });
    546589
    547         round : function(num) {
    548                 var s;
    549                 num = Math.round(num);
     590                                if ( sel = this.iasapi.getSelection(true) ) {
     591                                        r = Math.ceil( sel.y1 + ( ( sel.x2 - sel.x1 ) / ( x / y ) ) );
    550592
    551                 if ( this.hold.sizer > 0.6 ) {
    552                         return num;
    553                 }
     593                                        if ( r > h ) {
     594                                                r = h;
     595                                                if ( n ) {
     596                                                        $('#imgedit-crop-height-' + postid).val('');
     597                                                } else {
     598                                                        $('#imgedit-crop-width-' + postid).val('');
     599                                                }
     600                                        }
    554601
    555                 s = num.toString().slice(-1);
     602                                        this.iasapi.setSelection( sel.x1, sel.y1, sel.x2, r );
     603                                        this.iasapi.update();
     604                                }
     605                        }
     606                },
    556607
    557                 if ( '1' === s ) {
    558                         return num - 1;
    559                 } else if ( '9' === s ) {
    560                         return num + 1;
    561                 }
    562608
    563                 return num;
    564         },
    565609
    566         setRatioSelection : function(postid, n, el) {
    567                 var sel, r, x = this.intval( $('#imgedit-crop-width-' + postid).val() ),
    568                         y = this.intval( $('#imgedit-crop-height-' + postid).val() ),
    569                         h = $('#image-preview-' + postid).height();
     610                /**
     611                 * Register an observer against a specific event type (one of the EVENT constants defined in .events)
     612                 *
     613                 * The event handler (callback) accepts a single argument - eventData, which is an object with the following structure:
     614                 * eventData = {
     615                 *      event : EVENT_TYPE,
     616                 *      [other data] : ( determined by the call to dispatchEvent ),
     617                 *      [registrationData] : ( any additional data to be passed to the event handler, as determined by the agent that called addEventListener - the 'args' parameter )
     618                 * }
     619                 *
     620                 * @param {string} event A constant defined in zgExtendImageEdit.events. Determines the type of event you want to listen for
     621                 * @param {function} callback The function that will be called. `this` gets set to the event target (often `imageEdit` )
     622                 * @param {int} priority Not yet implemented. Allows you to specify a priority for the event
     623                 * @param {object} args Any number of additional arguments you would like passed to the event handler. These are available in  eventData.registrationData
     624                 * @returns {boolean} Success
     625                 */
     626                addEventListener : function( event, callback, priority, args ){
     627                        if ( typeof callback === 'function' ){
     628                                this.eventListeners[event] = this.eventListeners[event] || [];
     629                                this.eventListeners[event].push( [callback, args] );
     630                                return true;
     631                        } else {
     632                                console.log( "Error: callback is not a function. Event listener registration failed." );
     633                                return false;
     634                        }
     635                },
    570636
    571                 if ( !this.intval( $(el).val() ) ) {
    572                         $(el).val('');
    573                         return;
    574                 }
     637                /**
     638                 * Dispatches an event, executing any callbacks registered against that event using {@link addEventListener}
     639                 *
     640                 * @param {string} event The event type, as found under zgExtendImageEdit.events.
     641                 * @param {object} target The object that should become `this` in the context of the callback function. Defaults to `window` if not defined.
     642                 * @param {object} data Additional data related to the event.
     643                 * @returns {int} 0 indicates there was a problem dispatching one of the events (incorrect callback); -1 indicates that there were no listeners (not necessarily an error); 1 indicates success. There is generally no need to check this return value.
     644                 */
     645                dispatchEvent : function( event, target, data ){
     646                        var listeners,
     647                                dispatchSuccess = true,
     648                                target = target || window,
     649                                eventData = data || {};
    575650
    576                 if ( x && y ) {
    577                         this.iasapi.setOptions({
    578                                 aspectRatio: x + ':' + y
    579                         });
     651                        eventData.event = event;
    580652
    581                         if ( sel = this.iasapi.getSelection(true) ) {
    582                                 r = Math.ceil( sel.y1 + ( ( sel.x2 - sel.x1 ) / ( x / y ) ) );
    583 
    584                                 if ( r > h ) {
    585                                         r = h;
    586                                         if ( n ) {
    587                                                 $('#imgedit-crop-height-' + postid).val('');
     653                        if ( listeners = this.eventListeners[event] ){
     654                                for ( var i=0, l=listeners.length; i<l; ++i){
     655                                        if ( 'function' === typeof listeners[i][0] ){
     656                                                // If the call to addEventListener() passed some arguments, collect these under `registrationData`.
     657                                                if ( listeners[i][1] ){
     658                                                        eventData.registrationData = listeners[i][1];
     659                                                }
     660                                                listeners[i][0].call( target, eventData );
    588661                                        } else {
    589                                                 $('#imgedit-crop-width-' + postid).val('');
     662                                                dispatchSuccess = false;
    590663                                        }
    591664                                }
    592665
    593                                 this.iasapi.setSelection( sel.x1, sel.y1, sel.x2, r );
    594                                 this.iasapi.update();
     666                                if ( dispatchSuccess ) return 1;
     667                                return 0;
    595668                        }
     669                        return -1;
     670                },
     671
     672                /**
     673                 *
     674                 * @type string Event type constants.
     675                 */
     676                events : {
     677                        INIT_EVENT : "zg-extend-image-edit-init-event",
     678                        OPEN_EVENT : "zg-extend-image-edit-open-event"
    596679                }
    597         }
    598 };
    599 })(jQuery);
     680        };
     681})( jQuery );