Imagify Image Optimizer - Version 1.8

Version Description

  • 2018/06/19 =
  • New: you can now optimize pdf files.
  • Improvement: custom folders, you can now optimize files located in the uploads folder.
  • Improvement: support for thumbnails dynamically generated by NextGen Gallery plugin.
  • Bug Fix: revamped support for WP Retina 2x plugin.
Download this release

Release Info

Developer wp_media
Plugin Icon 128x128 Imagify Image Optimizer
Version 1.8
Comparing to
See all releases

Code changes from version 1.7.1.3 to 1.8

Files changed (107) hide show
  1. assets/css/files-list.css +0 -0
  2. assets/css/files-list.min.css +0 -0
  3. assets/images/big-blue-check.png +0 -0
  4. assets/images/icon-alert.svg +0 -0
  5. assets/images/icon-arrow-choice.png +0 -0
  6. assets/images/icon-arrow-choice.svg +0 -0
  7. assets/images/icon-doc-image.svg +0 -0
  8. assets/images/icon-external.svg +0 -0
  9. assets/images/icon-level.svg +0 -0
  10. assets/images/icon-load.svg +0 -0
  11. assets/images/icon-lock.png +0 -0
  12. assets/images/icon-lock.svg +0 -0
  13. assets/images/icon-pack.png +0 -0
  14. assets/images/icon-pack.svg +0 -0
  15. assets/images/icon-time.svg +0 -0
  16. assets/images/imagify-logo.png +0 -0
  17. assets/images/imagify-menu-bar.jpg +0 -0
  18. assets/images/lazyload.png +0 -0
  19. assets/images/logo-wprocket.png +0 -0
  20. assets/images/logo-wprocket.svg +0 -0
  21. assets/images/logo-wprocket@2x.png +0 -0
  22. assets/images/pic-ericwaltr.jpg +0 -0
  23. assets/images/pic-srhdesign.jpg +0 -0
  24. assets/images/spinner.gif +0 -0
  25. assets/images/upload-image.png +0 -0
  26. assets/js/chart.js +0 -0
  27. assets/js/chart.min.js +0 -0
  28. assets/js/files-list.js +0 -0
  29. assets/js/files-list.min.js +0 -0
  30. assets/js/formdata-polyfill.js +390 -0
  31. assets/js/formdata-polyfill.min.js +16 -0
  32. assets/js/imagify-gulp.js +0 -0
  33. assets/js/imagify-gulp.min.js +0 -0
  34. assets/js/imagify-wp-retina-2x.js +87 -0
  35. assets/js/imagify-wp-retina-2x.min.js +1 -0
  36. assets/js/options.js +1 -1
  37. assets/js/options.min.js +1 -1
  38. assets/js/weakmap-polyfill.js +144 -0
  39. assets/js/weakmap-polyfill.min.js +7 -0
  40. imagify.php +2 -2
  41. inc/3rd-party/3rd-party.php +1 -1
  42. inc/3rd-party/amazon-s3-and-cloudfront/inc/classes/class-imagify-as3cf-attachment.php +46 -24
  43. inc/3rd-party/amazon-s3-and-cloudfront/inc/classes/class-imagify-as3cf.php +0 -2
  44. inc/3rd-party/hosting/siteground.php +0 -0
  45. inc/3rd-party/hosting/wpengine.php +0 -0
  46. inc/3rd-party/nextgen-gallery/inc/admin/ajax.php +0 -0
  47. inc/3rd-party/nextgen-gallery/inc/admin/bulk.php +0 -0
  48. inc/3rd-party/nextgen-gallery/inc/admin/enqueue.php +0 -0
  49. inc/3rd-party/nextgen-gallery/inc/admin/gallery.php +0 -0
  50. inc/3rd-party/nextgen-gallery/inc/admin/heartbeat.php +0 -0
  51. inc/3rd-party/nextgen-gallery/inc/admin/menu.php +0 -0
  52. inc/3rd-party/nextgen-gallery/inc/classes/class-imagify-ngg-attachment.php +300 -121
  53. inc/3rd-party/nextgen-gallery/inc/classes/class-imagify-ngg-db.php +0 -0
  54. inc/3rd-party/nextgen-gallery/inc/classes/class-imagify-ngg-dynamic-thumbnails-background-process.php +162 -0
  55. inc/3rd-party/nextgen-gallery/inc/common/attachments.php +58 -1
  56. inc/3rd-party/nextgen-gallery/inc/functions/admin-stats.php +0 -0
  57. inc/3rd-party/nextgen-gallery/inc/functions/attachments.php +0 -0
  58. inc/3rd-party/nextgen-gallery/inc/functions/common.php +0 -0
  59. inc/3rd-party/nextgen-gallery/nextgen-gallery.php +1 -0
  60. inc/3rd-party/regenerate-thumbnails/inc/classes/class-imagify-regenerate-thumbnails.php +6 -2
  61. inc/3rd-party/wp-retina-2x.php +0 -87
  62. inc/3rd-party/wp-retina-2x/inc/classes/class-imagify-wp-retina-2x-core.php +1684 -0
  63. inc/3rd-party/wp-retina-2x/inc/classes/class-imagify-wp-retina-2x.php +680 -0
  64. inc/3rd-party/wp-retina-2x/wp-retina-2x.php +8 -0
  65. inc/3rd-party/wp-rocket.php +0 -0
  66. inc/admin/custom-folders.php +0 -0
  67. inc/admin/media.php +1 -1
  68. inc/admin/upload.php +4 -4
  69. inc/classes/class-imagify-abstract-attachment.php +134 -31
  70. inc/classes/class-imagify-abstract-cron.php +0 -0
  71. inc/classes/class-imagify-abstract-db.php +0 -0
  72. inc/classes/class-imagify-abstract-options.php +0 -0
  73. inc/classes/class-imagify-admin-ajax-post.php +14 -7
  74. inc/classes/class-imagify-attachment.php +77 -48
  75. inc/classes/class-imagify-cron-library-size.php +0 -0
  76. inc/classes/class-imagify-cron-rating.php +0 -0
  77. inc/classes/class-imagify-cron-sync-files.php +0 -0
  78. inc/classes/class-imagify-custom-folders.php +3 -3
  79. inc/classes/class-imagify-data.php +0 -0
  80. inc/classes/class-imagify-db.php +0 -0
  81. inc/classes/class-imagify-file-attachment.php +5 -5
  82. inc/classes/class-imagify-files-db.php +0 -0
  83. inc/classes/class-imagify-files-iterator.php +7 -2
  84. inc/classes/class-imagify-files-list-table.php +28 -20
  85. inc/classes/class-imagify-files-recursive-iterator.php +5 -1
  86. inc/classes/class-imagify-files-scan.php +232 -74
  87. inc/classes/class-imagify-files-stats.php +0 -0
  88. inc/classes/class-imagify-filesystem.php +244 -20
  89. inc/classes/class-imagify-folders-db.php +0 -0
  90. inc/classes/class-imagify-options.php +0 -0
  91. inc/classes/class-imagify-requirements.php +0 -0
  92. inc/classes/class-imagify-settings.php +0 -0
  93. inc/classes/class-imagify-views.php +3 -3
  94. inc/classes/class-imagify.php +0 -0
  95. inc/classes/class-wp-async-request.php +163 -0
  96. inc/classes/class-wp-background-process.php +506 -0
  97. inc/functions/admin-stats.php +12 -8
  98. inc/functions/admin-ui.php +18 -10
  99. inc/functions/api.php +3 -3
  100. inc/functions/attachments.php +19 -8
  101. inc/functions/common.php +14 -9
  102. inc/functions/deprecated.php +78 -0
  103. inc/functions/i18n.php +16 -12
  104. inc/functions/process.php +15 -15
  105. readme.txt +8 -2
  106. uninstall.php +6 -5
  107. views/part-settings-custom-folders.php +14 -0
assets/css/files-list.css CHANGED
File without changes
assets/css/files-list.min.css CHANGED
File without changes
assets/images/big-blue-check.png CHANGED
File without changes
assets/images/icon-alert.svg CHANGED
File without changes
assets/images/icon-arrow-choice.png CHANGED
File without changes
assets/images/icon-arrow-choice.svg CHANGED
File without changes
assets/images/icon-doc-image.svg CHANGED
File without changes
assets/images/icon-external.svg CHANGED
File without changes
assets/images/icon-level.svg CHANGED
File without changes
assets/images/icon-load.svg CHANGED
File without changes
assets/images/icon-lock.png CHANGED
File without changes
assets/images/icon-lock.svg CHANGED
File without changes
assets/images/icon-pack.png CHANGED
File without changes
assets/images/icon-pack.svg CHANGED
File without changes
assets/images/icon-time.svg CHANGED
File without changes
assets/images/imagify-logo.png CHANGED
File without changes
assets/images/imagify-menu-bar.jpg CHANGED
File without changes
assets/images/lazyload.png CHANGED
File without changes
assets/images/logo-wprocket.png CHANGED
File without changes
assets/images/logo-wprocket.svg CHANGED
File without changes
assets/images/logo-wprocket@2x.png CHANGED
File without changes
assets/images/pic-ericwaltr.jpg CHANGED
File without changes
assets/images/pic-srhdesign.jpg CHANGED
File without changes
assets/images/spinner.gif CHANGED
File without changes
assets/images/upload-image.png CHANGED
File without changes
assets/js/chart.js CHANGED
File without changes
assets/js/chart.min.js CHANGED
File without changes
assets/js/files-list.js CHANGED
File without changes
assets/js/files-list.min.js CHANGED
File without changes
assets/js/formdata-polyfill.js ADDED
@@ -0,0 +1,390 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ if (typeof FormData === 'undefined' || !FormData.prototype.keys) {
2
+ const global = typeof window === 'object'
3
+ ? window : typeof self === 'object'
4
+ ? self : this
5
+
6
+ // keep a reference to native implementation
7
+ const _FormData = global.FormData
8
+
9
+ // To be monkey patched
10
+ const _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send
11
+ const _fetch = global.Request && global.fetch
12
+
13
+ // Unable to patch Request constructor correctly
14
+ // const _Request = global.Request
15
+ // only way is to use ES6 class extend
16
+ // https://github.com/babel/babel/issues/1966
17
+
18
+ const stringTag = global.Symbol && Symbol.toStringTag
19
+ const map = new WeakMap
20
+ const wm = o => map.get(o)
21
+ const arrayFrom = Array.from || (obj => [].slice.call(obj))
22
+
23
+ // Add missing stringTags to blob and files
24
+ if (stringTag) {
25
+ if (!Blob.prototype[stringTag]) {
26
+ Blob.prototype[stringTag] = 'Blob'
27
+ }
28
+
29
+ if ('File' in global && !File.prototype[stringTag]) {
30
+ File.prototype[stringTag] = 'File'
31
+ }
32
+ }
33
+
34
+ // Fix so you can construct your own File
35
+ try {
36
+ new File([], '')
37
+ } catch (a) {
38
+ global.File = function(b, d, c) {
39
+ const blob = new Blob(b, c)
40
+ const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date
41
+
42
+ Object.defineProperties(blob, {
43
+ name: {
44
+ value: d
45
+ },
46
+ lastModifiedDate: {
47
+ value: t
48
+ },
49
+ lastModified: {
50
+ value: +t
51
+ },
52
+ toString: {
53
+ value() {
54
+ return '[object File]'
55
+ }
56
+ }
57
+ })
58
+
59
+ if (stringTag) {
60
+ Object.defineProperty(blob, stringTag, {
61
+ value: 'File'
62
+ })
63
+ }
64
+
65
+ return blob
66
+ }
67
+ }
68
+
69
+ function normalizeValue([value, filename]) {
70
+ if (value instanceof Blob)
71
+ // Should always returns a new File instance
72
+ // console.assert(fd.get(x) !== fd.get(x))
73
+ value = new File([value], filename, {
74
+ type: value.type,
75
+ lastModified: value.lastModified
76
+ })
77
+
78
+ return value
79
+ }
80
+
81
+ function stringify(name) {
82
+ if (!arguments.length)
83
+ throw new TypeError('1 argument required, but only 0 present.')
84
+
85
+ return [name + '']
86
+ }
87
+
88
+ function normalizeArgs(name, value, filename) {
89
+ if (arguments.length < 2)
90
+ throw new TypeError(
91
+ `2 arguments required, but only ${arguments.length} present.`
92
+ )
93
+
94
+ return value instanceof Blob
95
+ // normalize name and filename if adding an attachment
96
+ ? [name + '', value, filename !== undefined
97
+ ? filename + '' // Cast filename to string if 3th arg isn't undefined
98
+ : typeof value.name === 'string' // if name prop exist
99
+ ? value.name // Use File.name
100
+ : 'Blob'] // otherwise fallback to Blob
101
+
102
+ // If no attachment, just cast the args to strings
103
+ : [name + '', value + '']
104
+ }
105
+
106
+ /**
107
+ * @implements {Iterable}
108
+ */
109
+ class FormDataPolyfill {
110
+
111
+ /**
112
+ * FormData class
113
+ *
114
+ * @param {HTMLElement=} form
115
+ */
116
+ constructor(form) {
117
+ map.set(this, Object.create(null))
118
+
119
+ if (!form)
120
+ return this
121
+
122
+ for (let elm of arrayFrom(form.elements)) {
123
+ if (!elm.name || elm.disabled) continue
124
+
125
+ if (elm.type === 'file')
126
+ for (let file of elm.files)
127
+ this.append(elm.name, file)
128
+ else if (elm.type === 'select-multiple' || elm.type === 'select-one')
129
+ for (let opt of arrayFrom(elm.options))
130
+ opt.selected && this.append(elm.name, opt.value)
131
+ else if (elm.type === 'checkbox' || elm.type === 'radio') {
132
+ if (elm.checked) this.append(elm.name, elm.value)
133
+ } else
134
+ this.append(elm.name, elm.value)
135
+ }
136
+ }
137
+
138
+
139
+ /**
140
+ * Append a field
141
+ *
142
+ * @param {String} name field name
143
+ * @param {String|Blob|File} value string / blob / file
144
+ * @param {String=} filename filename to use with blob
145
+ * @return {Undefined}
146
+ */
147
+ append(name, value, filename) {
148
+ const map = wm(this)
149
+
150
+ if (!map[name])
151
+ map[name] = []
152
+
153
+ map[name].push([value, filename])
154
+ }
155
+
156
+
157
+ /**
158
+ * Delete all fields values given name
159
+ *
160
+ * @param {String} name Field name
161
+ * @return {Undefined}
162
+ */
163
+ delete(name) {
164
+ delete wm(this)[name]
165
+ }
166
+
167
+
168
+ /**
169
+ * Iterate over all fields as [name, value]
170
+ *
171
+ * @return {Iterator}
172
+ */
173
+ *entries() {
174
+ const map = wm(this)
175
+
176
+ for (let name in map)
177
+ for (let value of map[name])
178
+ yield [name, normalizeValue(value)]
179
+ }
180
+
181
+ /**
182
+ * Iterate over all fields
183
+ *
184
+ * @param {Function} callback Executed for each item with parameters (value, name, thisArg)
185
+ * @param {Object=} thisArg `this` context for callback function
186
+ * @return {Undefined}
187
+ */
188
+ forEach(callback, thisArg) {
189
+ for (let [name, value] of this)
190
+ callback.call(thisArg, value, name, this)
191
+ }
192
+
193
+
194
+ /**
195
+ * Return first field value given name
196
+ * or null if non existen
197
+ *
198
+ * @param {String} name Field name
199
+ * @return {String|File|null} value Fields value
200
+ */
201
+ get(name) {
202
+ const map = wm(this)
203
+ return map[name] ? normalizeValue(map[name][0]) : null
204
+ }
205
+
206
+
207
+ /**
208
+ * Return all fields values given name
209
+ *
210
+ * @param {String} name Fields name
211
+ * @return {Array} [{String|File}]
212
+ */
213
+ getAll(name) {
214
+ return (wm(this)[name] || []).map(normalizeValue)
215
+ }
216
+
217
+
218
+ /**
219
+ * Check for field name existence
220
+ *
221
+ * @param {String} name Field name
222
+ * @return {boolean}
223
+ */
224
+ has(name) {
225
+ return name in wm(this)
226
+ }
227
+
228
+
229
+ /**
230
+ * Iterate over all fields name
231
+ *
232
+ * @return {Iterator}
233
+ */
234
+ *keys() {
235
+ for (let [name] of this)
236
+ yield name
237
+ }
238
+
239
+
240
+ /**
241
+ * Overwrite all values given name
242
+ *
243
+ * @param {String} name Filed name
244
+ * @param {String} value Field value
245
+ * @param {String=} filename Filename (optional)
246
+ * @return {Undefined}
247
+ */
248
+ set(name, value, filename) {
249
+ wm(this)[name] = [[value, filename]]
250
+ }
251
+
252
+
253
+ /**
254
+ * Iterate over all fields
255
+ *
256
+ * @return {Iterator}
257
+ */
258
+ *values() {
259
+ for (let [name, value] of this)
260
+ yield value
261
+ }
262
+
263
+
264
+ /**
265
+ * Return a native (perhaps degraded) FormData with only a `append` method
266
+ * Can throw if it's not supported
267
+ *
268
+ * @return {FormData}
269
+ */
270
+ ['_asNative']() {
271
+ const fd = new _FormData
272
+
273
+ for (let [name, value] of this)
274
+ fd.append(name, value)
275
+
276
+ return fd
277
+ }
278
+
279
+
280
+ /**
281
+ * [_blob description]
282
+ *
283
+ * @return {Blob} [description]
284
+ */
285
+ ['_blob']() {
286
+ const boundary = '----formdata-polyfill-' + Math.random()
287
+ const chunks = []
288
+
289
+ for (let [name, value] of this) {
290
+ chunks.push(`--${boundary}\r\n`)
291
+
292
+ if (value instanceof Blob) {
293
+ chunks.push(
294
+ `Content-Disposition: form-data; name="${name}"; filename="${value.name}"\r\n`,
295
+ `Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`,
296
+ value,
297
+ '\r\n'
298
+ )
299
+ } else {
300
+ chunks.push(
301
+ `Content-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`
302
+ )
303
+ }
304
+ }
305
+
306
+ chunks.push(`--${boundary}--`)
307
+
308
+ return new Blob(chunks, {type: 'multipart/form-data; boundary=' + boundary})
309
+ }
310
+
311
+
312
+ /**
313
+ * The class itself is iterable
314
+ * alias for formdata.entries()
315
+ *
316
+ * @return {Iterator}
317
+ */
318
+ [Symbol.iterator]() {
319
+ return this.entries()
320
+ }
321
+
322
+
323
+ /**
324
+ * Create the default string description.
325
+ *
326
+ * @return {String} [object FormData]
327
+ */
328
+ toString() {
329
+ return '[object FormData]'
330
+ }
331
+ }
332
+
333
+
334
+ if (stringTag) {
335
+ /**
336
+ * Create the default string description.
337
+ * It is accessed internally by the Object.prototype.toString().
338
+ *
339
+ * @return {String} FormData
340
+ */
341
+ FormDataPolyfill.prototype[stringTag] = 'FormData'
342
+ }
343
+
344
+ const decorations = [
345
+ ['append', normalizeArgs],
346
+ ['delete', stringify],
347
+ ['get', stringify],
348
+ ['getAll', stringify],
349
+ ['has', stringify],
350
+ ['set', normalizeArgs]
351
+ ]
352
+
353
+ decorations.forEach(arr => {
354
+ const orig = FormDataPolyfill.prototype[arr[0]]
355
+ FormDataPolyfill.prototype[arr[0]] = function() {
356
+ return orig.apply(this, arr[1].apply(this, arrayFrom(arguments)))
357
+ }
358
+ })
359
+
360
+ // Patch xhr's send method to call _blob transparently
361
+ if (_send) {
362
+ XMLHttpRequest.prototype.send = function(data) {
363
+ // I would check if Content-Type isn't already set
364
+ // But xhr lacks getRequestHeaders functionallity
365
+ // https://github.com/jimmywarting/FormData/issues/44
366
+ if (data instanceof FormDataPolyfill) {
367
+ const blob = data['_blob']()
368
+ this.setRequestHeader('Content-Type', blob.type)
369
+ _send.call(this, blob)
370
+ } else {
371
+ _send.call(this, data)
372
+ }
373
+ }
374
+ }
375
+
376
+ // Patch fetch's function to call _blob transparently
377
+ if (_fetch) {
378
+ const _fetch = global.fetch
379
+
380
+ global.fetch = function(input, init) {
381
+ if (init && init.body && init.body instanceof FormDataPolyfill) {
382
+ init.body = init.body['_blob']()
383
+ }
384
+
385
+ return _fetch(input, init)
386
+ }
387
+ }
388
+
389
+ global['FormData'] = FormDataPolyfill
390
+ }
assets/js/formdata-polyfill.min.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ;(function(){var k,l="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,e){a!=Array.prototype&&a!=Object.prototype&&(a[b]=e.value)},m="undefined"!=typeof window&&window===this?this:"undefined"!=typeof global&&null!=global?global:this;function n(){n=function(){};m.Symbol||(m.Symbol=p)}var p=function(){var a=0;return function(b){return"jscomp_symbol_"+(b||"")+a++}}();
2
+ function r(){n();var a=m.Symbol.iterator;a||(a=m.Symbol.iterator=m.Symbol("iterator"));"function"!=typeof Array.prototype[a]&&l(Array.prototype,a,{configurable:!0,writable:!0,value:function(){return u(this)}});r=function(){}}function u(a){var b=0;return v(function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}})}function v(a){r();a={next:a};a[m.Symbol.iterator]=function(){return this};return a}function w(a){r();n();r();var b=a[Symbol.iterator];return b?b.call(a):u(a)}
3
+ function x(){this.g=!1;this.c=null;this.m=void 0;this.b=1;this.l=this.o=0;this.f=null}function z(a){if(a.g)throw new TypeError("Generator is already running");a.g=!0}x.prototype.h=function(a){this.m=a};x.prototype.i=function(a){this.f={s:a,u:!0};this.b=this.o||this.l};x.prototype["return"]=function(a){this.f={"return":a};this.b=this.l};function A(a,b,e){a.b=e;return{value:b}}function B(a){this.v=a;this.j=[];for(var b in a)this.j.push(b);this.j.reverse()}function C(a){this.a=new x;this.w=a}
4
+ C.prototype.h=function(a){z(this.a);if(this.a.c)return D(this,this.a.c.next,a,this.a.h);this.a.h(a);return E(this)};function F(a,b){z(a.a);var e=a.a.c;if(e)return D(a,"return"in e?e["return"]:function(a){return{value:a,done:!0}},b,a.a["return"]);a.a["return"](b);return E(a)}C.prototype.i=function(a){z(this.a);if(this.a.c)return D(this,this.a.c["throw"],a,this.a.h);this.a.i(a);return E(this)};
5
+ function D(a,b,e,c){try{var d=b.call(a.a.c,e);if(!(d instanceof Object))throw new TypeError("Iterator result "+d+" is not an object");if(!d.done)return a.a.g=!1,d;var f=d.value}catch(g){return a.a.c=null,a.a.i(g),E(a)}a.a.c=null;c.call(a.a,f);return E(a)}function E(a){for(;a.a.b;)try{var b=a.w(a.a);if(b)return a.a.g=!1,{value:b.value,done:!1}}catch(e){a.a.m=void 0,a.a.i(e)}a.a.g=!1;if(a.a.f){b=a.a.f;a.a.f=null;if(b.u)throw b.s;return{value:b["return"],done:!0}}return{value:void 0,done:!0}}
6
+ function G(a){this.next=function(b){return a.h(b)};this["throw"]=function(b){return a.i(b)};this["return"]=function(b){return F(a,b)};r();this[Symbol.iterator]=function(){return this}}function H(a,b){G.prototype=a.prototype;return new G(new C(b))}
7
+ if("undefined"===typeof FormData||!FormData.prototype.keys){var I=function(a,b,e){if(2>arguments.length)throw new TypeError("2 arguments required, but only "+arguments.length+" present.");return b instanceof Blob?[a+"",b,void 0!==e?e+"":"string"===typeof b.name?b.name:"Blob"]:[a+"",b+""]},J=function(a){if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");return[a+""]},K=function(a){var b=w(a);a=b.next().value;b=b.next().value;a instanceof Blob&&(a=new File([a],b,{type:a.type,
8
+ lastModified:a.lastModified}));return a},L="object"===typeof window?window:"object"===typeof self?self:this,M=L.FormData,N=L.XMLHttpRequest&&L.XMLHttpRequest.prototype.send,O=L.Request&&L.fetch;n();var P=L.Symbol&&Symbol.toStringTag,Q=new WeakMap,R=Array.from||function(a){return[].slice.call(a)};P&&(Blob.prototype[P]||(Blob.prototype[P]="Blob"),"File"in L&&!File.prototype[P]&&(File.prototype[P]="File"));try{new File([],"")}catch(a){L.File=function(b,e,c){b=new Blob(b,c);c=c&&void 0!==c.lastModified?
9
+ new Date(c.lastModified):new Date;Object.defineProperties(b,{name:{value:e},lastModifiedDate:{value:c},lastModified:{value:+c},toString:{value:function(){return"[object File]"}}});P&&Object.defineProperty(b,P,{value:"File"});return b}}var S=function(a){Q.set(this,Object.create(null));if(!a)return this;a=w(R(a.elements));for(var b=a.next();!b.done;b=a.next())if(b=b.value,b.name&&!b.disabled)if("file"===b.type)for(var e=w(b.files),c=e.next();!c.done;c=e.next())this.append(b.name,c.value);else if("select-multiple"===
10
+ b.type||"select-one"===b.type)for(e=w(R(b.options)),c=e.next();!c.done;c=e.next())c=c.value,c.selected&&this.append(b.name,c.value);else"checkbox"===b.type||"radio"===b.type?b.checked&&this.append(b.name,b.value):this.append(b.name,b.value)};k=S.prototype;k.append=function(a,b,e){var c=Q.get(this);c[a]||(c[a]=[]);c[a].push([b,e])};k["delete"]=function(a){delete Q.get(this)[a]};k.entries=function b(){var e=this,c,d,f,g,h,q;return H(b,function(b){switch(b.b){case 1:c=Q.get(e),f=new B(c);case 2:var t;
11
+ a:{for(t=f;0<t.j.length;){var y=t.j.pop();if(y in t.v){t=y;break a}}t=null}if(null==(d=t)){b.b=0;break}g=w(c[d]);h=g.next();case 5:if(h.done){b.b=2;break}q=h.value;return A(b,[d,K(q)],6);case 6:h=g.next(),b.b=5}})};k.forEach=function(b,e){for(var c=w(this),d=c.next();!d.done;d=c.next()){var f=w(d.value);d=f.next().value;f=f.next().value;b.call(e,f,d,this)}};k.get=function(b){var e=Q.get(this);return e[b]?K(e[b][0]):null};k.getAll=function(b){return(Q.get(this)[b]||[]).map(K)};k.has=function(b){return b in
12
+ Q.get(this)};k.keys=function e(){var c=this,d,f,g,h,q;return H(e,function(e){switch(e.b){case 1:d=w(c),f=d.next();case 2:if(f.done){e.b=0;break}g=f.value;h=w(g);q=h.next().value;return A(e,q,3);case 3:f=d.next(),e.b=2}})};k.set=function(e,c,d){Q.get(this)[e]=[[c,d]]};k.values=function c(){var d=this,f,g,h,q,y;return H(c,function(c){switch(c.b){case 1:f=w(d),g=f.next();case 2:if(g.done){c.b=0;break}h=g.value;q=w(h);q.next();y=q.next().value;return A(c,y,3);case 3:g=f.next(),c.b=2}})};S.prototype._asNative=
13
+ function(){for(var c=new M,d=w(this),f=d.next();!f.done;f=d.next()){var g=w(f.value);f=g.next().value;g=g.next().value;c.append(f,g)}return c};S.prototype._blob=function(){for(var c="----formdata-polyfill-"+Math.random(),d=[],f=w(this),g=f.next();!g.done;g=f.next()){var h=w(g.value);g=h.next().value;h=h.next().value;d.push("--"+c+"\r\n");h instanceof Blob?d.push('Content-Disposition: form-data; name="'+g+'"; filename="'+h.name+'"\r\n',"Content-Type: "+(h.type||"application/octet-stream")+"\r\n\r\n",
14
+ h,"\r\n"):d.push('Content-Disposition: form-data; name="'+g+'"\r\n\r\n'+h+"\r\n")}d.push("--"+c+"--");return new Blob(d,{type:"multipart/form-data; boundary="+c})};n();r();S.prototype[Symbol.iterator]=function(){return this.entries()};S.prototype.toString=function(){return"[object FormData]"};P&&(S.prototype[P]="FormData");[["append",I],["delete",J],["get",J],["getAll",J],["has",J],["set",I]].forEach(function(c){var d=S.prototype[c[0]];S.prototype[c[0]]=function(){return d.apply(this,c[1].apply(this,
15
+ R(arguments)))}});N&&(XMLHttpRequest.prototype.send=function(c){c instanceof S?(c=c._blob(),this.setRequestHeader("Content-Type",c.type),N.call(this,c)):N.call(this,c)});if(O){var T=L.fetch;L.fetch=function(c,d){d&&d.body&&d.body instanceof S&&(d.body=d.body._blob());return T(c,d)}}L.FormData=S};
16
+ })();
assets/js/imagify-gulp.js CHANGED
File without changes
assets/js/imagify-gulp.min.js CHANGED
File without changes
assets/js/imagify-wp-retina-2x.js ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ window.imagify = window.imagify || {};
2
+
3
+ jQuery.extend( window.imagify, {
4
+ retina2x: {
5
+ hookAjax: function() {
6
+ jQuery( document ).ajaxSend( this.addData ).ajaxSuccess( this.updateInfo );
7
+ },
8
+ addData: function( e, jqXHR, settings ) {
9
+ var isString, isFormData, actions, action, id;
10
+
11
+ if ( ! settings.data ) {
12
+ return;
13
+ }
14
+
15
+ isString = typeof settings.data === 'string';
16
+ isFormData = settings.data instanceof FormData;
17
+ actions = window.imagifyRetina2x;
18
+
19
+ if ( isString ) {
20
+ action = settings.data.match( /(?:^|&)action=([^&]+)/ );
21
+ action = action ? action[1] : false;
22
+ id = settings.data.match( /(?:^|&)attachmentId=([^&]+)/ );
23
+ id = id ? parseInt( id[1], 10 ) : 0;
24
+ } else if ( isFormData ) {
25
+ action = settings.data.get( 'action' );
26
+ id = settings.data.get( 'attachmentId' );
27
+ } else {
28
+ action = settings.data.action;
29
+ id = settings.data.id;
30
+ }
31
+
32
+ if ( ! action || ! actions[ action ] ) {
33
+ return;
34
+ }
35
+
36
+ if ( ! jQuery( '.imagify_optimized_file' ).length ) {
37
+ id = 0;
38
+ }
39
+
40
+ if ( isString ) {
41
+ settings.data += '&imagify_nonce=' + actions[ action ];
42
+
43
+ if ( id > 0 ) {
44
+ settings.data += '&imagify_info=1';
45
+ }
46
+ } else if ( isFormData ) {
47
+ settings.data.append( 'imagify_nonce', actions[ action ] );
48
+
49
+ if ( id > 0 ) {
50
+ settings.data.append( 'imagify_info', 1 );
51
+ }
52
+ } else {
53
+ settings.data = jQuery.extend( settings.data, { 'imagify_nonce': actions[ action ] } );
54
+
55
+ if ( id > 0 ) {
56
+ settings.data = jQuery.extend( settings.data, { 'imagify_info': 1 } );
57
+ }
58
+ }
59
+ },
60
+ updateInfo: function( e, jqXHR, settings, data ) {
61
+ if ( typeof settings.data !== 'string' ) {
62
+ return;
63
+ }
64
+
65
+ try {
66
+ data = jQuery.parseJSON( data );
67
+ }
68
+ catch ( error ) {
69
+ return;
70
+ }
71
+
72
+ if ( 'imagify' !== data.source || 'wr2x' !== data.context || ! data.imagify_info ) {
73
+ return;
74
+ }
75
+
76
+ jQuery.each( data.imagify_info, function ( id, html ) {
77
+ var $wrapper = jQuery( '#post-' + id ).children( '.imagify_optimized_file' );
78
+
79
+ if ( $wrapper.length ) {
80
+ $wrapper.html( html );
81
+ }
82
+ } );
83
+ }
84
+ }
85
+ } );
86
+
87
+ window.imagify.retina2x.hookAjax();
assets/js/imagify-wp-retina-2x.min.js ADDED
@@ -0,0 +1 @@
 
1
+ window.imagify=window.imagify||{},jQuery.extend(window.imagify,{retina2x:{hookAjax:function(){jQuery(document).ajaxSend(this.addData).ajaxSuccess(this.updateInfo)},addData:function(a,b,c){var d,e,f,g,h;c.data&&(d="string"==typeof c.data,e=c.data instanceof FormData,f=window.imagifyRetina2x,d?(g=c.data.match(/(?:^|&)action=([^&]+)/),g=!!g&&g[1],h=c.data.match(/(?:^|&)attachmentId=([^&]+)/),h=h?parseInt(h[1],10):0):e?(g=c.data.get("action"),h=c.data.get("attachmentId")):(g=c.data.action,h=c.data.id),g&&f[g]&&(jQuery(".imagify_optimized_file").length||(h=0),d?(c.data+="&imagify_nonce="+f[g],h>0&&(c.data+="&imagify_info=1")):e?(c.data.append("imagify_nonce",f[g]),h>0&&c.data.append("imagify_info",1)):(c.data=jQuery.extend(c.data,{imagify_nonce:f[g]}),h>0&&(c.data=jQuery.extend(c.data,{imagify_info:1})))))},updateInfo:function(a,b,c,d){if("string"==typeof c.data){try{d=jQuery.parseJSON(d)}catch(a){return}"imagify"===d.source&&"wr2x"===d.context&&d.imagify_info&&jQuery.each(d.imagify_info,function(a,b){var c=jQuery("#post-"+a).children(".imagify_optimized_file");c.length&&c.html(b)})}}}}),window.imagify.retina2x.hookAjax();
assets/js/options.js CHANGED
@@ -383,7 +383,7 @@
383
  w.setTimeout( function() {
384
  $row.remove();
385
  // Display a message.
386
- $( '#imagify-custom-folders-selected' ).next( '.hidden' ).removeClass( 'hidden' );
387
  }, 750 );
388
  } );
389
 
383
  w.setTimeout( function() {
384
  $row.remove();
385
  // Display a message.
386
+ $( '#imagify-custom-folders-selected' ).siblings( '.imagify-success.hidden' ).removeClass( 'hidden' );
387
  }, 750 );
388
  } );
389
 
assets/js/options.min.js CHANGED
@@ -1 +1 @@
1
- !function(a,b,c,d){var e=!1,f=!1;a("#imagify-settings #api_key").on("blur",function(){var b=a(this),d=b.val();return""!==a.trim(d)&&(a("#check_api_key").val()===d?(a("#imagify-check-api-container").html('<span class="dashicons dashicons-yes"></span> '+imagifyOptions.labels.ValidApiKeyText),!1):(!0===e?f.abort():(a("#imagify-check-api-container").remove(),b.after('<span id="imagify-check-api-container"><span class="imagify-spinner"></span>'+imagifyOptions.labels.waitApiKeyCheckText+"</span>")),e=!0,void(f=a.get(ajaxurl+c.imagify.concat+"action=imagify_check_api_key_validity&api_key="+b.val()+"&imagifycheckapikeynonce="+a("#imagifycheckapikeynonce").val()).done(function(b){b.success?(a("#imagify-check-api-container").remove(),swal({title:imagifyOptions.labels.ApiKeyCheckSuccessTitle,html:imagifyOptions.labels.ApiKeyCheckSuccessText,type:"success",padding:0,customClass:"imagify-sweet-alert"}).then(function(){location.reload()})):a("#imagify-check-api-container").html('<span class="dashicons dashicons-no"></span> '+b.data),e=!1}))))}),a(".imagify-options-line").css("cursor","pointer").on("click",function(b){if("INPUT"!==b.target.nodeName)return a('input[aria-describedby="'+a(this).attr("id")+'"]').trigger("click"),!1}),a(".imagify-settings th span").on("click",function(){var b=a(this).parent().next("td").find("input:checkbox");1===b.length&&b.trigger("click")}),a(".imagify-options-line").find("input").on("change focus",function(){var b=a(this).closest(".imagify-options-line").prev("label").prev("input");b[0].checked||b.prop("checked",!0)}),a(".imagify-settings-section").find("#imagify_backup").on("change",function(){var b=a(this),c=b.siblings("#backup-dir-is-writable"),d={action:"imagify_check_backup_dir_is_writable",_wpnonce:c.data("nonce")};if(b.is(":checked"))return void a.getJSON(ajaxurl,d).done(function(b){a.isPlainObject(b)&&b.success&&(b.data.is_writable?c.addClass("hidden"):c.removeClass("hidden"))});swal({title:imagifyOptions.labels.noBackupTitle,html:imagifyOptions.labels.noBackupText,type:"warning",customClass:"imagify-sweet-alert",padding:0,showCancelButton:!0,cancelButtonText:imagifySwal.labels.cancelButtonText,reverseButtons:!0}).then(function(){c.addClass("hidden")},function(){b.prop("checked",!0)})})}(jQuery,document,window),function(a,b,c,d){a.imagifyUser&&c.getJSON(ajaxurl,a.imagifyUser).done(function(a){c.isPlainObject(a)&&a.success&&(a.data.id=null,a.data.plan_id=null,a.data.is=[],c.each(a.data,function(b,d){var e=".imagify-user-"+b.replace(/_/g,"-");0===b.indexOf("is_")?d&&a.data.is.push(e):"is"!==b&&c(e).text(d)}),a.data.is.push("best-plan"),c(a.data.is.join(",")).removeClass("hidden"))})}(window,document,jQuery),function(a,b,c,d){function e(b){var d,e,f,g,h,i=!1,j=null;b&&(f=c("#imagify-custom-folders-selected"),g=f.find(".imagify-custom-folder-line"),h=g.find('[value="'+b+'"]'),h.length||(b=b.split("#///#"),d=b[1].replace(/\/+$/,"").toLowerCase(),e=a.imagify.template("imagify-custom-folder"),g.each(function(){var a=c(this),h=a.data("path").replace(/\/+$/,"").toLowerCase();return""!==h&&0===d.indexOf(h)?(i=!0,!1):d<h?(a.before(e({value:b[0],label:b[1]})),g=f.find(".imagify-custom-folder-line"),i=!0,!1):void 0}),i||(f.append(e({value:b[0],label:b[1]})),g=f.find(".imagify-custom-folder-line")),""!==d&&g.each(function(){var a=c(this),b=a.data("path").toLowerCase();null!==j&&0===b.indexOf(j)?a.find(".imagify-custom-folders-remove").trigger("click.imagify"):j=b}),f.next(".hidden").removeClass("hidden")))}imagifyOptions.getFilesTree&&(c("#imagify-add-custom-folder").on("click.imagify",function(){var a,b=c(this),d=[];b.attr("disabled")||(b.attr("disabled","disabled").next("img").attr("aria-hidden","false"),a=c("#imagify-custom-folders-selected"),a.find("input").each(function(){d.push(this.value)}),c.post({url:imagifyOptions.getFilesTree,dataType:"json",data:{folder:"/",selected:d}}).done(function(a){if(!a.success)return void swal({title:imagifyOptions.labels.error,html:a.data||"",type:"error",padding:0,customClass:"imagify-sweet-alert"});swal({title:imagifyOptions.labels.filesTreeTitle,html:'<div class="imagify-swal-subtitle">'+imagifyOptions.labels.filesTreeSubTitle+'</div><div class="imagify-swal-content"><p class="imagify-folders-information"><i class="dashicons dashicons-info" aria-hidden="true"></i>'+imagifyOptions.labels.cleaningInfo+'</p><ul id="imagify-folders-tree" class="imagify-folders-tree">'+a.data+"</ul></div>",type:"",customClass:"imagify-sweet-alert imagify-swal-has-subtitle imagify-folders-selection",showCancelButton:!0,padding:0,confirmButtonText:imagifyOptions.labels.confirmFilesTreeBtn,cancelButtonText:imagifySwal.labels.cancelButtonText,reverseButtons:!0}).then(function(){var a=c("#imagify-folders-tree input").serializeArray();a.length&&c.each(a,function(a,b){e(b.value)})}).catch(swal.noop)}).fail(function(){swal({title:imagifyOptions.labels.error,type:"error",customClass:"imagify-sweet-alert",padding:0})}).always(function(){b.removeAttr("disabled").next("img").attr("aria-hidden","true")}))}),c(b).on("click.imagify","#imagify-folders-tree [data-folder]",function(){var a=c(this),b=a.nextAll(".imagify-folders-sub-tree"),d=[];if(!a.attr("disabled")&&!a.siblings(":checkbox").is(":checked")){if(a.attr("disabled","disabled"),b.length)return a.hasClass("imagify-is-open")?(b.addClass("hidden"),a.removeClass(" imagify-is-open")):(b.removeClass("hidden"),a.addClass(" imagify-is-open")),void a.removeAttr("disabled");c("#imagify-custom-folders-selected").find("input").each(function(){d.push(this.value)}),c.post({url:imagifyOptions.getFilesTree,dataType:"json",data:{folder:a.data("folder"),selected:d}}).done(function(b){if(!b.success)return void swal({title:imagifyOptions.labels.error,html:b.data||"",type:"error",padding:0,customClass:"imagify-sweet-alert"});a.addClass("imagify-is-open").parent().append('<ul class="imagify-folders-sub-tree">'+b.data+"</ul>")}).fail(function(){swal({title:imagifyOptions.labels.error,type:"error",padding:0,customClass:"imagify-sweet-alert"})}).always(function(){a.removeAttr("disabled")})}}),c("#imagify-custom-folders").on("click.imagify",".imagify-custom-folders-remove",function(){var b=c(this).closest(".imagify-custom-folder-line").addClass("imagify-will-remove");a.setTimeout(function(){b.remove(),c("#imagify-custom-folders-selected").next(".hidden").removeClass("hidden")},750)}),c("#imagify-add-themes-to-custom-folder").on("click.imagify",function(){var a=c(this);e(a.data("theme")),e(a.data("theme-parent")),a.replaceWith("<p>"+imagifyOptions.labels.themesAdded+"</p>")}))}(window,document,jQuery),function(a,b,c,d){var e=c.propHooks.checked;c.propHooks.checked={set:function(a,b,d){var f;return f=void 0===e?a[d]=b:e(a,b,d),c(a).trigger("change.imagify"),f}},c(".imagify-select-all").on("click.imagify",function(){var a=c(this),b=a.data("action"),d=a.closest(".imagify-select-all-buttons"),e=d.prev(".imagify-check-group"),f="imagify-is-inactive";if(a.hasClass(f))return!1;d.find(".imagify-select-all").removeClass(f).attr("aria-disabled","false"),a.addClass(f).attr("aria-disabled","true"),e.find(".imagify-row-check").prop("checked",function(){return!c(this).is(":hidden,:disabled")&&"select"===b})}),c(".imagify-check-group .imagify-row-check").on("change.imagify",function(){var a=c(this).closest(".imagify-check-group"),b=a.find(".imagify-row-check"),d=b.filter(":visible:enabled").length,e=b.filter(":visible:enabled:checked").length,f=a.next(".imagify-select-all-buttons"),g="imagify-is-inactive";0===e&&f.find('[data-action="unselect"]').addClass(g).attr("aria-disabled","true"),e===d&&f.find('[data-action="select"]').addClass(g).attr("aria-disabled","true"),e!==d&&e>0&&f.find(".imagify-select-all").removeClass(g).attr("aria-disabled","false")})}(window,document,jQuery);
1
+ !function(a,b,c,d){var e=!1,f=!1;a("#imagify-settings #api_key").on("blur",function(){var b=a(this),d=b.val();return""!==a.trim(d)&&(a("#check_api_key").val()===d?(a("#imagify-check-api-container").html('<span class="dashicons dashicons-yes"></span> '+imagifyOptions.labels.ValidApiKeyText),!1):(!0===e?f.abort():(a("#imagify-check-api-container").remove(),b.after('<span id="imagify-check-api-container"><span class="imagify-spinner"></span>'+imagifyOptions.labels.waitApiKeyCheckText+"</span>")),e=!0,void(f=a.get(ajaxurl+c.imagify.concat+"action=imagify_check_api_key_validity&api_key="+b.val()+"&imagifycheckapikeynonce="+a("#imagifycheckapikeynonce").val()).done(function(b){b.success?(a("#imagify-check-api-container").remove(),swal({title:imagifyOptions.labels.ApiKeyCheckSuccessTitle,html:imagifyOptions.labels.ApiKeyCheckSuccessText,type:"success",padding:0,customClass:"imagify-sweet-alert"}).then(function(){location.reload()})):a("#imagify-check-api-container").html('<span class="dashicons dashicons-no"></span> '+b.data),e=!1}))))}),a(".imagify-options-line").css("cursor","pointer").on("click",function(b){if("INPUT"!==b.target.nodeName)return a('input[aria-describedby="'+a(this).attr("id")+'"]').trigger("click"),!1}),a(".imagify-settings th span").on("click",function(){var b=a(this).parent().next("td").find("input:checkbox");1===b.length&&b.trigger("click")}),a(".imagify-options-line").find("input").on("change focus",function(){var b=a(this).closest(".imagify-options-line").prev("label").prev("input");b[0].checked||b.prop("checked",!0)}),a(".imagify-settings-section").find("#imagify_backup").on("change",function(){var b=a(this),c=b.siblings("#backup-dir-is-writable"),d={action:"imagify_check_backup_dir_is_writable",_wpnonce:c.data("nonce")};if(b.is(":checked"))return void a.getJSON(ajaxurl,d).done(function(b){a.isPlainObject(b)&&b.success&&(b.data.is_writable?c.addClass("hidden"):c.removeClass("hidden"))});swal({title:imagifyOptions.labels.noBackupTitle,html:imagifyOptions.labels.noBackupText,type:"warning",customClass:"imagify-sweet-alert",padding:0,showCancelButton:!0,cancelButtonText:imagifySwal.labels.cancelButtonText,reverseButtons:!0}).then(function(){c.addClass("hidden")},function(){b.prop("checked",!0)})})}(jQuery,document,window),function(a,b,c,d){a.imagifyUser&&c.getJSON(ajaxurl,a.imagifyUser).done(function(a){c.isPlainObject(a)&&a.success&&(a.data.id=null,a.data.plan_id=null,a.data.is=[],c.each(a.data,function(b,d){var e=".imagify-user-"+b.replace(/_/g,"-");0===b.indexOf("is_")?d&&a.data.is.push(e):"is"!==b&&c(e).text(d)}),a.data.is.push("best-plan"),c(a.data.is.join(",")).removeClass("hidden"))})}(window,document,jQuery),function(a,b,c,d){function e(b){var d,e,f,g,h,i=!1,j=null;b&&(f=c("#imagify-custom-folders-selected"),g=f.find(".imagify-custom-folder-line"),h=g.find('[value="'+b+'"]'),h.length||(b=b.split("#///#"),d=b[1].replace(/\/+$/,"").toLowerCase(),e=a.imagify.template("imagify-custom-folder"),g.each(function(){var a=c(this),h=a.data("path").replace(/\/+$/,"").toLowerCase();return""!==h&&0===d.indexOf(h)?(i=!0,!1):d<h?(a.before(e({value:b[0],label:b[1]})),g=f.find(".imagify-custom-folder-line"),i=!0,!1):void 0}),i||(f.append(e({value:b[0],label:b[1]})),g=f.find(".imagify-custom-folder-line")),""!==d&&g.each(function(){var a=c(this),b=a.data("path").toLowerCase();null!==j&&0===b.indexOf(j)?a.find(".imagify-custom-folders-remove").trigger("click.imagify"):j=b}),f.next(".hidden").removeClass("hidden")))}imagifyOptions.getFilesTree&&(c("#imagify-add-custom-folder").on("click.imagify",function(){var a,b=c(this),d=[];b.attr("disabled")||(b.attr("disabled","disabled").next("img").attr("aria-hidden","false"),a=c("#imagify-custom-folders-selected"),a.find("input").each(function(){d.push(this.value)}),c.post({url:imagifyOptions.getFilesTree,dataType:"json",data:{folder:"/",selected:d}}).done(function(a){if(!a.success)return void swal({title:imagifyOptions.labels.error,html:a.data||"",type:"error",padding:0,customClass:"imagify-sweet-alert"});swal({title:imagifyOptions.labels.filesTreeTitle,html:'<div class="imagify-swal-subtitle">'+imagifyOptions.labels.filesTreeSubTitle+'</div><div class="imagify-swal-content"><p class="imagify-folders-information"><i class="dashicons dashicons-info" aria-hidden="true"></i>'+imagifyOptions.labels.cleaningInfo+'</p><ul id="imagify-folders-tree" class="imagify-folders-tree">'+a.data+"</ul></div>",type:"",customClass:"imagify-sweet-alert imagify-swal-has-subtitle imagify-folders-selection",showCancelButton:!0,padding:0,confirmButtonText:imagifyOptions.labels.confirmFilesTreeBtn,cancelButtonText:imagifySwal.labels.cancelButtonText,reverseButtons:!0}).then(function(){var a=c("#imagify-folders-tree input").serializeArray();a.length&&c.each(a,function(a,b){e(b.value)})}).catch(swal.noop)}).fail(function(){swal({title:imagifyOptions.labels.error,type:"error",customClass:"imagify-sweet-alert",padding:0})}).always(function(){b.removeAttr("disabled").next("img").attr("aria-hidden","true")}))}),c(b).on("click.imagify","#imagify-folders-tree [data-folder]",function(){var a=c(this),b=a.nextAll(".imagify-folders-sub-tree"),d=[];if(!a.attr("disabled")&&!a.siblings(":checkbox").is(":checked")){if(a.attr("disabled","disabled"),b.length)return a.hasClass("imagify-is-open")?(b.addClass("hidden"),a.removeClass(" imagify-is-open")):(b.removeClass("hidden"),a.addClass(" imagify-is-open")),void a.removeAttr("disabled");c("#imagify-custom-folders-selected").find("input").each(function(){d.push(this.value)}),c.post({url:imagifyOptions.getFilesTree,dataType:"json",data:{folder:a.data("folder"),selected:d}}).done(function(b){if(!b.success)return void swal({title:imagifyOptions.labels.error,html:b.data||"",type:"error",padding:0,customClass:"imagify-sweet-alert"});a.addClass("imagify-is-open").parent().append('<ul class="imagify-folders-sub-tree">'+b.data+"</ul>")}).fail(function(){swal({title:imagifyOptions.labels.error,type:"error",padding:0,customClass:"imagify-sweet-alert"})}).always(function(){a.removeAttr("disabled")})}}),c("#imagify-custom-folders").on("click.imagify",".imagify-custom-folders-remove",function(){var b=c(this).closest(".imagify-custom-folder-line").addClass("imagify-will-remove");a.setTimeout(function(){b.remove(),c("#imagify-custom-folders-selected").siblings(".imagify-success.hidden").removeClass("hidden")},750)}),c("#imagify-add-themes-to-custom-folder").on("click.imagify",function(){var a=c(this);e(a.data("theme")),e(a.data("theme-parent")),a.replaceWith("<p>"+imagifyOptions.labels.themesAdded+"</p>")}))}(window,document,jQuery),function(a,b,c,d){var e=c.propHooks.checked;c.propHooks.checked={set:function(a,b,d){var f;return f=void 0===e?a[d]=b:e(a,b,d),c(a).trigger("change.imagify"),f}},c(".imagify-select-all").on("click.imagify",function(){var a=c(this),b=a.data("action"),d=a.closest(".imagify-select-all-buttons"),e=d.prev(".imagify-check-group"),f="imagify-is-inactive";if(a.hasClass(f))return!1;d.find(".imagify-select-all").removeClass(f).attr("aria-disabled","false"),a.addClass(f).attr("aria-disabled","true"),e.find(".imagify-row-check").prop("checked",function(){return!c(this).is(":hidden,:disabled")&&"select"===b})}),c(".imagify-check-group .imagify-row-check").on("change.imagify",function(){var a=c(this).closest(".imagify-check-group"),b=a.find(".imagify-row-check"),d=b.filter(":visible:enabled").length,e=b.filter(":visible:enabled:checked").length,f=a.next(".imagify-select-all-buttons"),g="imagify-is-inactive";0===e&&f.find('[data-action="unselect"]').addClass(g).attr("aria-disabled","true"),e===d&&f.find('[data-action="select"]').addClass(g).attr("aria-disabled","true"),e!==d&&e>0&&f.find(".imagify-select-all").removeClass(g).attr("aria-disabled","false")})}(window,document,jQuery);
assets/js/weakmap-polyfill.js ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * weakmap-polyfill v2.0.0 - ECMAScript6 WeakMap polyfill
3
+ * https://github.com/polygonplanet/weakmap-polyfill
4
+ * Copyright (c) 2015-2016 polygon planet <polygon.planet.aqua@gmail.com>
5
+ * @license MIT
6
+ */
7
+
8
+ (function(self) {
9
+ 'use strict';
10
+
11
+ if (self.WeakMap) {
12
+ return;
13
+ }
14
+
15
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
16
+ var defineProperty = function(object, name, value) {
17
+ if (Object.defineProperty) {
18
+ Object.defineProperty(object, name, {
19
+ configurable: true,
20
+ writable: true,
21
+ value: value
22
+ });
23
+ } else {
24
+ object[name] = value;
25
+ }
26
+ };
27
+
28
+ self.WeakMap = (function() {
29
+
30
+ // ECMA-262 23.3 WeakMap Objects
31
+ function WeakMap() {
32
+ if (this === void 0) {
33
+ throw new TypeError("Constructor WeakMap requires 'new'");
34
+ }
35
+
36
+ defineProperty(this, '_id', genId('_WeakMap'));
37
+
38
+ // ECMA-262 23.3.1.1 WeakMap([iterable])
39
+ if (arguments.length > 0) {
40
+ // Currently, WeakMap `iterable` argument is not supported
41
+ throw new TypeError('WeakMap iterable is not supported');
42
+ }
43
+ }
44
+
45
+ // ECMA-262 23.3.3.2 WeakMap.prototype.delete(key)
46
+ defineProperty(WeakMap.prototype, 'delete', function(key) {
47
+ checkInstance(this, 'delete');
48
+
49
+ if (!isObject(key)) {
50
+ return false;
51
+ }
52
+
53
+ var entry = key[this._id];
54
+ if (entry && entry[0] === key) {
55
+ delete key[this._id];
56
+ return true;
57
+ }
58
+
59
+ return false;
60
+ });
61
+
62
+ // ECMA-262 23.3.3.3 WeakMap.prototype.get(key)
63
+ defineProperty(WeakMap.prototype, 'get', function(key) {
64
+ checkInstance(this, 'get');
65
+
66
+ if (!isObject(key)) {
67
+ return void 0;
68
+ }
69
+
70
+ var entry = key[this._id];
71
+ if (entry && entry[0] === key) {
72
+ return entry[1];
73
+ }
74
+
75
+ return void 0;
76
+ });
77
+
78
+ // ECMA-262 23.3.3.4 WeakMap.prototype.has(key)
79
+ defineProperty(WeakMap.prototype, 'has', function(key) {
80
+ checkInstance(this, 'has');
81
+
82
+ if (!isObject(key)) {
83
+ return false;
84
+ }
85
+
86
+ var entry = key[this._id];
87
+ if (entry && entry[0] === key) {
88
+ return true;
89
+ }
90
+
91
+ return false;
92
+ });
93
+
94
+ // ECMA-262 23.3.3.5 WeakMap.prototype.set(key, value)
95
+ defineProperty(WeakMap.prototype, 'set', function(key, value) {
96
+ checkInstance(this, 'set');
97
+
98
+ if (!isObject(key)) {
99
+ throw new TypeError('Invalid value used as weak map key');
100
+ }
101
+
102
+ var entry = key[this._id];
103
+ if (entry && entry[0] === key) {
104
+ entry[1] = value;
105
+ return this;
106
+ }
107
+
108
+ defineProperty(key, this._id, [key, value]);
109
+ return this;
110
+ });
111
+
112
+
113
+ function checkInstance(x, methodName) {
114
+ if (!isObject(x) || !hasOwnProperty.call(x, '_id')) {
115
+ throw new TypeError(
116
+ methodName + ' method called on incompatible receiver ' +
117
+ typeof x
118
+ );
119
+ }
120
+ }
121
+
122
+ function genId(prefix) {
123
+ return prefix + '_' + rand() + '.' + rand();
124
+ }
125
+
126
+ function rand() {
127
+ return Math.random().toString().substring(2);
128
+ }
129
+
130
+
131
+ defineProperty(WeakMap, '_polyfill', true);
132
+ return WeakMap;
133
+ })();
134
+
135
+
136
+ function isObject(x) {
137
+ return Object(x) === x;
138
+ }
139
+
140
+ })(
141
+ typeof self !== 'undefined' ? self :
142
+ typeof window !== 'undefined' ? window :
143
+ typeof global !== 'undefined' ? global : this
144
+ );
assets/js/weakmap-polyfill.min.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ /*!
2
+ * weakmap-polyfill v2.0.0 - ECMAScript6 WeakMap polyfill
3
+ * https://github.com/polygonplanet/weakmap-polyfill
4
+ * Copyright (c) 2015-2016 polygon planet <polygon.planet.aqua@gmail.com>
5
+ * @license MIT
6
+ */
7
+ (function(e){"use strict";if(e.WeakMap){return}var t=Object.prototype.hasOwnProperty;var r=function(e,t,r){if(Object.defineProperty){Object.defineProperty(e,t,{configurable:true,writable:true,value:r})}else{e[t]=r}};e.WeakMap=function(){function WeakMap(){if(this===void 0){throw new TypeError("Constructor WeakMap requires 'new'")}r(this,"_id",genId("_WeakMap"));if(arguments.length>0){throw new TypeError("WeakMap iterable is not supported")}}r(WeakMap.prototype,"delete",function(e){checkInstance(this,"delete");if(!isObject(e)){return false}var t=e[this._id];if(t&&t[0]===e){delete e[this._id];return true}return false});r(WeakMap.prototype,"get",function(e){checkInstance(this,"get");if(!isObject(e)){return void 0}var t=e[this._id];if(t&&t[0]===e){return t[1]}return void 0});r(WeakMap.prototype,"has",function(e){checkInstance(this,"has");if(!isObject(e)){return false}var t=e[this._id];if(t&&t[0]===e){return true}return false});r(WeakMap.prototype,"set",function(e,t){checkInstance(this,"set");if(!isObject(e)){throw new TypeError("Invalid value used as weak map key")}var n=e[this._id];if(n&&n[0]===e){n[1]=t;return this}r(e,this._id,[e,t]);return this});function checkInstance(e,r){if(!isObject(e)||!t.call(e,"_id")){throw new TypeError(r+" method called on incompatible receiver "+typeof e)}}function genId(e){return e+"_"+rand()+"."+rand()}function rand(){return Math.random().toString().substring(2)}r(WeakMap,"_polyfill",true);return WeakMap}();function isObject(e){return Object(e)===e}})(typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:this);
imagify.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: Imagify
4
  * Plugin URI: https://wordpress.org/plugins/imagify/
5
  * Description: Dramaticaly reduce image file sizes without losing quality, make your website load faster, boost your SEO and save money on your bandwidth using Imagify, the new most advanced image optimization tool.
6
- * Version: 1.7.1.3
7
  * Author: WP Media
8
  * Author URI: https://wp-media.me/
9
  * Licence: GPLv2
@@ -17,7 +17,7 @@
17
  defined( 'ABSPATH' ) || die( 'Cheatin\' uh?' );
18
 
19
  // Imagify defines.
20
- define( 'IMAGIFY_VERSION' , '1.7.1.3' );
21
  define( 'IMAGIFY_SLUG' , 'imagify' );
22
  define( 'IMAGIFY_FILE' , __FILE__ );
23
  define( 'IMAGIFY_PATH' , realpath( plugin_dir_path( IMAGIFY_FILE ) ) . '/' );
3
  * Plugin Name: Imagify
4
  * Plugin URI: https://wordpress.org/plugins/imagify/
5
  * Description: Dramaticaly reduce image file sizes without losing quality, make your website load faster, boost your SEO and save money on your bandwidth using Imagify, the new most advanced image optimization tool.
6
+ * Version: 1.8
7
  * Author: WP Media
8
  * Author URI: https://wp-media.me/
9
  * Licence: GPLv2
17
  defined( 'ABSPATH' ) || die( 'Cheatin\' uh?' );
18
 
19
  // Imagify defines.
20
+ define( 'IMAGIFY_VERSION' , '1.8' );
21
  define( 'IMAGIFY_SLUG' , 'imagify' );
22
  define( 'IMAGIFY_FILE' , __FILE__ );
23
  define( 'IMAGIFY_PATH' , realpath( plugin_dir_path( IMAGIFY_FILE ) ) . '/' );
inc/3rd-party/3rd-party.php CHANGED
@@ -9,9 +9,9 @@ require( IMAGIFY_3RD_PARTY_PATH . 'enable-media-replace/enable-media-replace.php
9
  require( IMAGIFY_3RD_PARTY_PATH . 'formidable-pro/formidable-pro.php' );
10
  require( IMAGIFY_3RD_PARTY_PATH . 'nextgen-gallery/nextgen-gallery.php' );
11
  require( IMAGIFY_3RD_PARTY_PATH . 'regenerate-thumbnails/regenerate-thumbnails.php' );
 
12
  require( IMAGIFY_3RD_PARTY_PATH . 'screets-lc.php' );
13
  require( IMAGIFY_3RD_PARTY_PATH . 'wp-real-media-library.php' );
14
- require( IMAGIFY_3RD_PARTY_PATH . 'wp-retina-2x.php' );
15
  require( IMAGIFY_3RD_PARTY_PATH . 'wp-rocket.php' );
16
  require( IMAGIFY_3RD_PARTY_PATH . 'yoast-seo.php' );
17
 
9
  require( IMAGIFY_3RD_PARTY_PATH . 'formidable-pro/formidable-pro.php' );
10
  require( IMAGIFY_3RD_PARTY_PATH . 'nextgen-gallery/nextgen-gallery.php' );
11
  require( IMAGIFY_3RD_PARTY_PATH . 'regenerate-thumbnails/regenerate-thumbnails.php' );
12
+ require( IMAGIFY_3RD_PARTY_PATH . 'wp-retina-2x/wp-retina-2x.php' );
13
  require( IMAGIFY_3RD_PARTY_PATH . 'screets-lc.php' );
14
  require( IMAGIFY_3RD_PARTY_PATH . 'wp-real-media-library.php' );
 
15
  require( IMAGIFY_3RD_PARTY_PATH . 'wp-rocket.php' );
16
  require( IMAGIFY_3RD_PARTY_PATH . 'yoast-seo.php' );
17
 
inc/3rd-party/amazon-s3-and-cloudfront/inc/classes/class-imagify-as3cf-attachment.php CHANGED
@@ -148,6 +148,7 @@ class Imagify_AS3CF_Attachment extends Imagify_Attachment {
148
 
149
  // Get file path of the full size.
150
  $attachment_path = $this->get_original_path();
 
151
 
152
  if ( ! $attachment_path ) {
153
  // We're in deep sh**.
@@ -174,7 +175,7 @@ class Imagify_AS3CF_Attachment extends Imagify_Attachment {
174
  $filesize_total = 0;
175
 
176
  // Maybe resize (and backup) the image.
177
- $resized = $this->maybe_resize( $attachment_path );
178
 
179
  if ( $resized ) {
180
  $size = $this->filesystem->get_image_size( $attachment_path );
@@ -210,6 +211,9 @@ class Imagify_AS3CF_Attachment extends Imagify_Attachment {
210
  }
211
  }
212
 
 
 
 
213
  // Save the optimization level.
214
  update_post_meta( $this->id, '_imagify_optimization_level', $optimization_level );
215
 
@@ -227,11 +231,12 @@ class Imagify_AS3CF_Attachment extends Imagify_Attachment {
227
 
228
  foreach ( $metadata['sizes'] as $size_key => $size_data ) {
229
  $thumbnail_path = $this->get_thumbnail_path( $size_data['file'] );
 
230
 
231
  if ( $this->delete_files ) {
232
  $to_delete[] = $thumbnail_path;
233
 
234
- // Even if this size must not be optimized ($disallowed_sizes), we must fetch the file from S3 to get its size.
235
  if ( ! $this->filesystem->exists( $thumbnail_path ) && ! $this->get_file_from_s3( $thumbnail_path ) ) {
236
  // Doesn't exist and couldn't be retrieved from S3.
237
  $data['sizes'][ $size_key ] = array(
@@ -250,11 +255,14 @@ class Imagify_AS3CF_Attachment extends Imagify_Attachment {
250
  }
251
 
252
  // Check if this size has to be optimized.
253
- if ( isset( $disallowed_sizes[ $size_key ] ) && ! $is_active_for_network ) {
254
  $data['sizes'][ $size_key ] = array(
255
  'success' => false,
256
  'error' => __( 'This size isn\'t authorized to be optimized. Update your Imagify settings if you want to optimize it.', 'imagify' ),
257
  );
 
 
 
258
  continue;
259
  }
260
 
@@ -267,7 +275,9 @@ class Imagify_AS3CF_Attachment extends Imagify_Attachment {
267
  continue;
268
  }
269
 
270
- $thumbnail_url = $this->get_thumbnail_url( $size_data['file'] );
 
 
271
 
272
  // Optimize the thumbnail size.
273
  $response = do_imagify( $thumbnail_path, array(
@@ -279,7 +289,7 @@ class Imagify_AS3CF_Attachment extends Imagify_Attachment {
279
  $data = $this->fill_data( $data, $response, $size_key );
280
 
281
  /** This filter is documented in /inc/classes/class-imagify-attachment.php. */
282
- $data = apply_filters( 'imagify_fill_thumbnail_data', $data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level );
283
  } // End foreach().
284
  } // End if().
285
 
@@ -321,7 +331,7 @@ class Imagify_AS3CF_Attachment extends Imagify_Attachment {
321
  */
322
  public function optimize_missing_thumbnails( $optimization_level = null ) {
323
  // Check if the attachment extension is allowed.
324
- if ( ! $this->is_extension_supported() ) {
325
  return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) );
326
  }
327
 
@@ -529,26 +539,39 @@ class Imagify_AS3CF_Attachment extends Imagify_Attachment {
529
  return false;
530
  }
531
 
532
- if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
533
- require_once( ABSPATH . 'wp-admin/includes/image.php' );
534
- }
535
 
536
  // Remove old optimization data.
537
  $this->delete_imagify_data();
538
 
539
- /** This hook is documented in /inc/classes/class-imagify-attachment.php. */
540
- do_action( 'after_imagify_restore_attachment', $this->id );
 
541
 
542
- $this->set_deletion_status();
 
 
 
 
 
 
 
 
 
 
 
 
 
543
 
544
- // If the files must be deleted, we need to store the file sizes.
545
- $filesize_total = 0;
546
  // Generate new thumbnails and new metadata.
547
- $metadata = wp_generate_attachment_metadata( $this->id, $attachment_path );
 
548
  // Send to S3.
549
  $sent = $this->maybe_send_attachment_to_s3( $metadata, $attachment_path );
550
  // Files restored (and maybe to delete).
551
  $files = array();
 
 
552
 
553
  if ( $sent ) {
554
  $files[] = $attachment_path;
@@ -590,6 +613,9 @@ class Imagify_AS3CF_Attachment extends Imagify_Attachment {
590
  update_post_meta( $this->id, 'wpos3_filesize_total', $filesize_total );
591
  }
592
 
 
 
 
593
  $to_delete = $this->delete_files ? $files : array();
594
 
595
  $this->cleanup( $metadata, $to_delete );
@@ -664,6 +690,10 @@ class Imagify_AS3CF_Attachment extends Imagify_Attachment {
664
  * @return bool True on success. False on failure.
665
  */
666
  protected function maybe_resize( $attachment_path ) {
 
 
 
 
667
  $do_resize = get_imagify_option( 'resize_larger' );
668
  $resize_width = get_imagify_option( 'resize_larger_w' );
669
  $attachment_size = $this->filesystem->get_image_size( $attachment_path );
@@ -684,15 +714,7 @@ class Imagify_AS3CF_Attachment extends Imagify_Attachment {
684
  return false;
685
  }
686
 
687
- $this->filesystem->move( $resized_attachment_path, $attachment_path, true );
688
- $this->filesystem->chmod_file( $attachment_path );
689
-
690
- // If resized temp file still exists, delete it.
691
- if ( $this->filesystem->exists( $resized_attachment_path ) ) {
692
- $this->filesystem->delete( $resized_attachment_path );
693
- }
694
-
695
- return $this->filesystem->exists( $attachment_path );
696
  }
697
 
698
  /**
148
 
149
  // Get file path of the full size.
150
  $attachment_path = $this->get_original_path();
151
+ $attachment_url = $this->get_original_url();
152
 
153
  if ( ! $attachment_path ) {
154
  // We're in deep sh**.
175
  $filesize_total = 0;
176
 
177
  // Maybe resize (and backup) the image.
178
+ $resized = $this->is_image() && $this->maybe_resize( $attachment_path );
179
 
180
  if ( $resized ) {
181
  $size = $this->filesystem->get_image_size( $attachment_path );
211
  }
212
  }
213
 
214
+ /** This filter is documented in /inc/classes/class-imagify-attachment.php. */
215
+ $data = apply_filters( 'imagify_fill_full_size_data', $data, $response, $this->id, $attachment_path, $attachment_url, 'full', $optimization_level, $metadata );
216
+
217
  // Save the optimization level.
218
  update_post_meta( $this->id, '_imagify_optimization_level', $optimization_level );
219
 
231
 
232
  foreach ( $metadata['sizes'] as $size_key => $size_data ) {
233
  $thumbnail_path = $this->get_thumbnail_path( $size_data['file'] );
234
+ $thumbnail_url = $this->get_thumbnail_url( $size_data['file'] );
235
 
236
  if ( $this->delete_files ) {
237
  $to_delete[] = $thumbnail_path;
238
 
239
+ // Even if this size must not be optimized ($disallowed_sizes), we must fetch the file from S3 to get its size, and not to trigger a `WP_Error` in `upload_attachment_to_s3()`.
240
  if ( ! $this->filesystem->exists( $thumbnail_path ) && ! $this->get_file_from_s3( $thumbnail_path ) ) {
241
  // Doesn't exist and couldn't be retrieved from S3.
242
  $data['sizes'][ $size_key ] = array(
255
  }
256
 
257
  // Check if this size has to be optimized.
258
+ if ( ! $is_active_for_network && isset( $disallowed_sizes[ $size_key ] ) && $this->is_image() ) {
259
  $data['sizes'][ $size_key ] = array(
260
  'success' => false,
261
  'error' => __( 'This size isn\'t authorized to be optimized. Update your Imagify settings if you want to optimize it.', 'imagify' ),
262
  );
263
+
264
+ /** This filter is documented in /inc/classes/class-imagify-attachment.php. */
265
+ $data = apply_filters( 'imagify_fill_unauthorized_thumbnail_data', $data, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level, $metadata );
266
  continue;
267
  }
268
 
275
  continue;
276
  }
277
 
278
+ if ( ! $this->is_image() ) {
279
+ continue;
280
+ }
281
 
282
  // Optimize the thumbnail size.
283
  $response = do_imagify( $thumbnail_path, array(
289
  $data = $this->fill_data( $data, $response, $size_key );
290
 
291
  /** This filter is documented in /inc/classes/class-imagify-attachment.php. */
292
+ $data = apply_filters( 'imagify_fill_thumbnail_data', $data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level, $metadata );
293
  } // End foreach().
294
  } // End if().
295
 
331
  */
332
  public function optimize_missing_thumbnails( $optimization_level = null ) {
333
  // Check if the attachment extension is allowed.
334
+ if ( ! $this->is_extension_supported() || ! $this->is_image() ) {
335
  return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) );
336
  }
337
 
539
  return false;
540
  }
541
 
542
+ $this->set_deletion_status();
 
 
543
 
544
  // Remove old optimization data.
545
  $this->delete_imagify_data();
546
 
547
+ if ( ! $this->is_image() ) {
548
+ // We need to delete the thumbnails for pdf, or new ones will be generated with new unique file names.
549
+ $metadata = wp_get_attachment_metadata( $this->id );
550
 
551
+ if ( ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ) {
552
+ foreach ( $metadata['sizes'] as $size_key => $size_data ) {
553
+ $thumbnail_path = $this->get_thumbnail_path( $size_data['file'] );
554
+
555
+ if ( $this->filesystem->exists( $thumbnail_path ) ) {
556
+ $this->filesystem->delete( $thumbnail_path );
557
+ }
558
+ }
559
+ }
560
+ }
561
+
562
+ if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
563
+ require_once( ABSPATH . 'wp-admin/includes/image.php' );
564
+ }
565
 
 
 
566
  // Generate new thumbnails and new metadata.
567
+ $metadata = wp_generate_attachment_metadata( $this->id, $attachment_path );
568
+
569
  // Send to S3.
570
  $sent = $this->maybe_send_attachment_to_s3( $metadata, $attachment_path );
571
  // Files restored (and maybe to delete).
572
  $files = array();
573
+ // If the files must be deleted, we need to store the file sizes.
574
+ $filesize_total = 0;
575
 
576
  if ( $sent ) {
577
  $files[] = $attachment_path;
613
  update_post_meta( $this->id, 'wpos3_filesize_total', $filesize_total );
614
  }
615
 
616
+ /** This hook is documented in /inc/classes/class-imagify-attachment.php. */
617
+ do_action( 'after_imagify_restore_attachment', $this->id );
618
+
619
  $to_delete = $this->delete_files ? $files : array();
620
 
621
  $this->cleanup( $metadata, $to_delete );
690
  * @return bool True on success. False on failure.
691
  */
692
  protected function maybe_resize( $attachment_path ) {
693
+ if ( ! $this->is_image() ) {
694
+ return false;
695
+ }
696
+
697
  $do_resize = get_imagify_option( 'resize_larger' );
698
  $resize_width = get_imagify_option( 'resize_larger_w' );
699
  $attachment_size = $this->filesystem->get_image_size( $attachment_path );
714
  return false;
715
  }
716
 
717
+ return $this->filesystem->move( $resized_attachment_path, $attachment_path, true );
 
 
 
 
 
 
 
 
718
  }
719
 
720
  /**
inc/3rd-party/amazon-s3-and-cloudfront/inc/classes/class-imagify-as3cf.php CHANGED
@@ -406,8 +406,6 @@ class Imagify_AS3CF extends Imagify_AS3CF_Deprecated {
406
  * @author Grégory Viguier
407
  */
408
  public function optimize() {
409
- global $as3cf;
410
-
411
  check_ajax_referer( 'imagify_async_optimize_as3cf' );
412
 
413
  if ( empty( $_POST['post_id'] ) || ! imagify_current_user_can( 'auto-optimize' ) ) {
406
  * @author Grégory Viguier
407
  */
408
  public function optimize() {
 
 
409
  check_ajax_referer( 'imagify_async_optimize_as3cf' );
410
 
411
  if ( empty( $_POST['post_id'] ) || ! imagify_current_user_can( 'auto-optimize' ) ) {
inc/3rd-party/hosting/siteground.php CHANGED
File without changes
inc/3rd-party/hosting/wpengine.php CHANGED
File without changes
inc/3rd-party/nextgen-gallery/inc/admin/ajax.php CHANGED
File without changes
inc/3rd-party/nextgen-gallery/inc/admin/bulk.php CHANGED
File without changes
inc/3rd-party/nextgen-gallery/inc/admin/enqueue.php CHANGED
File without changes
inc/3rd-party/nextgen-gallery/inc/admin/gallery.php CHANGED
File without changes
inc/3rd-party/nextgen-gallery/inc/admin/heartbeat.php CHANGED
File without changes
inc/3rd-party/nextgen-gallery/inc/admin/menu.php CHANGED
File without changes
inc/3rd-party/nextgen-gallery/inc/classes/class-imagify-ngg-attachment.php CHANGED
@@ -14,7 +14,7 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
14
  *
15
  * @var string
16
  */
17
- const VERSION = '1.3.2';
18
 
19
  /**
20
  * The attachment SQL DB class.
@@ -31,10 +31,19 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
31
  * @var object A nggImage object.
32
  * @since 1.5
33
  * @since 1.7 Not public anymore.
34
- * @access public
35
  */
36
  protected $image;
37
 
 
 
 
 
 
 
 
 
 
38
  /**
39
  * Tell if the file mime type can be optimized by Imagify.
40
  *
@@ -56,8 +65,13 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
56
  */
57
  public function __construct( $id ) {
58
  if ( is_object( $id ) ) {
59
- $this->image = $id;
60
- $this->id = (int) $id->pid;
 
 
 
 
 
61
  } else {
62
  $this->image = nggdb::find_image( absint( $id ) );
63
  $this->id = ! empty( $this->image->pid ) ? (int) $this->image->pid : 0;
@@ -65,6 +79,12 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
65
 
66
  $this->get_row();
67
 
 
 
 
 
 
 
68
  $this->filesystem = Imagify_Filesystem::get_instance();
69
  $this->optimization_state_transient = 'imagify-ngg-async-in-progress-' . $this->id;
70
 
@@ -72,7 +92,7 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
72
  $ngg_admin_functions_path = WP_PLUGIN_DIR . '/' . NGGFOLDER . '/products/photocrati_nextgen/modules/ngglegacy/admin/functions.php';
73
 
74
  if ( ! class_exists( 'nggAdmin' ) && $this->filesystem->exists( $ngg_admin_functions_path ) ) {
75
- require_once( $ngg_admin_functions_path );
76
  }
77
  }
78
 
@@ -185,6 +205,21 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
185
  return isset( $row['status'] ) ? $row['status'] : false;
186
  }
187
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  /**
189
  * Get width and height of the original image.
190
  *
@@ -202,45 +237,28 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
202
  }
203
 
204
  /**
205
- * Delete the data related to optimization.
206
  *
207
- * @since 1.7
208
  * @access public
209
- * @author Grégory Viguier
210
- */
211
- public function delete_imagify_data() {
212
- if ( ! $this->get_row() ) {
213
- return;
214
- }
215
-
216
- $this->delete_row();
217
- }
218
-
219
- /**
220
- * Tell if the current file mime type is supported.
221
- *
222
- * @since 1.6.9
223
  * @author Grégory Viguier
224
  *
225
- * @return bool
226
  */
227
- public function is_mime_type_supported() {
228
- if ( isset( $this->is_mime_type_supported ) ) {
229
- return $this->is_mime_type_supported;
230
  }
231
 
232
- $mime_type = $this->filesystem->get_mime_type( $this->get_original_path() );
233
-
234
- if ( ! $mime_type ) {
235
- $this->is_mime_type_supported = false;
236
- return $this->is_mime_type_supported;
237
  }
238
 
239
- $mime_types = imagify_get_mime_types();
240
- $mime_types = array_flip( $mime_types );
241
 
242
- $this->is_mime_type_supported = isset( $mime_types[ $mime_type ] );
243
- return $this->is_mime_type_supported;
244
  }
245
 
246
  /**
@@ -255,7 +273,7 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
255
  static $sizes;
256
 
257
  if ( ! isset( $sizes ) ) {
258
- $sizes = C_Gallery_Storage::get_instance()->get_image_sizes();
259
  }
260
 
261
  return $sizes && $this->get_original_path();
@@ -272,14 +290,16 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
272
  public function update_metadata_size() {
273
  $size = $this->filesystem->get_image_size( $this->get_original_path() );
274
 
275
- if ( $size ) {
276
- $this->image->meta_data['width'] = $size['width'];
277
- $this->image->meta_data['height'] = $size['height'];
278
- $this->image->meta_data['full']['width'] = $size['width'];
279
- $this->image->meta_data['full']['height'] = $size['height'];
280
-
281
- nggdb::update_image_meta( $this->id, $this->image->meta_data );
282
  }
 
 
 
 
 
 
 
283
  }
284
 
285
  /**
@@ -413,14 +433,16 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
413
 
414
  // Get file path for original image.
415
  $attachment_path = $this->get_original_path();
 
416
 
417
  /**
418
  * Fires before optimizing an attachment.
419
  *
420
- * @since 1.5
 
421
  *
422
- * @param int $id The attachment ID
423
- */
424
  do_action( 'before_imagify_ngg_optimize_attachment', $this->id );
425
 
426
  $this->set_running_status();
@@ -436,6 +458,22 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
436
 
437
  $data = $this->fill_data( null, $response );
438
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
439
  // Save the optimization level.
440
  $this->update_row( array(
441
  // The pid column is needed in case the row doesn't exist yet.
@@ -444,7 +482,7 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
444
  ) );
445
 
446
  if ( ! $data ) {
447
- // Already optimized.
448
  $this->delete_running_status();
449
  return;
450
  }
@@ -457,6 +495,39 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
457
  'status' => 'success',
458
  ) );
459
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  /**
461
  * Fires after optimizing an attachment.
462
  *
@@ -467,39 +538,6 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
467
  */
468
  do_action( 'after_imagify_ngg_optimize_attachment', $this->id, $data );
469
 
470
- /**
471
- * Update NGG meta data.
472
- */
473
- $storage = C_Gallery_Storage::get_instance()->object;
474
- $image = $storage->_image_mapper->find( $this->id );
475
-
476
- if ( $image ) {
477
- $dimensions = $this->filesystem->get_image_size( $attachment_path );
478
- $md5 = md5_file( $attachment_path );
479
-
480
- if ( ( $dimensions || $md5 ) && ( empty( $image->meta_data['full'] ) || ! is_array( $image->meta_data['full'] ) ) ) {
481
- $image->meta_data['full'] = array(
482
- 'width' => 0,
483
- 'height' => 0,
484
- 'md5' => '',
485
- );
486
- }
487
-
488
- if ( $dimensions ) {
489
- $image->meta_data['width'] = $dimensions['width'];
490
- $image->meta_data['height'] = $dimensions['height'];
491
- $image->meta_data['full']['width'] = $dimensions['width'];
492
- $image->meta_data['full']['height'] = $dimensions['height'];
493
- }
494
-
495
- if ( $md5 ) {
496
- $image->meta_data['md5'] = $md5;
497
- $image->meta_data['full']['md5'] = $md5;
498
- }
499
-
500
- $storage->_image_mapper->save( $image );
501
- }
502
-
503
  $this->delete_running_status();
504
 
505
  return $data;
@@ -517,9 +555,8 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
517
  * @return array $data The optimization data.
518
  */
519
  public function optimize_thumbnails( $optimization_level = null, $data = array() ) {
520
- $storage = C_Gallery_Storage::get_instance();
521
- $sizes = $storage->get_image_sizes();
522
- $data = $data ? $data : $this->get_data();
523
 
524
  // Stop if the original image has an error.
525
  if ( $this->has_error() ) {
@@ -531,20 +568,23 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
531
  /**
532
  * Fires before optimizing all thumbnails.
533
  *
534
- * @since 1.5
 
535
  *
536
  * @param int $id The image ID.
537
- */
538
  do_action( 'before_imagify_ngg_optimize_thumbnails', $this->id );
539
 
540
  if ( $sizes ) {
 
 
541
  foreach ( $sizes as $size_key ) {
542
  if ( 'full' === $size_key || isset( $data['sizes'][ $size_key ]['success'] ) ) {
543
  continue;
544
  }
545
 
546
- $thumbnail_path = $storage->get_image_abspath( $this->image, $size_key );
547
- $thumbnail_url = $storage->get_image_url( $this->image, $size_key );
548
 
549
  // Optimize the thumbnail size.
550
  $response = do_imagify( $thumbnail_path, array(
@@ -557,19 +597,20 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
557
  $data = $this->fill_data( $data, $response, $size_key );
558
 
559
  /**
560
- * Filter the optimization data of a specific thumbnail.
561
- *
562
- * @since 1.5
563
- *
564
- * @param array $data The statistics data.
565
- * @param object $response The API response.
566
- * @param int $id The attachment ID.
567
- * @param string $thumbnail_path The attachment path.
568
- * @param string $thumbnail_url The attachment URL.
569
- * @param string $size_key The attachment size key.
570
- * @param bool $is_aggressive The optimization level.
571
- * @return array $data The new optimization data.
572
- */
 
573
  $data = apply_filters( 'imagify_fill_ngg_thumbnail_data', $data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level );
574
  }
575
 
@@ -581,16 +622,100 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
581
  /**
582
  * Fires after optimizing all thumbnails.
583
  *
584
- * @since 1.5
 
585
  *
586
  * @param int $id The image ID.
587
  * @param array $data The optimization data.
588
- */
589
  do_action( 'after_imagify_ngg_optimize_thumbnails', $this->id, $data );
590
 
591
  return $data;
592
  }
593
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
594
  /**
595
  * Re-optimize the given thumbnail sizes to the same level.
596
  * This is not used in this context.
@@ -626,18 +751,17 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
626
  return new WP_Error( 'no_backup', __( 'Backup image not found.', 'imagify' ) );
627
  }
628
 
629
- $storage = C_Gallery_Storage::get_instance()->object;
630
- $image = $storage->_image_mapper->find( $this->id );
631
 
632
- if ( ! $image ) {
633
  return new WP_Error( 'no_image', __( 'Image not found in NextGen Gallery data.', 'imagify' ) );
634
  }
635
 
636
  /**
637
  * Make some more tests before restoring the backup.
638
  */
639
- $full_abspath = $storage->get_image_abspath( $image );
640
- $backup_abspath = $storage->get_image_abspath( $image, 'backup' );
641
 
642
  if ( $backup_abspath === $full_abspath ) {
643
  return new WP_Error( 'same_path', __( 'Image path and backup path are identical.', 'imagify' ) );
@@ -650,10 +774,11 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
650
  /**
651
  * Fires before restoring an attachment.
652
  *
653
- * @since 1.5
 
654
  *
655
  * @param int $id The attachment ID.
656
- */
657
  do_action( 'before_imagify_ngg_restore_attachment', $this->id );
658
 
659
  if ( ! $this->filesystem->copy( $backup_abspath, $full_abspath, true, FS_CHMOD_FILE ) ) {
@@ -717,7 +842,7 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
717
 
718
  // 4- Common meta data.
719
  require_once( NGGALLERY_ABSPATH . '/lib/meta.php' );
720
- $meta_obj = new nggMeta( $image );
721
  $common_data = $meta_obj->get_common_meta();
722
 
723
  if ( $common_data ) {
@@ -738,9 +863,9 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
738
  'keywords' => '',
739
  );
740
 
741
- if ( ! empty( $image->meta_data ) && is_array( $image->meta_data ) ) {
742
- $image->meta_data = array_merge( $common_data, $image->meta_data );
743
- $common_data = array_intersect_key( $image->meta_data, $common_data );
744
  }
745
  }
746
 
@@ -751,15 +876,15 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
751
  */
752
  $failed = array();
753
 
754
- foreach ( $storage->get_image_sizes( $image ) as $named_size ) {
755
  if ( 'full' === $named_size ) {
756
  continue;
757
  }
758
 
759
- $params = $storage->get_image_size_params( $image, $named_size );
760
- $thumbnail = $storage->generate_image_clone(
761
  $backup_abspath,
762
- $storage->get_image_abspath( $image, $named_size ),
763
  $params
764
  );
765
 
@@ -790,14 +915,18 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
790
  $thumbnails_data[ $named_size ] = $size_meta;
791
  } // End foreach().
792
 
793
- do_action( 'ngg_recovered_image', $image );
794
 
795
  /**
796
  * Save the meta data.
797
  */
798
- $image->meta_data = array_merge( $backup_data, $full_data, $thumbnails_data, $common_data );
 
 
 
 
799
 
800
- $post_id = $storage->_image_mapper->save( $image );
801
 
802
  if ( ! $post_id ) {
803
  return new WP_Error( 'meta_data_not_saved', __( 'Related data could not be saved.', 'imagify' ) );
@@ -814,12 +943,62 @@ class Imagify_NGG_Attachment extends Imagify_Attachment {
814
  /**
815
  * Fires after restoring an attachment.
816
  *
817
- * @since 1.5
 
818
  *
819
  * @param int $id The attachment ID.
820
- */
821
  do_action( 'after_imagify_ngg_restore_attachment', $this->id );
822
 
823
  return true;
824
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
825
  }
14
  *
15
  * @var string
16
  */
17
+ const VERSION = '1.4';
18
 
19
  /**
20
  * The attachment SQL DB class.
31
  * @var object A nggImage object.
32
  * @since 1.5
33
  * @since 1.7 Not public anymore.
34
+ * @access protected
35
  */
36
  protected $image;
37
 
38
+ /**
39
+ * The storage object used by NGG.
40
+ *
41
+ * @var object A C_Gallery_Storage object (by default).
42
+ * @since 1.8.
43
+ * @access protected
44
+ */
45
+ protected $storage;
46
+
47
  /**
48
  * Tell if the file mime type can be optimized by Imagify.
49
  *
65
  */
66
  public function __construct( $id ) {
67
  if ( is_object( $id ) ) {
68
+ if ( $id instanceof nggImage ) {
69
+ $this->image = $id;
70
+ $this->id = (int) $id->pid;
71
+ } else {
72
+ $this->image = nggdb::find_image( (int) $id->pid );
73
+ $this->id = ! empty( $this->image->pid ) ? (int) $this->image->pid : 0;
74
+ }
75
  } else {
76
  $this->image = nggdb::find_image( absint( $id ) );
77
  $this->id = ! empty( $this->image->pid ) ? (int) $this->image->pid : 0;
79
 
80
  $this->get_row();
81
 
82
+ if ( ! empty( $this->image->_ngiw ) ) {
83
+ $this->storage = $this->image->_ngiw->get_storage()->object;
84
+ } else {
85
+ $this->storage = C_Gallery_Storage::get_instance()->object;
86
+ }
87
+
88
  $this->filesystem = Imagify_Filesystem::get_instance();
89
  $this->optimization_state_transient = 'imagify-ngg-async-in-progress-' . $this->id;
90
 
92
  $ngg_admin_functions_path = WP_PLUGIN_DIR . '/' . NGGFOLDER . '/products/photocrati_nextgen/modules/ngglegacy/admin/functions.php';
93
 
94
  if ( ! class_exists( 'nggAdmin' ) && $this->filesystem->exists( $ngg_admin_functions_path ) ) {
95
+ require_once $ngg_admin_functions_path;
96
  }
97
  }
98
 
205
  return isset( $row['status'] ) ? $row['status'] : false;
206
  }
207
 
208
+ /**
209
+ * Delete the data related to optimization.
210
+ *
211
+ * @since 1.7
212
+ * @access public
213
+ * @author Grégory Viguier
214
+ */
215
+ public function delete_imagify_data() {
216
+ if ( ! $this->get_row() ) {
217
+ return;
218
+ }
219
+
220
+ $this->delete_row();
221
+ }
222
+
223
  /**
224
  * Get width and height of the original image.
225
  *
237
  }
238
 
239
  /**
240
+ * Get the file mime type + file extension (if the file is supported).
241
  *
242
+ * @since 1.8
243
  * @access public
244
+ * @see wp_check_filetype()
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  * @author Grégory Viguier
246
  *
247
+ * @return object
248
  */
249
+ public function get_file_type() {
250
+ if ( isset( $this->file_type ) ) {
251
+ return $this->file_type;
252
  }
253
 
254
+ if ( ! $this->is_valid() ) {
255
+ $this->file_type = (object) array( 'ext' => '', 'type' => '' );
256
+ return $this->file_type;
 
 
257
  }
258
 
259
+ $this->file_type = (object) wp_check_filetype( $this->get_original_path(), imagify_get_mime_types( 'image' ) );
 
260
 
261
+ return $this->file_type;
 
262
  }
263
 
264
  /**
273
  static $sizes;
274
 
275
  if ( ! isset( $sizes ) ) {
276
+ $sizes = $this->get_image_sizes();
277
  }
278
 
279
  return $sizes && $this->get_original_path();
290
  public function update_metadata_size() {
291
  $size = $this->filesystem->get_image_size( $this->get_original_path() );
292
 
293
+ if ( ! $size ) {
294
+ return;
 
 
 
 
 
295
  }
296
+
297
+ $this->image->meta_data['width'] = $size['width'];
298
+ $this->image->meta_data['height'] = $size['height'];
299
+ $this->image->meta_data['full']['width'] = $size['width'];
300
+ $this->image->meta_data['full']['height'] = $size['height'];
301
+
302
+ nggdb::update_image_meta( $this->id, $this->image->meta_data );
303
  }
304
 
305
  /**
433
 
434
  // Get file path for original image.
435
  $attachment_path = $this->get_original_path();
436
+ $attachment_url = $this->get_original_url();
437
 
438
  /**
439
  * Fires before optimizing an attachment.
440
  *
441
+ * @since 1.5
442
+ * @author Jonathan Buttigieg
443
  *
444
+ * @param int $id The image ID
445
+ */
446
  do_action( 'before_imagify_ngg_optimize_attachment', $this->id );
447
 
448
  $this->set_running_status();
458
 
459
  $data = $this->fill_data( null, $response );
460
 
461
+ /**
462
+ * Filter the optimization data of the full size.
463
+ *
464
+ * @since 1.8
465
+ * @author Grégory Viguier
466
+ *
467
+ * @param array $data The statistics data.
468
+ * @param object $response The API response.
469
+ * @param int $id The attachment ID.
470
+ * @param string $attachment_path The attachment path.
471
+ * @param string $attachment_url The attachment URL.
472
+ * @param string $size_key The attachment size key. The value is obviously 'full' but it's kept for concistancy with other filters.
473
+ * @param int $optimization_level The optimization level.
474
+ */
475
+ $data = apply_filters( 'imagify_fill_ngg_full_size_data', $data, $response, $this->id, $attachment_path, $attachment_url, 'full', $optimization_level );
476
+
477
  // Save the optimization level.
478
  $this->update_row( array(
479
  // The pid column is needed in case the row doesn't exist yet.
482
  ) );
483
 
484
  if ( ! $data ) {
485
+ // Error or already optimized.
486
  $this->delete_running_status();
487
  return;
488
  }
495
  'status' => 'success',
496
  ) );
497
 
498
+ /**
499
+ * Update NGG meta data.
500
+ */
501
+ $image_data = $this->storage->_image_mapper->find( $this->id );
502
+
503
+ if ( ! $image_data ) {
504
+ $this->delete_running_status();
505
+ return $data;
506
+ }
507
+
508
+ $dimensions = $this->filesystem->get_image_size( $attachment_path );
509
+ $md5 = md5_file( $attachment_path );
510
+
511
+ if ( ( $dimensions || $md5 ) && ( empty( $image_data->meta_data['full'] ) || ! is_array( $image_data->meta_data['full'] ) ) ) {
512
+ $image_data->meta_data['full'] = array(
513
+ 'width' => 0,
514
+ 'height' => 0,
515
+ 'md5' => '',
516
+ );
517
+ }
518
+
519
+ if ( $dimensions ) {
520
+ $image_data->meta_data['width'] = $dimensions['width'];
521
+ $image_data->meta_data['height'] = $dimensions['height'];
522
+ $image_data->meta_data['full']['width'] = $dimensions['width'];
523
+ $image_data->meta_data['full']['height'] = $dimensions['height'];
524
+ }
525
+
526
+ if ( $md5 ) {
527
+ $image_data->meta_data['md5'] = $md5;
528
+ $image_data->meta_data['full']['md5'] = $md5;
529
+ }
530
+
531
  /**
532
  * Fires after optimizing an attachment.
533
  *
538
  */
539
  do_action( 'after_imagify_ngg_optimize_attachment', $this->id, $data );
540
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
  $this->delete_running_status();
542
 
543
  return $data;
555
  * @return array $data The optimization data.
556
  */
557
  public function optimize_thumbnails( $optimization_level = null, $data = array() ) {
558
+ $sizes = $this->get_image_sizes();
559
+ $data = $data ? $data : $this->get_data();
 
560
 
561
  // Stop if the original image has an error.
562
  if ( $this->has_error() ) {
568
  /**
569
  * Fires before optimizing all thumbnails.
570
  *
571
+ * @since 1.5
572
+ * @author Jonathan Buttigieg
573
  *
574
  * @param int $id The image ID.
575
+ */
576
  do_action( 'before_imagify_ngg_optimize_thumbnails', $this->id );
577
 
578
  if ( $sizes ) {
579
+ $image_data = $this->storage->_image_mapper->find( $this->id );
580
+
581
  foreach ( $sizes as $size_key ) {
582
  if ( 'full' === $size_key || isset( $data['sizes'][ $size_key ]['success'] ) ) {
583
  continue;
584
  }
585
 
586
+ $thumbnail_path = $this->storage->get_image_abspath( $image_data, $size_key );
587
+ $thumbnail_url = $this->storage->get_image_url( $image_data, $size_key );
588
 
589
  // Optimize the thumbnail size.
590
  $response = do_imagify( $thumbnail_path, array(
597
  $data = $this->fill_data( $data, $response, $size_key );
598
 
599
  /**
600
+ * Filter the optimization data of a specific thumbnail.
601
+ *
602
+ * @since 1.5
603
+ * @author Jonathan Buttigieg
604
+ *
605
+ * @param array $data The statistics data.
606
+ * @param object $response The API response.
607
+ * @param int $id The image ID.
608
+ * @param string $thumbnail_path The image path.
609
+ * @param string $thumbnail_url The image URL.
610
+ * @param string $size_key The image size key.
611
+ * @param bool $is_aggressive The optimization level.
612
+ * @return array $data The new optimization data.
613
+ */
614
  $data = apply_filters( 'imagify_fill_ngg_thumbnail_data', $data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level );
615
  }
616
 
622
  /**
623
  * Fires after optimizing all thumbnails.
624
  *
625
+ * @since 1.5
626
+ * @author Jonathan Buttigieg
627
  *
628
  * @param int $id The image ID.
629
  * @param array $data The optimization data.
630
+ */
631
  do_action( 'after_imagify_ngg_optimize_thumbnails', $this->id, $data );
632
 
633
  return $data;
634
  }
635
 
636
+ /**
637
+ * Optimize one size.
638
+ *
639
+ * @since 1.8
640
+ * @access public
641
+ * @author Grégory Viguier
642
+ *
643
+ * @param string $size The thumbnail size.
644
+ */
645
+ public function optimize_new_thumbnail( $size ) {
646
+ // Check if the attachment extension is allowed.
647
+ if ( ! $this->is_extension_supported() ) {
648
+ return;
649
+ }
650
+
651
+ if ( ! $this->is_optimized() ) {
652
+ // The main image is not optimized.
653
+ return;
654
+ }
655
+
656
+ $data = $this->get_data();
657
+
658
+ if ( isset( $data['sizes'][ $size ]['success'] ) ) {
659
+ // This thumbnail has already been processed.
660
+ return;
661
+ }
662
+
663
+ $sizes = $this->get_image_sizes();
664
+ $sizes = array_flip( $sizes );
665
+
666
+ if ( ! isset( $sizes[ $size ] ) ) {
667
+ // This size doesn't exist.
668
+ return;
669
+ }
670
+
671
+ /**
672
+ * Fires before optimizing a thumbnail.
673
+ *
674
+ * @since 1.8
675
+ * @author Grégory Viguier
676
+ *
677
+ * @param int $id The image ID.
678
+ */
679
+ do_action( 'before_imagify_ngg_optimize_new_thumbnail', $this->id );
680
+
681
+ $this->set_running_status();
682
+
683
+ $image_data = $this->storage->_image_mapper->find( $this->id );
684
+ $thumbnail_path = $this->storage->get_image_abspath( $image_data, $size );
685
+ $thumbnail_url = $this->storage->get_image_url( $image_data, $size );
686
+ $optimization_level = $this->get_optimization_level();
687
+
688
+ // Optimize the thumbnail size.
689
+ $response = do_imagify( $thumbnail_path, array(
690
+ 'optimization_level' => $optimization_level,
691
+ 'context' => 'NGG',
692
+ 'keep_exif' => true,
693
+ 'backup' => false,
694
+ ) );
695
+
696
+ $data = $this->fill_data( $data, $response, $size );
697
+
698
+ /** This filter is documented in inc/3rd-party/nextgen-gallery/inc/classes/class-imagify-ngg-attachment.php. */
699
+ $data = apply_filters( 'imagify_fill_ngg_thumbnail_data', $data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size, $optimization_level );
700
+
701
+ $this->update_row( array(
702
+ 'data' => $data,
703
+ ) );
704
+
705
+ /**
706
+ * Fires after optimizing a thumbnail.
707
+ *
708
+ * @since 1.8
709
+ * @author Grégory Viguier
710
+ *
711
+ * @param int $id The image ID.
712
+ * @param array $data The optimization data.
713
+ */
714
+ do_action( 'after_imagify_ngg_optimize_new_thumbnail', $this->id, $data );
715
+
716
+ $this->delete_running_status();
717
+ }
718
+
719
  /**
720
  * Re-optimize the given thumbnail sizes to the same level.
721
  * This is not used in this context.
751
  return new WP_Error( 'no_backup', __( 'Backup image not found.', 'imagify' ) );
752
  }
753
 
754
+ $image_data = $this->storage->_image_mapper->find( $this->id );
 
755
 
756
+ if ( ! $image_data ) {
757
  return new WP_Error( 'no_image', __( 'Image not found in NextGen Gallery data.', 'imagify' ) );
758
  }
759
 
760
  /**
761
  * Make some more tests before restoring the backup.
762
  */
763
+ $full_abspath = $this->storage->get_image_abspath( $image_data );
764
+ $backup_abspath = $this->storage->get_image_abspath( $image_data, 'backup' );
765
 
766
  if ( $backup_abspath === $full_abspath ) {
767
  return new WP_Error( 'same_path', __( 'Image path and backup path are identical.', 'imagify' ) );
774
  /**
775
  * Fires before restoring an attachment.
776
  *
777
+ * @since 1.5
778
+ * @author Jonathan Buttigieg
779
  *
780
  * @param int $id The attachment ID.
781
+ */
782
  do_action( 'before_imagify_ngg_restore_attachment', $this->id );
783
 
784
  if ( ! $this->filesystem->copy( $backup_abspath, $full_abspath, true, FS_CHMOD_FILE ) ) {
842
 
843
  // 4- Common meta data.
844
  require_once( NGGALLERY_ABSPATH . '/lib/meta.php' );
845
+ $meta_obj = new nggMeta( $image_data );
846
  $common_data = $meta_obj->get_common_meta();
847
 
848
  if ( $common_data ) {
863
  'keywords' => '',
864
  );
865
 
866
+ if ( ! empty( $image_data->meta_data ) && is_array( $image_data->meta_data ) ) {
867
+ $image_data->meta_data = array_merge( $common_data, $image_data->meta_data );
868
+ $common_data = array_intersect_key( $image_data->meta_data, $common_data );
869
  }
870
  }
871
 
876
  */
877
  $failed = array();
878
 
879
+ foreach ( $this->get_image_sizes() as $named_size ) {
880
  if ( 'full' === $named_size ) {
881
  continue;
882
  }
883
 
884
+ $params = $this->storage->get_image_size_params( $image_data, $named_size );
885
+ $thumbnail = $this->storage->generate_image_clone(
886
  $backup_abspath,
887
+ $this->storage->get_image_abspath( $image_data, $named_size ),
888
  $params
889
  );
890
 
915
  $thumbnails_data[ $named_size ] = $size_meta;
916
  } // End foreach().
917
 
918
+ do_action( 'ngg_recovered_image', $image_data );
919
 
920
  /**
921
  * Save the meta data.
922
  */
923
+ $image_data->meta_data = array_merge( $backup_data, $full_data, $thumbnails_data, $common_data );
924
+
925
+ // Keep our property up to date.
926
+ $this->image->_ngiw->_cache['meta_data'] = $image_data->meta_data;
927
+ $this->image->_ngiw->_orig_image = $image_data;
928
 
929
+ $post_id = $this->storage->_image_mapper->save( $image_data );
930
 
931
  if ( ! $post_id ) {
932
  return new WP_Error( 'meta_data_not_saved', __( 'Related data could not be saved.', 'imagify' ) );
943
  /**
944
  * Fires after restoring an attachment.
945
  *
946
+ * @since 1.5
947
+ * @author Jonathan Buttigieg
948
  *
949
  * @param int $id The attachment ID.
950
+ */
951
  do_action( 'after_imagify_ngg_restore_attachment', $this->id );
952
 
953
  return true;
954
  }
955
+
956
+ /**
957
+ * Get the image sizes.
958
+ *
959
+ * @since 1.8
960
+ * @access public
961
+ * @author Grégory Viguier
962
+ *
963
+ * @return array
964
+ */
965
+ public function get_image_sizes() {
966
+ $sizes = array(
967
+ 'full',
968
+ );
969
+
970
+ // Remove common values (that have no value for us here, lol).
971
+ $image_data = array_diff_key( $this->image->meta_data, array(
972
+ 'backup' => 1,
973
+ 'width' => 1,
974
+ 'height' => 1,
975
+ 'md5' => 1,
976
+ 'full' => 1,
977
+ 'aperture' => 1,
978
+ 'credit' => 1,
979
+ 'camera' => 1,
980
+ 'caption' => 1,
981
+ 'created_timestamp' => 1,
982
+ 'copyright' => 1,
983
+ 'focal_length' => 1,
984
+ 'iso' => 1,
985
+ 'shutter_speed' => 1,
986
+ 'flash' => 1,
987
+ 'title' => 1,
988
+ 'keywords' => 1,
989
+ 'saved' => 1,
990
+ ) );
991
+
992
+ if ( ! $image_data ) {
993
+ return $sizes;
994
+ }
995
+
996
+ foreach ( $image_data as $size_name => $size_data ) {
997
+ if ( isset( $size_data['width'], $size_data['height'], $size_data['filename'], $size_data['generated'] ) ) {
998
+ $sizes[] = $size_name;
999
+ }
1000
+ }
1001
+
1002
+ return $sizes;
1003
+ }
1004
  }
inc/3rd-party/nextgen-gallery/inc/classes/class-imagify-ngg-db.php CHANGED
File without changes
inc/3rd-party/nextgen-gallery/inc/classes/class-imagify-ngg-dynamic-thumbnails-background-process.php ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ defined( 'ABSPATH' ) || die( 'Cheatin\' uh?' );
3
+
4
+ /**
5
+ * Class that handles background processing of thumbnails dynamically generated.
6
+ *
7
+ * @since 1.8
8
+ * @author Grégory Viguier
9
+ */
10
+ class Imagify_NGG_Dynamic_Thumbnails_Background_Process extends WP_Background_Process {
11
+
12
+ /**
13
+ * Class version.
14
+ *
15
+ * @var string
16
+ * @since 1.8
17
+ * @author Grégory Viguier
18
+ */
19
+ const VERSION = '1.0';
20
+
21
+ /**
22
+ * Prefix used to build the global process identifier.
23
+ *
24
+ * @var string
25
+ * @since 1.8
26
+ * @access protected
27
+ * @author Grégory Viguier
28
+ */
29
+ protected $prefix = 'imagify';
30
+
31
+ /**
32
+ * Action.
33
+ *
34
+ * @var string
35
+ * @since 1.8
36
+ * @access protected
37
+ * @author Grégory Viguier
38
+ */
39
+ protected $action = 'ngg_dynamic_thumbnails';
40
+
41
+ /**
42
+ * The single instance of the class.
43
+ *
44
+ * @var object
45
+ * @since 1.8
46
+ * @access protected
47
+ * @author Grégory Viguier
48
+ */
49
+ protected static $_instance;
50
+
51
+
52
+ /**
53
+ * Get the main Instance.
54
+ *
55
+ * @since 1.8
56
+ * @access public
57
+ * @author Grégory Viguier
58
+ *
59
+ * @return object Main instance.
60
+ */
61
+ public static function get_instance() {
62
+ if ( ! isset( self::$_instance ) ) {
63
+ self::$_instance = new self();
64
+ }
65
+
66
+ return self::$_instance;
67
+ }
68
+
69
+
70
+ /** ----------------------------------------------------------------------------------------- */
71
+ /** BACKGROUND PROCESS ====================================================================== */
72
+ /** ----------------------------------------------------------------------------------------- */
73
+
74
+ /**
75
+ * Push to queue.
76
+ *
77
+ * @since 1.8
78
+ * @access public
79
+ * @author Grégory Viguier
80
+ *
81
+ * @param array $data {
82
+ * The data to push in queue.
83
+ *
84
+ * @type int $id The image ID. Required.
85
+ * @type string $size The thumbnail size. Required.
86
+ * }
87
+ * @return object Class instance.
88
+ */
89
+ public function push_to_queue( $data ) {
90
+ $key = $data['id'] . '|' . $data['size'];
91
+
92
+ $this->data[ $key ] = $data;
93
+
94
+ return $this;
95
+ }
96
+
97
+ /**
98
+ * Dispatch.
99
+ *
100
+ * @since 1.8
101
+ * @access public
102
+ * @author Grégory Viguier
103
+ *
104
+ * @return array|WP_Error
105
+ */
106
+ public function dispatch() {
107
+ if ( ! empty( $this->data ) ) {
108
+ return parent::dispatch();
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Tell if a task is already in the queue.
114
+ *
115
+ * @since 1.8
116
+ * @access public
117
+ * @author Grégory Viguier
118
+ *
119
+ * @param array $data {
120
+ * The data to test against the queue.
121
+ *
122
+ * @type int $id The image ID. Required.
123
+ * @type string $size The thumbnail size. Required.
124
+ * }
125
+ * @return bool
126
+ */
127
+ public function is_in_queue( $data ) {
128
+ $key = $data['id'] . '|' . $data['size'];
129
+
130
+ return isset( $this->data[ $key ] );
131
+ }
132
+
133
+ /**
134
+ * Task: optimize the thumbnail.
135
+ *
136
+ * @since 1.8
137
+ * @access public
138
+ * @author Grégory Viguier
139
+ *
140
+ * @param array $item {
141
+ * The data to test against the queue.
142
+ *
143
+ * @type int $id The image ID. Required.
144
+ * @type string $size The thumbnail size. Required.
145
+ * }
146
+ * @return bool False to remove the item from the queue.
147
+ */
148
+ protected function task( $item ) {
149
+ $attachment_id = absint( $item['id'] );
150
+ $size = sanitize_text_field( $item['size'] );
151
+
152
+ if ( ! $attachment_id || ! $size ) {
153
+ return false;
154
+ }
155
+
156
+ $attachment = get_imagify_attachment( 'NGG', $attachment_id, 'ngg_optimize_dynamic_thumbnail' );
157
+
158
+ $attachment->optimize_new_thumbnail( $size );
159
+
160
+ return false;
161
+ }
162
+ }
inc/3rd-party/nextgen-gallery/inc/common/attachments.php CHANGED
@@ -66,7 +66,7 @@ add_filter( 'ngg_medialibrary_imported_image', '_imagify_ngg_media_library_impor
66
  function _imagify_ngg_media_library_imported_image_data( $image, $attachment ) {
67
  $attachment = get_imagify_attachment( 'wp', $attachment->ID, 'ngg_medialibrary_imported_image' );
68
 
69
- if ( ! $attachment->get_status() ) {
70
  // The image is not optimized.
71
  return $image;
72
  }
@@ -108,6 +108,63 @@ function _imagify_ngg_media_library_imported_image_data( $image, $attachment ) {
108
  return $image;
109
  }
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  /**
112
  * Delete the Imagify data when an image is deleted.
113
  *
66
  function _imagify_ngg_media_library_imported_image_data( $image, $attachment ) {
67
  $attachment = get_imagify_attachment( 'wp', $attachment->ID, 'ngg_medialibrary_imported_image' );
68
 
69
+ if ( ! $attachment->is_optimized() ) {
70
  // The image is not optimized.
71
  return $image;
72
  }
108
  return $image;
109
  }
110
 
111
+ add_action( 'ngg_generated_image', 'imagify_ngg_maybe_add_dynamic_thumbnail_to_background_process', IMAGIFY_INT_MAX, 2 );
112
+ /**
113
+ * Add a dynamically generated thumbnail to the background process queue.
114
+ *
115
+ * @since 1.8
116
+ * @author Grégory Viguier
117
+ *
118
+ * @param object $image A NGG image object.
119
+ * @param string $size The thumbnail size name.
120
+ */
121
+ function imagify_ngg_maybe_add_dynamic_thumbnail_to_background_process( $image, $size ) {
122
+ static $done = false;
123
+
124
+ $attachment = get_imagify_attachment( 'NGG', $image, 'ngg_maybe_add_dynamic_thumbnail_to_background_process' );
125
+
126
+ if ( ! $attachment->is_optimized() ) {
127
+ // The main image is not optimized.
128
+ return;
129
+ }
130
+
131
+ $data = $attachment->get_data();
132
+
133
+ if ( isset( $data['sizes'][ $size ]['success'] ) ) {
134
+ // This thumbnail has already been processed.
135
+ return;
136
+ }
137
+
138
+ $process = Imagify_NGG_Dynamic_Thumbnails_Background_Process::get_instance();
139
+ $data = array(
140
+ 'id' => $attachment->get_id(),
141
+ 'size' => $size,
142
+ );
143
+
144
+ if ( $process->is_in_queue( $data ) ) {
145
+ // Duplicate.
146
+ return;
147
+ }
148
+
149
+ // Do the optimization asynchroniously.
150
+ $process->push_to_queue( $data );
151
+
152
+ if ( ! $done ) {
153
+ $done = true;
154
+ add_action( 'shutdown', 'imagify_ngg_dispatch_dynamic_thumbnail_background_process' );
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Dispatch the optimization process.
160
+ *
161
+ * @since 1.8
162
+ * @author Grégory Viguier
163
+ */
164
+ function imagify_ngg_dispatch_dynamic_thumbnail_background_process() {
165
+ Imagify_NGG_Dynamic_Thumbnails_Background_Process::get_instance()->save()->dispatch();
166
+ }
167
+
168
  /**
169
  * Delete the Imagify data when an image is deleted.
170
  *
inc/3rd-party/nextgen-gallery/inc/functions/admin-stats.php CHANGED
File without changes
inc/3rd-party/nextgen-gallery/inc/functions/attachments.php CHANGED
File without changes
inc/3rd-party/nextgen-gallery/inc/functions/common.php CHANGED
File without changes
inc/3rd-party/nextgen-gallery/nextgen-gallery.php CHANGED
@@ -19,6 +19,7 @@ require( IMAGIFY_NGG_COMMON_PATH . 'attachments.php' );
19
 
20
  Imagify_NGG::get_instance()->init();
21
  Imagify_NGG_DB::get_instance()->init();
 
22
 
23
  if ( is_admin() ) {
24
  require( IMAGIFY_NGG_ADMIN_PATH . 'enqueue.php' );
19
 
20
  Imagify_NGG::get_instance()->init();
21
  Imagify_NGG_DB::get_instance()->init();
22
+ Imagify_NGG_Dynamic_Thumbnails_Background_Process::get_instance();
23
 
24
  if ( is_admin() ) {
25
  require( IMAGIFY_NGG_ADMIN_PATH . 'enqueue.php' );
inc/3rd-party/regenerate-thumbnails/inc/classes/class-imagify-regenerate-thumbnails.php CHANGED
@@ -185,6 +185,10 @@ class Imagify_Regenerate_Thumbnails {
185
 
186
  $attachment = get_imagify_attachment( $context, $attachment_id, self::ACTION );
187
 
 
 
 
 
188
  // Optimize.
189
  $attachment->reoptimize_thumbnails( $_POST['sizes'] );
190
 
@@ -216,7 +220,7 @@ class Imagify_Regenerate_Thumbnails {
216
 
217
  $attachment = get_imagify_attachment( 'wp', $attachment_id, 'regenerate_thumbnails' );
218
 
219
- if ( ! $attachment->is_valid() || ! $attachment->is_extension_supported() || ! $attachment->is_optimized() ) {
220
  return false;
221
  }
222
 
@@ -285,7 +289,7 @@ class Imagify_Regenerate_Thumbnails {
285
  }
286
 
287
  /**
288
- * Backup the optimized full-sized file and replace it by the original backup file.
289
  *
290
  * @since 1.7.1
291
  * @access protected
185
 
186
  $attachment = get_imagify_attachment( $context, $attachment_id, self::ACTION );
187
 
188
+ if ( ! $attachment->is_valid() || ! $attachment->is_image() ) {
189
+ wp_send_json_error();
190
+ }
191
+
192
  // Optimize.
193
  $attachment->reoptimize_thumbnails( $_POST['sizes'] );
194
 
220
 
221
  $attachment = get_imagify_attachment( 'wp', $attachment_id, 'regenerate_thumbnails' );
222
 
223
+ if ( ! $attachment->is_valid() || ! $attachment->is_image() || ! $attachment->is_optimized() ) {
224
  return false;
225
  }
226
 
289
  }
290
 
291
  /**
292
+ * Put the optimized full-sized file back.
293
  *
294
  * @since 1.7.1
295
  * @access protected
inc/3rd-party/wp-retina-2x.php DELETED
@@ -1,87 +0,0 @@
1
- <?php
2
- defined( 'ABSPATH' ) || die( 'Cheatin\' uh?' );
3
-
4
- if ( function_exists( 'wr2x_delete_attachment' ) && function_exists( 'wr2x_generate_images' ) ) :
5
-
6
- /**
7
- * Process to restore all retina versions for an attachment.
8
- *
9
- * @since 1.0
10
- */
11
-
12
- add_action( 'before_imagify_restore_attachment', '_imagify_wr2x_delete_attachment_on_restore' );
13
- /**
14
- * Remove all retina versions if exists.
15
- *
16
- * @since 1.0
17
- *
18
- * @param int $attachment_id An attachment ID.
19
- */
20
- function _imagify_wr2x_delete_attachment_on_restore( $attachment_id ) {
21
- wr2x_delete_attachment( $attachment_id );
22
- }
23
-
24
- add_action( 'after_imagify_restore_attachment', '_imagify_wr2x_generate_images_on_restore' );
25
- /**
26
- * Regenerate all retina versions.
27
- *
28
- * @since 1.0
29
- *
30
- * @param int $attachment_id An attachment ID.
31
- */
32
- function _imagify_wr2x_generate_images_on_restore( $attachment_id ) {
33
- $metadata = wp_get_attachment_metadata( $attachment_id );
34
- wr2x_generate_images( $metadata );
35
- }
36
-
37
- endif;
38
-
39
- if ( function_exists( 'wr2x_get_retina' ) ) :
40
-
41
- /**
42
- * Process to generate the retina version of a thumbnail.
43
- *
44
- * @since 1.0
45
- */
46
-
47
- add_filter( 'imagify_fill_thumbnail_data', '_imagify_optimize_wr2x', 10, 7 );
48
- /**
49
- * Filter the optimization data of each thumbnail.
50
- *
51
- * @since 1.0
52
- *
53
- * @param array $data The statistics data.
54
- * @param object $response The API response.
55
- * @param int $id The attachment ID.
56
- * @param string $path The attachment path.
57
- * @param string $url The attachment URL.
58
- * @param string $size_key The attachment size key.
59
- * @param bool $optimization_level The optimization level.
60
- * @return array $data The new optimization data.
61
- */
62
- function _imagify_optimize_wr2x( $data, $response, $id, $path, $url, $size_key, $optimization_level ) {
63
- /**
64
- * Allow to optimize the retina version generated by WP Retina x2.
65
- *
66
- * @since 1.0
67
- *
68
- * @param bool $do_retina True will force the optimization.
69
- */
70
- $do_retina = apply_filters( 'do_imagify_optimize_retina', true );
71
- $retina_path = wr2x_get_retina( $path );
72
-
73
- if ( empty( $retina_path ) || ! $do_retina ) {
74
- return $data;
75
- }
76
-
77
- $response = do_imagify( $retina_path, array(
78
- 'backup' => false,
79
- 'optimization_level' => $optimization_level,
80
- 'context' => 'wp-retina',
81
- ) );
82
- $attachment = get_imagify_attachment( 'wp', $id, 'imagify_fill_thumbnail_data' );
83
-
84
- return $attachment->fill_data( $data, $response, $size_key . '@2x' );
85
- }
86
-
87
- endif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/3rd-party/wp-retina-2x/inc/classes/class-imagify-wp-retina-2x-core.php ADDED
@@ -0,0 +1,1684 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ defined( 'ABSPATH' ) || die( 'Cheatin\' uh?' );
3
+
4
+ /**
5
+ * Class that handles all the main tools for compatibility with WP Retina 2x plugin.
6
+ *
7
+ * @since 1.8
8
+ * @author Grégory Viguier
9
+ */
10
+ class Imagify_WP_Retina_2x_Core {
11
+
12
+ /**
13
+ * Class version.
14
+ *
15
+ * @var string
16
+ * @since 1.8
17
+ * @author Grégory Viguier
18
+ */
19
+ const VERSION = '1.0';
20
+
21
+ /**
22
+ * Filesystem object.
23
+ *
24
+ * @var object Imagify_Filesystem
25
+ * @since 1.8
26
+ * @access protected
27
+ * @author Grégory Viguier
28
+ */
29
+ protected $filesystem;
30
+
31
+ /**
32
+ * Used to store methods that should not run for a time being.
33
+ *
34
+ * @var array
35
+ * @since 1.8
36
+ * @access protected
37
+ * @author Grégory Viguier
38
+ */
39
+ protected static $prevented = array();
40
+
41
+ /**
42
+ * The constructor.
43
+ *
44
+ * @since 1.8
45
+ * @access public
46
+ * @author Grégory Viguier
47
+ */
48
+ public function __construct() {
49
+ $this->filesystem = Imagify_Filesystem::get_instance();
50
+ }
51
+
52
+
53
+ /** ----------------------------------------------------------------------------------------- */
54
+ /** GENERATE RETINA IMAGES ================================================================== */
55
+ /** ----------------------------------------------------------------------------------------- */
56
+
57
+ /**
58
+ * Generate retina images (except full size), and optimize them if the non-retina images are.
59
+ *
60
+ * @since 1.8
61
+ * @access public
62
+ * @author Grégory Viguier
63
+ *
64
+ * @param object $attachment An Imagify attachment.
65
+ * @return bool|object True on success, false if prevented, a WP_Error object on failure.
66
+ */
67
+ public function generate_retina_images( $attachment ) {
68
+ $tests = $this->validate( __FUNCTION__, $attachment );
69
+
70
+ if ( true !== $tests ) {
71
+ return $tests;
72
+ }
73
+
74
+ // Backup the optimized full-sized image and replace it by the original backup file, so it can be used to create new retina images.
75
+ $this->backup_optimized_file( $attachment );
76
+
77
+ if ( ! $this->filesystem->exists( $attachment->get_original_path() ) ) {
78
+ return new WP_Error( 'file_missing', 'The main file does not exist.' );
79
+ }
80
+
81
+ // Create retina images.
82
+ wr2x_generate_images( wp_get_attachment_metadata( $attachment->get_id() ) );
83
+
84
+ // Put the optimized full-sized file back.
85
+ $this->put_optimized_file_back( $attachment );
86
+
87
+ /**
88
+ * If the non-retina images are optimized by Imagify (or at least the user wanted it to be optimized at some point, and now has a "already optimized" or "error" status), optimize newly created retina files.
89
+ * If the retina version of the full size exists and is not optimized yet, it will be processed as well.
90
+ */
91
+ if ( $attachment->is_optimized() && $this->can_auto_optimize() ) {
92
+ $this->optimize_retina_images( $attachment );
93
+ }
94
+
95
+ return true;
96
+ }
97
+
98
+ /**
99
+ * Delete previous retina images and recreate them (except full size), and optimize them if they previously were.
100
+ *
101
+ * @since 1.8
102
+ * @access public
103
+ * @author Grégory Viguier
104
+ *
105
+ * @param object $attachment An Imagify attachment.
106
+ * @return bool|object True on success, false if prevented, a WP_Error object on failure.
107
+ */
108
+ public function regenerate_retina_images( $attachment ) {
109
+ $tests = $this->validate( __FUNCTION__, $attachment );
110
+
111
+ if ( true !== $tests ) {
112
+ return $tests;
113
+ }
114
+
115
+ // Delete the retina files and remove retina sizes from Imagify data.
116
+ $result = $this->delete_retina_images( $attachment );
117
+
118
+ if ( is_wp_error( $result ) ) {
119
+ return $result;
120
+ }
121
+
122
+ // Create new retina files (and optimize them if they previously were).
123
+ return $this->generate_retina_images( $attachment );
124
+ }
125
+
126
+
127
+ /** ----------------------------------------------------------------------------------------- */
128
+ /** DELETE RETINA IMAGES ==================================================================== */
129
+ /** ----------------------------------------------------------------------------------------- */
130
+
131
+ /**
132
+ * Delete the retina images. Also removes the related Imagify data.
133
+ *
134
+ * @since 1.8
135
+ * @access public
136
+ * @author Grégory Viguier
137
+ *
138
+ * @param object $attachment An Imagify attachment.
139
+ * @param bool $delete_full_image True to also delete the retina version of the full size.
140
+ * @return bool|object True on success, false if prevented, a WP_Error object on failure.
141
+ */
142
+ public function delete_retina_images( $attachment, $delete_full_image = false ) {
143
+ $tests = $this->validate( __FUNCTION__, $attachment, array(
144
+ 'metadata_dimensions' => 'error',
145
+ ) );
146
+
147
+ if ( true !== $tests ) {
148
+ return $tests;
149
+ }
150
+
151
+ /**
152
+ * To be a bit faster we update the data at once at the end.
153
+ *
154
+ * @see Imagify_WP_Retina_2x::remove_retina_thumbnail_data_hook().
155
+ */
156
+ $this->prevent( 'remove_retina_image_data_by_filename' );
157
+
158
+ // Delete the retina thumbnails.
159
+ wr2x_delete_attachment( $attachment->get_id(), $delete_full_image );
160
+
161
+ $this->allow( 'remove_retina_image_data_by_filename' );
162
+
163
+ // Remove retina sizes from Imagify data.
164
+ $this->remove_retina_images_data( $attachment, $delete_full_image );
165
+
166
+ return true;
167
+ }
168
+
169
+ /**
170
+ * Delete the retina version of the full size.
171
+ *
172
+ * @since 1.8
173
+ * @access public
174
+ * @author Grégory Viguier
175
+ *
176
+ * @param object $attachment An Imagify attachment.
177
+ * @return bool|object True on success, false if prevented, a WP_Error object on failure.
178
+ */
179
+ public function delete_full_retina_image( $attachment ) {
180
+ $tests = $this->validate( __FUNCTION__, $attachment, array(
181
+ 'metadata_file' => false,
182
+ 'metadata_sizes' => false,
183
+ ) );
184
+
185
+ if ( true !== $tests ) {
186
+ return $tests;
187
+ }
188
+
189
+ $retina_path = wr2x_get_retina( $attachment->get_original_path() );
190
+
191
+ if ( $retina_path ) {
192
+ // The file exists.
193
+ $this->filesystem->delete( $retina_path );
194
+ }
195
+
196
+ // Delete related Imagify data.
197
+ return $this->remove_size_from_imagify_data( $attachment, 'full@2x' );
198
+ }
199
+
200
+
201
+ /** ----------------------------------------------------------------------------------------- */
202
+ /** REPLACE IMAGES ========================================================================== */
203
+ /** ----------------------------------------------------------------------------------------- */
204
+
205
+ /**
206
+ * Replace an attachment (except the retina version of the full size).
207
+ *
208
+ * @since 1.8
209
+ * @access public
210
+ * @author Grégory Viguier
211
+ *
212
+ * @param object $attachment An Imagify attachment.
213
+ * @param string $file_path Path to the new file.
214
+ * @return bool|object True on success, false if prevented, a WP_Error object on failure.
215
+ */
216
+ public function replace_attachment( $attachment, $file_path ) {
217
+ $tests = $this->validate( __FUNCTION__, $attachment, array(
218
+ 'metadata_sizes' => false,
219
+ ) );
220
+
221
+ if ( true !== $tests ) {
222
+ return $tests;
223
+ }
224
+
225
+ $attachment_id = $attachment->get_id();
226
+ $sizes = $this->get_attachment_sizes( $attachment );
227
+ $original_path = $attachment->get_original_path();
228
+ $dir_path = $this->filesystem->path_info( $original_path, 'dir_path' );
229
+
230
+ // Insert the new file (and overwrite the full size).
231
+ $moved = $this->filesystem->move( $file_path, $original_path, true );
232
+
233
+ if ( ! $moved ) {
234
+ return new WP_Error( 'not_writable', __( 'Replacement failed.', 'imagify' ) );
235
+ }
236
+
237
+ // Delete retina images.
238
+ $this->delete_retina_images( $attachment );
239
+
240
+ // Delete the non-retina images.
241
+ if ( $sizes ) {
242
+ foreach ( $sizes as $name => $attr ) {
243
+ $size_path = $dir_path . $attr['file'];
244
+
245
+ if ( $this->filesystem->exists( $size_path ) && $this->filesystem->is_file( $size_path ) ) {
246
+ // If the deletion fails, we're screwed anyway since the main file has been deleted, so no need to return an error here.
247
+ $this->filesystem->delete( $size_path );
248
+ }
249
+ }
250
+ }
251
+
252
+ // Get some Imagify data before deleting everything.
253
+ $optimization_level = $this->get_optimization_level( $attachment );
254
+ $full_retina_data = $attachment->get_data();
255
+ $full_retina_data = ! empty( $full_retina_data['sizes']['full@2x'] ) ? $full_retina_data['sizes']['full@2x'] : false;
256
+ $full_retina_optimized = $full_retina_data && ! empty( $full_retina_data['success'] );
257
+
258
+ // Delete the Imagify data.
259
+ $attachment->delete_imagify_data();
260
+
261
+ // Delete the backup file.
262
+ $attachment->delete_backup();
263
+
264
+ // Prevent auto-optimization.
265
+ add_filter( 'imagify_auto_optimize_attachment', '__return_false', 442 );
266
+
267
+ // Generate the non-retina images and the related WP metadata.
268
+ wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $original_path ) );
269
+
270
+ // Allow auto-optimization back.
271
+ remove_filter( 'imagify_auto_optimize_attachment', '__return_false', 442 );
272
+
273
+ // Generate retina images (since the Imagify data has been deleted, the images won't be optimized here).
274
+ $result = $this->generate_retina_images( $attachment );
275
+
276
+ if ( is_wp_error( $result ) ) {
277
+ if ( $full_retina_optimized ) {
278
+ // The retina version of the full size is optimized: restore it overwise the user may optimize it again some day.
279
+ $this->restore_full_retina_file( $attachment );
280
+ }
281
+
282
+ return $result;
283
+ }
284
+
285
+ if ( $this->can_auto_optimize() ) {
286
+ if ( $full_retina_optimized ) {
287
+ // Don't optimize the retina full size, it already is.
288
+ remove_filter( 'imagify_fill_full_size_data', array( Imagify_WP_Retina_2x::get_instance(), 'optimize_full_retina_version_hook' ) );
289
+ }
290
+
291
+ /**
292
+ * Optimize everyone.
293
+ *
294
+ * @see Imagify_WP_Retina_2x::optimize_full_retina_version_hook()
295
+ * @see Imagify_WP_Retina_2x::optimize_retina_version_hook()
296
+ * @see Imagify_WP_Retina_2x::maybe_optimize_unauthorized_retina_version_hook().
297
+ */
298
+ $attachment->optimize( $optimization_level );
299
+
300
+ if ( $full_retina_optimized ) {
301
+ add_filter( 'imagify_fill_full_size_data', array( Imagify_WP_Retina_2x::get_instance(), 'optimize_full_retina_version_hook' ), 10, 8 );
302
+
303
+ if ( $attachment->is_optimized() ) {
304
+ // Put data back.
305
+ $data = $attachment->get_data();
306
+ $data['sizes']['full@2x'] = $full_retina_data;
307
+ update_post_meta( $attachment_id, '_imagify_data', $data );
308
+ } else {
309
+ $this->restore_full_retina_file( $attachment );
310
+ }
311
+ }
312
+ } elseif ( $full_retina_optimized ) {
313
+ // The retina version of the full size is optimized: restore it overwise the user may optimize it again some day.
314
+ $this->restore_full_retina_file( $attachment );
315
+ }
316
+
317
+ return true;
318
+ }
319
+
320
+ /**
321
+ * Replace an attachment (except the retina version of the full size).
322
+ *
323
+ * @since 1.8
324
+ * @access public
325
+ * @author Grégory Viguier
326
+ *
327
+ * @param object $attachment An Imagify attachment.
328
+ * @param string $file_path Path to the new file.
329
+ * @return bool|object True on success, false if prevented, a WP_Error object on failure.
330
+ */
331
+ public function replace_full_retina_image( $attachment, $file_path ) {
332
+ $tests = $this->validate( __FUNCTION__, $attachment, array(
333
+ 'metadata_file' => false,
334
+ 'metadata_sizes' => false,
335
+ ) );
336
+
337
+ if ( true !== $tests ) {
338
+ return $tests;
339
+ }
340
+
341
+ // Replace the file.
342
+ $retina_path = $this->get_retina_path( $attachment->get_original_path() );
343
+ $moved = $this->filesystem->move( $file_path, $retina_path, true );
344
+
345
+ if ( ! $moved ) {
346
+ return new WP_Error( 'not_writable', __( 'Replacement failed.', 'imagify' ) );
347
+ }
348
+
349
+ // Delete related Imagify data.
350
+ $this->remove_size_from_imagify_data( $attachment, 'full@2x' );
351
+
352
+ // Delete previous backup file.
353
+ $result = $this->delete_file_backup( $retina_path );
354
+
355
+ if ( is_wp_error( $result ) ) {
356
+ $this->filesystem->delete( $file_path );
357
+ return $result;
358
+ }
359
+
360
+ // Optimize.
361
+ if ( $attachment->is_optimized() && $this->can_auto_optimize() ) {
362
+ return $this->optimize_full_retina_image( $attachment );
363
+ }
364
+ }
365
+
366
+
367
+ /** ----------------------------------------------------------------------------------------- */
368
+ /** OPTIMIZE RETINA IMAGES ================================================================== */
369
+ /** ----------------------------------------------------------------------------------------- */
370
+
371
+ /**
372
+ * Optimize retina images.
373
+ *
374
+ * @since 1.8
375
+ * @access public
376
+ * @author Grégory Viguier
377
+ *
378
+ * @param object $attachment An Imagify attachment.
379
+ * @param bool $optimize_full_size False to not optimize the retina version of the full size.
380
+ * @return bool|object True on success, false if prevented or not supported or no sizes, a WP_Error object on failure.
381
+ */
382
+ public function optimize_retina_images( $attachment, $optimize_full_size = true ) {
383
+ $tests = $this->validate( __FUNCTION__, $attachment, array(
384
+ 'supported' => true,
385
+ 'can_optimize' => 'error',
386
+ 'metadata_dimensions' => 'error',
387
+ ) );
388
+
389
+ if ( true !== $tests ) {
390
+ return $tests;
391
+ }
392
+
393
+ $metadata = wp_get_attachment_metadata( $attachment->get_id() );
394
+
395
+ if ( $optimize_full_size ) {
396
+ $metadata['sizes']['full'] = array(
397
+ 'file' => $this->filesystem->file_name( $metadata['file'] ),
398
+ 'width' => (int) $metadata['width'],
399
+ 'height' => (int) $metadata['height'],
400
+ 'mime-type' => get_post_mime_type( $attachment->get_id() ),
401
+ );
402
+ }
403
+
404
+ return $this->optimize_retina_sizes( $attachment, $metadata['sizes'] );
405
+ }
406
+
407
+ /**
408
+ * Optimize the full size retina image.
409
+ *
410
+ * @since 1.8
411
+ * @access public
412
+ * @author Grégory Viguier
413
+ *
414
+ * @param object $attachment An Imagify attachment.
415
+ * @return bool|object True on success, false if prevented or not supported or no sizes, a WP_Error object on failure.
416
+ */
417
+ public function optimize_full_retina_image( $attachment ) {
418
+ $tests = $this->validate( __FUNCTION__, $attachment, array(
419
+ 'supported' => true,
420
+ 'can_optimize' => 'error',
421
+ 'metadata_dimensions' => 'error',
422
+ ) );
423
+
424
+ if ( true !== $tests ) {
425
+ return $tests;
426
+ }
427
+
428
+ $metadata = wp_get_attachment_metadata( $attachment->get_id() );
429
+
430
+ $sizes = array(
431
+ 'full' => array(
432
+ 'file' => $this->filesystem->file_name( $metadata['file'] ),
433
+ 'width' => (int) $metadata['width'],
434
+ 'height' => (int) $metadata['height'],
435
+ 'mime-type' => get_post_mime_type( $attachment->get_id() ),
436
+ ),
437
+ );
438
+
439
+ return $this->optimize_retina_sizes( $attachment, $sizes );
440
+ }
441
+
442
+ /**
443
+ * Optimize the given retina images.
444
+ *
445
+ * @since 1.8
446
+ * @access public
447
+ * @author Grégory Viguier
448
+ *
449
+ * @param object $attachment An Imagify attachment.
450
+ * @param array $sizes A list of non-retina sizes, formatted like in wp_get_attachment_metadata().
451
+ * @return bool|object True on success, false if prevented or not supported or no sizes, a WP_Error object on failure.
452
+ */
453
+ public function optimize_retina_sizes( $attachment, $sizes ) {
454
+ $tests = $this->validate( __FUNCTION__, $attachment, array(
455
+ 'supported' => true,
456
+ 'can_optimize' => 'error',
457
+ 'metadata_dimensions' => 'error',
458
+ ) );
459
+
460
+ if ( true !== $tests ) {
461
+ return $tests;
462
+ }
463
+
464
+ $attachment_id = $attachment->get_id();
465
+ $optimization_level = $this->get_optimization_level( $attachment );
466
+
467
+ /**
468
+ * Filter the retina thumbnail sizes to optimize for a given attachment. This includes the sizes disabled in Imagify’ settings.
469
+ *
470
+ * @since 1.8
471
+ * @author Grégory Viguier
472
+ *
473
+ * @param array $sizes An array of non-retina thumbnail sizes.
474
+ * @param int $attachment_id The attachment ID.
475
+ * @param int $optimization_level The optimization level.
476
+ */
477
+ $sizes = apply_filters( 'imagify_attachment_retina_sizes', $sizes, $attachment_id, $optimization_level );
478
+
479
+ if ( ! $sizes || ! is_array( $sizes ) ) {
480
+ return false;
481
+ }
482
+
483
+ $original_dirpath = $this->filesystem->dir_path( $attachment->get_original_path() );
484
+
485
+ foreach ( $sizes as $size_key => $image_data ) {
486
+ $retina_path = wr2x_get_retina( $original_dirpath . $image_data['file'] );
487
+
488
+ if ( ! $retina_path ) {
489
+ unset( $sizes[ $size_key ] );
490
+ continue;
491
+ }
492
+
493
+ // The file exists.
494
+ $sizes[ $size_key ]['retina-path'] = $retina_path;
495
+ }
496
+
497
+ if ( ! $sizes ) {
498
+ return false;
499
+ }
500
+
501
+ $attachment->set_running_status();
502
+
503
+ /**
504
+ * Fires before optimizing the retina images.
505
+ *
506
+ * @since 1.8
507
+ * @author Grégory Viguier
508
+ *
509
+ * @param int $attachment_id The attachment ID.
510
+ * @param array $sizes An array of non-retina thumbnail sizes.
511
+ * @param int $optimization_level The optimization level.
512
+ */
513
+ do_action( 'before_imagify_optimize_retina_images', $attachment_id, $sizes, $optimization_level );
514
+
515
+ $imagify_data = $attachment->get_data();
516
+
517
+ foreach ( $sizes as $size_key => $image_data ) {
518
+ $imagify_data = $this->optimize_retina_image( array(
519
+ 'data' => $imagify_data,
520
+ 'attachment' => $attachment,
521
+ 'retina_path' => $image_data['retina-path'],
522
+ 'size_key' => $size_key,
523
+ 'optimization_level' => $optimization_level,
524
+ ) );
525
+ }
526
+
527
+ $this->update_imagify_data( $attachment, $imagify_data );
528
+
529
+ /**
530
+ * Fires after optimizing the retina images.
531
+ *
532
+ * @since 1.8
533
+ * @author Grégory Viguier
534
+ *
535
+ * @param int $attachment_id The attachment ID.
536
+ * @param array $sizes An array of non-retina thumbnail sizes.
537
+ * @param int $optimization_level The optimization level.
538
+ */
539
+ do_action( 'after_imagify_optimize_retina_images', $attachment_id, $sizes, $optimization_level );
540
+
541
+ $attachment->delete_running_status();
542
+
543
+ return true;
544
+ }
545
+
546
+ /**
547
+ * Optimize the retina version of an image.
548
+ *
549
+ * @since 1.8
550
+ * @access public
551
+ * @author Grégory Viguier
552
+ *
553
+ * @param array $args {
554
+ * An array of required arguments.
555
+ *
556
+ * @type array $data The statistics data.
557
+ * @type object $attachment An Imagify attachment.
558
+ * @type string $retina_path The path to the retina file.
559
+ * @type string $size_key The attachment size key (without '@2x').
560
+ * @type int $optimization_level The optimization level. Optionnal.
561
+ * @type array $metadata WP metadata. If omitted, wp_get_attachment_metadata() will be used.
562
+ * }
563
+ * @return array The new optimization data.
564
+ */
565
+ public function optimize_retina_image( $args ) {
566
+ static $backup;
567
+
568
+ $args = array_merge( array(
569
+ 'data' => array(),
570
+ 'attachment' => false,
571
+ 'retina_path' => '',
572
+ 'size_key' => '',
573
+ 'optimization_level' => false,
574
+ 'metadata' => array(),
575
+ ), $args );
576
+
577
+ if ( $this->is_prevented( __FUNCTION__ ) || ! $args['retina_path'] || $this->has_filesystem_error() ) {
578
+ return $args['data'];
579
+ }
580
+
581
+ $retina_key = $args['size_key'] . '@2x';
582
+
583
+ if ( isset( $args['data'][ $retina_key ] ) ) {
584
+ // Don't optimize something that already is.
585
+ return $args['data'];
586
+ }
587
+
588
+ $disallowed = $this->size_is_disallowed( $args['size_key'] );
589
+ $do_retina = ! $disallowed;
590
+ /**
591
+ * Allow to optimize the retina version generated by WP Retina x2.
592
+ *
593
+ * @since 1.0
594
+ * @since 1.8 Added $args parameter.
595
+ *
596
+ * @param bool $do_retina True will allow the optimization. False to prevent it.
597
+ * @param string $args The arguments passed to the method.
598
+ */
599
+ $do_retina = apply_filters( 'do_imagify_optimize_retina', $do_retina, $args );
600
+
601
+ if ( ! $do_retina ) {
602
+ if ( $disallowed ) {
603
+ $message = __( 'This size is not authorized to be optimized. Update your Imagify settings if you want to optimize it.', 'imagify' );
604
+ } else {
605
+ $message = __( 'This size optimization has been prevented by a filter.', 'imagify' );
606
+ }
607
+
608
+ $args['data']['sizes'][ $retina_key ] = array(
609
+ 'success' => false,
610
+ 'error' => $message,
611
+ );
612
+ return $args['data'];
613
+ }
614
+
615
+ if ( ! $args['metadata'] || ! is_array( $args['metadata'] ) ) {
616
+ $args['metadata'] = wp_get_attachment_metadata( $args['attachment']->get_id() );
617
+ }
618
+
619
+ $is_a_copy = $this->size_is_a_full_copy( array(
620
+ 'size_name' => $args['size_key'],
621
+ 'metadata' => $args['metadata'],
622
+ 'imagify_data' => $args['data'],
623
+ 'retina_path' => $args['retina_path'],
624
+ ) );
625
+
626
+ if ( $is_a_copy ) {
627
+ // This thumbnail is a copy of the full size image, which is already optimized.
628
+ $args['data']['sizes'][ $retina_key ] = $args['data']['sizes']['full'];
629
+
630
+ if ( isset( $args['data']['sizes']['full']['original_size'], $args['data']['sizes']['full']['optimized_size'] ) ) {
631
+ // Concistancy only.
632
+ $args['data']['stats']['original_size'] += $args['data']['sizes']['full']['original_size'];
633
+ $args['data']['stats']['optimized_size'] += $args['data']['sizes']['full']['optimized_size'];
634
+ }
635
+
636
+ return $args['data'];
637
+ }
638
+
639
+ if ( ! is_int( $args['optimization_level'] ) ) {
640
+ $args['optimization_level'] = get_imagify_option( 'optimization_level' );
641
+ }
642
+
643
+ // Hammer time.
644
+ $response = do_imagify( $args['retina_path'], array(
645
+ // Backup only if it's the full size.
646
+ 'backup' => 'full' === $args['size_key'],
647
+ 'optimization_level' => $args['optimization_level'],
648
+ 'context' => 'wp-retina',
649
+ ) );
650
+
651
+ return $args['attachment']->fill_data( $args['data'], $response, $retina_key );
652
+ }
653
+
654
+
655
+ /** ----------------------------------------------------------------------------------------- */
656
+ /** HANDLE BACKUPS ========================================================================== */
657
+ /** ----------------------------------------------------------------------------------------- */
658
+
659
+ /**
660
+ * Backup a retina file.
661
+ *
662
+ * @since 1.8
663
+ * @access public
664
+ * @author Grégory Viguier
665
+ *
666
+ * @param string $file_path Path to the file.
667
+ * @return bool|object True on success, false if prevented or no need for backup, a WP_Error object on failure.
668
+ */
669
+ public function backup_file( $file_path ) {
670
+ static $backup;
671
+
672
+ if ( $this->is_prevented( __FUNCTION__ ) ) {
673
+ return false;
674
+ }
675
+
676
+ if ( ! isset( $backup ) ) {
677
+ $backup = get_imagify_option( 'backup' );
678
+ }
679
+
680
+ if ( ! $backup ) {
681
+ return false;
682
+ }
683
+
684
+ if ( $this->has_filesystem_error() ) {
685
+ return new WP_Error( 'filesystem', __( 'Filesystem error.', 'imagify' ) );
686
+ }
687
+
688
+ $upload_basedir = get_imagify_upload_basedir();
689
+
690
+ if ( ! $upload_basedir ) {
691
+ $file_path = make_path_relative( $file_path );
692
+
693
+ /* translators: %s is a file path. */
694
+ return new WP_Error( 'upload_basedir', sprintf( __( 'The file %s could not be backed up. Image optimization aborted.', 'imagify' ), '<code>' . esc_html( $file_path ) . '</code>' ) );
695
+ }
696
+
697
+ $file_path = wp_normalize_path( $file_path );
698
+ $backup_dir = get_imagify_backup_dir_path();
699
+ $backup_path = str_replace( $upload_basedir, $backup_dir, $file_path );
700
+
701
+ if ( $this->filesystem->exists( $backup_path ) ) {
702
+ $this->filesystem->delete( $backup_path );
703
+ }
704
+
705
+ $backup_result = imagify_backup_file( $file_path, $backup_path );
706
+
707
+ if ( is_wp_error( $backup_result ) ) {
708
+ return $backup_result;
709
+ }
710
+
711
+ return true;
712
+ }
713
+
714
+ /**
715
+ * Delete a retina file backup.
716
+ *
717
+ * @since 1.8
718
+ * @access public
719
+ * @author Grégory Viguier
720
+ *
721
+ * @param string $file_path Path to the file.
722
+ * @return bool|object True on success, false if the file doesn't exist, a WP_Error object on failure.
723
+ */
724
+ public function delete_file_backup( $file_path ) {
725
+ $tests = $this->validate( __FUNCTION__ );
726
+
727
+ if ( true !== $tests ) {
728
+ return $tests;
729
+ }
730
+
731
+ $upload_basedir = get_imagify_upload_basedir();
732
+
733
+ if ( ! $upload_basedir ) {
734
+ $file_path = make_path_relative( $file_path );
735
+
736
+ /* translators: %s is a file path. */
737
+ return new WP_Error( 'upload_basedir', sprintf( __( 'Previous backup file for %s could not be deleted.', 'imagify' ), '<code>' . esc_html( $file_path ) . '</code>' ) );
738
+ }
739
+
740
+ $file_path = wp_normalize_path( $file_path );
741
+ $backup_dir = get_imagify_backup_dir_path();
742
+ $backup_path = str_replace( $upload_basedir, $backup_dir, $file_path );
743
+
744
+ if ( ! $this->filesystem->exists( $backup_path ) ) {
745
+ return false;
746
+ }
747
+
748
+ $result = $this->filesystem->delete( $backup_path );
749
+
750
+ if ( ! $result ) {
751
+ $file_path = make_path_relative( $file_path );
752
+
753
+ /* translators: %s is a file path. */
754
+ return new WP_Error( 'not_deleted', sprintf( __( 'Previous backup file for %s could not be deleted.', 'imagify' ), '<code>' . esc_html( $file_path ) . '</code>' ) );
755
+ }
756
+
757
+ return true;
758
+ }
759
+
760
+ /**
761
+ * Restore the retina version of the full size.
762
+ * This doesn't remove the Imagify data.
763
+ *
764
+ * @since 1.8
765
+ * @access public
766
+ * @author Grégory Viguier
767
+ *
768
+ * @param object $attachment An Imagify attachment.
769
+ * @return bool|object True on success, false if prevented or backup doesn't exist, a WP_Error object on failure.
770
+ */
771
+ public function restore_full_retina_file( $attachment ) {
772
+ $tests = $this->validate( __FUNCTION__, $attachment, array(
773
+ 'metadata_file' => false,
774
+ 'metadata_sizes' => false,
775
+ ) );
776
+
777
+ if ( true !== $tests ) {
778
+ return $tests;
779
+ }
780
+
781
+ $has_backup = $this->full_retina_has_backup( $attachment );
782
+
783
+ if ( is_wp_error( $has_backup ) ) {
784
+ return $has_backup;
785
+ }
786
+
787
+ if ( ! $has_backup ) {
788
+ return new WP_Error( 'no_backup', __( 'The retina version of the full size of this image does not have backup.', 'imagify' ) );
789
+ }
790
+
791
+ $file_path = $this->get_retina_path( $attachment->get_original_path() );
792
+ $backup_path = $this->get_full_retina_backup_path( $attachment );
793
+
794
+ /**
795
+ * Fires before restoring the retina version of the full size.
796
+ *
797
+ * @since 1.8
798
+ * @author Grégory Viguier
799
+ *
800
+ * @param string $backup_path Path to the backup file.
801
+ * @param string $file_path Path to the source file.
802
+ */
803
+ do_action( 'before_imagify_restore_full_retina_file', $backup_path, $file_path );
804
+
805
+ // Save disc space by moving it instead of copying it.
806
+ $moved = $this->filesystem->move( $backup_path, $file_path, true );
807
+
808
+ /**
809
+ * Fires after restoring the retina version of the full size.
810
+ *
811
+ * @since 1.8
812
+ * @author Grégory Viguier
813
+ *
814
+ * @param string $backup_path Path to the backup file.
815
+ * @param string $file_path Path to the source file.
816
+ * @param bool $moved Restore success.
817
+ */
818
+ do_action( 'after_imagify_restore_full_retina_file', $backup_path, $file_path, $moved );
819
+
820
+ if ( ! $moved ) {
821
+ return new WP_Error( 'upload_basedir', __( 'Backup of the retina version of the full size image could not be restored.', 'imagify' ) );
822
+ }
823
+
824
+ return true;
825
+ }
826
+
827
+ /**
828
+ * Get the path to the retina version of the full size.
829
+ *
830
+ * @since 1.8
831
+ * @access public
832
+ * @author Grégory Viguier
833
+ *
834
+ * @param object $attachment An Imagify attachment.
835
+ * @return string|object The path on success, a WP_Error object on failure.
836
+ */
837
+ public function get_full_retina_backup_path( $attachment ) {
838
+ $file_path = $this->get_retina_path( $attachment->get_original_path() );
839
+ $backup_path = get_imagify_attachment_backup_path( $file_path );
840
+
841
+ if ( ! $backup_path ) {
842
+ return new WP_Error( 'upload_basedir', __( 'Could not retrieve the path to the backup of the retina version of the full size image.', 'imagify' ) );
843
+ }
844
+
845
+ return $backup_path;
846
+ }
847
+
848
+ /**
849
+ * Tell if the retina version of the full size has a backup.
850
+ *
851
+ * @since 1.8
852
+ * @access public
853
+ * @author Grégory Viguier
854
+ *
855
+ * @param object $attachment An Imagify attachment.
856
+ * @return bool|object A WP_Error object on failure.
857
+ */
858
+ public function full_retina_has_backup( $attachment ) {
859
+ $backup_path = $this->get_full_retina_backup_path( $attachment );
860
+
861
+ if ( is_wp_error( $backup_path ) ) {
862
+ return $backup_path;
863
+ }
864
+
865
+ return $this->filesystem->exists( $backup_path );
866
+ }
867
+
868
+
869
+ /** ----------------------------------------------------------------------------------------- */
870
+ /** HANDLE IMAGIFY DATA ===================================================================== */
871
+ /** ----------------------------------------------------------------------------------------- */
872
+
873
+ /**
874
+ * Remove retina versions from Imagify data.
875
+ * It also rebuilds the attachment stats.
876
+ *
877
+ * @since 1.8
878
+ * @access public
879
+ * @author Grégory Viguier
880
+ *
881
+ * @param object $attachment An Imagify attachment.
882
+ * @param bool $remove_full_size True to also remove the full size data.
883
+ */
884
+ public function remove_retina_images_data( $attachment, $remove_full_size = false ) {
885
+ if ( $this->is_prevented( __FUNCTION__ ) ) {
886
+ return;
887
+ }
888
+
889
+ $imagify_data = $attachment->get_data();
890
+
891
+ if ( empty( $imagify_data['sizes'] ) || ! is_array( $imagify_data['sizes'] ) ) {
892
+ return;
893
+ }
894
+
895
+ $sizes = $this->get_attachment_sizes( $attachment );
896
+
897
+ if ( ! $sizes ) {
898
+ return;
899
+ }
900
+
901
+ $update = false;
902
+
903
+ if ( $remove_full_size && isset( $imagify_data['sizes']['full@2x'] ) ) {
904
+ unset( $imagify_data['sizes']['full@2x'] );
905
+ $update = true;
906
+ }
907
+
908
+ foreach ( $sizes as $size => $attr ) {
909
+ $size .= '@2x';
910
+
911
+ if ( isset( $imagify_data['sizes'][ $size ] ) ) {
912
+ unset( $imagify_data['sizes'][ $size ] );
913
+ $update = true;
914
+ }
915
+ }
916
+
917
+ if ( ! $update ) {
918
+ return;
919
+ }
920
+
921
+ $this->update_imagify_data( $attachment, $imagify_data );
922
+ }
923
+
924
+ /**
925
+ * Remove a retina thumbnail from attachment's Imagify data, given the retina file name.
926
+ *
927
+ * @since 1.8
928
+ * @access public
929
+ * @author Grégory Viguier
930
+ *
931
+ * @param object $attachment An Imagify attachment.
932
+ * @param string $retina_filename Retina thumbnail file name.
933
+ */
934
+ public function remove_retina_image_data_by_filename( $attachment, $retina_filename ) {
935
+ if ( $this->is_prevented( __FUNCTION__ ) ) {
936
+ return;
937
+ }
938
+
939
+ $imagify_data = $attachment->get_data();
940
+
941
+ if ( empty( $imagify_data['sizes'] ) || ! is_array( $imagify_data['sizes'] ) ) {
942
+ return;
943
+ }
944
+
945
+ $sizes = $this->get_attachment_sizes( $attachment );
946
+
947
+ if ( ! $sizes ) {
948
+ return;
949
+ }
950
+
951
+ $image_filename = str_replace( $this->get_suffix(), '.', $retina_filename );
952
+ $size = false;
953
+
954
+ foreach ( $sizes as $name => $attr ) {
955
+ if ( $image_filename === $attr['file'] ) {
956
+ $size = $name;
957
+ break;
958
+ }
959
+ }
960
+
961
+ if ( ! $size || ! isset( $imagify_data['sizes'][ $size ] ) ) {
962
+ return;
963
+ }
964
+
965
+ unset( $imagify_data['sizes'][ $size ] );
966
+
967
+ $this->update_imagify_data( $attachment, $imagify_data );
968
+ }
969
+
970
+ /**
971
+ * Rebuild the attachment stats and store the data.
972
+ * Delete all Imagify data if the sizes are empty.
973
+ *
974
+ * @since 1.8
975
+ * @access public
976
+ * @author Grégory Viguier
977
+ *
978
+ * @param object $attachment An Imagify attachment.
979
+ * @param array $imagify_data Imagify data.
980
+ * @return bool True on update, false on delete or prevented.
981
+ */
982
+ public function update_imagify_data( $attachment, $imagify_data ) {
983
+ if ( $this->is_prevented( __FUNCTION__ ) ) {
984
+ return false;
985
+ }
986
+
987
+ if ( empty( $imagify_data['sizes'] ) || ! is_array( $imagify_data['sizes'] ) ) {
988
+ // No new sizes.
989
+ $attachment->delete_imagify_data();
990
+ return false;
991
+ }
992
+
993
+ $imagify_data['stats'] = array(
994
+ 'original_size' => 0,
995
+ 'optimized_size' => 0,
996
+ 'percent' => 0,
997
+ );
998
+
999
+ foreach ( $imagify_data['sizes'] as $size_data ) {
1000
+ $imagify_data['stats']['original_size'] += ! empty( $size_data['original_size'] ) ? $size_data['original_size'] : 0;
1001
+ $imagify_data['stats']['optimized_size'] += ! empty( $size_data['optimized_size'] ) ? $size_data['optimized_size'] : 0;
1002
+ }
1003
+
1004
+ if ( $imagify_data['stats']['original_size'] && $imagify_data['stats']['optimized_size'] ) {
1005
+ $imagify_data['stats']['percent'] = round( ( ( $imagify_data['stats']['original_size'] - $imagify_data['stats']['optimized_size'] ) / $imagify_data['stats']['original_size'] ) * 100, 2 );
1006
+ }
1007
+
1008
+ update_post_meta( $attachment->get_id(), '_imagify_data', $imagify_data );
1009
+
1010
+ return true;
1011
+ }
1012
+
1013
+ /**
1014
+ * Remove a size from Imagify data.
1015
+ *
1016
+ * @since 1.8
1017
+ * @access public
1018
+ * @author Grégory Viguier
1019
+ *
1020
+ * @param object $attachment An Imagify attachment.
1021
+ * @param string $size_name Name of the size.
1022
+ */
1023
+ public function remove_size_from_imagify_data( $attachment, $size_name ) {
1024
+ if ( $this->is_prevented( __FUNCTION__ ) ) {
1025
+ return;
1026
+ }
1027
+
1028
+ $imagify_data = $attachment->get_data();
1029
+
1030
+ if ( ! isset( $imagify_data['sizes'][ $size_name ] ) ) {
1031
+ return;
1032
+ }
1033
+
1034
+ unset( $imagify_data['sizes'][ $size_name ] );
1035
+
1036
+ $this->update_imagify_data( $attachment, $imagify_data );
1037
+ }
1038
+
1039
+
1040
+ /** ----------------------------------------------------------------------------------------- */
1041
+ /** INTERNAL TOOLS ========================================================================== */
1042
+ /** ----------------------------------------------------------------------------------------- */
1043
+
1044
+ /**
1045
+ * Tell if a file extension is supported by WP Retina 2x.
1046
+ * It uses $wr2x_core->is_supported_image() if available.
1047
+ *
1048
+ * @since 1.8
1049
+ * @access public
1050
+ * @see $wr2x_core->is_supported_image()
1051
+ * @author Grégory Viguier
1052
+ *
1053
+ * @param string|int $file_path Path to the file or attachment ID.
1054
+ * @return bool
1055
+ */
1056
+ public function is_supported_format( $file_path ) {
1057
+ global $wr2x_core;
1058
+ static $method;
1059
+ static $results = array();
1060
+
1061
+ if ( ! $file_path ) {
1062
+ return false;
1063
+ }
1064
+
1065
+ if ( isset( $results[ $file_path ] ) ) {
1066
+ // $file_path can be a path or an attachment ID.
1067
+ return $results[ $file_path ];
1068
+ }
1069
+
1070
+ if ( is_int( $file_path ) ) {
1071
+ $attachment_id = $file_path;
1072
+ $file_path = get_attached_file( $attachment_id );
1073
+
1074
+ if ( ! $file_path ) {
1075
+ $results[ $attachment_id ] = false;
1076
+ return false;
1077
+ }
1078
+
1079
+ if ( isset( $results[ $file_path ] ) ) {
1080
+ // $file_path is now a path for sure.
1081
+ $results[ $attachment_id ] = $results[ $file_path ];
1082
+ return $results[ $file_path ];
1083
+ }
1084
+ }
1085
+
1086
+ if ( ! isset( $method ) ) {
1087
+ if ( $wr2x_core && is_object( $wr2x_core ) && method_exists( $wr2x_core, 'is_supported_image' ) ) {
1088
+ $method = array( $wr2x_core, 'is_supported_image' );
1089
+ } else {
1090
+ $method = array( $this, 'is_supported_extension' );
1091
+ }
1092
+ }
1093
+
1094
+ $results[ $file_path ] = call_user_func( $method, $file_path );
1095
+ $results[ $attachment_id ] = $results[ $file_path ];
1096
+
1097
+ return $results[ $file_path ];
1098
+ }
1099
+
1100
+ /**
1101
+ * Tell if a file extension is supported by WP Retina 2x.
1102
+ * Internal version of $wr2x_core->is_supported_image().
1103
+ *
1104
+ * @since 1.8
1105
+ * @access public
1106
+ * @see $this->is_supported_format()
1107
+ * @author Grégory Viguier
1108
+ *
1109
+ * @param string $file_path Path to a file.
1110
+ * @return bool
1111
+ */
1112
+ protected function is_supported_extension( $file_path ) {
1113
+ $extension = strtolower( $this->filesystem->path_info( $file_path, 'extension' ) );
1114
+ $extensions = array(
1115
+ 'jpg' => 1,
1116
+ 'jpeg' => 1,
1117
+ 'png' => 1,
1118
+ 'gif' => 1,
1119
+ );
1120
+
1121
+ return isset( $extensions[ $extension ] );
1122
+ }
1123
+
1124
+ /**
1125
+ * Get the path to the retina version of an image.
1126
+ *
1127
+ * @since 1.8
1128
+ * @access public
1129
+ * @author Grégory Viguier
1130
+ *
1131
+ * @param string $file_path Path to the non-retina image.
1132
+ * @return string
1133
+ */
1134
+ public function get_retina_path( $file_path ) {
1135
+ $path_info = $this->filesystem->path_info( $file_path );
1136
+ $suffix = rtrim( $this->get_suffix(), '.' );
1137
+ $extension = isset( $path_info['extension'] ) ? '.' . $path_info['extension'] : '';
1138
+
1139
+ return $path_info['dir_path'] . $path_info['file_base'] . $suffix . $extension;
1140
+ }
1141
+
1142
+ /**
1143
+ * Tell if the attchment has at least one retina image.
1144
+ *
1145
+ * @since 1.8
1146
+ * @access public
1147
+ * @author Grégory Viguier
1148
+ *
1149
+ * @param object $attachment An Imagify attachment.
1150
+ * @return bool
1151
+ */
1152
+ public function has_retina_images( $attachment ) {
1153
+ $dir_path = $this->filesystem->path_info( $attachment->get_original_path(), 'dir_path' );
1154
+ $metadata = wp_get_attachment_metadata( $attachment->get_id() );
1155
+ $sizes = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : array();
1156
+ $sizes['full'] = array(
1157
+ 'file' => $this->filesystem->file_name( $metadata['file'] ),
1158
+ );
1159
+
1160
+ foreach ( $sizes as $name => $attr ) {
1161
+ $size_path = $this->get_retina_path( $dir_path . $attr['file'] );
1162
+
1163
+ if ( $this->filesystem->exists( $size_path ) ) {
1164
+ return true;
1165
+ }
1166
+ }
1167
+
1168
+ return false;
1169
+ }
1170
+
1171
+ /**
1172
+ * Prevent a method to do its job.
1173
+ *
1174
+ * @since 1.8
1175
+ * @access public
1176
+ * @author Grégory Viguier
1177
+ *
1178
+ * @param string $method_name Name of the method to prevent.
1179
+ */
1180
+ public function prevent( $method_name ) {
1181
+ if ( empty( self::$prevented[ $method_name ] ) || self::$prevented[ $method_name ] < 1 ) {
1182
+ self::$prevented[ $method_name ] = 1;
1183
+ } else {
1184
+ ++self::$prevented[ $method_name ];
1185
+ }
1186
+ }
1187
+
1188
+ /**
1189
+ * Allow a method to do its job.
1190
+ *
1191
+ * @since 1.8
1192
+ * @access public
1193
+ * @author Grégory Viguier
1194
+ *
1195
+ * @param string $method_name Name of the method to allow.
1196
+ */
1197
+ public function allow( $method_name ) {
1198
+ if ( empty( self::$prevented[ $method_name ] ) || self::$prevented[ $method_name ] <= 1 ) {
1199
+ unset( self::$prevented[ $method_name ] );
1200
+ } else {
1201
+ --self::$prevented[ $method_name ];
1202
+ }
1203
+ }
1204
+
1205
+ /**
1206
+ * Tell if a method is prevented to do its job.
1207
+ *
1208
+ * @since 1.8
1209
+ * @access public
1210
+ * @author Grégory Viguier
1211
+ *
1212
+ * @param string $method_name Name of the method.
1213
+ * @return bool
1214
+ */
1215
+ public function is_prevented( $method_name ) {
1216
+ return ! empty( self::$prevented[ $method_name ] );
1217
+ }
1218
+
1219
+ /**
1220
+ * Tell if a thumbnail size is disallowed for optimization..
1221
+ *
1222
+ * @since 1.8
1223
+ * @access public
1224
+ * @author Grégory Viguier
1225
+ *
1226
+ * @param string $size_name The size name.
1227
+ * @return bool
1228
+ */
1229
+ public function size_is_disallowed( $size_name ) {
1230
+ static $disallowed_sizes;
1231
+
1232
+ if ( imagify_is_active_for_network() ) {
1233
+ return false;
1234
+ }
1235
+
1236
+ if ( ! isset( $disallowed_sizes ) ) {
1237
+ $disallowed_sizes = get_imagify_option( 'disallowed-sizes' );
1238
+ }
1239
+
1240
+ return isset( $disallowed_sizes[ $size_name ] );
1241
+ }
1242
+
1243
+ /**
1244
+ * Tell if a thumbnail file is a copy of the full size image. Will return false if the full size is not optimized.
1245
+ * Make sure both files exist before using this.
1246
+ *
1247
+ * @since 1.8
1248
+ * @access public
1249
+ * @author Grégory Viguier
1250
+ *
1251
+ * @param array $args {
1252
+ * An array of arguments.
1253
+ *
1254
+ * @type string $size_name The size name. Required.
1255
+ * @type array $metadata WP metadata. Required.
1256
+ * @type array $imagify_data Imagify data. Required.
1257
+ * @type string $retina_path Path to the image we're testing. Required.
1258
+ * @type string $full_path Path to the full size image. Optional but should be provided.
1259
+ * }
1260
+ * @return bool
1261
+ */
1262
+ public function size_is_a_full_copy( $args ) {
1263
+ $size_name = $args['size_name'];
1264
+ $metadata = $args['metadata'];
1265
+ $imagify_data = $args['imagify_data'];
1266
+
1267
+ if ( empty( $imagify_data['sizes']['full'] ) ) {
1268
+ // The full size is not optimized, so there is no point in checking if the given file is a copy.
1269
+ return false;
1270
+ }
1271
+
1272
+ if ( ! isset( $metadata['width'], $metadata['height'], $metadata['file'] ) ) {
1273
+ return false;
1274
+ }
1275
+
1276
+ if ( ! isset( $metadata['sizes'][ $size_name ]['width'], $metadata['sizes'][ $size_name ]['height'] ) ) {
1277
+ return false;
1278
+ }
1279
+
1280
+ $size = $metadata['sizes'][ $size_name ];
1281
+
1282
+ if ( $size['width'] * 2 !== $metadata['width'] || $size['height'] * 2 !== $metadata['height'] ) {
1283
+ // The full size image doesn't have the right dimensions.
1284
+ return false;
1285
+ }
1286
+
1287
+ if ( empty( $args['full_path'] ) ) {
1288
+ $dir_path = $this->filesystem->path_info( $args['retina_path'], 'dir_path' );
1289
+ $args['full_path'] = $dir_path . $metadata['file'];
1290
+ }
1291
+
1292
+ $full_hash = md5_file( $args['full_path'] );
1293
+ $retina_hash = md5_file( $args['retina_path'] );
1294
+
1295
+ return hash_equals( $full_hash, $retina_hash );
1296
+ }
1297
+
1298
+ /**
1299
+ * Tell if there is a filesystem error.
1300
+ *
1301
+ * @since 1.8
1302
+ * @access public
1303
+ * @author Grégory Viguier
1304
+ *
1305
+ * @return bool
1306
+ */
1307
+ public function has_filesystem_error() {
1308
+ return ! empty( $this->filesystem->errors->errors );
1309
+ }
1310
+
1311
+ /**
1312
+ * Do few tests: method is not prevented, attachment is valid, filesystem has no error.
1313
+ *
1314
+ * @since 1.8
1315
+ * @access public
1316
+ * @author Grégory Viguier
1317
+ *
1318
+ * @param string $method The name of the method using this.
1319
+ * @param object $attachment An Imagify attachment.
1320
+ * @param array $args A list of additional tests.
1321
+ * @return bool|object True if ok, false if prevented, a WP_Error object on failure.
1322
+ */
1323
+ public function validate( $method, $attachment = false, $args = array() ) {
1324
+ $args = array_merge( array(
1325
+ 'supported' => false,
1326
+ 'can_optimize' => false,
1327
+ 'metadata_dimensions' => false,
1328
+ 'metadata_file' => $attachment ? 'error' : false,
1329
+ 'metadata_sizes' => $attachment ? 'error' : false,
1330
+ ), $args );
1331
+
1332
+ if ( $this->is_prevented( $method ) ) {
1333
+ return false;
1334
+ }
1335
+
1336
+ if ( $attachment && ! $attachment->is_valid() ) {
1337
+ return new WP_Error( 'invalid_attachment', __( 'Invalid attachment.', 'imagify' ) );
1338
+ }
1339
+
1340
+ if ( $args['supported'] && ! $attachment->is_extension_supported() ) {
1341
+ if ( 'error' !== $args['supported'] ) {
1342
+ return false;
1343
+ }
1344
+
1345
+ return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) );
1346
+ }
1347
+
1348
+ if ( $args['can_optimize'] ) {
1349
+ if ( 'error' !== $args['can_optimize'] ) {
1350
+ if ( ! Imagify_Requirements::is_api_key_valid() || Imagify_Requirements::is_over_quota() ) {
1351
+ return false;
1352
+ }
1353
+ } else {
1354
+ if ( ! Imagify_Requirements::is_api_key_valid() ) {
1355
+ return new WP_Error( 'invalid_api_key', __( 'Your API key is not valid!', 'imagify' ) );
1356
+ }
1357
+ if ( Imagify_Requirements::is_over_quota() ) {
1358
+ return new WP_Error( 'over_quota', __( 'You have used all your credits!', 'imagify' ) );
1359
+ }
1360
+ }
1361
+ }
1362
+
1363
+ if ( $this->has_filesystem_error() ) {
1364
+ return new WP_Error( 'filesystem', __( 'Filesystem error.', 'imagify' ) );
1365
+ }
1366
+
1367
+ if ( $args['metadata_dimensions'] ) {
1368
+ $metadata = wp_get_attachment_metadata( $attachment->get_id() );
1369
+
1370
+ if ( empty( $metadata['width'] ) || empty( $metadata['height'] ) ) {
1371
+ if ( 'error' !== $args['metadata_sizes'] ) {
1372
+ return false;
1373
+ }
1374
+
1375
+ return new WP_Error( 'metadata_dimensions', __( 'This attachment lacks the required metadata.', 'imagify' ) );
1376
+ }
1377
+ }
1378
+
1379
+ if ( $args['metadata_file'] ) {
1380
+ $metadata = isset( $metadata ) ? $metadata : wp_get_attachment_metadata( $attachment->get_id() );
1381
+
1382
+ if ( empty( $metadata['file'] ) ) {
1383
+ if ( 'error' !== $args['metadata_file'] ) {
1384
+ return false;
1385
+ }
1386
+
1387
+ return new WP_Error( 'metadata_file', __( 'This attachment lacks the required metadata.', 'imagify' ) );
1388
+ }
1389
+ }
1390
+
1391
+ if ( $args['metadata_sizes'] ) {
1392
+ $metadata = isset( $metadata ) ? $metadata : wp_get_attachment_metadata( $attachment->get_id() );
1393
+
1394
+ if ( empty( $metadata['sizes'] ) || ! is_array( $metadata['sizes'] ) ) {
1395
+ if ( 'error' !== $args['metadata_sizes'] ) {
1396
+ return false;
1397
+ }
1398
+
1399
+ return new WP_Error( 'metadata_sizes', __( 'This attachment has no registered thumbnail sizes.', 'imagify' ) );
1400
+ }
1401
+ }
1402
+
1403
+ return true;
1404
+ }
1405
+
1406
+ /**
1407
+ * Tell if Imagify can optimize the files.
1408
+ *
1409
+ * @since 1.8
1410
+ * @access public
1411
+ * @author Grégory Viguier
1412
+ *
1413
+ * @return bool
1414
+ */
1415
+ public function can_optimize() {
1416
+ return ! $this->has_filesystem_error() && Imagify_Requirements::is_api_key_valid() && ! Imagify_Requirements::is_over_quota();
1417
+ }
1418
+
1419
+ /**
1420
+ * Tell if Imagify can auto-optimize the files.
1421
+ *
1422
+ * @since 1.8
1423
+ * @access public
1424
+ * @author Grégory Viguier
1425
+ *
1426
+ * @return bool
1427
+ */
1428
+ public function can_auto_optimize() {
1429
+ return $this->can_optimize() && get_imagify_option( 'auto_optimize' );
1430
+ }
1431
+
1432
+ /**
1433
+ * Get thumbnail sizes from an attachment.
1434
+ *
1435
+ * @since 1.8
1436
+ * @access public
1437
+ * @author Grégory Viguier
1438
+ *
1439
+ * @param object $attachment An Imagify attachment.
1440
+ * @return array
1441
+ */
1442
+ public function get_attachment_sizes( $attachment ) {
1443
+ $metadata = wp_get_attachment_metadata( $attachment->get_id() );
1444
+ return ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : array();
1445
+ }
1446
+
1447
+ /**
1448
+ * Get the optimization level used to optimize the given attachment.
1449
+ *
1450
+ * @since 1.8
1451
+ * @access public
1452
+ * @author Grégory Viguier
1453
+ *
1454
+ * @param object $attachment An Imagify attachment.
1455
+ * @return int The attachment optimization level. The default level if not optimized.
1456
+ */
1457
+ public function get_optimization_level( $attachment ) {
1458
+ static $default;
1459
+
1460
+ if ( $attachment->get_status() ) {
1461
+ $level = $attachment->get_optimization_level();
1462
+
1463
+ if ( is_int( $level ) ) {
1464
+ return $level;
1465
+ }
1466
+ }
1467
+
1468
+ if ( ! isset( $default ) ) {
1469
+ $default = get_imagify_option( 'optimization_level' );
1470
+ }
1471
+
1472
+ return $default;
1473
+ }
1474
+
1475
+ /**
1476
+ * Get the path to the temporary file.
1477
+ *
1478
+ * @since 1.8
1479
+ * @access public
1480
+ * @author Grégory Viguier
1481
+ *
1482
+ * @param string $file_path The optimized full-sized file path.
1483
+ * @return string
1484
+ */
1485
+ public function get_temporary_file_path( $file_path ) {
1486
+ return $file_path . '_backup';
1487
+ }
1488
+
1489
+ /**
1490
+ * Backup the optimized full-sized file and replace it by the original backup file.
1491
+ *
1492
+ * @since 1.8
1493
+ * @access public
1494
+ * @author Grégory Viguier
1495
+ *
1496
+ * @param object $attachment An Imagify attachment.
1497
+ */
1498
+ public function backup_optimized_file( $attachment ) {
1499
+ if ( $this->is_prevented( __FUNCTION__ ) ) {
1500
+ return;
1501
+ }
1502
+
1503
+ $backup_path = $attachment->get_backup_path();
1504
+
1505
+ if ( ! $backup_path || ! $attachment->is_optimized() ) {
1506
+ return;
1507
+ }
1508
+
1509
+ /**
1510
+ * Replace the optimized full-sized file by the backup, so any optimization will not use an optimized file, but the original one.
1511
+ * The optimized full-sized file is kept and renamed, and will be put back in place at the end of the optimization process.
1512
+ */
1513
+ $file_path = $attachment->get_original_path();
1514
+ $tmp_file_path = $this->get_temporary_file_path( $file_path );
1515
+
1516
+ if ( $this->filesystem->exists( $file_path ) ) {
1517
+ $this->filesystem->move( $file_path, $tmp_file_path, true );
1518
+ }
1519
+
1520
+ $copied = $this->filesystem->copy( $backup_path, $file_path );
1521
+
1522
+ if ( ! $copied ) {
1523
+ // Uh ho...
1524
+ $this->filesystem->move( $tmp_file_path, $file_path, true );
1525
+ return;
1526
+ }
1527
+
1528
+ // Make sure the dimensions are in sync in post meta.
1529
+ $this->maybe_update_image_dimensions( $attachment, $file_path );
1530
+ }
1531
+
1532
+ /**
1533
+ * Put the optimized full-sized file back.
1534
+ *
1535
+ * @since 1.8
1536
+ * @access public
1537
+ * @author Grégory Viguier
1538
+ *
1539
+ * @param object $attachment An Imagify attachment.
1540
+ */
1541
+ public function put_optimized_file_back( $attachment ) {
1542
+ if ( $this->is_prevented( __FUNCTION__ ) ) {
1543
+ return;
1544
+ }
1545
+
1546
+ $file_path = $attachment->get_original_path();
1547
+ $tmp_file_path = $this->get_temporary_file_path( $file_path );
1548
+
1549
+ if ( ! $this->filesystem->exists( $tmp_file_path ) ) {
1550
+ return;
1551
+ }
1552
+
1553
+ $moved = $this->filesystem->move( $tmp_file_path, $file_path, true );
1554
+
1555
+ if ( ! $moved ) {
1556
+ // Uh ho...
1557
+ return;
1558
+ }
1559
+
1560
+ // Make sure the dimensions are in sync in post meta.
1561
+ $this->maybe_update_image_dimensions( $attachment, $file_path );
1562
+ }
1563
+
1564
+ /**
1565
+ * Make sure the dimensions are in sync in post meta.
1566
+ *
1567
+ * @since 1.8
1568
+ * @access public
1569
+ * @author Grégory Viguier
1570
+ *
1571
+ * @param object $attachment An Imagify attachment.
1572
+ * @param string $file_path Path to the file.
1573
+ * @return bool True when updated.
1574
+ */
1575
+ public function maybe_update_image_dimensions( $attachment, $file_path ) {
1576
+ if ( $this->is_prevented( __FUNCTION__ ) ) {
1577
+ return false;
1578
+ }
1579
+
1580
+ $metadata = wp_get_attachment_metadata( $attachment->get_id() );
1581
+ $width = ! empty( $metadata['width'] ) ? (int) $metadata['width'] : 0;
1582
+ $height = ! empty( $metadata['height'] ) ? (int) $metadata['height'] : 0;
1583
+ $dimensions = $this->filesystem->get_image_size( $file_path );
1584
+
1585
+ if ( ! $dimensions ) {
1586
+ return false;
1587
+ }
1588
+
1589
+ if ( $width === $dimensions['width'] && $height === $dimensions['height'] ) {
1590
+ return false;
1591
+ }
1592
+
1593
+ $metadata['width'] = $dimensions['width'];
1594
+ $metadata['height'] = $dimensions['height'];
1595
+
1596
+ wp_update_attachment_metadata( $attachment->get_id(), $metadata );
1597
+ return true;
1598
+ }
1599
+
1600
+
1601
+ /** ----------------------------------------------------------------------------------------- */
1602
+ /** WR2X COMPAT' TOOLS ====================================================================== */
1603
+ /** ----------------------------------------------------------------------------------------- */
1604
+
1605
+ /**
1606
+ * Get the suffix added to the file name, with a trailing dot.
1607
+ * Don't use it for the size name.
1608
+ *
1609
+ * @since 1.8
1610
+ * @access public
1611
+ * @author Grégory Viguier
1612
+ *
1613
+ * @return string
1614
+ */
1615
+ public function get_suffix() {
1616
+ global $wr2x_core;
1617
+ static $suffix;
1618
+
1619
+ if ( ! isset( $suffix ) ) {
1620
+ $suffix = $wr2x_core && is_object( $wr2x_core ) && method_exists( $wr2x_core, 'retina_extension' ) ? $wr2x_core->retina_extension() : '@2x.';
1621
+ }
1622
+
1623
+ return $suffix;
1624
+ }
1625
+
1626
+ /**
1627
+ * Get info about retina version.
1628
+ *
1629
+ * @since 1.8
1630
+ * @access public
1631
+ * @author Grégory Viguier
1632
+ *
1633
+ * @param object $attachment An Imagify attachment.
1634
+ * @param string $type The type of info. Possible values are 'basic' and 'full' (for the full size).
1635
+ * @return array An array containing some HTML, indexed by the attachment ID.
1636
+ */
1637
+ public function get_retina_info( $attachment, $type = 'basic' ) {
1638
+ global $wr2x_core;
1639
+ static $can_get_info;
1640
+
1641
+ if ( ! isset( $can_get_info ) ) {
1642
+ $can_get_info = $wr2x_core && is_object( $wr2x_core ) && method_exists( $wr2x_core, 'retina_info' ) && method_exists( $wr2x_core, 'html_get_basic_retina_info_full' ) && method_exists( $wr2x_core, 'html_get_basic_retina_info' );
1643
+ }
1644
+
1645
+ if ( ! $can_get_info ) {
1646
+ return '';
1647
+ }
1648
+
1649
+ $attachment_id = $attachment->get_id();
1650
+ $info = $wr2x_core->retina_info( $attachment_id );
1651
+
1652
+ if ( 'full' === $type ) {
1653
+ return array(
1654
+ $attachment_id => $wr2x_core->html_get_basic_retina_info_full( $attachment_id, $info ),
1655
+ );
1656
+ }
1657
+
1658
+ return array(
1659
+ $attachment_id => $wr2x_core->html_get_basic_retina_info( $attachment_id, $info ),
1660
+ );
1661
+ }
1662
+
1663
+ /**
1664
+ * Log.
1665
+ *
1666
+ * @since 1.8
1667
+ * @access public
1668
+ * @author Grégory Viguier
1669
+ *
1670
+ * @param string $text Text to log.
1671
+ */
1672
+ public function log( $text ) {
1673
+ global $wr2x_core;
1674
+ static $can_log;
1675
+
1676
+ if ( ! isset( $can_log ) ) {
1677
+ $can_log = $wr2x_core && is_object( $wr2x_core ) && method_exists( $wr2x_core, 'log' );
1678
+ }
1679
+
1680
+ if ( $can_log ) {
1681
+ $wr2x_core->log( $text );
1682
+ }
1683
+ }
1684
+ }
inc/3rd-party/wp-retina-2x/inc/classes/class-imagify-wp-retina-2x.php ADDED
@@ -0,0 +1,680 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ defined( 'ABSPATH' ) || die( 'Cheatin\' uh?' );
3
+
4
+ /**
5
+ * Class that handles compatibility with WP Retina 2x plugin.
6
+ *
7
+ * @since 1.8
8
+ * @author Grégory Viguier
9
+ */
10
+ class Imagify_WP_Retina_2x {
11
+
12
+ /**
13
+ * Class version.
14
+ *
15
+ * @var string
16
+ * @since 1.8
17
+ * @author Grégory Viguier
18
+ */
19
+ const VERSION = '1.0';
20
+
21
+ /**
22
+ * Core instance.
23
+ *
24
+ * @var object Imagify_WP_Retina_2x_Core
25
+ * @since 1.8
26
+ * @access protected
27
+ * @author Grégory Viguier
28
+ */
29
+ protected $core;
30
+
31
+ /**
32
+ * The single instance of the class.
33
+ *
34
+ * @var object Imagify_WP_Retina_2x
35
+ * @since 1.8
36
+ * @access protected
37
+ * @author Grégory Viguier
38
+ */
39
+ protected static $_instance;
40
+
41
+
42
+ /** ----------------------------------------------------------------------------------------- */
43
+ /** INSTANCE ================================================================================ */
44
+ /** ----------------------------------------------------------------------------------------- */
45
+
46
+ /**
47
+ * Get the main Instance.
48
+ *
49
+ * @since 1.8
50
+ * @access public
51
+ * @author Grégory Viguier
52
+ *
53
+ * @return object Main instance.
54
+ */
55
+ public static function get_instance() {
56
+ if ( ! isset( self::$_instance ) ) {
57
+ self::$_instance = new self();
58
+ }
59
+
60
+ return self::$_instance;
61
+ }
62
+
63
+ /**
64
+ * The constructor.
65
+ *
66
+ * @since 1.8
67
+ * @access protected
68
+ * @author Grégory Viguier
69
+ */
70
+ protected function __construct() {}
71
+
72
+ /**
73
+ * Get the core Instance.
74
+ *
75
+ * @since 1.8
76
+ * @access public
77
+ * @author Grégory Viguier
78
+ *
79
+ * @return object Imagify_WP_Retina_2x_Core instance.
80
+ */
81
+ public function get_core() {
82
+ if ( ! isset( $this->core ) ) {
83
+ $this->core = new Imagify_WP_Retina_2x_Core();
84
+ }
85
+
86
+ return $this->core;
87
+ }
88
+
89
+
90
+ /** ----------------------------------------------------------------------------------------- */
91
+ /** INIT ==================================================================================== */
92
+ /** ----------------------------------------------------------------------------------------- */
93
+
94
+ /**
95
+ * Launch the hooks.
96
+ *
97
+ * @since 1.8
98
+ * @access public
99
+ * @author Grégory Viguier
100
+ */
101
+ public function init() {
102
+ // Deal with Imagify when WPR2X is working.
103
+ add_action( 'wp_ajax_wr2x_generate', array( $this, 'wr2x_generate_ajax_cb' ), 5 );
104
+ add_action( 'wp_ajax_wr2x_delete', array( $this, 'wr2x_delete_all_retina_ajax_cb' ), 5 );
105
+ add_action( 'wp_ajax_wr2x_delete_full', array( $this, 'wr2x_delete_full_retina_ajax_cb' ), 5 );
106
+ add_action( 'wp_ajax_wr2x_replace', array( $this, 'wr2x_replace_all_ajax_cb' ), 5 );
107
+ add_action( 'wp_ajax_wr2x_upload', array( $this, 'wr2x_replace_full_retina_ajax_cb' ), 5 );
108
+ add_action( 'imagify_assets_enqueued', array( $this, 'enqueue_scripts' ) );
109
+ add_action( 'wr2x_retina_file_removed', array( $this, 'remove_retina_thumbnail_data_hook' ), 10, 2 );
110
+ // Deal with Imagify when WP is working.
111
+ add_action( 'delete_attachment', array( $this, 'delete_full_retina_backup_file_hook' ) );
112
+ // Deal with retina thumbnails when Imagify processes the "normal" images.
113
+ add_filter( 'imagify_fill_full_size_data', array( $this, 'optimize_full_retina_version_hook' ), 10, 8 );
114
+ add_filter( 'imagify_fill_thumbnail_data', array( $this, 'optimize_retina_version_hook' ), 10, 8 );
115
+ add_filter( 'imagify_fill_unauthorized_thumbnail_data', array( $this, 'maybe_optimize_unauthorized_retina_version_hook' ), 10, 7 );
116
+ add_action( 'after_imagify_restore_attachment', array( $this, 'restore_retina_images_hook' ) );
117
+ }
118
+
119
+
120
+ /** ----------------------------------------------------------------------------------------- */
121
+ /** AJAX CALLBACKS ========================================================================== */
122
+ /** ----------------------------------------------------------------------------------------- */
123
+
124
+ /**
125
+ * (Re)generate the retina thumbnails (except the full size).
126
+ *
127
+ * @since 1.8
128
+ * @access public
129
+ * @author Grégory Viguier
130
+ */
131
+ public function wr2x_generate_ajax_cb() {
132
+ $this->check_nonce( 'imagify_wr2x_generate' );
133
+ $this->check_user_capacity();
134
+
135
+ $attachment = $this->get_requested_attachment( 'wr2x_generate' );
136
+
137
+ // Delete previous retina images and recreate them.
138
+ $result = $this->get_core()->regenerate_retina_images( $attachment );
139
+
140
+ // Send results.
141
+ $this->maybe_send_json_error( $result );
142
+
143
+ $this->send_json( array(
144
+ 'results' => $this->get_core()->get_retina_info( $attachment ),
145
+ 'message' => __( 'Retina files generated.', 'imagify' ),
146
+ 'imagify_info' => $this->get_imagify_info( $attachment ),
147
+ ) );
148
+ }
149
+
150
+ /**
151
+ * Delete all retina images, including the one for the full size.
152
+ *
153
+ * @since 1.8
154
+ * @access public
155
+ * @author Grégory Viguier
156
+ */
157
+ public function wr2x_delete_all_retina_ajax_cb() {
158
+ $this->check_nonce( 'imagify_wr2x_delete' );
159
+ $this->check_user_capacity();
160
+
161
+ $attachment = $this->get_requested_attachment( 'wr2x_delete_all' );
162
+
163
+ // Delete the retina versions, including the full size.
164
+ $result = $this->get_core()->delete_retina_images( $attachment, true );
165
+
166
+ // Send results.
167
+ $this->maybe_send_json_error( $result );
168
+
169
+ $this->send_json( array(
170
+ 'results' => $this->get_core()->get_retina_info( $attachment ),
171
+ 'results_full' => $this->get_core()->get_retina_info( $attachment, 'full' ),
172
+ 'message' => __( 'Retina files deleted.', 'imagify' ),
173
+ ) );
174
+ }
175
+
176
+ /**
177
+ * Delete the retina version of the full size.
178
+ *
179
+ * @since 1.8
180
+ * @access public
181
+ * @author Grégory Viguier
182
+ */
183
+ public function wr2x_delete_full_retina_ajax_cb() {
184
+ $this->check_nonce( 'imagify_wr2x_delete_full' );
185
+ $this->check_user_capacity();
186
+
187
+ $attachment = $this->get_requested_attachment( 'wr2x_delete_full' );
188
+
189
+ $result = $this->get_core()->delete_full_retina_image( $attachment );
190
+
191
+ // Send results.
192
+ $this->maybe_send_json_error( $result );
193
+
194
+ $this->send_json( array(
195
+ 'results' => $this->get_core()->get_retina_info( $attachment, 'full' ),
196
+ 'message' => __( 'Full retina file deleted.', 'imagify' ),
197
+ ) );
198
+ }
199
+
200
+ /**
201
+ * Replace an attachment (except the retina version of the full size).
202
+ *
203
+ * @since 1.8
204
+ * @access public
205
+ * @author Grégory Viguier
206
+ */
207
+ public function wr2x_replace_all_ajax_cb() {
208
+ $this->check_nonce( 'imagify_wr2x_replace' );
209
+ $this->check_user_capacity();
210
+
211
+ $attachment = $this->get_requested_attachment( 'wr2x_replace_all' );
212
+ $tmp_file_path = $this->get_uploaded_file_path();
213
+
214
+ $result = $this->get_core()->replace_attachment( $attachment, $tmp_file_path );
215
+
216
+ // Send results.
217
+ $this->maybe_send_json_error( $result );
218
+
219
+ $this->send_json( array(
220
+ 'results' => $this->get_core()->get_retina_info( $attachment ),
221
+ 'message' => __( 'Images replaced successfully.', 'imagify' ),
222
+ ) );
223
+ }
224
+
225
+ /**
226
+ * Upload a new retina version for the full size.
227
+ *
228
+ * @since 1.8
229
+ * @access public
230
+ * @author Grégory Viguier
231
+ */
232
+ public function wr2x_replace_full_retina_ajax_cb() {
233
+ $this->check_nonce( 'imagify_wr2x_upload' );
234
+ $this->check_user_capacity();
235
+
236
+ $attachment = $this->get_requested_attachment( 'wr2x_replace_full' );
237
+ $tmp_file_path = $this->get_uploaded_file_path();
238
+
239
+ $result = $this->get_core()->replace_full_retina_image( $attachment, $tmp_file_path );
240
+
241
+ // Send results.
242
+ $this->maybe_send_json_error( $result );
243
+
244
+ $this->send_json( array(
245
+ 'results' => $this->get_core()->get_retina_info( $attachment ),
246
+ 'message' => __( 'Image replaced successfully.', 'imagify' ),
247
+ ) );
248
+ }
249
+
250
+
251
+ /** ----------------------------------------------------------------------------------------- */
252
+ /** OTHER HOOKS ============================================================================= */
253
+ /** ----------------------------------------------------------------------------------------- */
254
+
255
+ /**
256
+ * Queue some JS to add our nonce parameter to all WR2X jQuery ajax requests.
257
+ *
258
+ * @since 1.8
259
+ * @access public
260
+ * @author Grégory Viguier
261
+ */
262
+ public function enqueue_scripts() {
263
+ if ( ! $this->user_can() ) {
264
+ return;
265
+ }
266
+
267
+ $assets = Imagify_Assets::get_instance();
268
+
269
+ $assets->register_script( 'weakmap-polyfill', 'weakmap-polyfill', array(), '2.0.0' );
270
+ $assets->register_script( 'formdata-polyfill', 'formdata-polyfill', array( 'weakmap-polyfill' ), '3.0.10-beta' );
271
+ $assets->register_script( 'wp-retina-2x', 'imagify-wp-retina-2x', array( 'formdata-polyfill', 'jquery' ) );
272
+
273
+ if ( imagify_is_screen( 'library' ) || imagify_is_screen( 'media_page_wp-retina-2x' ) ) {
274
+ $assets->localize_script( 'wp-retina-2x', 'imagifyRetina2x', array(
275
+ 'wr2x_generate' => wp_create_nonce( 'imagify_wr2x_generate' ),
276
+ 'wr2x_delete' => wp_create_nonce( 'imagify_wr2x_delete' ),
277
+ 'wr2x_delete_full' => wp_create_nonce( 'imagify_wr2x_delete_full' ),
278
+ 'wr2x_replace' => wp_create_nonce( 'imagify_wr2x_replace' ),
279
+ 'wr2x_upload' => wp_create_nonce( 'imagify_wr2x_upload' ),
280
+ ) );
281
+ $assets->enqueue( 'wp-retina-2x' );
282
+ }
283
+ }
284
+
285
+ /**
286
+ * After a retina thumbnail is deleted, remove its Imagify data.
287
+ * This should be useless since we replaced every AJAX callbacks.
288
+ *
289
+ * @since 1.8
290
+ * @access public
291
+ * @see wr2x_delete_attachment()
292
+ * @author Grégory Viguier
293
+ *
294
+ * @param int $attachment_id An attachment ID.
295
+ * @param string $retina_filename The retina thumbnail file name.
296
+ */
297
+ public function remove_retina_thumbnail_data_hook( $attachment_id, $retina_filename ) {
298
+ $attachment = get_imagify_attachment( 'wp', $attachment_id, 'wr2x_delete' );
299
+
300
+ $this->get_core()->remove_retina_image_data_by_filename( $attachment, $retina_filename );
301
+ }
302
+
303
+ /**
304
+ * Delete the backup of the retina version of the full size file when an attachement is deleted.
305
+ *
306
+ * @since 1.8
307
+ * @access public
308
+ * @author Grégory Viguier
309
+ *
310
+ * @param int $attachment_id An attachment ID.
311
+ */
312
+ public function delete_full_retina_backup_file_hook( $attachment_id ) {
313
+ if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) {
314
+ return;
315
+ }
316
+
317
+ $attachment = get_imagify_attachment( 'wp', $attachment_id, 'delete_attachment' );
318
+ $retina_path = $this->get_core()->get_retina_path( $attachment->get_original_path() );
319
+
320
+ if ( $retina_path ) {
321
+ $this->get_core()->delete_file_backup( $retina_path );
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Filter the optimization data of the full size.
327
+ *
328
+ * @since 1.8
329
+ * @access public
330
+ * @author Grégory Viguier
331
+ *
332
+ * @param array $data The statistics data.
333
+ * @param object $response The API response.
334
+ * @param int $attachment_id The attachment ID.
335
+ * @param string $path The attachment path.
336
+ * @param string $url The attachment URL.
337
+ * @param string $size_key The attachment size key. The value is obviously 'full' but it's kept for oncistancy with other filters.
338
+ * @param int $optimization_level The optimization level.
339
+ * @param array $metadata WP metadata.
340
+ * @return array $data The new optimization data.
341
+ */
342
+ public function optimize_full_retina_version_hook( $data, $response, $attachment_id, $path, $url, $size_key, $optimization_level, $metadata ) {
343
+ if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) {
344
+ return $data;
345
+ }
346
+
347
+ $attachment = get_imagify_attachment( 'wp', $attachment_id, 'optimize_full_retina_version_hook' );
348
+
349
+ return $this->get_core()->optimize_retina_image( array(
350
+ 'data' => $data,
351
+ 'attachment' => get_imagify_attachment( 'wp', $attachment_id, 'optimize_full_retina_version_hook' ),
352
+ 'retina_path' => wr2x_get_retina( $path ),
353
+ 'size_key' => $size_key,
354
+ 'optimization_level' => $optimization_level,
355
+ 'metadata' => $metadata,
356
+ ) );
357
+ }
358
+
359
+ /**
360
+ * Filter the optimization data of each thumbnail.
361
+ *
362
+ * @since 1.8
363
+ * @access public
364
+ * @author Grégory Viguier
365
+ *
366
+ * @param array $data The statistics data.
367
+ * @param object $response The API response.
368
+ * @param int $attachment_id The attachment ID.
369
+ * @param string $path The thumbnail path.
370
+ * @param string $url The thumbnail URL.
371
+ * @param string $size_key The thumbnail size key.
372
+ * @param int $optimization_level The optimization level.
373
+ * @param array $metadata WP metadata.
374
+ * @return array $data The new optimization data.
375
+ */
376
+ public function optimize_retina_version_hook( $data, $response, $attachment_id, $path, $url, $size_key, $optimization_level, $metadata ) {
377
+ if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) {
378
+ return $data;
379
+ }
380
+
381
+ return $this->get_core()->optimize_retina_image( array(
382
+ 'data' => $data,
383
+ 'attachment' => get_imagify_attachment( 'wp', $attachment_id, 'optimize_retina_version_hook' ),
384
+ 'retina_path' => wr2x_get_retina( $path ),
385
+ 'size_key' => $size_key,
386
+ 'optimization_level' => $optimization_level,
387
+ 'metadata' => $metadata,
388
+ ) );
389
+ }
390
+
391
+ /**
392
+ * If a thumbnail size is disallowed in Imagify' settings, we can still try to optimize its "@2x" version.
393
+ *
394
+ * @since 1.8
395
+ * @access public
396
+ * @author Grégory Viguier
397
+ *
398
+ * @param array $data The statistics data.
399
+ * @param int $attachment_id The attachment ID.
400
+ * @param string $path The thumbnail path.
401
+ * @param string $url The thumbnail URL.
402
+ * @param string $size_key The thumbnail size key.
403
+ * @param int $optimization_level The optimization level.
404
+ * @param array $metadata WP metadata.
405
+ * @return array $data The new optimization data.
406
+ */
407
+ public function maybe_optimize_unauthorized_retina_version_hook( $data, $attachment_id, $path, $url, $size_key, $optimization_level, $metadata ) {
408
+ if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) {
409
+ return $data;
410
+ }
411
+
412
+ return $this->get_core()->optimize_retina_image( array(
413
+ 'data' => $data,
414
+ 'attachment' => get_imagify_attachment( 'wp', $attachment_id, 'maybe_optimize_unauthorized_retina_version_hook' ),
415
+ 'retina_path' => wr2x_get_retina( $path ),
416
+ 'size_key' => $size_key,
417
+ 'optimization_level' => $optimization_level,
418
+ 'metadata' => $metadata,
419
+ ) );
420
+ }
421
+
422
+ /**
423
+ * Delete previous retina images and recreate them.
424
+ *
425
+ * @since 1.8
426
+ * @access public
427
+ * @author Grégory Viguier
428
+ *
429
+ * @param int $attachment_id An attachment ID.
430
+ */
431
+ public function restore_retina_images_hook( $attachment_id ) {
432
+ if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) {
433
+ return;
434
+ }
435
+
436
+ $attachment = get_imagify_attachment( 'wp', $attachment_id, 'restore_retina_images_hook' );
437
+
438
+ if ( ! $this->get_core()->has_retina_images( $attachment ) ) {
439
+ return;
440
+ }
441
+
442
+ // At this point, previous Imagify data has been removed.
443
+ $this->get_core()->regenerate_retina_images( $attachment );
444
+ $this->get_core()->restore_full_retina_file( $attachment );
445
+ }
446
+
447
+
448
+ /** ----------------------------------------------------------------------------------------- */
449
+ /** INTERNAL TOOLS ========================================================================== */
450
+ /** ----------------------------------------------------------------------------------------- */
451
+
452
+ /**
453
+ * Check for nonce.
454
+ *
455
+ * @since 1.8
456
+ * @access public
457
+ * @author Grégory Viguier
458
+ *
459
+ * @param string $action Action nonce.
460
+ * @param string|bool $query_arg Optional. Key to check for the nonce in `$_REQUEST`. If false, `$_REQUEST` values will be evaluated for '_ajax_nonce', and '_wpnonce' (in that order). Default false.
461
+ */
462
+ public function check_nonce( $action, $query_arg = 'imagify_nonce' ) {
463
+ if ( ! check_ajax_referer( $action, $query_arg, false ) ) {
464
+ $this->send_json( array(
465
+ 'success' => false,
466
+ 'message' => __( 'Sorry, you are not allowed to do that.', 'imagify' ),
467
+ ) );
468
+ }
469
+ }
470
+
471
+ /**
472
+ * Check for user capacity.
473
+ *
474
+ * @since 1.8
475
+ * @access public
476
+ * @author Grégory Viguier
477
+ */
478
+ public function check_user_capacity() {
479
+ if ( ! $this->user_can() ) {
480
+ $this->send_json( array(
481
+ 'success' => false,
482
+ 'message' => __( 'Sorry, you are not allowed to do that.', 'imagify' ),
483
+ ) );
484
+ }
485
+ }
486
+
487
+ /**
488
+ * Tell if the current user can re-optimize files.
489
+ *
490
+ * @since 1.8
491
+ * @access public
492
+ * @author Grégory Viguier
493
+ */
494
+ public function user_can() {
495
+ return imagify_current_user_can( 'auto-optimize' );
496
+ }
497
+
498
+ /**
499
+ * Shorthand to get the attachment ID sent via $_POST.
500
+ *
501
+ * @since 1.8
502
+ * @access public
503
+ * @author Grégory Viguier
504
+ *
505
+ * @param string $context The context to use in get_imagify_attachment().
506
+ * @param string $key The $_POST key.
507
+ * @return int $attachment_id
508
+ */
509
+ public function get_requested_attachment( $context, $key = 'attachmentId' ) {
510
+ $attachment_id = filter_input( INPUT_POST, $key, FILTER_VALIDATE_INT );
511
+
512
+ if ( $attachment_id <= 0 ) {
513
+ $this->send_json( array(
514
+ 'success' => false,
515
+ 'message' => __( 'The attachment ID is missing.', 'imagify' ),
516
+ ) );
517
+ }
518
+
519
+ if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) {
520
+ $this->send_json( array(
521
+ 'success' => false,
522
+ 'message' => __( 'This format is not supported.', 'imagify' ),
523
+ ) );
524
+ }
525
+
526
+ $attachment = get_imagify_attachment( 'wp', $attachment_id, $context );
527
+
528
+ if ( ! $this->has_required_metadata( $attachment ) ) {
529
+ $this->send_json( array(
530
+ 'success' => false,
531
+ 'message' => __( 'This attachment lacks the required metadata.', 'imagify' ),
532
+ ) );
533
+ }
534
+
535
+ return $attachment;
536
+ }
537
+
538
+ /**
539
+ * Shorthand to get the path to the uploaded file.
540
+ *
541
+ * @since 1.8
542
+ * @access public
543
+ * @author Grégory Viguier
544
+ *
545
+ * @return string Path to the temporary file.
546
+ */
547
+ public function get_uploaded_file_path() {
548
+ $tmp_file_path = ! empty( $_FILES['file']['tmp_name'] ) && is_uploaded_file( $_FILES['file']['tmp_name'] ) ? $_FILES['file']['tmp_name'] : '';
549
+ $filesystem = Imagify_Filesystem::get_instance();
550
+
551
+ if ( ! $tmp_file_path || ! $filesystem->is_image( $tmp_file_path ) ) {
552
+ $this->get_core()->log( 'The file is not an image or the upload went wrong.' );
553
+ $filesystem->delete( $tmp_file_path );
554
+
555
+ $this->send_json_string( array(
556
+ 'success' => false,
557
+ 'message' => __( 'The file is not an image or the upload went wrong.', 'imagify' ),
558
+ ) );
559
+ }
560
+
561
+ $file_name = filter_input( INPUT_POST, 'filename', FILTER_SANITIZE_STRING );
562
+ $file_data = wp_check_filetype_and_ext( $tmp_file_path, $file_name );
563
+
564
+ if ( empty( $file_data['ext'] ) ) {
565
+ $this->get_core()->log( 'You cannot use this file (wrong extension? wrong type?).' );
566
+ $filesystem->delete( $tmp_file_path );
567
+
568
+ $this->send_json_string( array(
569
+ 'success' => false,
570
+ 'message' => __( 'You cannot use this file (wrong extension? wrong type?).', 'imagify' ),
571
+ ) );
572
+ }
573
+
574
+ $this->get_core()->log( 'The temporary file was written successfully.' );
575
+
576
+ return $tmp_file_path;
577
+ }
578
+
579
+ /**
580
+ * Tell if Imagify's column content has been requested.
581
+ *
582
+ * @since 1.8
583
+ * @access public
584
+ * @author Grégory Viguier
585
+ *
586
+ * @return bool
587
+ */
588
+ public function needs_info() {
589
+ return filter_input( INPUT_POST, 'imagify_info', FILTER_VALIDATE_INT ) === 1;
590
+ }
591
+
592
+ /**
593
+ * Tell if the attachment has the required WP metadata.
594
+ *
595
+ * @since 1.8
596
+ * @access public
597
+ * @see $wr2x_core->is_image_meta()
598
+ * @author Grégory Viguier
599
+ *
600
+ * @param object $attachment An Imagify attachment.
601
+ * @return bool
602
+ */
603
+ public function has_required_metadata( $attachment ) {
604
+ if ( ! $attachment->has_required_metadata() ) {
605
+ return false;
606
+ }
607
+
608
+ $metadata = wp_get_attachment_metadata( $attachment->get_id() );
609
+
610
+ if ( ! isset( $metadata['sizes'], $metadata['width'], $metadata['height'] ) ) {
611
+ return false;
612
+ }
613
+
614
+ return is_array( $metadata['sizes'] );
615
+ }
616
+
617
+ /**
618
+ * Get info about Imagify.
619
+ *
620
+ * @since 1.8
621
+ * @access public
622
+ * @author Grégory Viguier
623
+ *
624
+ * @param object $attachment An Imagify attachment.
625
+ * @return array An array containing some HTML, indexed by the attachment ID.
626
+ */
627
+ public function get_imagify_info( $attachment ) {
628
+ if ( ! $this->needs_info() ) {
629
+ return array();
630
+ }
631
+
632
+ return array(
633
+ $attachment->get_id() => get_imagify_media_column_content( $attachment ),
634
+ );
635
+ }
636
+
637
+ /**
638
+ * Send a JSON response back to an Ajax request.
639
+ * It sends a "success" by default.
640
+ *
641
+ * @since 1.8
642
+ * @access public
643
+ * @author Grégory Viguier
644
+ *
645
+ * @param array $data An array of data to print and die.
646
+ */
647
+ public function send_json( $data ) {
648
+ // Use the same JSON format than WPR2X.
649
+ $data = array_merge( array(
650
+ 'success' => true,
651
+ 'message' => '',
652
+ 'source' => 'imagify',
653
+ 'context' => 'wr2x',
654
+ ), $data );
655
+
656
+ echo wp_json_encode( $data );
657
+ die;
658
+ }
659
+
660
+ /**
661
+ * Send a JSON error response if the given argument is a WP_Error object.
662
+ *
663
+ * @since 1.8
664
+ * @access public
665
+ * @author Grégory Viguier
666
+ *
667
+ * @param mixed $result Result of an operation.
668
+ */
669
+ public function maybe_send_json_error( $result ) {
670
+ if ( ! is_wp_error( $result ) ) {
671
+ return;
672
+ }
673
+
674
+ // Oh no.
675
+ $this->send_json( array(
676
+ 'success' => false,
677
+ 'message' => $result->get_error_message(),
678
+ ) );
679
+ }
680
+ }
inc/3rd-party/wp-retina-2x/wp-retina-2x.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+ defined( 'ABSPATH' ) || die( 'Cheatin\' uh?' );
3
+
4
+ if ( ! class_exists( 'Meow_WR2X_Core' ) ) {
5
+ return;
6
+ }
7
+
8
+ Imagify_WP_Retina_2x::get_instance()->init();
inc/3rd-party/wp-rocket.php CHANGED
File without changes
inc/admin/custom-folders.php CHANGED
File without changes
inc/admin/media.php CHANGED
@@ -55,7 +55,7 @@ function _imagify_add_actions_to_media_list_row( $actions, $post ) {
55
  $attachment = get_imagify_attachment( 'wp', $post->ID, 'media_row_actions' );
56
 
57
  // If this attachment is not an image, do nothing.
58
- if ( ! $attachment->is_extension_supported() ) {
59
  return $actions;
60
  }
61
 
55
  $attachment = get_imagify_attachment( 'wp', $post->ID, 'media_row_actions' );
56
 
57
  // If this attachment is not an image, do nothing.
58
+ if ( ! $attachment->is_extension_supported() || ! $attachment->is_image() ) {
59
  return $actions;
60
  }
61
 
inc/admin/upload.php CHANGED
@@ -56,14 +56,14 @@ function _imagify_attachments_filter_dropdown() {
56
  $errors = imagify_count_error_attachments();
57
  $status = isset( $_GET['imagify-status'] ) ? $_GET['imagify-status'] : 0; // WPCS: CSRF ok.
58
  $options = array(
59
- 'optimized' => __( 'Optimized','imagify' ),
60
- 'unoptimized' => __( 'Unoptimized','imagify' ),
61
- 'errors' => __( 'Errors','imagify' ),
62
  );
63
 
64
  echo '<label class="screen-reader-text" for="filter-by-optimization-status">' . __( 'Filter by status','imagify' ) . '</label>';
65
  echo '<select id="filter-by-optimization-status" name="imagify-status">';
66
- echo '<option value="0" selected="selected">' . __( 'All images','imagify' ) . '</option>';
67
 
68
  foreach ( $options as $value => $label ) {
69
  echo '<option value="' . $value . '" ' . selected( $status, $value, false ) . '>' . $label . ' (' . ${$value} . ')</option>';
56
  $errors = imagify_count_error_attachments();
57
  $status = isset( $_GET['imagify-status'] ) ? $_GET['imagify-status'] : 0; // WPCS: CSRF ok.
58
  $options = array(
59
+ 'optimized' => _x( 'Optimized', 'Media Files','imagify' ),
60
+ 'unoptimized' => _x( 'Unoptimized', 'Media Files','imagify' ),
61
+ 'errors' => _x( 'Errors', 'Media Files','imagify' ),
62
  );
63
 
64
  echo '<label class="screen-reader-text" for="filter-by-optimization-status">' . __( 'Filter by status','imagify' ) . '</label>';
65
  echo '<select id="filter-by-optimization-status" name="imagify-status">';
66
+ echo '<option value="0" selected="selected">' . __( 'All Media Files','imagify' ) . '</option>';
67
 
68
  foreach ( $options as $value => $label ) {
69
  echo '<option value="' . $value . '" ' . selected( $status, $value, false ) . '>' . $label . ' (' . ${$value} . ')</option>';
inc/classes/class-imagify-abstract-attachment.php CHANGED
@@ -50,18 +50,47 @@ abstract class Imagify_Abstract_Attachment extends Imagify_Abstract_Attachment_D
50
  * @since 1.7
51
  * @access protected
52
  */
53
- protected $row = null;
54
 
55
  /**
56
- * Tell if the file extension can be optimized by Imagify.
57
- * This is used to cache the result of $this->is_extension_supported().
58
  *
59
  * @var bool
60
- * @since 1.7
61
  * @access protected
62
- * @see $this->is_extension_supported()
63
  */
64
- protected $is_extension_supported;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  /**
67
  * Filesystem object.
@@ -116,12 +145,12 @@ abstract class Imagify_Abstract_Attachment extends Imagify_Abstract_Attachment_D
116
  global $post;
117
 
118
  if ( $id ) {
119
- if ( is_a( $id, 'WP_Post' ) ) {
120
  $this->id = $id->ID;
121
  } elseif ( is_numeric( $id ) ) {
122
  $this->id = $id;
123
  }
124
- } elseif ( $post && is_a( $post, 'WP_Post' ) ) {
125
  $this->id = $post->ID;
126
  }
127
 
@@ -356,6 +385,89 @@ abstract class Imagify_Abstract_Attachment extends Imagify_Abstract_Attachment_D
356
  );
357
  }
358
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  /**
360
  * Get the attachment extension.
361
  *
@@ -365,11 +477,18 @@ abstract class Imagify_Abstract_Attachment extends Imagify_Abstract_Attachment_D
365
  * @return string|null
366
  */
367
  public function get_extension() {
 
 
 
 
368
  if ( ! $this->is_valid() ) {
369
- return '';
 
370
  }
371
 
372
- return $this->filesystem->path_info( $this->get_original_path(), 'extension' );
 
 
373
  }
374
 
375
  /**
@@ -382,37 +501,21 @@ abstract class Imagify_Abstract_Attachment extends Imagify_Abstract_Attachment_D
382
  * @return bool
383
  */
384
  public function is_extension_supported() {
385
- if ( isset( $this->is_extension_supported ) ) {
386
- return $this->is_extension_supported;
387
- }
388
-
389
- if ( ! $this->is_valid() ) {
390
- $this->is_extension_supported = false;
391
- return $this->is_extension_supported;
392
- }
393
-
394
- $file_type = wp_check_filetype( $this->get_original_path(), imagify_get_mime_types() );
395
-
396
- $this->is_extension_supported = (bool) $file_type['ext'];
397
-
398
- return $this->is_extension_supported;
399
  }
400
 
401
  /**
402
  * Tell if the current file mime type is supported.
403
  *
404
  * @since 1.6.9
 
405
  * @access public
406
  * @author Grégory Viguier
407
  *
408
  * @return bool
409
  */
410
  public function is_mime_type_supported() {
411
- if ( ! $this->is_valid() ) {
412
- return false;
413
- }
414
-
415
- return imagify_is_attachment_mime_type_supported( $this->id );
416
  }
417
 
418
  /**
@@ -778,7 +881,7 @@ abstract class Imagify_Abstract_Attachment extends Imagify_Abstract_Attachment_D
778
  */
779
  public function get_unoptimized_sizes() {
780
  // The attachment must have been optimized once and have a backup.
781
- if ( ! $this->is_valid() || ! $this->is_optimized() || ! $this->has_backup() ) {
782
  return array();
783
  }
784
 
@@ -917,7 +1020,7 @@ abstract class Imagify_Abstract_Attachment extends Imagify_Abstract_Attachment_D
917
  * @return string Path the the resized image or the original image if the resize failed.
918
  */
919
  public function resize( $attachment_path, $attachment_sizes, $max_width ) {
920
- if ( ! $this->is_valid() ) {
921
  return '';
922
  }
923
 
50
  * @since 1.7
51
  * @access protected
52
  */
53
+ protected $row;
54
 
55
  /**
56
+ * Tell if the file is an image.
 
57
  *
58
  * @var bool
59
+ * @since 1.8
60
  * @access protected
61
+ * @see $this->is_image()
62
  */
63
+ protected $is_image;
64
+
65
+ /**
66
+ * Tell if the file is a pdf.
67
+ *
68
+ * @var bool
69
+ * @since 1.8
70
+ * @access protected
71
+ * @see $this->is_pdf()
72
+ */
73
+ protected $is_pdf;
74
+
75
+ /**
76
+ * Stores the file extension (even if the extension is not supported by Imagify).
77
+ *
78
+ * @var string|null
79
+ * @since 1.8
80
+ * @access protected
81
+ * @see $this->get_extension()
82
+ */
83
+ protected $extension = false;
84
+
85
+ /**
86
+ * Stores the file mime type + file extension (if the file is supported).
87
+ *
88
+ * @var array
89
+ * @since 1.8
90
+ * @access protected
91
+ * @see $this->get_file_type()
92
+ */
93
+ protected $file_type;
94
 
95
  /**
96
  * Filesystem object.
145
  global $post;
146
 
147
  if ( $id ) {
148
+ if ( $id instanceof WP_Post ) {
149
  $this->id = $id->ID;
150
  } elseif ( is_numeric( $id ) ) {
151
  $this->id = $id;
152
  }
153
+ } elseif ( $post && $id instanceof WP_Post ) {
154
  $this->id = $post->ID;
155
  }
156
 
385
  );
386
  }
387
 
388
+ /**
389
+ * Tell if the current item refers to an image, based on file extension.
390
+ *
391
+ * @since 1.8
392
+ * @access public
393
+ * @author Grégory Viguier
394
+ *
395
+ * @return bool Returns false in case it's an image but not in a supported format (bmp for example).
396
+ */
397
+ public function is_image() {
398
+ if ( isset( $this->is_image ) ) {
399
+ return $this->is_image;
400
+ }
401
+
402
+ $this->is_image = strpos( (string) $this->get_mime_type(), 'image/' ) === 0;
403
+
404
+ return $this->is_image;
405
+ }
406
+
407
+ /**
408
+ * Tell if the current item refers to a pdf, based on file extension.
409
+ *
410
+ * @since 1.8
411
+ * @access public
412
+ * @author Grégory Viguier
413
+ *
414
+ * @return bool
415
+ */
416
+ public function is_pdf() {
417
+ if ( isset( $this->is_pdf ) ) {
418
+ return $this->is_pdf;
419
+ }
420
+
421
+ $this->is_pdf = 'application/pdf' === $this->get_mime_type();
422
+
423
+ return $this->is_pdf;
424
+ }
425
+
426
+ /**
427
+ * Get the file mime type.
428
+ *
429
+ * @since 1.8
430
+ * @access public
431
+ * @author Grégory Viguier
432
+ *
433
+ * @return bool
434
+ */
435
+ public function get_mime_type() {
436
+ return $this->get_file_type()->type;
437
+ }
438
+
439
+ /**
440
+ * Get the file mime type + file extension (if the file is supported).
441
+ *
442
+ * @since 1.8
443
+ * @access public
444
+ * @see wp_check_filetype()
445
+ * @author Grégory Viguier
446
+ *
447
+ * @return object
448
+ */
449
+ public function get_file_type() {
450
+ if ( isset( $this->file_type ) ) {
451
+ return $this->file_type;
452
+ }
453
+
454
+ if ( ! $this->is_valid() ) {
455
+ $this->file_type = (object) array( 'ext' => '', 'type' => '' );
456
+ return $this->file_type;
457
+ }
458
+
459
+ $path = $this->get_original_path();
460
+
461
+ if ( ! $path ) {
462
+ $this->file_type = (object) array( 'ext' => '', 'type' => '' );
463
+ return $this->file_type;
464
+ }
465
+
466
+ $this->file_type = (object) wp_check_filetype( $path, imagify_get_mime_types() );
467
+
468
+ return $this->file_type;
469
+ }
470
+
471
  /**
472
  * Get the attachment extension.
473
  *
477
  * @return string|null
478
  */
479
  public function get_extension() {
480
+ if ( false !== $this->extension ) {
481
+ return $this->extension;
482
+ }
483
+
484
  if ( ! $this->is_valid() ) {
485
+ $this->extension = null;
486
+ return $this->extension;
487
  }
488
 
489
+ $this->extension = $this->filesystem->path_info( $this->get_original_path(), 'extension' );
490
+
491
+ return $this->extension;
492
  }
493
 
494
  /**
501
  * @return bool
502
  */
503
  public function is_extension_supported() {
504
+ return (bool) $this->get_file_type()->ext;
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  }
506
 
507
  /**
508
  * Tell if the current file mime type is supported.
509
  *
510
  * @since 1.6.9
511
+ * @since 1.8 Does the same has this->is_extension_supported().
512
  * @access public
513
  * @author Grégory Viguier
514
  *
515
  * @return bool
516
  */
517
  public function is_mime_type_supported() {
518
+ return (bool) $this->get_mime_type();
 
 
 
 
519
  }
520
 
521
  /**
881
  */
882
  public function get_unoptimized_sizes() {
883
  // The attachment must have been optimized once and have a backup.
884
+ if ( ! $this->is_valid() || ! $this->is_optimized() || ! $this->has_backup() || ! $this->is_image() ) {
885
  return array();
886
  }
887
 
1020
  * @return string Path the the resized image or the original image if the resize failed.
1021
  */
1022
  public function resize( $attachment_path, $attachment_sizes, $max_width ) {
1023
+ if ( ! $this->is_valid() || ! $this->is_image() ) {
1024
  return '';
1025
  }
1026
 
inc/classes/class-imagify-abstract-cron.php CHANGED
File without changes
inc/classes/class-imagify-abstract-db.php CHANGED
File without changes
inc/classes/class-imagify-abstract-options.php CHANGED
File without changes
inc/classes/class-imagify-admin-ajax-post.php CHANGED
@@ -16,7 +16,7 @@ class Imagify_Admin_Ajax_Post {
16
  * @since 1.6.11
17
  * @author Grégory Viguier
18
  */
19
- const VERSION = '1.0.3';
20
 
21
  /**
22
  * Actions to be triggered on admin ajax and admin post.
@@ -244,6 +244,12 @@ class Imagify_Admin_Ajax_Post {
244
  imagify_check_user_capacity( 'manual-optimize', $attachment_id );
245
 
246
  $attachment = get_imagify_attachment( $context, $attachment_id, 'imagify_optimize_missing_sizes' );
 
 
 
 
 
 
247
 
248
  // Optimize the missing thumbnails.
249
  $attachment->optimize_missing_thumbnails();
@@ -1359,13 +1365,14 @@ class Imagify_Admin_Ajax_Post {
1359
  imagify_die( __( 'This file is not a folder.', 'imagify' ) );
1360
  }
1361
 
 
 
1362
  if ( Imagify_Files_Scan::is_path_forbidden( $folder ) ) {
1363
  imagify_die( __( 'This folder is not allowed.', 'imagify' ) );
1364
  }
1365
 
1366
  // Finally we made all our validations.
1367
  $selected = ! empty( $_POST['selected'] ) && is_array( $_POST['selected'] ) ? array_flip( $_POST['selected'] ) : array();
1368
- $folder = $this->filesystem->normalize_dir_path( $folder );
1369
  $views = Imagify_Views::get_instance();
1370
  $output = '';
1371
 
@@ -1381,9 +1388,9 @@ class Imagify_Admin_Ajax_Post {
1381
  ) );
1382
  }
1383
 
1384
- $dir = new DirectoryIterator( $folder );
1385
- $dir = new Imagify_Files_Iterator( $dir );
1386
- $images = 0;
1387
 
1388
  foreach ( new IteratorIterator( $dir ) as $file ) {
1389
  if ( ! $file->isDir() ) {
@@ -1401,13 +1408,13 @@ class Imagify_Admin_Ajax_Post {
1401
  'checkbox_value' => esc_attr( $placeholder ) . '#///#' . esc_attr( $relative_path ),
1402
  'checkbox_id' => sanitize_html_class( $placeholder ),
1403
  'checkbox_selected' => isset( $selected[ $placeholder ] ),
1404
- 'label' => str_replace( $folder, '', untrailingslashit( $folder_path ) ),
1405
  ) );
1406
  }
1407
 
1408
  if ( $images ) {
1409
  /* translators: %s is a formatted number, dont use %d. */
1410
- $output .= '<li class="imagify-number-of-images-in-folder"><em><span class="dashicons dashicons-images-alt"></span> ' . sprintf( _n( '%s image', '%s images', $images, 'imagify' ), number_format_i18n( $images ) ) . '</em></li>';
1411
  }
1412
 
1413
  if ( ! $output ) {
16
  * @since 1.6.11
17
  * @author Grégory Viguier
18
  */
19
+ const VERSION = '1.0.4';
20
 
21
  /**
22
  * Actions to be triggered on admin ajax and admin post.
244
  imagify_check_user_capacity( 'manual-optimize', $attachment_id );
245
 
246
  $attachment = get_imagify_attachment( $context, $attachment_id, 'imagify_optimize_missing_sizes' );
247
+ $context = $attachment->get_context();
248
+
249
+ if ( ! $attachment->is_image() ) {
250
+ $output = get_imagify_attachment_optimization_text( $attachment, $context );
251
+ wp_send_json_error( $output );
252
+ }
253
 
254
  // Optimize the missing thumbnails.
255
  $attachment->optimize_missing_thumbnails();
1365
  imagify_die( __( 'This file is not a folder.', 'imagify' ) );
1366
  }
1367
 
1368
+ $folder = $this->filesystem->normalize_dir_path( $folder );
1369
+
1370
  if ( Imagify_Files_Scan::is_path_forbidden( $folder ) ) {
1371
  imagify_die( __( 'This folder is not allowed.', 'imagify' ) );
1372
  }
1373
 
1374
  // Finally we made all our validations.
1375
  $selected = ! empty( $_POST['selected'] ) && is_array( $_POST['selected'] ) ? array_flip( $_POST['selected'] ) : array();
 
1376
  $views = Imagify_Views::get_instance();
1377
  $output = '';
1378
 
1388
  ) );
1389
  }
1390
 
1391
+ $dir = new DirectoryIterator( $folder );
1392
+ $dir = new Imagify_Files_Iterator( $dir );
1393
+ $images = 0;
1394
 
1395
  foreach ( new IteratorIterator( $dir ) as $file ) {
1396
  if ( ! $file->isDir() ) {
1408
  'checkbox_value' => esc_attr( $placeholder ) . '#///#' . esc_attr( $relative_path ),
1409
  'checkbox_id' => sanitize_html_class( $placeholder ),
1410
  'checkbox_selected' => isset( $selected[ $placeholder ] ),
1411
+ 'label' => $this->filesystem->file_name( $folder_path ),
1412
  ) );
1413
  }
1414
 
1415
  if ( $images ) {
1416
  /* translators: %s is a formatted number, dont use %d. */
1417
+ $output .= '<li class="imagify-number-of-images-in-folder"><em><span class="dashicons dashicons-images-alt"></span> ' . sprintf( _n( '%s Media File', '%s Media Files', $images, 'imagify' ), number_format_i18n( $images ) ) . '</em></li>';
1418
  }
1419
 
1420
  if ( ! $output ) {
inc/classes/class-imagify-attachment.php CHANGED
@@ -101,6 +101,10 @@ class Imagify_Attachment extends Imagify_Abstract_Attachment {
101
  * @return array
102
  */
103
  public function get_dimensions() {
 
 
 
 
104
  $values = wp_get_attachment_image_src( $this->id, 'full' );
105
 
106
  return array(
@@ -119,7 +123,7 @@ class Imagify_Attachment extends Imagify_Abstract_Attachment {
119
  */
120
  public function update_metadata_size() {
121
  // Check if the attachment extension is allowed.
122
- if ( ! $this->is_extension_supported() ) {
123
  return false;
124
  }
125
 
@@ -248,18 +252,12 @@ class Imagify_Attachment extends Imagify_Abstract_Attachment {
248
  $thumbnail_path = $original_dirname . $result[ $thumbnail_size ]['file'];
249
 
250
  // Since we used the backup image as source, the new image is still in the backup folder, we need to move it.
251
- $this->filesystem->move( $backup_thumb_path, $thumbnail_path, true );
252
-
253
- if ( $this->filesystem->exists( $backup_thumb_path ) ) {
254
- $this->filesystem->delete( $backup_thumb_path );
255
- }
256
 
257
- if ( ! $this->filesystem->exists( $thumbnail_path ) ) {
258
  return new WP_Error( 'image_resize_error' );
259
  }
260
 
261
- $this->filesystem->chmod_file( $thumbnail_path );
262
-
263
  return reset( $result );
264
  }
265
 
@@ -274,7 +272,7 @@ class Imagify_Attachment extends Imagify_Abstract_Attachment {
274
  * @return array An array of thumbnail data (width, height, crop, file).
275
  */
276
  protected function create_missing_thumbnails( $missing_sizes ) {
277
- if ( ! $missing_sizes ) {
278
  return array();
279
  }
280
 
@@ -328,7 +326,7 @@ class Imagify_Attachment extends Imagify_Abstract_Attachment {
328
 
329
  $optimization_level = isset( $optimization_level ) ? (int) $optimization_level : get_imagify_option( 'optimization_level' );
330
  $metadata = $metadata ? $metadata : wp_get_attachment_metadata( $this->id );
331
- $sizes = isset( $metadata['sizes'] ) ? (array) $metadata['sizes'] : array();
332
 
333
  // To avoid issue with "original_size" at 0 in "_imagify_data".
334
  if ( 0 === (int) $this->get_stats_data( 'original_size' ) ) {
@@ -336,7 +334,7 @@ class Imagify_Attachment extends Imagify_Abstract_Attachment {
336
  }
337
 
338
  // Check if the full size is already optimized.
339
- if ( $this->is_optimized() && ( $this->get_optimization_level() === $optimization_level ) ) {
340
  return;
341
  }
342
 
@@ -351,14 +349,14 @@ class Imagify_Attachment extends Imagify_Abstract_Attachment {
351
  * @since 1.0
352
  *
353
  * @param int $id The attachment ID.
354
- */
355
  do_action( 'before_imagify_optimize_attachment', $this->id );
356
 
357
  $this->set_running_status();
358
 
359
  // Get the resize values for the original size.
360
  $resized = false;
361
- $do_resize = get_imagify_option( 'resize_larger' );
362
 
363
  if ( $do_resize ) {
364
  $resize_width = get_imagify_option( 'resize_larger_w' );
@@ -372,12 +370,6 @@ class Imagify_Attachment extends Imagify_Abstract_Attachment {
372
  imagify_backup_file( $attachment_path );
373
 
374
  $this->filesystem->move( $resized_attachment_path, $attachment_path, true );
375
- $this->filesystem->chmod_file( $attachment_path );
376
-
377
- // If resized temp file still exists, delete it.
378
- if ( $this->filesystem->exists( $resized_attachment_path ) ) {
379
- $this->filesystem->delete( $resized_attachment_path );
380
- }
381
 
382
  $resized = true;
383
  }
@@ -394,6 +386,23 @@ class Imagify_Attachment extends Imagify_Abstract_Attachment {
394
 
395
  $data = $this->fill_data( null, $response );
396
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  // Save the optimization level.
398
  update_post_meta( $this->id, '_imagify_optimization_level', $optimization_level );
399
 
@@ -416,18 +425,34 @@ class Imagify_Attachment extends Imagify_Abstract_Attachment {
416
  $attachment_url_dirname = $this->filesystem->dir_path( $attachment_url );
417
 
418
  foreach ( $sizes as $size_key => $size_data ) {
 
 
 
419
  // Check if this size has to be optimized.
420
  if ( ! $is_active_for_network && isset( $disallowed_sizes[ $size_key ] ) ) {
421
  $data['sizes'][ $size_key ] = array(
422
  'success' => false,
423
  'error' => __( 'This size isn\'t authorized to be optimized. Update your Imagify settings if you want to optimize it.', 'imagify' ),
424
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  continue;
426
  }
427
 
428
- $thumbnail_path = $attachment_path_dirname . $size_data['file'];
429
- $thumbnail_url = $attachment_url_dirname . $size_data['file'];
430
-
431
  // Optimize the thumbnail size.
432
  $response = do_imagify( $thumbnail_path, array(
433
  'backup' => false,
@@ -438,20 +463,21 @@ class Imagify_Attachment extends Imagify_Abstract_Attachment {
438
  $data = $this->fill_data( $data, $response, $size_key );
439
 
440
  /**
441
- * Filter the optimization data of a specific thumbnail.
442
- *
443
- * @since 1.0
444
- *
445
- * @param array $data The statistics data.
446
- * @param object $response The API response.
447
- * @param int $id The attachment ID.
448
- * @param string $thumbnail_path The attachment path.
449
- * @param string $thumbnail_url The attachment URL.
450
- * @param string $size_key The attachment size key.
451
- * @param bool $is_aggressive The optimization level.
452
- * @return array $data The new optimization data.
453
- */
454
- $data = apply_filters( 'imagify_fill_thumbnail_data', $data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level );
 
455
  } // End foreach().
456
  } // End if().
457
 
@@ -489,7 +515,7 @@ class Imagify_Attachment extends Imagify_Abstract_Attachment {
489
  */
490
  public function optimize_missing_thumbnails( $optimization_level = null ) {
491
  // Check if the attachment extension is allowed.
492
- if ( ! $this->is_extension_supported() ) {
493
  return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) );
494
  }
495
 
@@ -556,9 +582,10 @@ class Imagify_Attachment extends Imagify_Abstract_Attachment {
556
  ) );
557
 
558
  $imagify_data = $this->fill_data( $imagify_data, $response, $size_name );
 
559
 
560
  /** This filter is documented in inc/classes/class-imagify-attachment.php. */
561
- $imagify_data = apply_filters( 'imagify_fill_thumbnail_data', $imagify_data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_name, $optimization_level );
562
  }
563
 
564
  // Save Imagify data.
@@ -602,7 +629,7 @@ class Imagify_Attachment extends Imagify_Abstract_Attachment {
602
  */
603
  public function reoptimize_thumbnails( $sizes ) {
604
  // Check if the attachment extension is allowed.
605
- if ( ! $this->is_extension_supported() ) {
606
  return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) );
607
  }
608
 
@@ -739,19 +766,21 @@ class Imagify_Attachment extends Imagify_Abstract_Attachment {
739
  $this->filesystem->copy( $backup_path, $attachment_path, true );
740
  $this->filesystem->chmod_file( $attachment_path );
741
 
742
- if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
743
- require_once( ABSPATH . 'wp-admin/includes/image.php' );
744
- }
 
745
 
746
- remove_filter( 'wp_generate_attachment_metadata', '_imagify_optimize_attachment', IMAGIFY_INT_MAX );
747
- wp_generate_attachment_metadata( $this->id, $attachment_path );
 
 
 
 
748
 
749
  // Remove old optimization data.
750
  $this->delete_imagify_data();
751
 
752
- // Restore the original size in the metadata.
753
- $this->update_metadata_size();
754
-
755
  /**
756
  * Fires after restoring an attachment.
757
  *
101
  * @return array
102
  */
103
  public function get_dimensions() {
104
+ if ( ! $this->is_image() ) {
105
+ return parent::get_dimensions();
106
+ }
107
+
108
  $values = wp_get_attachment_image_src( $this->id, 'full' );
109
 
110
  return array(
123
  */
124
  public function update_metadata_size() {
125
  // Check if the attachment extension is allowed.
126
+ if ( ! $this->is_extension_supported() || ! $this->is_image() ) {
127
  return false;
128
  }
129
 
252
  $thumbnail_path = $original_dirname . $result[ $thumbnail_size ]['file'];
253
 
254
  // Since we used the backup image as source, the new image is still in the backup folder, we need to move it.
255
+ $moved = $this->filesystem->move( $backup_thumb_path, $thumbnail_path, true );
 
 
 
 
256
 
257
+ if ( ! $moved ) {
258
  return new WP_Error( 'image_resize_error' );
259
  }
260
 
 
 
261
  return reset( $result );
262
  }
263
 
272
  * @return array An array of thumbnail data (width, height, crop, file).
273
  */
274
  protected function create_missing_thumbnails( $missing_sizes ) {
275
+ if ( ! $missing_sizes || ! $this->is_image() ) {
276
  return array();
277
  }
278
 
326
 
327
  $optimization_level = isset( $optimization_level ) ? (int) $optimization_level : get_imagify_option( 'optimization_level' );
328
  $metadata = $metadata ? $metadata : wp_get_attachment_metadata( $this->id );
329
+ $sizes = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) && $this->is_image() ? $metadata['sizes'] : array();
330
 
331
  // To avoid issue with "original_size" at 0 in "_imagify_data".
332
  if ( 0 === (int) $this->get_stats_data( 'original_size' ) ) {
334
  }
335
 
336
  // Check if the full size is already optimized.
337
+ if ( $this->is_optimized() && $this->get_optimization_level() === $optimization_level ) {
338
  return;
339
  }
340
 
349
  * @since 1.0
350
  *
351
  * @param int $id The attachment ID.
352
+ */
353
  do_action( 'before_imagify_optimize_attachment', $this->id );
354
 
355
  $this->set_running_status();
356
 
357
  // Get the resize values for the original size.
358
  $resized = false;
359
+ $do_resize = $this->is_image() && get_imagify_option( 'resize_larger' );
360
 
361
  if ( $do_resize ) {
362
  $resize_width = get_imagify_option( 'resize_larger_w' );
370
  imagify_backup_file( $attachment_path );
371
 
372
  $this->filesystem->move( $resized_attachment_path, $attachment_path, true );
 
 
 
 
 
 
373
 
374
  $resized = true;
375
  }
386
 
387
  $data = $this->fill_data( null, $response );
388
 
389
+ /**
390
+ * Filter the optimization data of the full size.
391
+ *
392
+ * @since 1.8
393
+ * @author Grégory Viguier
394
+ *
395
+ * @param array $data The statistics data.
396
+ * @param object $response The API response.
397
+ * @param int $id The attachment ID.
398
+ * @param string $attachment_path The attachment path.
399
+ * @param string $attachment_url The attachment URL.
400
+ * @param string $size_key The attachment size key. The value is obviously 'full' but it's kept for concistancy with other filters.
401
+ * @param int $optimization_level The optimization level.
402
+ * @param array $metadata WP metadata.
403
+ */
404
+ $data = apply_filters( 'imagify_fill_full_size_data', $data, $response, $this->id, $attachment_path, $attachment_url, 'full', $optimization_level, $metadata );
405
+
406
  // Save the optimization level.
407
  update_post_meta( $this->id, '_imagify_optimization_level', $optimization_level );
408
 
425
  $attachment_url_dirname = $this->filesystem->dir_path( $attachment_url );
426
 
427
  foreach ( $sizes as $size_key => $size_data ) {
428
+ $thumbnail_path = $attachment_path_dirname . $size_data['file'];
429
+ $thumbnail_url = $attachment_url_dirname . $size_data['file'];
430
+
431
  // Check if this size has to be optimized.
432
  if ( ! $is_active_for_network && isset( $disallowed_sizes[ $size_key ] ) ) {
433
  $data['sizes'][ $size_key ] = array(
434
  'success' => false,
435
  'error' => __( 'This size isn\'t authorized to be optimized. Update your Imagify settings if you want to optimize it.', 'imagify' ),
436
  );
437
+
438
+ /**
439
+ * Filter the optimization data of an unauthorized thumbnail.
440
+ *
441
+ * @since 1.8
442
+ * @author Grégory Viguier
443
+ *
444
+ * @param array $data The statistics data.
445
+ * @param int $id The attachment ID.
446
+ * @param string $thumbnail_path The thumbnail path.
447
+ * @param string $thumbnail_url The thumbnail URL.
448
+ * @param string $size_key The thumbnail size key.
449
+ * @param int $optimization_level The optimization level.
450
+ * @param array $metadata WP metadata.
451
+ */
452
+ $data = apply_filters( 'imagify_fill_unauthorized_thumbnail_data', $data, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level, $metadata );
453
  continue;
454
  }
455
 
 
 
 
456
  // Optimize the thumbnail size.
457
  $response = do_imagify( $thumbnail_path, array(
458
  'backup' => false,
463
  $data = $this->fill_data( $data, $response, $size_key );
464
 
465
  /**
466
+ * Filter the optimization data of a specific thumbnail.
467
+ *
468
+ * @since 1.0
469
+ * @since 1.8 Added $metadata.
470
+ *
471
+ * @param array $data The statistics data.
472
+ * @param object $response The API response.
473
+ * @param int $id The attachment ID.
474
+ * @param string $thumbnail_path The thumbnail path.
475
+ * @param string $thumbnail_url The thumbnail URL.
476
+ * @param string $size_key The thumbnail size key.
477
+ * @param int $optimization_level The optimization level.
478
+ * @param array $metadata WP metadata.
479
+ */
480
+ $data = apply_filters( 'imagify_fill_thumbnail_data', $data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level, $metadata );
481
  } // End foreach().
482
  } // End if().
483
 
515
  */
516
  public function optimize_missing_thumbnails( $optimization_level = null ) {
517
  // Check if the attachment extension is allowed.
518
+ if ( ! $this->is_extension_supported() || ! $this->is_image() ) {
519
  return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) );
520
  }
521
 
582
  ) );
583
 
584
  $imagify_data = $this->fill_data( $imagify_data, $response, $size_name );
585
+ $metadata = wp_get_attachment_metadata( $this->id );
586
 
587
  /** This filter is documented in inc/classes/class-imagify-attachment.php. */
588
+ $imagify_data = apply_filters( 'imagify_fill_thumbnail_data', $imagify_data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_name, $optimization_level, $metadata );
589
  }
590
 
591
  // Save Imagify data.
629
  */
630
  public function reoptimize_thumbnails( $sizes ) {
631
  // Check if the attachment extension is allowed.
632
+ if ( ! $this->is_extension_supported() || ! $this->is_image() ) {
633
  return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) );
634
  }
635
 
766
  $this->filesystem->copy( $backup_path, $attachment_path, true );
767
  $this->filesystem->chmod_file( $attachment_path );
768
 
769
+ if ( $this->is_image() ) {
770
+ if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
771
+ require_once( ABSPATH . 'wp-admin/includes/image.php' );
772
+ }
773
 
774
+ remove_filter( 'wp_generate_attachment_metadata', '_imagify_optimize_attachment', IMAGIFY_INT_MAX );
775
+ wp_generate_attachment_metadata( $this->id, $attachment_path );
776
+
777
+ // Restore the original size in the metadata.
778
+ $this->update_metadata_size();
779
+ }
780
 
781
  // Remove old optimization data.
782
  $this->delete_imagify_data();
783
 
 
 
 
784
  /**
785
  * Fires after restoring an attachment.
786
  *
inc/classes/class-imagify-cron-library-size.php CHANGED
File without changes
inc/classes/class-imagify-cron-rating.php CHANGED
File without changes
inc/classes/class-imagify-cron-sync-files.php CHANGED
File without changes
inc/classes/class-imagify-custom-folders.php CHANGED
@@ -14,7 +14,7 @@ class Imagify_Custom_Folders {
14
  *
15
  * @var string
16
  */
17
- const VERSION = '1.0.1';
18
 
19
 
20
  /** ----------------------------------------------------------------------------------------- */
@@ -176,7 +176,7 @@ class Imagify_Custom_Folders {
176
  ), $args );
177
 
178
  $filesystem = imagify_get_filesystem();
179
- $file = $args['file'] && is_a( $args['file'], 'Imagify_File_Attachment' ) ? $args['file'] : false;
180
 
181
  // The file.
182
  if ( ! $args['file_path'] && $args['path'] ) {
@@ -947,7 +947,7 @@ class Imagify_Custom_Folders {
947
  continue;
948
  }
949
 
950
- if ( Imagify_Files_Scan::is_path_forbidden( $full_path ) ) {
951
  continue;
952
  }
953
 
14
  *
15
  * @var string
16
  */
17
+ const VERSION = '1.0.2';
18
 
19
 
20
  /** ----------------------------------------------------------------------------------------- */
176
  ), $args );
177
 
178
  $filesystem = imagify_get_filesystem();
179
+ $file = $args['file'] && $args['file'] instanceof Imagify_File_Attachment ? $args['file'] : false;
180
 
181
  // The file.
182
  if ( ! $args['file_path'] && $args['path'] ) {
947
  continue;
948
  }
949
 
950
+ if ( Imagify_Files_Scan::is_path_forbidden( trailingslashit( $full_path ) ) ) {
951
  continue;
952
  }
953
 
inc/classes/class-imagify-data.php CHANGED
File without changes
inc/classes/class-imagify-db.php CHANGED
File without changes
inc/classes/class-imagify-file-attachment.php CHANGED
@@ -426,11 +426,11 @@ class Imagify_File_Attachment extends Imagify_Attachment {
426
  }
427
 
428
  $data = imagify_merge_intersect( $this->get_row(), array(
429
- 'original_size' => 0,
430
- 'optimized_size' => false,
431
- 'percent' => 0,
432
- 'status' => false,
433
- 'error' => false,
434
  ) );
435
 
436
  $data['success'] = 'success' === $data['status'];
426
  }
427
 
428
  $data = imagify_merge_intersect( $this->get_row(), array(
429
+ 'original_size' => 0,
430
+ 'optimized_size' => false,
431
+ 'percent' => 0,
432
+ 'status' => false,
433
+ 'error' => false,
434
  ) );
435
 
436
  $data['success'] = 'success' === $data['status'];
inc/classes/class-imagify-files-db.php CHANGED
File without changes
inc/classes/class-imagify-files-iterator.php CHANGED
@@ -17,7 +17,7 @@ class Imagify_Files_Iterator extends FilterIterator {
17
  * @since 1.7
18
  * @author Grégory Viguier
19
  */
20
- const VERSION = '1.0.1';
21
 
22
  /**
23
  * Tell if the iterator will return both folders and images, or only images.
@@ -66,14 +66,19 @@ class Imagify_Files_Iterator extends FilterIterator {
66
  static $extensions, $has_extension_method;
67
 
68
  // Forbidden file/folder paths and names.
 
69
  $file_path = $this->current()->getPathname();
70
 
 
 
 
 
71
  if ( Imagify_Files_Scan::is_path_forbidden( $file_path ) ) {
72
  return false;
73
  }
74
 
75
  // OK for folders.
76
- if ( $this->include_folders && $this->isDir() ) {
77
  return true;
78
  }
79
 
17
  * @since 1.7
18
  * @author Grégory Viguier
19
  */
20
+ const VERSION = '1.0.2';
21
 
22
  /**
23
  * Tell if the iterator will return both folders and images, or only images.
66
  static $extensions, $has_extension_method;
67
 
68
  // Forbidden file/folder paths and names.
69
+ $is_dir = $this->isDir();
70
  $file_path = $this->current()->getPathname();
71
 
72
+ if ( $is_dir ) {
73
+ $file_path = trailingslashit( $file_path );
74
+ }
75
+
76
  if ( Imagify_Files_Scan::is_path_forbidden( $file_path ) ) {
77
  return false;
78
  }
79
 
80
  // OK for folders.
81
+ if ( $this->include_folders && $is_dir ) {
82
  return true;
83
  }
84
 
inc/classes/class-imagify-files-list-table.php CHANGED
@@ -352,10 +352,10 @@ class Imagify_Files_List_Table extends WP_List_Table {
352
  }
353
 
354
  $status_filters = array(
355
- '' => __( 'All images', 'imagify' ),
356
- 'optimized' => __( 'Optimized','imagify' ) . ' (' . $status_filters['optimized'] . ')',
357
- 'unoptimized' => __( 'Unoptimized','imagify' ) . ' (' . $status_filters['unoptimized'] . ')',
358
- 'errors' => __( 'Errors','imagify' ) . ' (' . $status_filters['errors'] . ')',
359
  );
360
 
361
  // Get submitted values.
@@ -503,25 +503,33 @@ class Imagify_Files_List_Table extends WP_List_Table {
503
  * @param object $item The current File object.
504
  */
505
  public function column_title( $item ) {
506
- $item = $this->maybe_set_item_folder( $item );
507
- $url = $item->get_original_url();
508
- $base = ! empty( $item->folder_path ) ? Imagify_Files_Scan::remove_placeholder( $item->folder_path ) : '';
509
- $title = $this->filesystem->make_path_relative( $item->get_original_path(), $base );
510
- $dimensions = $item->get_dimensions();
511
- $orientation = $dimensions['width'] > $dimensions['height'] ? ' landscape' : ' portrait';
512
- $orientation = $dimensions['width'] && $dimensions['height'] ? $orientation : '';
513
-
514
- if ( ! wp_doing_ajax() && $item->get_optimized_size( false ) > 100000 ) {
515
- // LazyLoad.
516
- $image_tag = '<img src="' . esc_url( IMAGIFY_ASSETS_IMG_URL . 'lazyload.png' ) . '" data-lazy-src="' . esc_url( $url ) . '" alt="" />';
517
- $image_tag .= '<noscript><img src="' . esc_url( $url ) . '" alt="" /></noscript>';
 
 
 
 
 
 
 
518
  } else {
519
- $image_tag = '<img src="' . esc_url( $url ) . '" class="hide-if-no-js" alt="" />';
 
520
  }
521
  ?>
522
  <strong class="has-media-icon">
523
  <a href="<?php echo esc_url( $url ); ?>" target="_blank">
524
- <span class="media-icon image-icon<?php echo $orientation; ?>">
525
  <span class="centered">
526
  <?php echo $image_tag; ?>
527
  </span>
@@ -628,7 +636,7 @@ class Imagify_Files_List_Table extends WP_List_Table {
628
 
629
  if ( ! $status ) {
630
  // File is not optimized.
631
- $messages[] = '<strong class="imagify-status-not-optimized">' . esc_html_x( 'Not optimized', 'image', 'imagify' ) . '</strong>';
632
  } elseif ( $error_text ) {
633
  // Error or already optimized.
634
  $messages[] = '<span class="imagify-status-' . $status . '">' . esc_html( imagify_translate_api_message( $error_text ) ) . '</span>';
@@ -869,7 +877,7 @@ class Imagify_Files_List_Table extends WP_List_Table {
869
  * @param object $item The current File object.
870
  */
871
  protected function comparison_tool_button( $item ) {
872
- if ( ! $item->is_optimized() || ! $item->has_backup() ) {
873
  return;
874
  }
875
 
352
  }
353
 
354
  $status_filters = array(
355
+ '' => __( 'All Media Files', 'imagify' ),
356
+ 'optimized' => _x( 'Optimized', 'Media Files','imagify' ) . ' (' . $status_filters['optimized'] . ')',
357
+ 'unoptimized' => _x( 'Unoptimized', 'Media Files','imagify' ) . ' (' . $status_filters['unoptimized'] . ')',
358
+ 'errors' => _x( 'Errors', 'Media Files','imagify' ) . ' (' . $status_filters['errors'] . ')',
359
  );
360
 
361
  // Get submitted values.
503
  * @param object $item The current File object.
504
  */
505
  public function column_title( $item ) {
506
+ $item = $this->maybe_set_item_folder( $item );
507
+ $url = $item->get_original_url();
508
+ $base = ! empty( $item->folder_path ) ? Imagify_Files_Scan::remove_placeholder( $item->folder_path ) : '';
509
+ $title = $this->filesystem->make_path_relative( $item->get_original_path(), $base );
510
+
511
+ list( $mime ) = explode( '/', $item->get_mime_type() );
512
+
513
+ if ( $item->is_image() ) {
514
+ $dimensions = $item->get_dimensions();
515
+ $orientation = $dimensions['width'] > $dimensions['height'] ? ' landscape' : ' portrait';
516
+ $orientation = $dimensions['width'] && $dimensions['height'] ? $orientation : '';
517
+
518
+ if ( ! wp_doing_ajax() && $item->get_optimized_size( false ) > 100000 ) {
519
+ // LazyLoad.
520
+ $image_tag = '<img src="' . esc_url( IMAGIFY_ASSETS_IMG_URL . 'lazyload.png' ) . '" data-lazy-src="' . esc_url( $url ) . '" alt="" />';
521
+ $image_tag .= '<noscript><img src="' . esc_url( $url ) . '" alt="" /></noscript>';
522
+ } else {
523
+ $image_tag = '<img src="' . esc_url( $url ) . '" class="hide-if-no-js" alt="" />';
524
+ }
525
  } else {
526
+ $orientation = '';
527
+ $image_tag = '<img src="' . esc_url( wp_mime_type_icon( $mime ) ) . '" class="hide-if-no-js" alt="" />';
528
  }
529
  ?>
530
  <strong class="has-media-icon">
531
  <a href="<?php echo esc_url( $url ); ?>" target="_blank">
532
+ <span class="media-icon <?php echo sanitize_html_class( $mime . '-icon' ); ?><?php echo $orientation; ?>">
533
  <span class="centered">
534
  <?php echo $image_tag; ?>
535
  </span>
636
 
637
  if ( ! $status ) {
638
  // File is not optimized.
639
+ $messages[] = '<strong class="imagify-status-not-optimized">' . esc_html_x( 'Not optimized', 'Media File', 'imagify' ) . '</strong>';
640
  } elseif ( $error_text ) {
641
  // Error or already optimized.
642
  $messages[] = '<span class="imagify-status-' . $status . '">' . esc_html( imagify_translate_api_message( $error_text ) ) . '</span>';
877
  * @param object $item The current File object.
878
  */
879
  protected function comparison_tool_button( $item ) {
880
+ if ( ! $item->is_optimized() || ! $item->has_backup() || ! $item->is_image() ) {
881
  return;
882
  }
883
 
inc/classes/class-imagify-files-recursive-iterator.php CHANGED
@@ -17,7 +17,7 @@ class Imagify_Files_Recursive_Iterator extends RecursiveFilterIterator {
17
  * @since 1.7
18
  * @author Grégory Viguier
19
  */
20
- const VERSION = '1.0.1';
21
 
22
  /**
23
  * Filesystem object.
@@ -58,6 +58,10 @@ class Imagify_Files_Recursive_Iterator extends RecursiveFilterIterator {
58
  // Forbidden file/folder paths and names.
59
  $file_path = $this->current()->getPathname();
60
 
 
 
 
 
61
  if ( Imagify_Files_Scan::is_path_forbidden( $file_path ) ) {
62
  return false;
63
  }
17
  * @since 1.7
18
  * @author Grégory Viguier
19
  */
20
+ const VERSION = '1.0.2';
21
 
22
  /**
23
  * Filesystem object.
58
  // Forbidden file/folder paths and names.
59
  $file_path = $this->current()->getPathname();
60
 
61
+ if ( $this->current()->isDir() ) {
62
+ $file_path = trailingslashit( $file_path );
63
+ }
64
+
65
  if ( Imagify_Files_Scan::is_path_forbidden( $file_path ) ) {
66
  return false;
67
  }
inc/classes/class-imagify-files-scan.php CHANGED
@@ -16,7 +16,7 @@ class Imagify_Files_Scan {
16
  * @since 1.7
17
  * @author Grégory Viguier
18
  */
19
- const VERSION = '1.0.1';
20
 
21
  /**
22
  * Get files (optimizable by Imagify) recursively from a specific folder.
@@ -46,7 +46,7 @@ class Imagify_Files_Scan {
46
  return new WP_Error( 'not_a_folder', __( 'This file is not a folder.', 'imagify' ) );
47
  }
48
 
49
- if ( self::is_path_forbidden( $folder ) ) {
50
  return new WP_Error( 'folder_forbidden', __( 'This folder is not allowed.', 'imagify' ) );
51
  }
52
 
@@ -76,10 +76,17 @@ class Imagify_Files_Scan {
76
  return $images;
77
  }
78
 
 
 
 
 
 
79
  /**
80
  * Tell if a path is autorized.
 
81
  *
82
  * @since 1.7.1
 
83
  * @access public
84
  * @author Grégory Viguier
85
  *
@@ -92,8 +99,10 @@ class Imagify_Files_Scan {
92
 
93
  /**
94
  * Tell if a path is forbidden.
 
95
  *
96
  * @since 1.7
 
97
  * @access public
98
  * @author Grégory Viguier
99
  *
@@ -126,11 +135,11 @@ class Imagify_Files_Scan {
126
  return true;
127
  }
128
 
129
- if ( self::get_forbidden_folder_patterns() ) {
130
- foreach ( self::get_forbidden_folder_patterns() as $pattern ) {
131
- if ( preg_match( '@^' . $pattern . '@', $file_path ) ) {
132
- return true;
133
- }
134
  }
135
  }
136
 
@@ -160,38 +169,43 @@ class Imagify_Files_Scan {
160
  }
161
 
162
  $filesystem = imagify_get_filesystem();
 
163
  $folders = array(
164
  // Server.
165
- $filesystem->get_abspath() . 'cgi-bin', // `cgi-bin`
166
  // WordPress.
167
- $filesystem->get_abspath() . 'wp-admin', // `wp-admin`
168
- $filesystem->get_abspath() . WPINC, // `wp-includes`
169
- get_imagify_upload_basedir( true ), // Media library.
170
- WP_CONTENT_DIR . '/languages', // Translations.
171
  WP_CONTENT_DIR . '/mu-plugins', // MU plugins.
172
  WP_CONTENT_DIR . '/upgrade', // Upgrade.
173
  // Plugins.
174
- WP_CONTENT_DIR . '/backups', // A folder commonly used by backup plugins.
175
- WP_CONTENT_DIR . '/cache', // A folder commonly used by cache plugins.
176
  WP_CONTENT_DIR . '/bps-backup', // BulletProof Security.
 
177
  WP_CONTENT_DIR . '/ngg', // NextGen Gallery.
178
  WP_CONTENT_DIR . '/ngg_styles', // NextGen Gallery.
179
  WP_CONTENT_DIR . '/w3tc-config', // W3 Total Cache.
180
  WP_CONTENT_DIR . '/wfcache', // WP Fastest Cache.
181
  WP_CONTENT_DIR . '/wp-rocket-config', // WP Rocket.
182
- Imagify_Custom_Folders::get_backup_dir_path(), // Imagify "Custom folders" backup.
183
- IMAGIFY_PATH, // Imagify plugin.
184
- self::get_wc_logs_path(), // WooCommerce Logs.
185
- self::get_ewww_tools_path(), // EWWW.
186
  );
187
 
188
- // NextGen Gallery.
189
  if ( ! is_multisite() ) {
190
- $folders[] = self::get_ngg_galleries_path();
 
 
 
 
 
 
 
 
 
 
191
  }
192
 
193
- $folders = array_map( 'trailingslashit', $folders );
194
- $folders = array_map( 'wp_normalize_path', $folders );
195
 
196
  /**
197
  * Add folders to the list of forbidden ones.
@@ -210,8 +224,7 @@ class Imagify_Files_Scan {
210
  return $folders;
211
  }
212
 
213
- $added_folders = array_map( 'trailingslashit', $added_folders );
214
- $added_folders = array_map( 'wp_normalize_path', $added_folders );
215
 
216
  $folders = array_merge( $folders, $added_folders );
217
  $folders = array_flip( array_flip( $folders ) );
@@ -220,8 +233,10 @@ class Imagify_Files_Scan {
220
  }
221
 
222
  /**
223
- * Get the list of folder patterns where Imagify won't look for files to optimize.
224
- * `^` will be prepended to each pattern (aka, the pattern must match an absolute path). Pattern delimiter is `@`. Paths tested against these patterns are lower-cased.
 
 
225
  *
226
  * @since 1.7
227
  * @access public
@@ -238,25 +253,25 @@ class Imagify_Files_Scan {
238
 
239
  $folders = array();
240
 
241
- // NextGen Gallery.
242
- if ( is_multisite() ) {
243
- $folders[] = self::get_ngg_galleries_path();
244
- }
245
 
246
- if ( $folders ) {
247
- $folders = array_map( 'trailingslashit', $folders );
248
- $folders = array_map( 'wp_normalize_path', $folders );
249
- $folders = array_map( 'preg_quote', $folders, array_fill( 1, count( $folders ), '@' ) );
250
-
251
- // Must be done after `wp_normalize_path()` and `preg_quote()`.
252
- foreach ( $folders as $i => $folder ) {
253
- $folders[ $i ] = str_replace( '%BLOG_ID%', '\d+', $folder );
 
 
254
  }
255
  }
256
 
257
  /**
258
  * Add folder patterns to the list of forbidden ones.
259
- * Don't forget to use `trailingslashit()`, `wp_normalize_path()` and `preg_quote()`!
260
  *
261
  * @since 1.7
262
  * @author Grégory Viguier
@@ -321,6 +336,12 @@ class Imagify_Files_Scan {
321
  '.DS_Store',
322
  '.git',
323
  '.svn',
 
 
 
 
 
 
324
  'node_modules',
325
  'Thumbs.db',
326
  );
@@ -350,6 +371,11 @@ class Imagify_Files_Scan {
350
  return $file_names;
351
  }
352
 
 
 
 
 
 
353
  /**
354
  * Add a placeholder to a path.
355
  *
@@ -418,14 +444,16 @@ class Imagify_Files_Scan {
418
  return $replacements;
419
  }
420
 
 
421
  $replacements = array(
422
- '{{PLUGINS}}/' => WP_PLUGIN_DIR . '/',
423
- '{{MU_PLUGINS}}/' => WPMU_PLUGIN_DIR . '/',
424
- '{{THEMES}}/' => WP_CONTENT_DIR . '/themes/',
425
- '{{CONTENT}}/' => WP_CONTENT_DIR . '/',
 
426
  '{{ABSPATH}}/' => ABSPATH,
427
  );
428
- $replacements = array_map( 'wp_normalize_path', $replacements );
429
 
430
  return $replacements;
431
  }
@@ -450,6 +478,7 @@ class Imagify_Files_Scan {
450
  '{{PLUGINS}}/' => plugins_url( '/' ),
451
  '{{MU_PLUGINS}}/' => plugins_url( '/', WPMU_PLUGIN_DIR . '/.' ),
452
  '{{THEMES}}/' => content_url( 'themes/' ),
 
453
  '{{CONTENT}}/' => content_url( '/' ),
454
  '{{ABSPATH}}/' => site_url( '/' ),
455
  );
@@ -471,58 +500,63 @@ class Imagify_Files_Scan {
471
  return imagify_get_filesystem()->is_readable( self::remove_placeholder( $file_path ) );
472
  }
473
 
474
- /**
475
- * Normalize a file path, aiming for path comparison.
476
- * The path is normalized, case-lowered, and a trailing slash is added.
477
- *
478
- * @since 1.7
479
- * @access public
480
- * @author Grégory Viguier
481
- *
482
- * @param string $file_path The file path.
483
- * @return string The normalized file path.
484
- */
485
- public static function normalize_path_for_comparison( $file_path ) {
486
- return strtolower( wp_normalize_path( trailingslashit( $file_path ) ) );
487
- }
488
 
489
  /**
490
- * Get the path to NextGen galleries. On multisite, the path contains `%BLOG_ID%`, and must be used as a regex pattern.
491
  *
492
  * @since 1.7
493
  * @access public
494
  * @author Grégory Viguier
495
  *
496
- * @return string An absolute path.
497
  */
498
  public static function get_ngg_galleries_path() {
499
- $ngg_options = get_site_option( 'ngg_options' );
500
 
501
- if ( empty( $ngg_options['gallerypath'] ) ) {
502
- if ( is_multisite() ) {
503
- return get_imagify_upload_basedir( true ) . 'sites/%BLOG_ID%/nggallery';
504
- }
505
-
506
- return WP_CONTENT_DIR . '/gallery/';
507
  }
508
 
 
 
 
 
509
  $ngg_root = defined( 'NGG_GALLERY_ROOT_TYPE' ) ? NGG_GALLERY_ROOT_TYPE : 'site';
510
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  if ( 'content' === $ngg_root ) {
512
- $ngg_root = WP_CONTENT_DIR . '/';
513
  } else {
514
- $ngg_root = imagify_get_filesystem()->get_abspath();
515
  }
516
 
517
- if ( is_multisite() ) {
518
- return $ngg_root . str_replace( '%BLOG_NAME%', get_bloginfo( 'name' ), $ngg_options['gallerypath'] );
519
  }
520
 
521
- return $ngg_root . $ngg_options['gallerypath'];
522
  }
523
 
524
  /**
525
- * Get the path to WooCommerce logs.
526
  *
527
  * @since 1.7
528
  * @access public
@@ -535,11 +569,12 @@ class Imagify_Files_Scan {
535
  return WC_LOG_DIR;
536
  }
537
 
538
- return get_imagify_upload_basedir( true ) . 'wc-logs/';
539
  }
540
 
541
  /**
542
  * Get the path to EWWW optimization tools.
 
543
  *
544
  * @since 1.7
545
  * @access public
@@ -554,4 +589,127 @@ class Imagify_Files_Scan {
554
 
555
  return WP_CONTENT_DIR . '/ewww/';
556
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
  }
16
  * @since 1.7
17
  * @author Grégory Viguier
18
  */
19
+ const VERSION = '1.1';
20
 
21
  /**
22
  * Get files (optimizable by Imagify) recursively from a specific folder.
46
  return new WP_Error( 'not_a_folder', __( 'This file is not a folder.', 'imagify' ) );
47
  }
48
 
49
+ if ( self::is_path_forbidden( trailingslashit( $folder ) ) ) {
50
  return new WP_Error( 'folder_forbidden', __( 'This folder is not allowed.', 'imagify' ) );
51
  }
52
 
76
  return $images;
77
  }
78
 
79
+
80
+ /** ----------------------------------------------------------------------------------------- */
81
+ /** FORBIDDEN FOLDERS AND FILES ============================================================= */
82
+ /** ----------------------------------------------------------------------------------------- */
83
+
84
  /**
85
  * Tell if a path is autorized.
86
+ * When testing a folder, the path MUST have a trailing slash.
87
  *
88
  * @since 1.7.1
89
+ * @since 1.8 The path must have a trailing slash if for a folder.
90
  * @access public
91
  * @author Grégory Viguier
92
  *
99
 
100
  /**
101
  * Tell if a path is forbidden.
102
+ * When testing a folder, the path MUST have a trailing slash.
103
  *
104
  * @since 1.7
105
+ * @since 1.8 The path must have a trailing slash if for a folder.
106
  * @access public
107
  * @author Grégory Viguier
108
  *
135
  return true;
136
  }
137
 
138
+ $delim = Imagify_Filesystem::PATTERN_DELIMITER;
139
+
140
+ foreach ( self::get_forbidden_folder_patterns() as $pattern ) {
141
+ if ( preg_match( $delim . '^' . $pattern . $delim, $file_path ) ) {
142
+ return true;
143
  }
144
  }
145
 
169
  }
170
 
171
  $filesystem = imagify_get_filesystem();
172
+ $abspath = $filesystem->get_abspath();
173
  $folders = array(
174
  // Server.
175
+ $abspath . 'cgi-bin', // `cgi-bin`
176
  // WordPress.
177
+ $abspath . 'wp-admin', // `wp-admin`
178
+ $abspath . WPINC, // `wp-includes`
 
 
179
  WP_CONTENT_DIR . '/mu-plugins', // MU plugins.
180
  WP_CONTENT_DIR . '/upgrade', // Upgrade.
181
  // Plugins.
 
 
182
  WP_CONTENT_DIR . '/bps-backup', // BulletProof Security.
183
+ self::get_ewww_tools_path(), // EWWW: /wp-content/ewww.
184
  WP_CONTENT_DIR . '/ngg', // NextGen Gallery.
185
  WP_CONTENT_DIR . '/ngg_styles', // NextGen Gallery.
186
  WP_CONTENT_DIR . '/w3tc-config', // W3 Total Cache.
187
  WP_CONTENT_DIR . '/wfcache', // WP Fastest Cache.
188
  WP_CONTENT_DIR . '/wp-rocket-config', // WP Rocket.
189
+ Imagify_Custom_Folders::get_backup_dir_path(), // Imagify "Custom folders" backup: /imagify-backup.
190
+ IMAGIFY_PATH, // Imagify plugin: /wp-content/plugins/imagify.
191
+ self::get_shortpixel_path(), // ShortPixel: /wp-content/uploads/ShortpixelBackups.
 
192
  );
193
 
 
194
  if ( ! is_multisite() ) {
195
+ $uploads_dir = $filesystem->get_upload_basedir( true );
196
+ $ngg_galleries = self::get_ngg_galleries_path();
197
+
198
+ if ( $ngg_galleries ) {
199
+ $folders[] = $ngg_galleries; // NextGen Gallery: /wp-content/gallery.
200
+ }
201
+
202
+ $folders[] = $uploads_dir . 'formidable'; // Formidable Forms: /wp-content/uploads/formidable.
203
+ $folders[] = get_imagify_backup_dir_path( true ); // Imagify Media Library backup: /wp-content/uploads/backup.
204
+ $folders[] = self::get_wc_logs_path(); // WooCommerce Logs: /wp-content/uploads/wc-logs.
205
+ $folders[] = $uploads_dir . 'woocommerce_uploads'; // WooCommerce uploads: /wp-content/uploads/woocommerce_uploads.
206
  }
207
 
208
+ $folders = array_map( array( $filesystem, 'normalize_dir_path' ), $folders );
 
209
 
210
  /**
211
  * Add folders to the list of forbidden ones.
224
  return $folders;
225
  }
226
 
227
+ $added_folders = array_map( array( $filesystem, 'normalize_dir_path' ), $added_folders );
 
228
 
229
  $folders = array_merge( $folders, $added_folders );
230
  $folders = array_flip( array_flip( $folders ) );
233
  }
234
 
235
  /**
236
+ * Get the list of folder patterns where Imagify won't look for files to optimize. This is meant for paths that are dynamic.
237
+ * `^` will be prepended to each pattern (aka, the pattern must match an absolute path).
238
+ * Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
239
+ * Paths tested against these patterns are lower-cased.
240
  *
241
  * @since 1.7
242
  * @access public
253
 
254
  $folders = array();
255
 
256
+ // Media Library: /wp\-content/uploads/(sites/\d+/)?\d{4}/\d{2}/.
257
+ $folders[] = self::get_media_library_pattern();
 
 
258
 
259
+ if ( is_multisite() ) {
260
+ /**
261
+ * On multisite we can't exclude Imagify's library backup folders, or any other folder located in the uploads folders (created by other plugins): there are too many ways it can fail.
262
+ * Only exception we're aware of so far is NextGen Gallery, because it provides a clear pattern to use.
263
+ */
264
+ $ngg_galleries = self::get_ngg_galleries_multisite_pattern();
265
+
266
+ if ( $ngg_galleries ) {
267
+ // NextGen Gallery: /wp\-content/uploads/sites/\d+/nggallery/.
268
+ $folders[] = $ngg_galleries;
269
  }
270
  }
271
 
272
  /**
273
  * Add folder patterns to the list of forbidden ones.
274
+ * Don't forget to use `Imagify_Files_Scan::normalize_path_for_regex( $path )`!
275
  *
276
  * @since 1.7
277
  * @author Grégory Viguier
336
  '.DS_Store',
337
  '.git',
338
  '.svn',
339
+ 'backup',
340
+ 'backups',
341
+ 'cache',
342
+ 'lang',
343
+ 'langs',
344
+ 'languages',
345
  'node_modules',
346
  'Thumbs.db',
347
  );
371
  return $file_names;
372
  }
373
 
374
+
375
+ /** ----------------------------------------------------------------------------------------- */
376
+ /** PLACEHOLDERS ============================================================================ */
377
+ /** ----------------------------------------------------------------------------------------- */
378
+
379
  /**
380
  * Add a placeholder to a path.
381
  *
444
  return $replacements;
445
  }
446
 
447
+ $filesystem = imagify_get_filesystem();
448
  $replacements = array(
449
+ '{{PLUGINS}}/' => WP_PLUGIN_DIR,
450
+ '{{MU_PLUGINS}}/' => WPMU_PLUGIN_DIR,
451
+ '{{THEMES}}/' => WP_CONTENT_DIR . '/themes',
452
+ '{{UPLOADS}}/' => $filesystem->get_main_upload_basedir(),
453
+ '{{CONTENT}}/' => WP_CONTENT_DIR,
454
  '{{ABSPATH}}/' => ABSPATH,
455
  );
456
+ $replacements = array_map( array( $filesystem, 'normalize_dir_path' ), $replacements );
457
 
458
  return $replacements;
459
  }
478
  '{{PLUGINS}}/' => plugins_url( '/' ),
479
  '{{MU_PLUGINS}}/' => plugins_url( '/', WPMU_PLUGIN_DIR . '/.' ),
480
  '{{THEMES}}/' => content_url( 'themes/' ),
481
+ '{{UPLOADS}}/' => imagify_get_filesystem()->get_main_upload_baseurl(),
482
  '{{CONTENT}}/' => content_url( '/' ),
483
  '{{ABSPATH}}/' => site_url( '/' ),
484
  );
500
  return imagify_get_filesystem()->is_readable( self::remove_placeholder( $file_path ) );
501
  }
502
 
503
+
504
+ /** ----------------------------------------------------------------------------------------- */
505
+ /** PATHS =================================================================================== */
506
+ /** ----------------------------------------------------------------------------------------- */
 
 
 
 
 
 
 
 
 
 
507
 
508
  /**
509
+ * Get the path to NextGen galleries on monosites.
510
  *
511
  * @since 1.7
512
  * @access public
513
  * @author Grégory Viguier
514
  *
515
+ * @return string|bool An absolute path. False if it can't be retrieved.
516
  */
517
  public static function get_ngg_galleries_path() {
518
+ $galleries_path = get_site_option( 'ngg_options' );
519
 
520
+ if ( empty( $galleries_path['gallerypath'] ) ) {
521
+ return false;
 
 
 
 
522
  }
523
 
524
+ $filesystem = imagify_get_filesystem();
525
+ $galleries_path = $filesystem->normalize_dir_path( $galleries_path['gallerypath'] );
526
+ $galleries_path = trim( $galleries_path, '/' ); // Something like `wp-content/gallery`.
527
+
528
  $ngg_root = defined( 'NGG_GALLERY_ROOT_TYPE' ) ? NGG_GALLERY_ROOT_TYPE : 'site';
529
 
530
+ if ( $galleries_path && 'content' === $ngg_root ) {
531
+ $ngg_root = $filesystem->normalize_dir_path( WP_CONTENT_DIR );
532
+ $ngg_root = trim( $ngg_root, '/' ); // Something like `abs-path/to/wp-content`.
533
+
534
+ $exploded_root = explode( '/', $ngg_root );
535
+ $exploded_galleries = explode( '/', $galleries_path );
536
+ $first_gallery_dirname = reset( $exploded_galleries );
537
+ $last_root_dirname = end( $exploded_root );
538
+
539
+ if ( $last_root_dirname === $first_gallery_dirname ) {
540
+ array_shift( $exploded_galleries );
541
+ $galleries_path = implode( '/', $exploded_galleries );
542
+ }
543
+ }
544
+
545
  if ( 'content' === $ngg_root ) {
546
+ $ngg_root = $filesystem->normalize_dir_path( WP_CONTENT_DIR );
547
  } else {
548
+ $ngg_root = $filesystem->get_abspath();
549
  }
550
 
551
+ if ( strpos( $galleries_path, $ngg_root ) !== 0 ) {
552
+ $galleries_path = $ngg_root . $galleries_path;
553
  }
554
 
555
+ return $galleries_path . '/';
556
  }
557
 
558
  /**
559
+ * Get the path to WooCommerce logs on monosites.
560
  *
561
  * @since 1.7
562
  * @access public
569
  return WC_LOG_DIR;
570
  }
571
 
572
+ return imagify_get_filesystem()->get_upload_basedir( true ) . 'wc-logs/';
573
  }
574
 
575
  /**
576
  * Get the path to EWWW optimization tools.
577
+ * It is the same for all sites on multisite.
578
  *
579
  * @since 1.7
580
  * @access public
589
 
590
  return WP_CONTENT_DIR . '/ewww/';
591
  }
592
+
593
+ /**
594
+ * Get the path to ShortPixel backup folder.
595
+ * It is the same for all sites on multisite (and yes, you'll get a surprise if your upload base dir -aka uploads/sites/12/- is not 2 folders deeper than theuploads folder).
596
+ *
597
+ * @since 1.8
598
+ * @access public
599
+ * @author Grégory Viguier
600
+ *
601
+ * @return string An absolute path.
602
+ */
603
+ public static function get_shortpixel_path() {
604
+ if ( defined( 'SHORTPIXEL_BACKUP_FOLDER' ) ) {
605
+ return trailingslashit( SHORTPIXEL_BACKUP_FOLDER );
606
+ }
607
+
608
+ $filesystem = imagify_get_filesystem();
609
+ $path = $filesystem->get_upload_basedir( true );
610
+ $path = is_main_site() ? $path : $filesystem->dir_path( $filesystem->dir_path( $path ) );
611
+
612
+ return $path . 'ShortpixelBackups/';
613
+ }
614
+
615
+
616
+ /** ----------------------------------------------------------------------------------------- */
617
+ /** REGEX PATTERNS ========================================================================== */
618
+ /** ----------------------------------------------------------------------------------------- */
619
+
620
+ /**
621
+ * Get the regex pattern used to match the paths to the media library.
622
+ * Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
623
+ * Paths tested against these patterns are lower-cased.
624
+ *
625
+ * @since 1.8
626
+ * @access public
627
+ * @author Grégory Viguier
628
+ *
629
+ * @return string Something like `/wp\-content/uploads/(sites/\d+/)?\d{4}/\d{2}/`.
630
+ */
631
+ public static function get_media_library_pattern() {
632
+ $filesystem = imagify_get_filesystem();
633
+ $uploads_dir = self::normalize_path_for_regex( $filesystem->get_main_upload_basedir() );
634
+
635
+ if ( ! is_multisite() ) {
636
+ if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
637
+ // In year/month folders.
638
+ return $uploads_dir . '\d{4}/\d{2}/';
639
+ }
640
+
641
+ // Not in year/month folders.
642
+ return $uploads_dir . '[^/]+$';
643
+ }
644
+
645
+ $pattern = $filesystem->get_multisite_uploads_subdir_pattern();
646
+
647
+ if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
648
+ // In year/month folders.
649
+ return $uploads_dir . '(' . $pattern . ')?\d{4}/\d{2}/';
650
+ }
651
+
652
+ // Not in year/month folders.
653
+ return $uploads_dir . '(' . $pattern . ')?[^/]+$';
654
+ }
655
+
656
+ /**
657
+ * Get the regex pattern used to match the paths to NextGen galleries on multisite.
658
+ * Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
659
+ * Paths tested against these patterns are lower-cased.
660
+ *
661
+ * @since 1.8
662
+ * @access public
663
+ * @author Grégory Viguier
664
+ *
665
+ * @return string|bool Something like `/wp-content/uploads/sites/\d+/nggallery/`. False if it can't be retrieved.
666
+ */
667
+ public static function get_ngg_galleries_multisite_pattern() {
668
+ $galleries_path = self::get_ngg_galleries_path(); // Something like `wp-content/uploads/sites/%BLOG_ID%/nggallery/`.
669
+
670
+ if ( ! $galleries_path ) {
671
+ return false;
672
+ }
673
+
674
+ $galleries_path = self::normalize_path_for_regex( $galleries_path );
675
+ $galleries_path = str_replace( array( '%blog_name%', '%blog_id%' ), array( '.+', '\d+' ), $galleries_path );
676
+
677
+ return $galleries_path;
678
+ }
679
+
680
+
681
+ /** ----------------------------------------------------------------------------------------- */
682
+ /** NORMALIZATION TOOLS ===================================================================== */
683
+ /** ----------------------------------------------------------------------------------------- */
684
+
685
+ /**
686
+ * Normalize a file path, aiming for path comparison.
687
+ * The path is normalized and case-lowered.
688
+ *
689
+ * @since 1.7
690
+ * @since 1.8 No trailing slash anymore, because it can be used for files.
691
+ * @access public
692
+ * @author Grégory Viguier
693
+ *
694
+ * @param string $file_path The file path.
695
+ * @return string The normalized file path.
696
+ */
697
+ public static function normalize_path_for_comparison( $file_path ) {
698
+ return strtolower( wp_normalize_path( $file_path ) );
699
+ }
700
+
701
+ /**
702
+ * Normalize a file path, aiming for use in a regex pattern.
703
+ * The path is normalized, case-lowered, and escaped.
704
+ *
705
+ * @since 1.8
706
+ * @access public
707
+ * @author Grégory Viguier
708
+ *
709
+ * @param string $file_path The file path.
710
+ * @return string The normalized file path.
711
+ */
712
+ public static function normalize_path_for_regex( $file_path ) {
713
+ return preg_quote( imagify_get_filesystem()->normalize_path_for_comparison( $file_path ), Imagify_Filesystem::PATTERN_DELIMITER );
714
+ }
715
  }
inc/classes/class-imagify-files-stats.php CHANGED
File without changes
inc/classes/class-imagify-filesystem.php CHANGED
@@ -17,7 +17,16 @@ class Imagify_Filesystem extends WP_Filesystem_Direct {
17
  *
18
  * @var string
19
  */
20
- const VERSION = '1.0';
 
 
 
 
 
 
 
 
 
21
 
22
  /**
23
  * The single instance of the class.
@@ -124,7 +133,7 @@ class Imagify_Filesystem extends WP_Filesystem_Direct {
124
  * @param string $file_path Path to the file.
125
  * @param string $option If present, specifies a specific element to be returned; one of 'dir_path', 'file_name', 'extension' or 'file_base'.
126
  * If option is not specified, returns all available elements.
127
- * @return array|string If the option parameter is not passed, an associative array containing the following elements is returned: 'dir_path' (with trailing slash), 'file_name' (with extension), 'extension' (if any), and 'file_base' (without extension).
128
  */
129
  public function path_info( $file_path, $option = null ) {
130
  if ( ! $file_path ) {
@@ -175,22 +184,6 @@ class Imagify_Filesystem extends WP_Filesystem_Direct {
175
  );
176
  }
177
 
178
- /**
179
- * Determine if a file or directory is writable.
180
- * This function is used to work around certain ACL issues in PHP primarily affecting Windows Servers.
181
- * Replacement for is_writable().
182
- *
183
- * @param string $file_path Path to the file.
184
- * @return bool
185
- */
186
- public function is_writable( $file_path ) {
187
- if ( ! $file_path ) {
188
- return false;
189
- }
190
-
191
- return wp_is_writable( $file_path );
192
- }
193
-
194
  /**
195
  * Recursive directory creation based on full path. Will attempt to set permissions on folders.
196
  * Replacement for recursive mkdir().
@@ -388,11 +381,132 @@ class Imagify_Filesystem extends WP_Filesystem_Direct {
388
  return false;
389
  }
390
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
 
392
  /** ----------------------------------------------------------------------------------------- */
393
  /** WORK WITH IMAGES ======================================================================== */
394
  /** ----------------------------------------------------------------------------------------- */
395
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  /**
397
  * Get an image data.
398
  * Replacement for getimagesize().
@@ -402,7 +516,7 @@ class Imagify_Filesystem extends WP_Filesystem_Direct {
402
  * @author Grégory Viguier
403
  *
404
  * @param string $file_path Path to the file.
405
- * @return array|bool The image data. An empty array on failure.
406
  */
407
  public function get_image_size( $file_path ) {
408
  if ( ! $file_path ) {
@@ -638,7 +752,7 @@ class Imagify_Filesystem extends WP_Filesystem_Direct {
638
 
639
  $abspath = trailingslashit( $abspath );
640
 
641
- if ( '/' !== substr( $abspath, 0, 1 ) && ':' !== substr( $path, 1, 1 ) ) {
642
  $abspath = '/' . $abspath;
643
  }
644
 
@@ -716,4 +830,114 @@ class Imagify_Filesystem extends WP_Filesystem_Direct {
716
 
717
  return $upload_baseurl;
718
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
719
  }
17
  *
18
  * @var string
19
  */
20
+ const VERSION = '1.1';
21
+
22
+ /**
23
+ * Delimiter used for regex patterns.
24
+ *
25
+ * @var string
26
+ * @since 1.8
27
+ * @author Grégory Viguier
28
+ */
29
+ const PATTERN_DELIMITER = '@';
30
 
31
  /**
32
  * The single instance of the class.
133
  * @param string $file_path Path to the file.
134
  * @param string $option If present, specifies a specific element to be returned; one of 'dir_path', 'file_name', 'extension' or 'file_base'.
135
  * If option is not specified, returns all available elements.
136
+ * @return array|string|null If the option parameter is not passed, an associative array containing the following elements is returned: 'dir_path' (with trailing slash), 'file_name' (with extension), 'extension' (if any), and 'file_base' (without extension).
137
  */
138
  public function path_info( $file_path, $option = null ) {
139
  if ( ! $file_path ) {
184
  );
185
  }
186
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  /**
188
  * Recursive directory creation based on full path. Will attempt to set permissions on folders.
189
  * Replacement for recursive mkdir().
381
  return false;
382
  }
383
 
384
+ /**
385
+ * Tell if a file is a pdf.
386
+ *
387
+ * @since 1.8
388
+ * @access public
389
+ * @author Grégory Viguier
390
+ *
391
+ * @param string $file_path Path to the file.
392
+ * @return bool
393
+ */
394
+ public function is_pdf( $file_path ) {
395
+ if ( function_exists( 'finfo_fopen' ) ) {
396
+ $finfo = finfo_open( FILEINFO_MIME );
397
+
398
+ if ( $finfo ) {
399
+ $mimetype = finfo_file( $finfo, $file_path );
400
+
401
+ if ( false !== $mimetype ) {
402
+ return 'application/pdf' === $mimetype;
403
+ }
404
+ }
405
+ }
406
+
407
+ if ( function_exists( 'mime_content_type' ) ) {
408
+ $mimetype = mime_content_type( $file_path );
409
+ return 'application/pdf' === $mimetype;
410
+ }
411
+
412
+ return false;
413
+ }
414
+
415
+
416
+ /** ----------------------------------------------------------------------------------------- */
417
+ /** CLASS OVERWRITES ======================================================================== */
418
+ /** ----------------------------------------------------------------------------------------- */
419
+
420
+ /**
421
+ * Move a file and apply chmod.
422
+ * If the file failed to be moved once, a 2nd attempt is made after applying chmod.
423
+ *
424
+ * @since 1.8
425
+ * @access public
426
+ * @author Grégory Viguier
427
+ *
428
+ * @param string $source Path to the file to move.
429
+ * @param string $destination Path to the destination.
430
+ * @param bool $overwrite Allow to overwrite existing file at destination.
431
+ * @return bool True on success, false on failure.
432
+ */
433
+ public function move( $source, $destination, $overwrite = false ) {
434
+ if ( parent::move( $source, $destination, $overwrite ) ) {
435
+ return $this->chmod_file( $destination );
436
+ }
437
+
438
+ if ( ! $this->chmod_file( $destination ) ) {
439
+ return false;
440
+ }
441
+
442
+ if ( parent::move( $source, $destination, $overwrite ) ) {
443
+ return $this->chmod_file( $destination );
444
+ }
445
+
446
+ return false;
447
+ }
448
+
449
+ /**
450
+ * Determine if a file or directory is writable.
451
+ * This function is used to work around certain ACL issues in PHP primarily affecting Windows Servers.
452
+ * Replacement for is_writable().
453
+ *
454
+ * @since 1.7.1
455
+ * @access public
456
+ * @author Grégory Viguier
457
+ *
458
+ * @param string $file_path Path to the file.
459
+ * @return bool
460
+ */
461
+ public function is_writable( $file_path ) {
462
+ if ( ! $file_path ) {
463
+ return false;
464
+ }
465
+
466
+ return wp_is_writable( $file_path );
467
+ }
468
+
469
 
470
  /** ----------------------------------------------------------------------------------------- */
471
  /** WORK WITH IMAGES ======================================================================== */
472
  /** ----------------------------------------------------------------------------------------- */
473
 
474
+ /**
475
+ * Tell if a file is an image.
476
+ *
477
+ * @since 1.8
478
+ * @access public
479
+ * @author Grégory Viguier
480
+ *
481
+ * @param string $file_path Path to the file.
482
+ * @return bool
483
+ */
484
+ public function is_image( $file_path ) {
485
+ if ( function_exists( 'finfo_fopen' ) ) {
486
+ $finfo = finfo_open( FILEINFO_MIME );
487
+
488
+ if ( $finfo ) {
489
+ $mimetype = finfo_file( $finfo, $file_path );
490
+
491
+ if ( false !== $mimetype ) {
492
+ return strpos( $mimetype, 'image/' ) === 0;
493
+ }
494
+ }
495
+ }
496
+
497
+ if ( function_exists( 'exif_imagetype' ) ) {
498
+ $mimetype = exif_imagetype( $file_path );
499
+ return (bool) $mimetype;
500
+ }
501
+
502
+ if ( function_exists( 'mime_content_type' ) ) {
503
+ $mimetype = mime_content_type( $file_path );
504
+ return strpos( $mimetype, 'image/' ) === 0;
505
+ }
506
+
507
+ return false;
508
+ }
509
+
510
  /**
511
  * Get an image data.
512
  * Replacement for getimagesize().
516
  * @author Grégory Viguier
517
  *
518
  * @param string $file_path Path to the file.
519
+ * @return array The image data. An empty array on failure.
520
  */
521
  public function get_image_size( $file_path ) {
522
  if ( ! $file_path ) {
752
 
753
  $abspath = trailingslashit( $abspath );
754
 
755
+ if ( '/' !== substr( $abspath, 0, 1 ) && ':' !== substr( $abspath, 1, 1 ) ) {
756
  $abspath = '/' . $abspath;
757
  }
758
 
830
 
831
  return $upload_baseurl;
832
  }
833
+
834
+ /**
835
+ * Get the path to the uploads base directory of the main site.
836
+ *
837
+ * @since 1.8
838
+ * @access public
839
+ * @author Grégory Viguier
840
+ *
841
+ * @return string
842
+ */
843
+ public function get_main_upload_basedir() {
844
+ static $basedir;
845
+
846
+ if ( isset( $basedir ) ) {
847
+ return $basedir;
848
+ }
849
+
850
+ $basedir = get_imagify_upload_basedir( true );
851
+
852
+ if ( is_multisite() ) {
853
+ $pattern = '/' . $this->get_multisite_uploads_subdir_pattern() . '$';
854
+ $basedir = preg_replace( self::PATTERN_DELIMITER . $pattern . self::PATTERN_DELIMITER, '/', $basedir );
855
+ }
856
+
857
+ return $basedir;
858
+ }
859
+
860
+ /**
861
+ * Get the URL of the uploads base directory of the main site.
862
+ *
863
+ * @since 1.8
864
+ * @access public
865
+ * @author Grégory Viguier
866
+ *
867
+ * @return string
868
+ */
869
+ public function get_main_upload_baseurl() {
870
+ static $baseurl;
871
+
872
+ if ( isset( $baseurl ) ) {
873
+ return $baseurl;
874
+ }
875
+
876
+ $baseurl = get_imagify_upload_baseurl( true );
877
+
878
+ if ( is_multisite() ) {
879
+ $pattern = '/' . $this->get_multisite_uploads_subdir_pattern() . '$';
880
+ $baseurl = preg_replace( self::PATTERN_DELIMITER . $pattern . self::PATTERN_DELIMITER, '/', $baseurl );
881
+ }
882
+
883
+ return $baseurl;
884
+ }
885
+
886
+ /**
887
+ * Get the regex pattern used to match the uploads subdir on multisite in a file path.
888
+ * Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
889
+ * Paths tested against these patterns are lower-cased.
890
+ *
891
+ * @since 1.8
892
+ * @access public
893
+ * @see _wp_upload_dir()
894
+ * @author Grégory Viguier
895
+ *
896
+ * @return string
897
+ */
898
+ public function get_multisite_uploads_subdir_pattern() {
899
+ static $pattern;
900
+
901
+ if ( isset( $pattern ) ) {
902
+ return $pattern;
903
+ }
904
+
905
+ $pattern = '';
906
+
907
+ if ( ! is_multisite() ) {
908
+ return $pattern;
909
+ }
910
+
911
+ if ( ! get_site_option( 'ms_files_rewriting' ) ) {
912
+ if ( defined( 'MULTISITE' ) ) {
913
+ $pattern = 'sites/\d+/';
914
+ } else {
915
+ $pattern = '\d+/';
916
+ }
917
+ } elseif ( defined( 'UPLOADS' ) ) {
918
+ $site_id = (string) get_current_blog_id();
919
+ $path = $this->get_upload_basedir( true ); // Something like `/absolute/path/to/wp-content/blogs.dir/3/files/`, also for site 1.
920
+ $path = strrev( $path );
921
+
922
+ if ( preg_match( self::PATTERN_DELIMITER . '^.*' . strrev( $site_id ) . '[^/]*/' . self::PATTERN_DELIMITER . 'U', $path, $matches ) ) {
923
+ $pattern = end( $matches );
924
+ $pattern = ltrim( strtolower( strrev( $pattern ) ), '/' );
925
+ $pattern = str_replace( $site_id, '\d+', $pattern );
926
+ }
927
+ }
928
+
929
+ /**
930
+ * Filter the regex pattern used to match the uploads subdir on multisite in a file path.
931
+ * Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
932
+ * Important: lowercase, no heading slash, mandatory trailing slash.
933
+ *
934
+ * @since 1.8
935
+ * @author Grégory Viguier
936
+ *
937
+ * @param string $pattern The regex pattern.
938
+ */
939
+ $pattern = apply_filters( 'imagify_multisite_uploads_subdir_pattern', $pattern );
940
+
941
+ return $pattern;
942
+ }
943
  }
inc/classes/class-imagify-folders-db.php CHANGED
File without changes
inc/classes/class-imagify-options.php CHANGED
File without changes
inc/classes/class-imagify-requirements.php CHANGED
File without changes
inc/classes/class-imagify-settings.php CHANGED
File without changes
inc/classes/class-imagify-views.php CHANGED
@@ -257,7 +257,7 @@ class Imagify_Views {
257
  'unoptimized_attachment_limit' => 0,
258
  // What to optimize.
259
  'icon' => 'images-alt2',
260
- 'title' => __( 'Optimize your images', 'imagify' ),
261
  'groups' => array(),
262
  );
263
 
@@ -301,7 +301,7 @@ class Imagify_Views {
301
  'context' => 'wp',
302
  'title' => __( 'Media Library', 'imagify' ),
303
  /* translators: 1 is the opening of a link, 2 is the closing of this link. */
304
- 'footer' => sprintf( __( 'You can also re-optimize your images from your %1$sMedia Library%2$s screen.', 'imagify' ), '<a href="' . esc_url( admin_url( 'upload.php' ) ) . '">', '</a>' ),
305
  );
306
  }
307
 
@@ -316,7 +316,7 @@ class Imagify_Views {
316
  'context' => 'File',
317
  'title' => __( 'Custom folders', 'imagify' ),
318
  /* translators: 1 is the opening of a link, 2 is the closing of this link. */
319
- 'footer' => sprintf( __( 'You can re-optimize your images more finely directly in the %1$simages management%2$s.', 'imagify' ), '<a href="' . esc_url( get_imagify_admin_url( 'files-list' ) ) . '">', '</a>' ),
320
  );
321
  }
322
  }
257
  'unoptimized_attachment_limit' => 0,
258
  // What to optimize.
259
  'icon' => 'images-alt2',
260
+ 'title' => __( 'Optimize your media files', 'imagify' ),
261
  'groups' => array(),
262
  );
263
 
301
  'context' => 'wp',
302
  'title' => __( 'Media Library', 'imagify' ),
303
  /* translators: 1 is the opening of a link, 2 is the closing of this link. */
304
+ 'footer' => sprintf( __( 'You can also re-optimize your media files from your %1$sMedia Library%2$s screen.', 'imagify' ), '<a href="' . esc_url( admin_url( 'upload.php' ) ) . '">', '</a>' ),
305
  );
306
  }
307
 
316
  'context' => 'File',
317
  'title' => __( 'Custom folders', 'imagify' ),
318
  /* translators: 1 is the opening of a link, 2 is the closing of this link. */
319
+ 'footer' => sprintf( __( 'You can re-optimize your media files more finely directly in the %1$smedia management%2$s.', 'imagify' ), '<a href="' . esc_url( get_imagify_admin_url( 'files-list' ) ) . '">', '</a>' ),
320
  );
321
  }
322
  }
inc/classes/class-imagify.php CHANGED
File without changes
inc/classes/class-wp-async-request.php ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WP Async Request
4
+ *
5
+ * @package WP-Background-Processing
6
+ */
7
+
8
+ if ( ! class_exists( 'WP_Async_Request' ) ) {
9
+
10
+ /**
11
+ * Abstract WP_Async_Request class.
12
+ *
13
+ * @abstract
14
+ */
15
+ abstract class WP_Async_Request {
16
+
17
+ /**
18
+ * Prefix
19
+ *
20
+ * (default value: 'wp')
21
+ *
22
+ * @var string
23
+ * @access protected
24
+ */
25
+ protected $prefix = 'wp';
26
+
27
+ /**
28
+ * Action
29
+ *
30
+ * (default value: 'async_request')
31
+ *
32
+ * @var string
33
+ * @access protected
34
+ */
35
+ protected $action = 'async_request';
36
+
37
+ /**
38
+ * Identifier
39
+ *
40
+ * @var mixed
41
+ * @access protected
42
+ */
43
+ protected $identifier;
44
+
45
+ /**
46
+ * Data
47
+ *
48
+ * (default value: array())
49
+ *
50
+ * @var array
51
+ * @access protected
52
+ */
53
+ protected $data = array();
54
+
55
+ /**
56
+ * Initiate new async request
57
+ */
58
+ public function __construct() {
59
+ $this->identifier = $this->prefix . '_' . $this->action;
60
+
61
+ add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) );
62
+ add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) );
63
+ }
64
+
65
+ /**
66
+ * Set data used during the request
67
+ *
68
+ * @param array $data Data.
69
+ *
70
+ * @return $this
71
+ */
72
+ public function data( $data ) {
73
+ $this->data = $data;
74
+
75
+ return $this;
76
+ }
77
+
78
+ /**
79
+ * Dispatch the async request
80
+ *
81
+ * @return array|WP_Error
82
+ */
83
+ public function dispatch() {
84
+ $url = add_query_arg( $this->get_query_args(), $this->get_query_url() );
85
+ $args = $this->get_post_args();
86
+
87
+ return wp_remote_post( esc_url_raw( $url ), $args );
88
+ }
89
+
90
+ /**
91
+ * Get query args
92
+ *
93
+ * @return array
94
+ */
95
+ protected function get_query_args() {
96
+ if ( property_exists( $this, 'query_args' ) ) {
97
+ return $this->query_args;
98
+ }
99
+
100
+ return array(
101
+ 'action' => $this->identifier,
102
+ 'nonce' => wp_create_nonce( $this->identifier ),
103
+ );
104
+ }
105
+
106
+ /**
107
+ * Get query URL
108
+ *
109
+ * @return string
110
+ */
111
+ protected function get_query_url() {
112
+ if ( property_exists( $this, 'query_url' ) ) {
113
+ return $this->query_url;
114
+ }
115
+
116
+ return admin_url( 'admin-ajax.php' );
117
+ }
118
+
119
+ /**
120
+ * Get post args
121
+ *
122
+ * @return array
123
+ */
124
+ protected function get_post_args() {
125
+ if ( property_exists( $this, 'post_args' ) ) {
126
+ return $this->post_args;
127
+ }
128
+
129
+ return array(
130
+ 'timeout' => 0.01,
131
+ 'blocking' => false,
132
+ 'body' => $this->data,
133
+ 'cookies' => $_COOKIE,
134
+ 'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
135
+ );
136
+ }
137
+
138
+ /**
139
+ * Maybe handle
140
+ *
141
+ * Check for correct nonce and pass to handler.
142
+ */
143
+ public function maybe_handle() {
144
+ // Don't lock up other requests while processing
145
+ session_write_close();
146
+
147
+ check_ajax_referer( $this->identifier, 'nonce' );
148
+
149
+ $this->handle();
150
+
151
+ wp_die();
152
+ }
153
+
154
+ /**
155
+ * Handle
156
+ *
157
+ * Override this method to perform any actions required
158
+ * during the async request.
159
+ */
160
+ abstract protected function handle();
161
+
162
+ }
163
+ }
inc/classes/class-wp-background-process.php ADDED
@@ -0,0 +1,506 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WP Background Process
4
+ *
5
+ * @package WP-Background-Processing
6
+ */
7
+
8
+ if ( ! class_exists( 'WP_Background_Process' ) ) {
9
+
10
+ /**
11
+ * Abstract WP_Background_Process class.
12
+ *
13
+ * @abstract
14
+ * @extends WP_Async_Request
15
+ */
16
+ abstract class WP_Background_Process extends WP_Async_Request {
17
+
18
+ /**
19
+ * Action
20
+ *
21
+ * (default value: 'background_process')
22
+ *
23
+ * @var string
24
+ * @access protected
25
+ */
26
+ protected $action = 'background_process';
27
+
28
+ /**
29
+ * Start time of current process.
30
+ *
31
+ * (default value: 0)
32
+ *
33
+ * @var int
34
+ * @access protected
35
+ */
36
+ protected $start_time = 0;
37
+
38
+ /**
39
+ * Cron_hook_identifier
40
+ *
41
+ * @var mixed
42
+ * @access protected
43
+ */
44
+ protected $cron_hook_identifier;
45
+
46
+ /**
47
+ * Cron_interval_identifier
48
+ *
49
+ * @var mixed
50
+ * @access protected
51
+ */
52
+ protected $cron_interval_identifier;
53
+
54
+ /**
55
+ * Initiate new background process
56
+ */
57
+ public function __construct() {
58
+ parent::__construct();
59
+
60
+ $this->cron_hook_identifier = $this->identifier . '_cron';
61
+ $this->cron_interval_identifier = $this->identifier . '_cron_interval';
62
+
63
+ add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) );
64
+ add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) );
65
+ }
66
+
67
+ /**
68
+ * Dispatch
69
+ *
70
+ * @access public
71
+ * @return void
72
+ */
73
+ public function dispatch() {
74
+ // Schedule the cron healthcheck.
75
+ $this->schedule_event();
76
+
77
+ // Perform remote post.
78
+ return parent::dispatch();
79
+ }
80
+
81
+ /**
82
+ * Push to queue
83
+ *
84
+ * @param mixed $data Data.
85
+ *
86
+ * @return $this
87
+ */
88
+ public function push_to_queue( $data ) {
89
+ $this->data[] = $data;
90
+
91
+ return $this;
92
+ }
93
+
94
+ /**
95
+ * Save queue
96
+ *
97
+ * @return $this
98
+ */
99
+ public function save() {
100
+ $key = $this->generate_key();
101
+
102
+ if ( ! empty( $this->data ) ) {
103
+ update_site_option( $key, $this->data );
104
+ }
105
+
106
+ return $this;
107
+ }
108
+
109
+ /**
110
+ * Update queue
111
+ *
112
+ * @param string $key Key.
113
+ * @param array $data Data.
114
+ *
115
+ * @return $this
116
+ */
117
+ public function update( $key, $data ) {
118
+ if ( ! empty( $data ) ) {
119
+ update_site_option( $key, $data );
120
+ }
121
+
122
+ return $this;
123
+ }
124
+
125
+ /**
126
+ * Delete queue
127
+ *
128
+ * @param string $key Key.
129
+ *
130
+ * @return $this
131
+ */
132
+ public function delete( $key ) {
133
+ delete_site_option( $key );
134
+
135
+ return $this;
136
+ }
137
+
138
+ /**
139
+ * Generate key
140
+ *
141
+ * Generates a unique key based on microtime. Queue items are
142
+ * given a unique key so that they can be merged upon save.
143
+ *
144
+ * @param int $length Length.
145
+ *
146
+ * @return string
147
+ */
148
+ protected function generate_key( $length = 64 ) {
149
+ $unique = md5( microtime() . rand() );
150
+ $prepend = $this->identifier . '_batch_';
151
+
152
+ return substr( $prepend . $unique, 0, $length );
153
+ }
154
+
155
+ /**
156
+ * Maybe process queue
157
+ *
158
+ * Checks whether data exists within the queue and that
159
+ * the process is not already running.
160
+ */
161
+ public function maybe_handle() {
162
+ // Don't lock up other requests while processing
163
+ session_write_close();
164
+
165
+ if ( $this->is_process_running() ) {
166
+ // Background process already running.
167
+ wp_die();
168
+ }
169
+
170
+ if ( $this->is_queue_empty() ) {
171
+ // No data to process.
172
+ wp_die();
173
+ }
174
+
175
+ check_ajax_referer( $this->identifier, 'nonce' );
176
+
177
+ $this->handle();
178
+
179
+ wp_die();
180
+ }
181
+
182
+ /**
183
+ * Is queue empty
184
+ *
185
+ * @return bool
186
+ */
187
+ protected function is_queue_empty() {
188
+ global $wpdb;
189
+
190
+ $table = $wpdb->options;
191
+ $column = 'option_name';
192
+
193
+ if ( is_multisite() ) {
194
+ $table = $wpdb->sitemeta;
195
+ $column = 'meta_key';
196
+ }
197
+
198
+ $key = Imagify_DB::esc_like( $this->identifier . '_batch_' ) . '%';
199
+
200
+ $count = $wpdb->get_var( $wpdb->prepare( "
201
+ SELECT COUNT(*)
202
+ FROM {$table}
203
+ WHERE {$column} LIKE %s
204
+ ", $key ) );
205
+
206
+ return ( $count > 0 ) ? false : true;
207
+ }
208
+
209
+ /**
210
+ * Is process running
211
+ *
212
+ * Check whether the current process is already running
213
+ * in a background process.
214
+ */
215
+ protected function is_process_running() {
216
+ if ( get_site_transient( $this->identifier . '_process_lock' ) ) {
217
+ // Process already running.
218
+ return true;
219
+ }
220
+
221
+ return false;
222
+ }
223
+
224
+ /**
225
+ * Lock process
226
+ *
227
+ * Lock the process so that multiple instances can't run simultaneously.
228
+ * Override if applicable, but the duration should be greater than that
229
+ * defined in the time_exceeded() method.
230
+ */
231
+ protected function lock_process() {
232
+ $this->start_time = time(); // Set start time of current process.
233
+
234
+ $lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute
235
+ $lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration );
236
+
237
+ set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration );
238
+ }
239
+
240
+ /**
241
+ * Unlock process
242
+ *
243
+ * Unlock the process so that other instances can spawn.
244
+ *
245
+ * @return $this
246
+ */
247
+ protected function unlock_process() {
248
+ delete_site_transient( $this->identifier . '_process_lock' );
249
+
250
+ return $this;
251
+ }
252
+
253
+ /**
254
+ * Get batch
255
+ *
256
+ * @return stdClass Return the first batch from the queue
257
+ */
258
+ protected function get_batch() {
259
+ global $wpdb;
260
+
261
+ $table = $wpdb->options;
262
+ $column = 'option_name';
263
+ $key_column = 'option_id';
264
+ $value_column = 'option_value';
265
+
266
+ if ( is_multisite() ) {
267
+ $table = $wpdb->sitemeta;
268
+ $column = 'meta_key';
269
+ $key_column = 'meta_id';
270
+ $value_column = 'meta_value';
271
+ }
272
+
273
+ $key = Imagify_DB::esc_like( $this->identifier . '_batch_' ) . '%';
274
+
275
+ $query = $wpdb->get_row( $wpdb->prepare( "
276
+ SELECT *
277
+ FROM {$table}
278
+ WHERE {$column} LIKE %s
279
+ ORDER BY {$key_column} ASC
280
+ LIMIT 1
281
+ ", $key ) );
282
+
283
+ $batch = new stdClass();
284
+ $batch->key = $query->$column;
285
+ $batch->data = maybe_unserialize( $query->$value_column );
286
+
287
+ return $batch;
288
+ }
289
+
290
+ /**
291
+ * Handle
292
+ *
293
+ * Pass each queue item to the task handler, while remaining
294
+ * within server memory and time limit constraints.
295
+ */
296
+ protected function handle() {
297
+ $this->lock_process();
298
+
299
+ do {
300
+ $batch = $this->get_batch();
301
+
302
+ foreach ( $batch->data as $key => $value ) {
303
+ $task = $this->task( $value );
304
+
305
+ if ( false !== $task ) {
306
+ $batch->data[ $key ] = $task;
307
+ } else {
308
+ unset( $batch->data[ $key ] );
309
+ }
310
+
311
+ if ( $this->time_exceeded() || $this->memory_exceeded() ) {
312
+ // Batch limits reached.
313
+ break;
314
+ }
315
+ }
316
+
317
+ // Update or delete current batch.
318
+ if ( ! empty( $batch->data ) ) {
319
+ $this->update( $batch->key, $batch->data );
320
+ } else {
321
+ $this->delete( $batch->key );
322
+ }
323
+ } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() );
324
+
325
+ $this->unlock_process();
326
+
327
+ // Start next batch or complete process.
328
+ if ( ! $this->is_queue_empty() ) {
329
+ $this->dispatch();
330
+ } else {
331
+ $this->complete();
332
+ }
333
+
334
+ wp_die();
335
+ }
336
+
337
+ /**
338
+ * Memory exceeded
339
+ *
340
+ * Ensures the batch process never exceeds 90%
341
+ * of the maximum WordPress memory.
342
+ *
343
+ * @return bool
344
+ */
345
+ protected function memory_exceeded() {
346
+ $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory
347
+ $current_memory = memory_get_usage( true );
348
+ $return = false;
349
+
350
+ if ( $current_memory >= $memory_limit ) {
351
+ $return = true;
352
+ }
353
+
354
+ return apply_filters( $this->identifier . '_memory_exceeded', $return );
355
+ }
356
+
357
+ /**
358
+ * Get memory limit
359
+ *
360
+ * @return int
361
+ */
362
+ protected function get_memory_limit() {
363
+ if ( function_exists( 'ini_get' ) ) {
364
+ $memory_limit = ini_get( 'memory_limit' );
365
+ } else {
366
+ // Sensible default.
367
+ $memory_limit = '128M';
368
+ }
369
+
370
+ if ( ! $memory_limit || -1 === intval( $memory_limit ) ) {
371
+ // Unlimited, set to 32GB.
372
+ $memory_limit = '32000M';
373
+ }
374
+
375
+ return intval( $memory_limit ) * 1024 * 1024;
376
+ }
377
+
378
+ /**
379
+ * Time exceeded.
380
+ *
381
+ * Ensures the batch never exceeds a sensible time limit.
382
+ * A timeout limit of 30s is common on shared hosting.
383
+ *
384
+ * @return bool
385
+ */
386
+ protected function time_exceeded() {
387
+ $finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds
388
+ $return = false;
389
+
390
+ if ( time() >= $finish ) {
391
+ $return = true;
392
+ }
393
+
394
+ return apply_filters( $this->identifier . '_time_exceeded', $return );
395
+ }
396
+
397
+ /**
398
+ * Complete.
399
+ *
400
+ * Override if applicable, but ensure that the below actions are
401
+ * performed, or, call parent::complete().
402
+ */
403
+ protected function complete() {
404
+ // Unschedule the cron healthcheck.
405
+ $this->clear_scheduled_event();
406
+ }
407
+
408
+ /**
409
+ * Schedule cron healthcheck
410
+ *
411
+ * @access public
412
+ * @param mixed $schedules Schedules.
413
+ * @return mixed
414
+ */
415
+ public function schedule_cron_healthcheck( $schedules ) {
416
+ $interval = apply_filters( $this->identifier . '_cron_interval', 5 );
417
+
418
+ if ( property_exists( $this, 'cron_interval' ) ) {
419
+ $interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval );
420
+ }
421
+
422
+ // Adds every 5 minutes to the existing schedules.
423
+ $schedules[ $this->identifier . '_cron_interval' ] = array(
424
+ 'interval' => MINUTE_IN_SECONDS * $interval,
425
+ 'display' => sprintf( __( 'Every %d Minutes' ), $interval ),
426
+ );
427
+
428
+ return $schedules;
429
+ }
430
+
431
+ /**
432
+ * Handle cron healthcheck
433
+ *
434
+ * Restart the background process if not already running
435
+ * and data exists in the queue.
436
+ */
437
+ public function handle_cron_healthcheck() {
438
+ if ( $this->is_process_running() ) {
439
+ // Background process already running.
440
+ exit;
441
+ }
442
+
443
+ if ( $this->is_queue_empty() ) {
444
+ // No data to process.
445
+ $this->clear_scheduled_event();
446
+ exit;
447
+ }
448
+
449
+ $this->handle();
450
+
451
+ exit;
452
+ }
453
+
454
+ /**
455
+ * Schedule event
456
+ */
457
+ protected function schedule_event() {
458
+ if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
459
+ wp_schedule_event( time(), $this->cron_interval_identifier, $this->cron_hook_identifier );
460
+ }
461
+ }
462
+
463
+ /**
464
+ * Clear scheduled event
465
+ */
466
+ protected function clear_scheduled_event() {
467
+ $timestamp = wp_next_scheduled( $this->cron_hook_identifier );
468
+
469
+ if ( $timestamp ) {
470
+ wp_unschedule_event( $timestamp, $this->cron_hook_identifier );
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Cancel Process
476
+ *
477
+ * Stop processing queue items, clear cronjob and delete batch.
478
+ *
479
+ */
480
+ public function cancel_process() {
481
+ if ( ! $this->is_queue_empty() ) {
482
+ $batch = $this->get_batch();
483
+
484
+ $this->delete( $batch->key );
485
+
486
+ wp_clear_scheduled_hook( $this->cron_hook_identifier );
487
+ }
488
+
489
+ }
490
+
491
+ /**
492
+ * Task
493
+ *
494
+ * Override this method to perform any actions required on each
495
+ * queue item. Return the modified item for further processing
496
+ * in the next pass through. Or, return false to remove the
497
+ * item from the queue.
498
+ *
499
+ * @param mixed $item Queue item to iterate over.
500
+ *
501
+ * @return mixed
502
+ */
503
+ abstract protected function task( $item );
504
+
505
+ }
506
+ }
inc/functions/admin-stats.php CHANGED
@@ -280,10 +280,12 @@ function imagify_count_saving_data( $key = '' ) {
280
  unset( $attachment_data['sizes']['full'] );
281
 
282
  // Increment the thumbnails sizes.
283
- foreach ( $attachment_data['sizes'] as $size_data ) {
284
- if ( ! empty( $size_data['success'] ) ) {
285
- $original_size += $size_data['original_size'] ? $size_data['original_size'] : 0;
286
- $optimized_size += $size_data['optimized_size'] ? $size_data['optimized_size'] : 0;
 
 
287
  }
288
  }
289
  }
@@ -367,10 +369,12 @@ function imagify_count_saving_data( $key = '' ) {
367
  unset( $attachment_data['sizes']['full'], $original_data );
368
 
369
  // Increment the thumbnails sizes.
370
- foreach ( $attachment_data['sizes'] as $size_data ) {
371
- if ( ! empty( $size_data['success'] ) ) {
372
- $original_size += ! empty( $size_data['original_size'] ) ? $size_data['original_size'] : 0;
373
- $optimized_size += ! empty( $size_data['optimized_size'] ) ? $size_data['optimized_size'] : 0;
 
 
374
  }
375
  }
376
 
280
  unset( $attachment_data['sizes']['full'] );
281
 
282
  // Increment the thumbnails sizes.
283
+ if ( $attachment_data['sizes'] ) {
284
+ foreach ( $attachment_data['sizes'] as $size_data ) {
285
+ if ( ! empty( $size_data['success'] ) ) {
286
+ $original_size += $size_data['original_size'] ? $size_data['original_size'] : 0;
287
+ $optimized_size += $size_data['optimized_size'] ? $size_data['optimized_size'] : 0;
288
+ }
289
  }
290
  }
291
  }
369
  unset( $attachment_data['sizes']['full'], $original_data );
370
 
371
  // Increment the thumbnails sizes.
372
+ if ( $attachment_data['sizes'] ) {
373
+ foreach ( $attachment_data['sizes'] as $size_data ) {
374
+ if ( ! empty( $size_data['success'] ) ) {
375
+ $original_size += ! empty( $size_data['original_size'] ) ? $size_data['original_size'] : 0;
376
+ $optimized_size += ! empty( $size_data['optimized_size'] ) ? $size_data['optimized_size'] : 0;
377
+ }
378
  }
379
  }
380
 
inc/functions/admin-ui.php CHANGED
@@ -82,11 +82,13 @@ function get_imagify_attachment_optimization_text( $attachment, $context = 'wp'
82
 
83
  $output .= $output_before . '<span class="data">' . __( 'Level:', 'imagify' ) . '</span> <strong>' . $optimization_level . '</strong>' . $output_after;
84
 
85
- $total_optimized_thumbnails = $attachment->get_optimized_sizes_count();
 
86
 
87
- if ( $total_optimized_thumbnails ) {
88
- $output .= $output_before . '<span class="data">' . __( 'Thumbnails Optimized:', 'imagify' ) . '</span> <strong>' . $total_optimized_thumbnails . '</strong>' . $output_after;
89
- $output .= $output_before . '<span class="data">' . __( 'Overall Saving:', 'imagify' ) . '</span> <strong>' . $attachment->get_overall_saving_percent() . '%</strong>' . $output_after;
 
90
  }
91
 
92
  // End of list.
@@ -109,13 +111,16 @@ function get_imagify_attachment_optimization_text( $attachment, $context = 'wp'
109
  $output .= '</a>';
110
 
111
  if ( ! $is_library_page ) {
112
- $dimensions = $attachment->get_dimensions();
113
-
114
  $output .= '<input id="imagify-original-src" type="hidden" value="' . esc_url( $attachment->get_backup_url() ) . '">';
115
  $output .= '<input id="imagify-original-size" type="hidden" value="' . $attachment->get_original_size() . '">';
116
  $output .= '<input id="imagify-full-src" type="hidden" value="' . esc_url( $attachment->get_original_url() ) . '">';
117
- $output .= '<input id="imagify-full-width" type="hidden" value="' . $dimensions['width'] . '">';
118
- $output .= '<input id="imagify-full-height" type="hidden" value="' . $dimensions['height'] . '">';
 
 
 
 
 
119
  }
120
  }
121
 
@@ -234,6 +239,9 @@ function get_imagify_attachment_reoptimize_link( $attachment, $context = 'wp' )
234
  * @return string The output to print.
235
  */
236
  function get_imagify_attachment_optimize_missing_thumbnails_link( $attachment, $context = 'wp' ) {
 
 
 
237
  /**
238
  * Allow to not display the "Optimize missing thumbnails" link.
239
  *
@@ -247,7 +255,7 @@ function get_imagify_attachment_optimize_missing_thumbnails_link( $attachment, $
247
  $display = apply_filters( 'imagify_display_missing_thumbnails_link', true, $attachment, $context );
248
 
249
  // Stop the process if the filter is false, or if the API key isn't valid, or if there is no backup file.
250
- if ( ! $display || ! Imagify_Requirements::is_api_key_valid() || ! $attachment->has_backup() ) {
251
  return '';
252
  }
253
 
@@ -389,7 +397,7 @@ function imagify_get_folder_type_data( $folder_type ) {
389
  * Format the data.
390
  */
391
  /* translators: %s is a formatted number, dont use %d. */
392
- $data['images-optimized'] = sprintf( _n( '%s Image Optimized', '%s Images Optimized', $data['images-optimized'], 'imagify' ), '<span>' . number_format_i18n( $data['images-optimized'] ) . '</span>' );
393
 
394
  if ( $data['errors'] ) {
395
  /* translators: %s is a formatted number, dont use %d. */
82
 
83
  $output .= $output_before . '<span class="data">' . __( 'Level:', 'imagify' ) . '</span> <strong>' . $optimization_level . '</strong>' . $output_after;
84
 
85
+ if ( $attachment->is_image() ) {
86
+ $total_optimized_thumbnails = $attachment->get_optimized_sizes_count();
87
 
88
+ if ( $total_optimized_thumbnails ) {
89
+ $output .= $output_before . '<span class="data">' . __( 'Thumbnails Optimized:', 'imagify' ) . '</span> <strong>' . $total_optimized_thumbnails . '</strong>' . $output_after;
90
+ $output .= $output_before . '<span class="data">' . __( 'Overall Saving:', 'imagify' ) . '</span> <strong>' . $attachment->get_overall_saving_percent() . '%</strong>' . $output_after;
91
+ }
92
  }
93
 
94
  // End of list.
111
  $output .= '</a>';
112
 
113
  if ( ! $is_library_page ) {
 
 
114
  $output .= '<input id="imagify-original-src" type="hidden" value="' . esc_url( $attachment->get_backup_url() ) . '">';
115
  $output .= '<input id="imagify-original-size" type="hidden" value="' . $attachment->get_original_size() . '">';
116
  $output .= '<input id="imagify-full-src" type="hidden" value="' . esc_url( $attachment->get_original_url() ) . '">';
117
+
118
+ if ( $attachment->is_image() ) {
119
+ $dimensions = $attachment->get_dimensions();
120
+
121
+ $output .= '<input id="imagify-full-width" type="hidden" value="' . $dimensions['width'] . '">';
122
+ $output .= '<input id="imagify-full-height" type="hidden" value="' . $dimensions['height'] . '">';
123
+ }
124
  }
125
  }
126
 
239
  * @return string The output to print.
240
  */
241
  function get_imagify_attachment_optimize_missing_thumbnails_link( $attachment, $context = 'wp' ) {
242
+ if ( ! $attachment->is_image() || ! Imagify_Requirements::is_api_key_valid() || ! $attachment->has_backup() ) {
243
+ return '';
244
+ }
245
  /**
246
  * Allow to not display the "Optimize missing thumbnails" link.
247
  *
255
  $display = apply_filters( 'imagify_display_missing_thumbnails_link', true, $attachment, $context );
256
 
257
  // Stop the process if the filter is false, or if the API key isn't valid, or if there is no backup file.
258
+ if ( ! $display ) {
259
  return '';
260
  }
261
 
397
  * Format the data.
398
  */
399
  /* translators: %s is a formatted number, dont use %d. */
400
+ $data['images-optimized'] = sprintf( _n( '%s Media File Optimized', '%s Media Files Optimized', $data['images-optimized'], 'imagify' ), '<span>' . number_format_i18n( $data['images-optimized'] ) . '</span>' );
401
 
402
  if ( $data['errors'] ) {
403
  /* translators: %s is a formatted number, dont use %d. */
inc/functions/api.php CHANGED
@@ -208,12 +208,12 @@ function imagify_translate_api_message( $message ) {
208
  $messages = array(
209
  // Local messages from Imagify::curl_http_call() and Imagify::handle_response().
210
  'Unknown error occurred' => __( 'Unknown error occurred.', 'imagify' ),
211
- 'Your image is too big to be uploaded on our server' => __( 'Your image is too big to be uploaded on our server.', 'imagify' ),
212
  'cURL isn\'t installed on the server' => __( 'cURL is not available on the server.', 'imagify' ),
213
  // API messages.
214
  'Authentification not provided' => __( 'Authentication not provided.', 'imagify' ),
215
  'Cannot create client token' => __( 'Cannot create client token.', 'imagify' ),
216
- 'Confirm your account to continue optimizing image' => __( 'Confirm your account to continue optimizing images.', 'imagify' ),
217
  'Coupon doesn\'t exist' => __( 'Coupon does not exist.', 'imagify' ),
218
  'Email field shouldn\'t be empty' => __( 'Email field should not be empty.', 'imagify' ),
219
  'Email or Password field shouldn\'t be empty' => __( 'This account already exists.', 'imagify' ),
@@ -226,7 +226,7 @@ function imagify_translate_api_message( $message ) {
226
  'Too many request, be patient' => __( 'Too many requests, please be patient.', 'imagify' ),
227
  'Unable to regenerate access token' => __( 'Unable to regenerate access token.', 'imagify' ),
228
  'User not valid' => __( 'User not valid.', 'imagify' ),
229
- 'WELL DONE. This image is already compressed, no further compression required' => __( 'WELL DONE. This image is already optimized, no further optimization is required.', 'imagify' ),
230
  'You are not authorized to perform this action' => __( 'You are not authorized to perform this action.', 'imagify' ),
231
  'You\'ve consumed all your data. You have to upgrade your account to continue' => __( 'You have consumed all your data. You have to upgrade your account to continue.', 'imagify' ),
232
  'Invalid token' => __( 'Invalid API key', 'imagify' ),
208
  $messages = array(
209
  // Local messages from Imagify::curl_http_call() and Imagify::handle_response().
210
  'Unknown error occurred' => __( 'Unknown error occurred.', 'imagify' ),
211
+ 'Your image is too big to be uploaded on our server' => __( 'Your file is too big to be uploaded on our server.', 'imagify' ),
212
  'cURL isn\'t installed on the server' => __( 'cURL is not available on the server.', 'imagify' ),
213
  // API messages.
214
  'Authentification not provided' => __( 'Authentication not provided.', 'imagify' ),
215
  'Cannot create client token' => __( 'Cannot create client token.', 'imagify' ),
216
+ 'Confirm your account to continue optimizing image' => __( 'Confirm your account to continue optimizing files.', 'imagify' ),
217
  'Coupon doesn\'t exist' => __( 'Coupon does not exist.', 'imagify' ),
218
  'Email field shouldn\'t be empty' => __( 'Email field should not be empty.', 'imagify' ),
219
  'Email or Password field shouldn\'t be empty' => __( 'This account already exists.', 'imagify' ),
226
  'Too many request, be patient' => __( 'Too many requests, please be patient.', 'imagify' ),
227
  'Unable to regenerate access token' => __( 'Unable to regenerate access token.', 'imagify' ),
228
  'User not valid' => __( 'User not valid.', 'imagify' ),
229
+ 'WELL DONE. This image is already compressed, no further compression required' => __( 'WELL DONE. This media file is already optimized, no further optimization is required.', 'imagify' ),
230
  'You are not authorized to perform this action' => __( 'You are not authorized to perform this action.', 'imagify' ),
231
  'You\'ve consumed all your data. You have to upgrade your account to continue' => __( 'You have consumed all your data. You have to upgrade your account to continue.', 'imagify' ),
232
  'Invalid token' => __( 'Invalid API key', 'imagify' ),
inc/functions/attachments.php CHANGED
@@ -5,21 +5,32 @@ defined( 'ABSPATH' ) || die( 'Cheatin\' uh?' );
5
  * Get all mime types which could be optimized by Imagify.
6
  *
7
  * @since 1.7
 
8
  *
9
- * @return array The mime types.
 
10
  */
11
- function imagify_get_mime_types() {
12
- return array(
13
- 'jpg|jpeg|jpe' => 'image/jpeg',
14
- 'png' => 'image/png',
15
- 'gif' => 'image/gif',
16
- );
 
 
 
 
 
 
 
 
 
 
17
  }
18
 
19
  /**
20
  * Tell if an attachment has a supported mime type.
21
  * Was previously Imagify_AS3CF::is_mime_type_supported() since 1.6.6.
22
- * Ironically, this function is used in Imagify::is_mime_type_supported() since 1.6.9.
23
  *
24
  * @since 1.6.8
25
  * @author Grégory Viguier
5
  * Get all mime types which could be optimized by Imagify.
6
  *
7
  * @since 1.7
8
+ * @since 1.8 Added $type parameter.
9
  *
10
+ * @param string $type One of 'image', 'not-image'. Any other value will return all mime types.
11
+ * @return array The mime types.
12
  */
13
+ function imagify_get_mime_types( $type = null ) {
14
+ $mimes = array();
15
+
16
+ if ( 'not-image' !== $type ) {
17
+ $mimes = array(
18
+ 'jpg|jpeg|jpe' => 'image/jpeg',
19
+ 'png' => 'image/png',
20
+ 'gif' => 'image/gif',
21
+ );
22
+ }
23
+
24
+ if ( 'image' !== $type ) {
25
+ $mimes['pdf'] = 'application/pdf';
26
+ }
27
+
28
+ return $mimes;
29
  }
30
 
31
  /**
32
  * Tell if an attachment has a supported mime type.
33
  * Was previously Imagify_AS3CF::is_mime_type_supported() since 1.6.6.
 
34
  *
35
  * @since 1.6.8
36
  * @author Grégory Viguier
inc/functions/common.php CHANGED
@@ -212,6 +212,8 @@ function imagify_autoload( $class ) {
212
  'Imagify_User' => 1,
213
  'Imagify_Views' => 1,
214
  'Imagify' => 1,
 
 
215
  );
216
 
217
  if ( isset( $classes[ $class ] ) ) {
@@ -222,15 +224,18 @@ function imagify_autoload( $class ) {
222
 
223
  // Third party classes.
224
  $classes = array(
225
- 'Imagify_AS3CF_Attachment' => 'amazon-s3-and-cloudfront',
226
- 'Imagify_AS3CF' => 'amazon-s3-and-cloudfront',
227
- 'Imagify_Enable_Media_Replace' => 'enable-media-replace',
228
- 'Imagify_Formidable_Pro' => 'formidable-pro',
229
- 'Imagify_NGG_Attachment' => 'nextgen-gallery',
230
- 'Imagify_NGG_DB' => 'nextgen-gallery',
231
- 'Imagify_NGG_Storage' => 'nextgen-gallery',
232
- 'Imagify_NGG' => 'nextgen-gallery',
233
- 'Imagify_Regenerate_Thumbnails' => 'regenerate-thumbnails',
 
 
 
234
  );
235
 
236
  if ( isset( $classes[ $class ] ) ) {
212
  'Imagify_User' => 1,
213
  'Imagify_Views' => 1,
214
  'Imagify' => 1,
215
+ 'WP_Async_Request' => 1,
216
+ 'WP_Background_Process' => 1,
217
  );
218
 
219
  if ( isset( $classes[ $class ] ) ) {
224
 
225
  // Third party classes.
226
  $classes = array(
227
+ 'Imagify_AS3CF_Attachment' => 'amazon-s3-and-cloudfront',
228
+ 'Imagify_AS3CF' => 'amazon-s3-and-cloudfront',
229
+ 'Imagify_Enable_Media_Replace' => 'enable-media-replace',
230
+ 'Imagify_Formidable_Pro' => 'formidable-pro',
231
+ 'Imagify_NGG_Attachment' => 'nextgen-gallery',
232
+ 'Imagify_NGG_DB' => 'nextgen-gallery',
233
+ 'Imagify_NGG_Dynamic_Thumbnails_Background_Process' => 'nextgen-gallery',
234
+ 'Imagify_NGG_Storage' => 'nextgen-gallery',
235
+ 'Imagify_NGG' => 'nextgen-gallery',
236
+ 'Imagify_Regenerate_Thumbnails' => 'regenerate-thumbnails',
237
+ 'Imagify_WP_Retina_2x' => 'wp-retina-2x',
238
+ 'Imagify_WP_Retina_2x_Core' => 'wp-retina-2x',
239
  );
240
 
241
  if ( isset( $classes[ $class ] ) ) {
inc/functions/deprecated.php CHANGED
@@ -628,6 +628,84 @@ if ( class_exists( 'C_NextGEN_Bootstrap' ) && class_exists( 'Mixin' ) && get_sit
628
 
629
  endif;
630
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
  /**
632
  * Combine two arrays with some specific keys.
633
  * We use this function to combine the result of 2 SQL queries.
628
 
629
  endif;
630
 
631
+ if ( function_exists( 'wr2x_delete_attachment' ) ) :
632
+
633
+ /**
634
+ * Remove all retina versions if they exist.
635
+ *
636
+ * @since 1.0
637
+ * @since 1.8 Deprecated.
638
+ * @deprecated
639
+ *
640
+ * @param int $attachment_id An attachment ID.
641
+ */
642
+ function _imagify_wr2x_delete_attachment_on_restore( $attachment_id ) {
643
+ _deprecated_function( __FUNCTION__ . '()', '1.8' );
644
+
645
+ wr2x_delete_attachment( $attachment_id );
646
+ }
647
+
648
+ /**
649
+ * Regenerate all retina versions.
650
+ *
651
+ * @since 1.0
652
+ * @since 1.8 Deprecated.
653
+ * @deprecated
654
+ *
655
+ * @param int $attachment_id An attachment ID.
656
+ */
657
+ function _imagify_wr2x_generate_images_on_restore( $attachment_id ) {
658
+ _deprecated_function( __FUNCTION__ . '()', '1.8' );
659
+
660
+ wr2x_delete_attachment( $attachment_id );
661
+ wr2x_generate_images( wp_get_attachment_metadata( $attachment_id ) );
662
+ }
663
+
664
+ /**
665
+ * Filter the optimization data of each thumbnail.
666
+ *
667
+ * @since 1.0
668
+ * @since 1.8 Deprecated.
669
+ * @deprecated
670
+ *
671
+ * @param array $data The statistics data.
672
+ * @param object $response The API response.
673
+ * @param int $id The attachment ID.
674
+ * @param string $path The attachment path.
675
+ * @param string $url The attachment URL.
676
+ * @param string $size_key The attachment size key.
677
+ * @param bool $optimization_level The optimization level.
678
+ * @return array $data The new optimization data.
679
+ */
680
+ function _imagify_optimize_wr2x( $data, $response, $id, $path, $url, $size_key, $optimization_level ) {
681
+ _deprecated_function( __FUNCTION__ . '()', '1.8', 'Imagify_WP_Retina_2x::optimize_retina_version()' );
682
+
683
+ /**
684
+ * Allow to optimize the retina version generated by WP Retina x2.
685
+ *
686
+ * @since 1.0
687
+ *
688
+ * @param bool $do_retina True will force the optimization.
689
+ */
690
+ $do_retina = apply_filters( 'do_imagify_optimize_retina', true );
691
+ $retina_path = wr2x_get_retina( $path );
692
+
693
+ if ( empty( $retina_path ) || ! $do_retina ) {
694
+ return $data;
695
+ }
696
+
697
+ $response = do_imagify( $retina_path, array(
698
+ 'backup' => false,
699
+ 'optimization_level' => $optimization_level,
700
+ 'context' => 'wp-retina',
701
+ ) );
702
+ $attachment = get_imagify_attachment( 'wp', $id, 'imagify_fill_thumbnail_data' );
703
+
704
+ return $attachment->fill_data( $data, $response, $size_key . '@2x' );
705
+ }
706
+
707
+ endif;
708
+
709
  /**
710
  * Combine two arrays with some specific keys.
711
  * We use this function to combine the result of 2 SQL queries.
inc/functions/i18n.php CHANGED
@@ -83,11 +83,15 @@ function get_imagify_localize_script_translations( $context ) {
83
  );
84
 
85
  case 'twentytwenty':
86
- if ( imagify_is_screen( 'attachment' ) ) {
87
- $image = wp_get_attachment_image_src( $post_id, 'full' );
88
- $image = $image && is_array( $image ) ? $image : array( '', 0, 0 );
89
- } else {
90
- $image = array( '', 0, 0 );
 
 
 
 
91
  }
92
 
93
  return array(
@@ -99,8 +103,8 @@ function get_imagify_localize_script_translations( $context ) {
99
  'filesize' => __( 'File Size:', 'imagify' ),
100
  'saving' => __( 'Original Saving:', 'imagify' ),
101
  'close' => __( 'Close', 'imagify' ),
102
- 'originalL' => __( 'Original Image', 'imagify' ),
103
- 'optimizedL' => __( 'Optimized Image', 'imagify' ),
104
  'compare' => __( 'Compare Original VS Optimized', 'imagify' ),
105
  'optimize' => __( 'Optimize', 'imagify' ),
106
  ),
@@ -150,13 +154,13 @@ function get_imagify_localize_script_translations( $context ) {
150
  'editorMissing' => __( 'No php extensions are available to edit images on the server.', 'imagify' ),
151
  'extHttpBlocked' => __( 'External HTTP requests are blocked.', 'imagify' ),
152
  'apiDown' => __( 'Sorry, our servers are temporarily unavailable. Please, try again in a couple of minutes.', 'imagify' ),
153
- 'invalidAPIKeyTitle' => __( 'Your API key isn\'t valid!', 'imagify' ),
154
  'overQuotaTitle' => __( 'You have used all your credits!', 'imagify' ),
155
  'processing' => __( 'Imagify is still processing. Are you sure you want to leave this page?', 'imagify' ),
156
  'waitTitle' => __( 'Please wait...', 'imagify' ),
157
- 'waitText' => __( 'We are trying to get your unoptimized images, it may take time depending on the number of images.', 'imagify' ),
158
  'noAttachmentToOptimizeTitle' => __( 'Hold on!', 'imagify' ),
159
- 'noAttachmentToOptimizeText' => __( 'All your images have been optimized by Imagify. Congratulations!', 'imagify' ),
160
  'optimizing' => __( 'Optimizing', 'imagify' ),
161
  'error' => __( 'Error', 'imagify' ),
162
  'ajaxErrorText' => __( 'The operation failed.', 'imagify' ),
@@ -171,10 +175,10 @@ function get_imagify_localize_script_translations( $context ) {
171
  'textToShare' => __( 'Discover @imagify, the new compression tool to optimize your images for free. I saved %1$s out of %2$s!', 'imagify' ),
172
  'twitterShareURL' => imagify_get_external_url( 'share-twitter' ),
173
  'getUnoptimizedImagesErrorTitle' => __( 'Oops, There is something wrong!', 'imagify' ),
174
- 'getUnoptimizedImagesErrorText' => __( 'An unknown error occurred when we tried to get all your unoptimized images. Try again and if the issue still persists, please contact us!', 'imagify' ),
175
  'waitingOtimizationsText' => __( 'Waiting other optimizations to finish.', 'imagify' ),
176
  /* translators: %s is a formatted number, dont use %d. */
177
- 'imagesOptimizedText' => __( '%s Image(s) Optimized', 'imagify' ),
178
  /* translators: %s is a formatted number, dont use %d. */
179
  'imagesErrorText' => __( '%s Error(s)', 'imagify' ),
180
  'bulkInfoTitle' => __( 'Information', 'imagify' ),
83
  );
84
 
85
  case 'twentytwenty':
86
+ $image = array( '', 0, 0 );
87
+
88
+ if ( imagify_is_screen( 'attachment' ) && wp_attachment_is_image( $post_id ) ) {
89
+ $attachment = get_imagify_attachment( 'wp', $post_id, 'imagify_localize_script_translations' );
90
+
91
+ if ( $attachment->is_image() ) {
92
+ $image = wp_get_attachment_image_src( $post_id, 'full' );
93
+ $image = $image && is_array( $image ) ? $image : array( '', 0, 0 );
94
+ }
95
  }
96
 
97
  return array(
103
  'filesize' => __( 'File Size:', 'imagify' ),
104
  'saving' => __( 'Original Saving:', 'imagify' ),
105
  'close' => __( 'Close', 'imagify' ),
106
+ 'originalL' => __( 'Original File', 'imagify' ),
107
+ 'optimizedL' => __( 'Optimized File', 'imagify' ),
108
  'compare' => __( 'Compare Original VS Optimized', 'imagify' ),
109
  'optimize' => __( 'Optimize', 'imagify' ),
110
  ),
154
  'editorMissing' => __( 'No php extensions are available to edit images on the server.', 'imagify' ),
155
  'extHttpBlocked' => __( 'External HTTP requests are blocked.', 'imagify' ),
156
  'apiDown' => __( 'Sorry, our servers are temporarily unavailable. Please, try again in a couple of minutes.', 'imagify' ),
157
+ 'invalidAPIKeyTitle' => __( 'Your API key is not valid!', 'imagify' ),
158
  'overQuotaTitle' => __( 'You have used all your credits!', 'imagify' ),
159
  'processing' => __( 'Imagify is still processing. Are you sure you want to leave this page?', 'imagify' ),
160
  'waitTitle' => __( 'Please wait...', 'imagify' ),
161
+ 'waitText' => __( 'We are trying to get your unoptimized media files, it may take time depending on the number of files.', 'imagify' ),
162
  'noAttachmentToOptimizeTitle' => __( 'Hold on!', 'imagify' ),
163
+ 'noAttachmentToOptimizeText' => __( 'All your media files have been optimized by Imagify. Congratulations!', 'imagify' ),
164
  'optimizing' => __( 'Optimizing', 'imagify' ),
165
  'error' => __( 'Error', 'imagify' ),
166
  'ajaxErrorText' => __( 'The operation failed.', 'imagify' ),
175
  'textToShare' => __( 'Discover @imagify, the new compression tool to optimize your images for free. I saved %1$s out of %2$s!', 'imagify' ),
176
  'twitterShareURL' => imagify_get_external_url( 'share-twitter' ),
177
  'getUnoptimizedImagesErrorTitle' => __( 'Oops, There is something wrong!', 'imagify' ),
178
+ 'getUnoptimizedImagesErrorText' => __( 'An unknown error occurred when we tried to get all your unoptimized media files. Try again and if the issue still persists, please contact us!', 'imagify' ),
179
  'waitingOtimizationsText' => __( 'Waiting other optimizations to finish.', 'imagify' ),
180
  /* translators: %s is a formatted number, dont use %d. */
181
+ 'imagesOptimizedText' => __( '%s Media File(s) Optimized', 'imagify' ),
182
  /* translators: %s is a formatted number, dont use %d. */
183
  'imagesErrorText' => __( '%s Error(s)', 'imagify' ),
184
  'bulkInfoTitle' => __( 'Information', 'imagify' ),
inc/functions/process.php CHANGED
@@ -43,8 +43,10 @@ function do_imagify( $file_path, $args = array() ) {
43
  return new WP_Error( 'curl', __( 'cURL is not available on the server.', 'imagify' ) );
44
  }
45
 
 
 
46
  // Check if imageMagick or GD is available.
47
- if ( ! Imagify_Requirements::supports_image_editor() ) {
48
  return new WP_Error( 'image_editor', __( 'No php extensions are available to edit images on the server.', 'imagify' ) );
49
  }
50
 
@@ -58,8 +60,6 @@ function do_imagify( $file_path, $args = array() ) {
58
  return new WP_Error( 'api_server_down', __( 'Sorry, our servers are temporarily unavailable. Please, try again in a couple of minutes.', 'imagify' ) );
59
  }
60
 
61
- $filesystem = imagify_get_filesystem();
62
-
63
  // Check that the file exists.
64
  if ( ! $filesystem->is_writable( $file_path ) || ! $filesystem->is_file( $file_path ) ) {
65
  /* translators: %s is a file path. */
@@ -82,6 +82,18 @@ function do_imagify( $file_path, $args = array() ) {
82
  */
83
  do_action( 'before_do_imagify', $file_path, $args['backup'] );
84
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  // Send image for optimization and fetch the response.
86
  $response = upload_imagify_image( array(
87
  'image' => $file_path,
@@ -99,12 +111,6 @@ function do_imagify( $file_path, $args = array() ) {
99
  return new WP_Error( 'api_error', $response->get_error_message() );
100
  }
101
 
102
- // Create a backup file.
103
- if ( $args['backup'] && ! $args['resized'] ) {
104
- // TODO (@Greg): Send an error message if the backup fails.
105
- imagify_backup_file( $file_path, $args['backup_path'] );
106
- }
107
-
108
  if ( ! function_exists( 'download_url' ) ) {
109
  require_once ABSPATH . 'wp-admin/includes/file.php';
110
  }
@@ -116,12 +122,6 @@ function do_imagify( $file_path, $args = array() ) {
116
  }
117
 
118
  $filesystem->move( $temp_file, $file_path, true );
119
- $filesystem->chmod_file( $file_path );
120
-
121
- // If temp file still exists, delete it.
122
- if ( $filesystem->exists( $temp_file ) ) {
123
- $filesystem->delete( $temp_file );
124
- }
125
 
126
  /**
127
  * Fires after to optimize the Image with Imagify.
43
  return new WP_Error( 'curl', __( 'cURL is not available on the server.', 'imagify' ) );
44
  }
45
 
46
+ $filesystem = imagify_get_filesystem();
47
+
48
  // Check if imageMagick or GD is available.
49
+ if ( $filesystem->is_image( $file_path ) && ! Imagify_Requirements::supports_image_editor() ) {
50
  return new WP_Error( 'image_editor', __( 'No php extensions are available to edit images on the server.', 'imagify' ) );
51
  }
52
 
60
  return new WP_Error( 'api_server_down', __( 'Sorry, our servers are temporarily unavailable. Please, try again in a couple of minutes.', 'imagify' ) );
61
  }
62
 
 
 
63
  // Check that the file exists.
64
  if ( ! $filesystem->is_writable( $file_path ) || ! $filesystem->is_file( $file_path ) ) {
65
  /* translators: %s is a file path. */
82
  */
83
  do_action( 'before_do_imagify', $file_path, $args['backup'] );
84
 
85
+ // Create a backup file before sending to optimization (to make sure we can backup the file).
86
+ $do_backup = $args['backup'] && ! $args['resized'];
87
+
88
+ if ( $do_backup ) {
89
+ $backup_result = imagify_backup_file( $file_path, $args['backup_path'] );
90
+
91
+ if ( is_wp_error( $backup_result ) ) {
92
+ // Stop the process if we can't backup the file.
93
+ return $backup_result;
94
+ }
95
+ }
96
+
97
  // Send image for optimization and fetch the response.
98
  $response = upload_imagify_image( array(
99
  'image' => $file_path,
111
  return new WP_Error( 'api_error', $response->get_error_message() );
112
  }
113
 
 
 
 
 
 
 
114
  if ( ! function_exists( 'download_url' ) ) {
115
  require_once ABSPATH . 'wp-admin/includes/file.php';
116
  }
122
  }
123
 
124
  $filesystem->move( $temp_file, $file_path, true );
 
 
 
 
 
 
125
 
126
  /**
127
  * Fires after to optimize the Image with Imagify.
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: wp_media, GregLone
3
  Tags: compress image, images, performance, optimization, photos, upload, resize, gif, png, jpg, reduce image size, retina
4
  Requires at least: 3.7.0
5
- Tested up to: 4.9.5
6
- Stable tag: 1.7.1.3
7
 
8
  Dramatically reduce image file sizes without losing quality, make your website load faster, boost your SEO and save money on your bandwidth.
9
 
@@ -138,6 +138,12 @@ When the plugin is disabled, your existing images remain optimized. Backups of t
138
  4. Other Media Page
139
 
140
  == Changelog ==
 
 
 
 
 
 
141
  = 1.7.1.3 - 2018/04/12 =
142
  * Bug Fix: a fatal error with outdated versions of php.
143
 
2
  Contributors: wp_media, GregLone
3
  Tags: compress image, images, performance, optimization, photos, upload, resize, gif, png, jpg, reduce image size, retina
4
  Requires at least: 3.7.0
5
+ Tested up to: 4.9.6
6
+ Stable tag: 1.8
7
 
8
  Dramatically reduce image file sizes without losing quality, make your website load faster, boost your SEO and save money on your bandwidth.
9
 
138
  4. Other Media Page
139
 
140
  == Changelog ==
141
+ = 1.8 - 2018/06/19 =
142
+ * New: you can now optimize pdf files.
143
+ * Improvement: custom folders, you can now optimize files located in the *uploads* folder.
144
+ * Improvement: support for thumbnails dynamically generated by NextGen Gallery plugin.
145
+ * Bug Fix: revamped support for WP Retina 2x plugin.
146
+
147
  = 1.7.1.3 - 2018/04/12 =
148
  * Bug Fix: a fatal error with outdated versions of php.
149
 
uninstall.php CHANGED
@@ -27,12 +27,13 @@ delete_transient( 'imagify_user' );
27
 
28
  // Delete transients.
29
  $transients = implode( '" OR option_name LIKE "', array(
30
- '_transient_%imagify-async-in-progress-%',
31
- '_transient_%imagify-ngg-async-in-progress-%',
32
- '_site_transient_%imagify-file-async-in-progress-%',
33
- '_transient_%imagify_rpc_%',
 
34
  ) );
35
- $wpdb->query( 'DELETE from ' . $wpdb->options . ' WHERE option_name LIKE "' . $transients . '"' ); // WPCS: unprepared SQL ok.
36
 
37
  // Clear scheduled hooks.
38
  wp_clear_scheduled_hook( 'imagify_rating_event' );
27
 
28
  // Delete transients.
29
  $transients = implode( '" OR option_name LIKE "', array(
30
+ '\_transient\_%imagify-async-in-progress-%',
31
+ '\_transient\_%imagify-ngg-async-in-progress-%',
32
+ '\_site\_transient\_%imagify-file-async-in-progress-%',
33
+ '\_transient\_%imagify\_rpc\_%',
34
+ '\_site\_transient\_imagify\_%\_process\_lock%',
35
  ) );
36
+ $wpdb->query( "DELETE from $wpdb->options WHERE option_name LIKE \"$transients\"" ); // WPCS: unprepared SQL ok.
37
 
38
  // Clear scheduled hooks.
39
  wp_clear_scheduled_hook( 'imagify_rating_event' );
views/part-settings-custom-folders.php CHANGED
@@ -12,6 +12,7 @@ $themes = array();
12
  if ( $custom_folders ) {
13
  $custom_folders = array_combine( $custom_folders, $custom_folders );
14
  $custom_folders = array_map( array( 'Imagify_Files_Scan', 'remove_placeholder' ), $custom_folders );
 
15
  $custom_folders = array_filter( $custom_folders, array( 'Imagify_Files_Scan', 'is_path_autorized' ) );
16
  }
17
 
@@ -154,6 +155,19 @@ if ( ! is_network_admin() ) {
154
  ?>
155
  </fieldset>
156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  <p class="imagify-success hidden"><?php _e( 'You changed your custom folder settings, don\'t forget to save your changes!', 'imagify' ); ?></p>
158
 
159
  <script type="text/html" id="tmpl-imagify-custom-folder">
12
  if ( $custom_folders ) {
13
  $custom_folders = array_combine( $custom_folders, $custom_folders );
14
  $custom_folders = array_map( array( 'Imagify_Files_Scan', 'remove_placeholder' ), $custom_folders );
15
+ $custom_folders = array_map( 'trailingslashit', $custom_folders );
16
  $custom_folders = array_filter( $custom_folders, array( 'Imagify_Files_Scan', 'is_path_autorized' ) );
17
  }
18
 
155
  ?>
156
  </fieldset>
157
 
158
+ <p>
159
+ <?php
160
+ printf(
161
+ /* translators: 1 and 2 are <strong> opening and closing tags. */
162
+ __( '%1$sSelecting a folder will also optimize images in sub-folders.%2$s The only exception is "Site’s root": when selected, only images that are directly at the site’s root will be optimized (sub-folders can be selected separately).', 'imagify' ),
163
+ '<strong>',
164
+ '</strong>'
165
+ );
166
+ ?>
167
+ <br/>
168
+ <?php _e( 'Folders that are hidden in the folder selector window are excluded and will not be optimized even if a parent folder is selected.', 'imagify' ); ?>
169
+ </p>
170
+
171
  <p class="imagify-success hidden"><?php _e( 'You changed your custom folder settings, don\'t forget to save your changes!', 'imagify' ); ?></p>
172
 
173
  <script type="text/html" id="tmpl-imagify-custom-folder">