ShortPixel Image Optimizer - Version 4.2.6

Version Description

  • add the webp files as thumbs to the sizes array in metadata so they are automatically used by themes that use srcset.
  • add option to optimize PDFs or not.
  • seamless integration with WP/LR Sync plugin.
  • gracefully ignore missing thumbs on disk when doing the optimization - just mark them as missing in the metadata.
  • gracefully add Media Library files that are present on disk but not present in the image metadata (sizes array).
  • option to dismiss the top toolbar ShortPixel alert when quota expired.
  • compute the backup folder size asynchronously in order to speed up the settings page.
  • editors/authors now are able to optimize/restore images from the Media Library list.
  • handle internationalized domain names (punycode encoded).
  • reset failed images from Custom Media when user launches a reprocessing of the images from Bulk.
  • bugfixes
Download this release

Release Info

Developer ShortPixel
Plugin Icon 128x128 ShortPixel Image Optimizer
Version 4.2.6
Comparing to
See all releases

Code changes from version 4.2.5 to 4.2.6

class/db/shortpixel-custom-meta-dao.php CHANGED
@@ -306,6 +306,11 @@ class ShortPixelCustomMetaDao {
306
  return $inserted;
307
  }
308
 
 
 
 
 
 
309
  public function getPaginatedMetas($hasNextGen, $count, $page, $orderby = false, $order = false) {
310
  $sql = "SELECT sm.id, sm.name, sm.path folder, "
311
  . ($hasNextGen ? "CASE WHEN ng.gid IS NOT NULL THEN 'NextGen' ELSE 'Custom' END media_type, " : "'Custom' media_type, ")
@@ -340,14 +345,14 @@ class ShortPixelCustomMetaDao {
340
  public function getPendingMetaCount() {
341
  $res = $this->db->query("SELECT COUNT(sm.id) recCount from {$this->db->getPrefix()}shortpixel_meta sm "
342
  . "INNER JOIN {$this->db->getPrefix()}shortpixel_folders sf on sm.folder_id = sf.id "
343
- . "WHERE sf.status <> -1 AND ( sm.status = 0 OR sm.status = 1 )");
344
  return isset($res[0]->recCount) ? $res[0]->recCount : null;
345
  }
346
 
347
  public function getCustomMetaCount() {
348
  $sql = "SELECT COUNT(sm.id) recCount FROM {$this->db->getPrefix()}shortpixel_meta sm "
349
  . "INNER JOIN {$this->db->getPrefix()}shortpixel_folders sf on sm.folder_id = sf.id "
350
- . "WHERE sf.status <> -1";
351
  $res = $this->db->query($sql);
352
  return isset($res[0]->recCount) ? $res[0]->recCount : 0;
353
  }
306
  return $inserted;
307
  }
308
 
309
+ public function resetFailed() {
310
+ $sql = "UPDATE {$this->db->getPrefix()}shortpixel_meta SET status = 0, retries = 0 WHERE status < 0";
311
+ $this->db->query($sql);
312
+ }
313
+
314
  public function getPaginatedMetas($hasNextGen, $count, $page, $orderby = false, $order = false) {
315
  $sql = "SELECT sm.id, sm.name, sm.path folder, "
316
  . ($hasNextGen ? "CASE WHEN ng.gid IS NOT NULL THEN 'NextGen' ELSE 'Custom' END media_type, " : "'Custom' media_type, ")
345
  public function getPendingMetaCount() {
346
  $res = $this->db->query("SELECT COUNT(sm.id) recCount from {$this->db->getPrefix()}shortpixel_meta sm "
347
  . "INNER JOIN {$this->db->getPrefix()}shortpixel_folders sf on sm.folder_id = sf.id "
348
+ . "WHERE sf.status <> -1 AND sm.status <> 3 AND ( sm.status = 0 OR sm.status = 1 OR (sm.status < 0 AND sm.retries < 3))");
349
  return isset($res[0]->recCount) ? $res[0]->recCount : null;
350
  }
351
 
352
  public function getCustomMetaCount() {
353
  $sql = "SELECT COUNT(sm.id) recCount FROM {$this->db->getPrefix()}shortpixel_meta sm "
354
  . "INNER JOIN {$this->db->getPrefix()}shortpixel_folders sf on sm.folder_id = sf.id "
355
+ . "WHERE sf.status <> -1 AND sm.status <> 3";
356
  $res = $this->db->query($sql);
357
  return isset($res[0]->recCount) ? $res[0]->recCount : 0;
358
  }
class/db/shortpixel-meta-facade.php CHANGED
@@ -46,21 +46,24 @@ class ShortPixelMetaFacade {
46
  }
47
 
48
  private static function rawMetaToMeta($ID, $rawMeta) {
 
49
  return new ShortPixelMeta(array(
50
  "id" => $ID,
51
- "path" => get_attached_file($ID),
 
52
  "webPath" => (isset($rawMeta["file"]) ? $rawMeta["file"] : null),
53
  "thumbs" => (isset($rawMeta["sizes"]) ? $rawMeta["sizes"] : array()),
54
  "message" =>(isset($rawMeta["ShortPixelImprovement"]) ? $rawMeta["ShortPixelImprovement"] : null),
55
  "compressionType" =>(isset($rawMeta["ShortPixel"]["type"]) ? ($rawMeta["ShortPixel"]["type"] == "lossy" ? 1 : 0) : null),
56
  "thumbsOpt" =>(isset($rawMeta["ShortPixel"]["thumbsOpt"]) ? $rawMeta["ShortPixel"]["thumbsOpt"] : null),
 
57
  "retinasOpt" =>(isset($rawMeta["ShortPixel"]["retinasOpt"]) ? $rawMeta["ShortPixel"]["retinasOpt"] : null),
58
  "thumbsTodo" =>(isset($rawMeta["ShortPixel"]["thumbsTodo"]) ? $rawMeta["ShortPixel"]["thumbsTodo"] : false),
59
  "backup" => !isset($rawMeta['ShortPixel']['NoBackup']),
60
  "status" => (!isset($rawMeta["ShortPixel"]) ? 0
61
  : (isset($rawMeta["ShortPixelImprovement"]) && is_numeric($rawMeta["ShortPixelImprovement"]) ? 2
62
  : (isset($rawMeta["ShortPixel"]["WaitingProcessing"]) ? 1
63
- : -500))),
64
  "retries" =>(isset($rawMeta["ShortPixel"]["Retries"]) ? $rawMeta["ShortPixel"]["Retries"] : 0),
65
  ));
66
  }
@@ -96,18 +99,55 @@ class ShortPixelMetaFacade {
96
  foreach($duplicates as $_ID) {
97
  $rawMeta = $this->sanitizeMeta(wp_get_attachment_metadata($_ID));
98
 
99
- $rawMeta['ShortPixel']['type'] = ($this->meta->getCompressionType() == 1 ? "lossy" : "lossless");
100
- $rawMeta['ShortPixel']['exifKept'] = $this->meta->getKeepExif();
101
- $rawMeta['ShortPixel']['date'] = date("Y-m-d", strtotime($this->meta->getTsOptimized()));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  //thumbs were processed if settings or if they were explicitely requested
103
- $rawMeta['ShortPixel']['thumbsOpt'] = $this->meta->getThumbsOpt();
104
- $rawMeta['ShortPixel']['retinasOpt'] = $this->meta->getRetinasOpt();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  //if thumbsTodo - this means there was an explicit request to process thumbs for an image that was previously processed without
106
  // don't update the ShortPixelImprovement ratio as this is only calculated based on main image
107
  if($this->meta->getThumbsTodo()) {
108
  $rawMeta['ShortPixel']['thumbsTodo'] = true;
109
  } else {
110
- $rawMeta['ShortPixelImprovement'] = "".round($this->meta->getImprovementPercent(),2);
 
 
111
  unset($rawMeta['ShortPixel']['thumbsTodo']);
112
  }
113
  if($this->meta->getActualWidth() && $this->meta->getActualHeight()) {
@@ -120,6 +160,10 @@ class ShortPixelMetaFacade {
120
  if($this->meta->getStatus() !== 1) {
121
  unset($rawMeta['ShortPixel']['WaitingProcessing']);
122
  }
 
 
 
 
123
  wp_update_attachment_metadata($this->ID, $rawMeta);
124
  $this->rawMeta = $rawMeta;
125
  }
@@ -156,6 +200,7 @@ class ShortPixelMetaFacade {
156
  } else {
157
  if($status) {
158
  $this->rawMeta['ShortPixel']['WaitingProcessing'] = true;
 
159
  } else {
160
  unset($this->rawMeta['ShortPixel']['WaitingProcessing']);
161
  }
@@ -174,6 +219,7 @@ class ShortPixelMetaFacade {
174
  }
175
  } else {
176
  $this->rawMeta['ShortPixelImprovement'] = $this->meta->getMessage();
 
177
  unset($this->rawMeta['ShortPixel']['WaitingProcessing']);
178
  wp_update_attachment_metadata($this->ID, $this->rawMeta);
179
  }
@@ -195,7 +241,27 @@ class ShortPixelMetaFacade {
195
  return trailingslashit((function_exists("is_multisite") && is_multisite()) ? network_site_url("/") : home_url());
196
  }
197
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  public function getURLsAndPATHs($processThumbnails, $onlyThumbs = false, $addRetina = true) {
 
 
199
  if($this->type == self::CUSTOM_TYPE) {
200
  $meta = $this->getMeta();
201
 
@@ -205,55 +271,73 @@ class ShortPixelMetaFacade {
205
 
206
  $filePaths[] = $meta->getPath();
207
  } else {
208
- $attURL = wp_get_attachment_url($this->ID);
209
- if(!$attURL || !strlen($attURL)) {
210
- throw new Exception("Post metadata is corrupt (No attachment URL)");
211
- }
212
- if ( !parse_url(WP_CONTENT_URL, PHP_URL_SCHEME) ) {//no absolute URLs used -> we implement a hack
213
- $url = get_site_url() . $attURL;//get the file URL
214
- }
215
- else {
216
- $url = $attURL;//get the file URL
217
- }
218
- $urlList[] = $url;
219
  $path = get_attached_file($this->ID);//get the full file PATH
220
- $filePaths[] = $path;
221
- if($addRetina) {
222
- $this->addRetina($path, $url, $filePaths, $urlList);
 
 
 
 
 
 
 
223
  }
224
 
225
  $meta = $this->getMeta();
226
  $sizes = $meta->getThumbs();
227
 
228
  //it is NOT a PDF file and thumbs are processable
229
- if ( strtolower(substr($filePaths[0],strrpos($filePaths[0], ".")+1)) != "pdf"
230
  && ($processThumbnails || $onlyThumbs)
231
- && count($sizes))
232
  {
233
- foreach( $sizes as $thumbnailInfo ) {
234
- $tUrl = str_replace(ShortPixelAPI::MB_basename($urlList[0]), $thumbnailInfo['file'], $url);
235
- $tPath = str_replace(ShortPixelAPI::MB_basename($filePaths[0]), $thumbnailInfo['file'], $path);
236
- $urlList[] = $tUrl;
237
- $filePaths[] = $tPath;
238
- if($addRetina) {
239
- $this->addRetina($tPath, $tUrl, $filePaths, $urlList);
 
 
 
 
 
240
  }
241
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  }
243
  if(!count($sizes)) {
244
  WPShortPixel::log("getURLsAndPATHs: no meta sizes for ID " . $this->ID . " : " . json_encode($this->rawMeta));
245
  }
246
 
247
- if($onlyThumbs) { //remove the main image
248
  array_shift($urlList);
249
  array_shift($filePaths);
250
- }
251
  }
252
 
253
  //convert the + which are replaced with spaces by wp_remote_post
254
  array_walk($urlList, array( &$this, 'replacePlusChar') );
255
 
256
- return array("URLs" => $urlList, "PATHs" => $filePaths);
 
 
 
257
  }
258
 
259
  protected function replacePlusChar(&$url) {
@@ -326,6 +410,32 @@ class ShortPixelMetaFacade {
326
  }
327
  }
328
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  public static function getMaxMediaId() {
330
  global $wpdb;
331
  $queryMax = "SELECT max(post_id) as QueryID FROM " . $wpdb->prefix . "postmeta";
@@ -382,18 +492,45 @@ class ShortPixelMetaFacade {
382
  * @param type $file
383
  * @return string
384
  */
385
- static public function returnSubDir($file, $type)
386
  {
387
  if(strstr($file, get_home_path())) {
388
  $path = str_replace( get_home_path(), "", $file);
389
  } else {
390
- $path = (substr($file, 1));
391
  }
392
  $pathArr = explode('/', $path);
393
  unset($pathArr[count($pathArr) - 1]);
394
  return implode('/', $pathArr) . '/';
395
- }
396
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  public static function isMediaSubfolder($path) {
398
  $uploadDir = wp_upload_dir();
399
  $uploadBase = $uploadDir["basedir"];
46
  }
47
 
48
  private static function rawMetaToMeta($ID, $rawMeta) {
49
+ $path = get_attached_file($ID);
50
  return new ShortPixelMeta(array(
51
  "id" => $ID,
52
+ "name" => basename($path),
53
+ "path" => $path,
54
  "webPath" => (isset($rawMeta["file"]) ? $rawMeta["file"] : null),
55
  "thumbs" => (isset($rawMeta["sizes"]) ? $rawMeta["sizes"] : array()),
56
  "message" =>(isset($rawMeta["ShortPixelImprovement"]) ? $rawMeta["ShortPixelImprovement"] : null),
57
  "compressionType" =>(isset($rawMeta["ShortPixel"]["type"]) ? ($rawMeta["ShortPixel"]["type"] == "lossy" ? 1 : 0) : null),
58
  "thumbsOpt" =>(isset($rawMeta["ShortPixel"]["thumbsOpt"]) ? $rawMeta["ShortPixel"]["thumbsOpt"] : null),
59
+ "thumbsMissing" =>(isset($rawMeta["ShortPixel"]["thumbsMissing"]) ? $rawMeta["ShortPixel"]["thumbsMissing"] : null),
60
  "retinasOpt" =>(isset($rawMeta["ShortPixel"]["retinasOpt"]) ? $rawMeta["ShortPixel"]["retinasOpt"] : null),
61
  "thumbsTodo" =>(isset($rawMeta["ShortPixel"]["thumbsTodo"]) ? $rawMeta["ShortPixel"]["thumbsTodo"] : false),
62
  "backup" => !isset($rawMeta['ShortPixel']['NoBackup']),
63
  "status" => (!isset($rawMeta["ShortPixel"]) ? 0
64
  : (isset($rawMeta["ShortPixelImprovement"]) && is_numeric($rawMeta["ShortPixelImprovement"]) ? 2
65
  : (isset($rawMeta["ShortPixel"]["WaitingProcessing"]) ? 1
66
+ : (isset($rawMeta["ShortPixel"]['ErrCode']) ? $rawMeta["ShortPixel"]['ErrCode'] : -500)))),
67
  "retries" =>(isset($rawMeta["ShortPixel"]["Retries"]) ? $rawMeta["ShortPixel"]["Retries"] : 0),
68
  ));
69
  }
99
  foreach($duplicates as $_ID) {
100
  $rawMeta = $this->sanitizeMeta(wp_get_attachment_metadata($_ID));
101
 
102
+ if(is_array($rawMeta['sizes'])) {
103
+ $rawMeta['sizes'] = array_merge($rawMeta['sizes'], $this->meta->getThumbs());
104
+ }
105
+
106
+ if(null === $this->meta->getCompressionType()) {
107
+ unset($rawMeta['ShortPixel']['type']);
108
+ } else {
109
+ $rawMeta['ShortPixel']['type'] = ($this->meta->getCompressionType() == 1 ? "lossy" : "lossless");
110
+ }
111
+
112
+ if(null === $this->meta->getKeepExif()) {
113
+ unset($rawMeta['ShortPixel']['exifKept']);
114
+ } else {
115
+ $rawMeta['ShortPixel']['exifKept'] = $this->meta->getKeepExif();
116
+ }
117
+
118
+ if(null === $this->meta->getTsOptimized()) {
119
+ unset($rawMeta['ShortPixel']['date']);
120
+ } else {
121
+ $rawMeta['ShortPixel']['date'] = date("Y-m-d", strtotime($this->meta->getTsOptimized()));
122
+ }
123
+
124
  //thumbs were processed if settings or if they were explicitely requested
125
+ if(null === $this->meta->getThumbsOpt()) {
126
+ unset($rawMeta['ShortPixel']['thumbsOpt']);
127
+ } else {
128
+ $rawMeta['ShortPixel']['thumbsOpt'] = $this->meta->getThumbsOpt();
129
+ }
130
+
131
+ $thumbsMissing = $this->meta->getThumbsMissing();
132
+ if(count($thumbsMissing)) {
133
+ $rawMeta['ShortPixel']['thumbsMissing'] = $this->meta->getThumbsMissing();
134
+ } else {
135
+ unset($rawMeta['ShortPixel']['thumbsMissing']);
136
+ }
137
+
138
+ if(null === $this->meta->getRetinasOpt()) {
139
+ unset($rawMeta['ShortPixel']['retinasOpt']);
140
+ } else {
141
+ $rawMeta['ShortPixel']['retinasOpt'] = $this->meta->getRetinasOpt();
142
+ }
143
  //if thumbsTodo - this means there was an explicit request to process thumbs for an image that was previously processed without
144
  // don't update the ShortPixelImprovement ratio as this is only calculated based on main image
145
  if($this->meta->getThumbsTodo()) {
146
  $rawMeta['ShortPixel']['thumbsTodo'] = true;
147
  } else {
148
+ if($this->meta->getStatus() > 0) {
149
+ $rawMeta['ShortPixelImprovement'] = "".round($this->meta->getImprovementPercent(),2);
150
+ }
151
  unset($rawMeta['ShortPixel']['thumbsTodo']);
152
  }
153
  if($this->meta->getActualWidth() && $this->meta->getActualHeight()) {
160
  if($this->meta->getStatus() !== 1) {
161
  unset($rawMeta['ShortPixel']['WaitingProcessing']);
162
  }
163
+ if($this->meta->getStatus() >= 0) {
164
+ unset($rawMeta['ShortPixel']['ErrCode']);
165
+ }
166
+
167
  wp_update_attachment_metadata($this->ID, $rawMeta);
168
  $this->rawMeta = $rawMeta;
169
  }
200
  } else {
201
  if($status) {
202
  $this->rawMeta['ShortPixel']['WaitingProcessing'] = true;
203
+ unset($this->rawMeta['ShortPixel']['ErrCode']);
204
  } else {
205
  unset($this->rawMeta['ShortPixel']['WaitingProcessing']);
206
  }
219
  }
220
  } else {
221
  $this->rawMeta['ShortPixelImprovement'] = $this->meta->getMessage();
222
+ $this->rawMeta['ShortPixel']['ErrCode'] = $errorCode;
223
  unset($this->rawMeta['ShortPixel']['WaitingProcessing']);
224
  wp_update_attachment_metadata($this->ID, $this->rawMeta);
225
  }
241
  return trailingslashit((function_exists("is_multisite") && is_multisite()) ? network_site_url("/") : home_url());
242
  }
243
 
244
+ //this is in test
245
+ public static function getHomeUrl2() {
246
+ return trailingslashit(ShortPixelTools::commonPrefix(self::getHomeUrl(), content_url()));
247
+ }
248
+
249
+ public static function safeGetAttachmentUrl($id) {
250
+ $attURL = wp_get_attachment_url($id);
251
+ if(!$attURL || !strlen($attURL)) {
252
+ throw new Exception("Post metadata is corrupt (No attachment URL)");
253
+ }
254
+ if ( !parse_url(WP_CONTENT_URL, PHP_URL_SCHEME) ) {//no absolute URLs used -> we implement a hack
255
+ return get_site_url() . $attURL;//get the file URL
256
+ }
257
+ else {
258
+ return $attURL;//get the file URL
259
+ }
260
+ }
261
+
262
  public function getURLsAndPATHs($processThumbnails, $onlyThumbs = false, $addRetina = true) {
263
+ $sizesMissing = array();
264
+
265
  if($this->type == self::CUSTOM_TYPE) {
266
  $meta = $this->getMeta();
267
 
271
 
272
  $filePaths[] = $meta->getPath();
273
  } else {
 
 
 
 
 
 
 
 
 
 
 
274
  $path = get_attached_file($this->ID);//get the full file PATH
275
+ $mainExists = file_exists($path);
276
+ $url = self::safeGetAttachmentUrl($this->ID);
277
+ $urlList = array(); $filePaths = array();
278
+
279
+ if($mainExists) {
280
+ $urlList[] = $url;
281
+ $filePaths[] = $path;
282
+ if($addRetina) {
283
+ $this->addRetina($path, $url, $filePaths, $urlList);
284
+ }
285
  }
286
 
287
  $meta = $this->getMeta();
288
  $sizes = $meta->getThumbs();
289
 
290
  //it is NOT a PDF file and thumbs are processable
291
+ if ( strtolower(substr($path,strrpos($path, ".")+1)) != "pdf"
292
  && ($processThumbnails || $onlyThumbs)
293
+ && count($sizes))
294
  {
295
+ $uploadDir = wp_upload_dir();
296
+ $Tmp = explode("/", $uploadDir['basedir']);
297
+ $TmpCount = count($Tmp);
298
+ $StichString = $Tmp[$TmpCount-2] . "/" . $Tmp[$TmpCount-1];
299
+
300
+ foreach( $sizes as $thumbnailName => $thumbnailInfo ) {
301
+ if(strpos($thumbnailName, ShortPixelMeta::WEBP_THUMB_PREFIX) === 0) {
302
+ continue;
303
+ }
304
+ $origPath = $tPath = str_replace(ShortPixelAPI::MB_basename($path), $thumbnailInfo['file'], $path);
305
+ if ( !file_exists($tPath) ) {
306
+ $tPath = $uploadDir['basedir'] . substr($tPath, strpos($tPath, $StichString) + strlen($StichString));
307
  }
308
+ if ( !file_exists($tPath) ) {
309
+ $tPath = trailingslashit($uploadDir['basedir']) . $origPath;
310
+ }
311
+ if (file_exists($tPath)) {
312
+ $tUrl = str_replace(ShortPixelAPI::MB_basename($url), $thumbnailInfo['file'], $url);
313
+ $urlList[] = $tUrl;
314
+ $filePaths[] = $tPath;
315
+ if($addRetina) {
316
+ $this->addRetina($tPath, $tUrl, $filePaths, $urlList);
317
+ }
318
+ }
319
+ else {
320
+ $sizesMissing[$thumbnailName] = ShortPixelAPI::MB_basename($tPath);
321
+ }
322
+ }
323
  }
324
  if(!count($sizes)) {
325
  WPShortPixel::log("getURLsAndPATHs: no meta sizes for ID " . $this->ID . " : " . json_encode($this->rawMeta));
326
  }
327
 
328
+ if($onlyThumbs && $mainExists) { //remove the main image
329
  array_shift($urlList);
330
  array_shift($filePaths);
331
+ }
332
  }
333
 
334
  //convert the + which are replaced with spaces by wp_remote_post
335
  array_walk($urlList, array( &$this, 'replacePlusChar') );
336
 
337
+ $filePaths = ShortPixelAPI::CheckAndFixImagePaths($filePaths);//check for images to make sure they exist on disk
338
+
339
+ //die(var_dump(array("URLs" => $urlList, "PATHs" => $filePaths)));
340
+ return array("URLs" => $urlList, "PATHs" => $filePaths, "sizesMissing" => $sizesMissing);
341
  }
342
 
343
  protected function replacePlusChar(&$url) {
410
  }
411
  }
412
 
413
+ public function getWebpSizeMeta($path) {
414
+ $meta = $this->getMeta();
415
+ foreach($meta->getThumbs() as $thumbKey => $thumbMeta) {
416
+ if(isset($thumbMeta['file']) && strpos($path, $thumbMeta['file']) !== false) {
417
+ $thumbMeta['file'] = preg_replace( '/\.' . pathinfo($path, PATHINFO_EXTENSION) . '$/', '.webp', $thumbMeta['file']);
418
+ $thumbMeta['mime-type'] = 'image/webp';
419
+ return array('key' => ShortPixelMeta::WEBP_THUMB_PREFIX . $thumbKey, 'val' => $thumbMeta);
420
+ }
421
+ }
422
+ $name = $meta->getName();
423
+ if(strpos($path, $name) !== false) {
424
+ if(!file_exists($path)) {
425
+ return false;
426
+ }
427
+ $size = getimagesize($path);
428
+ return array('key' => ShortPixelMeta::WEBP_THUMB_PREFIX . 'main',
429
+ 'val' => array( // it's a file that has no corresponding thumb so it's the WEBP for the main file
430
+ 'file' => pathinfo(ShortPixelAPI::MB_basename($path), PATHINFO_FILENAME) . '.webp',
431
+ 'width' => $size[0],
432
+ 'height' => $size[1],
433
+ 'mime-type' => 'image/webp'
434
+ ));
435
+ }
436
+ return false;
437
+ }
438
+
439
  public static function getMaxMediaId() {
440
  global $wpdb;
441
  $queryMax = "SELECT max(post_id) as QueryID FROM " . $wpdb->prefix . "postmeta";
492
  * @param type $file
493
  * @return string
494
  */
495
+ static public function returnSubDirOld($file, $type)
496
  {
497
  if(strstr($file, get_home_path())) {
498
  $path = str_replace( get_home_path(), "", $file);
499
  } else {
500
+ $path = (substr($file, 1));
501
  }
502
  $pathArr = explode('/', $path);
503
  unset($pathArr[count($pathArr) - 1]);
504
  return implode('/', $pathArr) . '/';
505
+ }
506
+
507
+ /**
508
+ * return subdir for that particular attached file - if it's media library then last 3 path items, otherwise substract the uploads path
509
+ * Has trailing directory separator (/)
510
+ * @param type $file
511
+ * @return string
512
+ */
513
+ static public function returnSubDir($file, $type)
514
+ {
515
+ $hp = wp_normalize_path(get_home_path());
516
+ $file = wp_normalize_path($file);
517
+ $sp__uploads = wp_upload_dir();
518
+ if(strstr($file, $hp)) {
519
+ $path = str_replace( $hp, "", $file);
520
+ } elseif( strstr($file, dirname( WP_CONTENT_DIR ))) { //in some situations the content dir is not inside the root, check this also (ex. single.shortpixel.com)
521
+ $path = str_replace( trailingslashit(dirname( WP_CONTENT_DIR )), "", $file);
522
+ } elseif( (strstr(realpath($file), realpath($hp)))) {
523
+ $path = str_replace( realpath($hp), "", realpath($file));
524
+ } elseif( strstr($file, trailingslashit(dirname(dirname( $sp__uploads['basedir'] )))) ) {
525
+ $path = str_replace( trailingslashit(dirname(dirname( $sp__uploads['basedir'] ))), "", $file);
526
+ } else {
527
+ $path = (substr($file, 1));
528
+ }
529
+ $pathArr = explode('/', $path);
530
+ unset($pathArr[count($pathArr) - 1]);
531
+ return implode('/', $pathArr) . '/';
532
+ }
533
+
534
  public static function isMediaSubfolder($path) {
535
  $uploadDir = wp_upload_dir();
536
  $uploadBase = $uploadDir["basedir"];
class/db/wp-shortpixel-media-library-adapter.php CHANGED
@@ -3,7 +3,7 @@
3
  class WpShortPixelMediaLbraryAdapter {
4
 
5
  //count all the processable files in media library (while limiting the results to max 10000)
6
- public static function countAllProcessableFiles($maxId = PHP_INT_MAX, $minId = 0){
7
  global $wpdb;
8
 
9
  $totalFiles = $mainFiles = $processedMainFiles = $processedTotalFiles =
@@ -43,12 +43,13 @@ class WpShortPixelMediaLbraryAdapter {
43
  else //_wp_attachment_metadata
44
  {
45
  $attachment = unserialize($file->meta_value);
 
46
  //processable
47
  $isProcessable = false;
48
  if(isset($attachment['file']) && !isset($filesMap[$attachment['file']]) && WPShortPixel::isProcessablePath($attachment['file'])){
49
  $isProcessable = true;
50
  if ( isset($attachment['sizes']) ) {
51
- $totalFiles += count($attachment['sizes']);
52
  }
53
  if ( isset($attachment['file']) )
54
  {
@@ -84,9 +85,11 @@ class WpShortPixelMediaLbraryAdapter {
84
  $thumbs = $attachment['ShortPixel']['thumbsOpt'];
85
  }
86
  elseif ( isset($attachment['sizes']) ) {
87
- $thumbs = count($attachment['sizes']);
88
  }
89
- if ( isset($attachment['sizes']) && count($attachment['sizes']) > $thumbs) {
 
 
90
  $mainUnprocessedThumbs++;
91
  }
92
 
@@ -129,27 +132,18 @@ class WpShortPixelMediaLbraryAdapter {
129
  );
130
  }
131
 
132
- public static function fixWPMediaMetaMissingThumbs($id) {
133
- $meta = wp_get_attachment_metadata($id);
134
-
135
- $path = get_attached_file($id);//get the full file PATH
136
- $filePath[] = $path;
137
-
138
- $missing = array();
139
- //it is NOT a PDF file and thumbs are processable
140
- if ( strtolower(substr($filePath[0],strrpos($filePath[0], ".")+1)) != "pdf"
141
- && count($meta['sizes']))
142
- {
143
- foreach( $meta['sizes'] as $size => $thumbnailInfo ) {
144
- if(!file_exists(str_replace(ShortPixelAPI::MB_basename($filePath[0]), $thumbnailInfo['file'], $path))) {
145
- $missing[] = $size;
146
- }
147
- }
148
- foreach($missing as $size) {
149
- unset($meta['sizes'][$size]);
150
- }
151
  }
152
- wp_update_attachment_metadata($id, $meta);
 
 
 
 
 
153
  }
154
 
155
  protected static function getOptimalChunkSize() {
3
  class WpShortPixelMediaLbraryAdapter {
4
 
5
  //count all the processable files in media library (while limiting the results to max 10000)
6
+ public static function countAllProcessableFiles($includePdfs = true, $maxId = PHP_INT_MAX, $minId = 0){
7
  global $wpdb;
8
 
9
  $totalFiles = $mainFiles = $processedMainFiles = $processedTotalFiles =
43
  else //_wp_attachment_metadata
44
  {
45
  $attachment = unserialize($file->meta_value);
46
+ $sizesCount = isset($attachment['sizes']) ? WpShortPixelMediaLbraryAdapter::countNonWebpSizes($attachment['sizes']) : 0;
47
  //processable
48
  $isProcessable = false;
49
  if(isset($attachment['file']) && !isset($filesMap[$attachment['file']]) && WPShortPixel::isProcessablePath($attachment['file'])){
50
  $isProcessable = true;
51
  if ( isset($attachment['sizes']) ) {
52
+ $totalFiles += $sizesCount;
53
  }
54
  if ( isset($attachment['file']) )
55
  {
85
  $thumbs = $attachment['ShortPixel']['thumbsOpt'];
86
  }
87
  elseif ( isset($attachment['sizes']) ) {
88
+ $thumbs = $sizesCount;
89
  }
90
+ $thumbsMissing = isset($attachment['ShortPixel']['thumbsMissing']) ? $attachment['ShortPixel']['thumbsMissing'] : array();
91
+
92
+ if ( isset($attachment['sizes']) && $sizesCount > $thumbs + count($thumbsMissing)) {
93
  $mainUnprocessedThumbs++;
94
  }
95
 
132
  );
133
  }
134
 
135
+ public static function countNonWebpSizes($sizes) {
136
+ $count = 0;
137
+ foreach($sizes as $key => $val) {
138
+ if (strpos($key, ShortPixelMeta::WEBP_THUMB_PREFIX) === 0) continue;
139
+ $count++;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  }
141
+ return $count;
142
+ }
143
+
144
+ public static function thumbsSearchPattern($mainFile) {
145
+ $ext = pathinfo($mainFile, PATHINFO_EXTENSION);
146
+ return substr($mainFile, 0, strlen($mainFile) - strlen($ext) - 1) . "*[0-9]x*[0-9]." . $ext;
147
  }
148
 
149
  protected static function getOptimalChunkSize() {
class/model/shortpixel-meta.php CHANGED
@@ -12,6 +12,7 @@ class ShortPixelMeta extends ShortPixelEntity{
12
  protected $compressionType;
13
  protected $compressedSize;
14
  protected $thumbsOpt;
 
15
  protected $retinasOpt;
16
  protected $thumbsTodo;
17
  protected $keepExif;
@@ -30,6 +31,8 @@ class ShortPixelMeta extends ShortPixelEntity{
30
  protected $thumbs;
31
 
32
  const TABLE_SUFFIX = 'meta';
 
 
33
 
34
  public function __construct($data = array()) {
35
  parent::__construct($data);
@@ -136,6 +139,15 @@ class ShortPixelMeta extends ShortPixelEntity{
136
  function setThumbsOpt($thumbsOpt) {
137
  $this->thumbsOpt = $thumbsOpt;
138
  }
 
 
 
 
 
 
 
 
 
139
  function getRetinasOpt() {
140
  return $this->retinasOpt;
141
  }
@@ -254,6 +266,9 @@ class ShortPixelMeta extends ShortPixelEntity{
254
 
255
  function setThumbs($thumbs) {
256
  $this->thumbs = $thumbs;
257
- }
258
-
 
 
 
259
  }
12
  protected $compressionType;
13
  protected $compressedSize;
14
  protected $thumbsOpt;
15
+ protected $thumbsMissing;
16
  protected $retinasOpt;
17
  protected $thumbsTodo;
18
  protected $keepExif;
31
  protected $thumbs;
32
 
33
  const TABLE_SUFFIX = 'meta';
34
+ const WEBP_THUMB_PREFIX = 'sp-webp-';
35
+ const FOUND_THUMB_PREFIX = 'sp-found-';
36
 
37
  public function __construct($data = array()) {
38
  parent::__construct($data);
139
  function setThumbsOpt($thumbsOpt) {
140
  $this->thumbsOpt = $thumbsOpt;
141
  }
142
+
143
+ function getThumbsMissing() {
144
+ return $this->thumbsMissing;
145
+ }
146
+
147
+ function setThumbsMissing($thumbsMissing) {
148
+ $this->thumbsMissing = $thumbsMissing;
149
+ }
150
+
151
  function getRetinasOpt() {
152
  return $this->retinasOpt;
153
  }
266
 
267
  function setThumbs($thumbs) {
268
  $this->thumbs = $thumbs;
269
+ }
270
+
271
+ function addThumbs($thumbs) {
272
+ $this->thumbs = array_merge($this->thumbs, $thumbs);
273
+ }
274
  }
class/shortpixel-tools.php CHANGED
@@ -38,5 +38,20 @@ class ShortPixelTools {
38
 
39
  //If no checks triggered, we end up here - not an AJAX request.
40
  return false;
41
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  }
38
 
39
  //If no checks triggered, we end up here - not an AJAX request.
40
  return false;
41
+ }
42
+
43
+ public static function commonPrefix($str1, $str2) {
44
+ $limit = min(strlen($str1), strlen($str2));
45
+ for ($i = 0; $i < $limit && $str1[$i] === $str2[$i]; $i++);
46
+ return substr($str1, 0, $i);
47
+ }
48
+
49
+ /**
50
+ * This is a simplified wp_send_json made compatible with WP 3.2.x to 3.4.x
51
+ * @param type $response
52
+ */
53
+ public static function sendJSON($response) {
54
+ @header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) );
55
+ die(json_encode($response));
56
+ }
57
  }
class/shortpixel_queue.php CHANGED
@@ -97,6 +97,19 @@ class ShortPixelQueue {
97
  WPShortPixel::log("PUSH: Updated: ".json_encode($_SESSION["wp-short-pixel-priorityQueue"]));//get_option("wp-short-pixel-priorityQueue")));
98
  }
99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  public function getFirst($count = 1)//return the first values added to priority queue
101
  {
102
  $priorityQueue = $_SESSION["wp-short-pixel-priorityQueue"];//self::getOpt("wp-short-pixel-priorityQueue", array());
@@ -230,7 +243,7 @@ class ShortPixelQueue {
230
 
231
  public function setBulkPreviousPercent() {
232
  //processable and already processed
233
- $res = WpShortPixelMediaLbraryAdapter::countAllProcessableFiles($this->getFlagBulkId(), $this->settings->stopBulkId);
234
  $this->settings->bulkCount = $res["mainFiles"];
235
 
236
  //if compression type changed, add also the images with the other compression type
97
  WPShortPixel::log("PUSH: Updated: ".json_encode($_SESSION["wp-short-pixel-priorityQueue"]));//get_option("wp-short-pixel-priorityQueue")));
98
  }
99
 
100
+ public function enqueue($ID)//add an ID to priority queue as LAST
101
+ {
102
+ $priorityQueue = $_SESSION["wp-short-pixel-priorityQueue"]; //get_option("wp-short-pixel-priorityQueue");
103
+ WPShortPixel::log("PUSH: Push ID $ID into queue ".json_encode($priorityQueue));
104
+ array_unshift($priorityQueue, $ID);
105
+ $prioQ = array_unique($priorityQueue);
106
+ $_SESSION["wp-short-pixel-priorityQueue"] = $prioQ;
107
+ //push also to the options queue, in case the session gets killed retrieve from there
108
+ $this->settings->priorityQueue = $prioQ;
109
+
110
+ WPShortPixel::log("ENQUEUE: Updated: ".json_encode($_SESSION["wp-short-pixel-priorityQueue"]));//get_option("wp-short-pixel-priorityQueue")));
111
+ }
112
+
113
  public function getFirst($count = 1)//return the first values added to priority queue
114
  {
115
  $priorityQueue = $_SESSION["wp-short-pixel-priorityQueue"];//self::getOpt("wp-short-pixel-priorityQueue", array());
243
 
244
  public function setBulkPreviousPercent() {
245
  //processable and already processed
246
+ $res = WpShortPixelMediaLbraryAdapter::countAllProcessableFiles($this->settings->optimizePdfs, $this->getFlagBulkId(), $this->settings->stopBulkId);
247
  $this->settings->bulkCount = $res["mainFiles"];
248
 
249
  //if compression type changed, add also the images with the other compression type
class/view/shortpixel-list-table.php CHANGED
@@ -43,7 +43,7 @@ class ShortPixelListTable extends WP_List_Table {
43
  function column_default( $item, $column_name ) {
44
  switch( $column_name ) {
45
  case 'name':
46
- $title = '<a href="" title="'.$item->folder.'"><strong>' . $item->name . '</strong></a>';
47
 
48
  $url = ShortPixelMetaFacade::pathToWebPath($item->folder);
49
  $actions = array(
@@ -102,7 +102,7 @@ class ShortPixelListTable extends WP_List_Table {
102
  if($item->status < 0) {
103
  $msg = $item->message . "(" . __('code','shortpixel-image-optimiser') . ": " . $item->status . ")";
104
  } else {
105
- $msg = "";
106
  }
107
  }
108
  return "<div id='sp-cust-msg-C-" . $item->id . "'>" . $msg . "</div>";
43
  function column_default( $item, $column_name ) {
44
  switch( $column_name ) {
45
  case 'name':
46
+ $title = '<a href="' . ShortPixelMetaFacade::pathToWebPath($item->folder) . '" title="'.$item->folder.'" target="_blank"><strong>' . $item->name . '</strong></a>';
47
 
48
  $url = ShortPixelMetaFacade::pathToWebPath($item->folder);
49
  $actions = array(
102
  if($item->status < 0) {
103
  $msg = $item->message . "(" . __('code','shortpixel-image-optimiser') . ": " . $item->status . ")";
104
  } else {
105
+ $msg = "<span style='display:none;'>" . $item->status . "</span>";
106
  }
107
  }
108
  return "<div id='sp-cust-msg-C-" . $item->id . "'>" . $msg . "</div>";
class/view/shortpixel_view.php CHANGED
@@ -16,11 +16,11 @@ class ShortPixelView {
16
  public function displayQuotaExceededAlert($quotaData, $averageCompression = false, $recheck = false)
17
  { ?>
18
  <br/>
19
- <div class="wrap sp-quota-exceeded-alert">
20
  <?php if($averageCompression) { ?>
21
  <div style="float:right; margin-top: 10px">
22
- <div class="bulk-progress-indicator">
23
- <div style="margin-bottom:5px"><?php _e('Average reduction','shortpixel-image-optimiser');?></div>
24
  <div id="sp-avg-optimization"><input type="text" id="sp-avg-optimization-dial" value="<?php echo("" . round($averageCompression))?>" class="dial"></div>
25
  <script>
26
  jQuery(function() {
@@ -36,12 +36,12 @@ class ShortPixelView {
36
  echo('<span style="color: red">' . __('You have no available image credits. If you just bought a package, please note that sometimes it takes a few minutes for the payment confirmation to be sent to us by the payment processor.','shortpixel-image-optimiser') . '</span><br>');
37
  }
38
  printf(__('The plugin has optimized <strong>%s images</strong> and stopped because it reached the available quota limit.','shortpixel-image-optimiser'),
39
- number_format($quotaData['APICallsMadeNumeric'] + $quotaData['APICallsMadeOneTimeNumeric']));?>
40
  <?php if($quotaData['totalProcessedFiles'] < $quotaData['totalFiles']) { ?>
41
  <?php
42
  printf(__('<strong>%s images and %s thumbnails</strong> are not yet optimized by ShortPixel.','shortpixel-image-optimiser'),
43
- number_format($quotaData['mainFiles'] - $quotaData['mainProcessedFiles']),
44
- number_format(($quotaData['totalFiles'] - $quotaData['mainFiles']) - ($quotaData['totalProcessedFiles'] - $quotaData['mainProcessedFiles']))); ?>
45
  <?php } ?></p>
46
  <div> <!-- style='float:right;margin-top:20px;'> -->
47
  <a class='button button-primary' href='https://shortpixel.com/login/<?php echo($this->ctrl->getApiKey());?>' target='_blank'><?php _e('Upgrade','shortpixel-image-optimiser');?></a>
@@ -68,12 +68,16 @@ class ShortPixelView {
68
  <?php
69
  }
70
 
71
- public static function displayActivationNotice($when = 'activate') { ?>
72
- <div class='notice notice-warning' id='short-pixel-notice-<?php echo($when);?>'>
 
 
73
  <?php if($when != 'activate') { ?>
74
  <div style="float:right;"><a href="javascript:dismissShortPixelNotice('<?php echo($when);?>')" class="button" style="margin-top:10px;"><?php _e('Dismiss','shortpixel-image-optimiser');?></a></div>
75
  <?php } ?>
76
- <h3><?php _e('ShortPixel Optimization','shortpixel-image-optimiser');?></h3> <?php
 
 
77
  switch($when) {
78
  case '2h' :
79
  _e("Action needed. Please <a href='https://shortpixel.com/wp-apikey' target='_blank'>get your API key</a> to activate your ShortPixel plugin.",'shortpixel-image-optimiser') . "<BR><BR>";
@@ -84,6 +88,16 @@ class ShortPixelView {
84
  case 'activate':
85
  self::displayApiKeyAlert();
86
  break;
 
 
 
 
 
 
 
 
 
 
87
  }
88
  ?>
89
  </div>
@@ -288,8 +302,8 @@ class ShortPixelView {
288
  $todo = $reopt = false;
289
  if($quotaData['totalProcessedFiles'] < $quotaData['totalFiles']) {
290
  $todo = true;
291
- $mainNotProcessed = $quotaData['mainFiles'] - $quotaData['mainProcessedFiles'];
292
- $thumbsNotProcessed = ($quotaData['totalFiles'] - $quotaData['mainFiles']) - ($quotaData['totalProcessedFiles'] - $quotaData['mainProcessedFiles']);
293
  ?>
294
  <p>
295
  <?php
@@ -380,13 +394,21 @@ class ShortPixelView {
380
  </div>
381
  <?php } ?>
382
 
383
- <div class="sp-floating-block notice bulk-notices-parent">
384
  <div class="bulk-notice-container">
385
  <div class="bulk-notice-msg bulk-lengthy">
386
  <img src="<?php echo(plugins_url( 'shortpixel-image-optimiser/res/img/loading-dark-big.gif' ));?>">
387
  <?php _e('Lengthy operation in progress:','shortpixel-image-optimiser');?><br>
388
  <?php _e('Optimizing image','shortpixel-image-optimiser');?> <a href="#" data-href="<?php echo(get_admin_url());?>/post.php?post=__ID__&action=edit" target="_blank">placeholder.png</a>
389
  </div>
 
 
 
 
 
 
 
 
390
  <div class="bulk-notice-msg bulk-error" id="bulk-error-template">
391
  <div style="float: right; margin-top: -4px; margin-right: -8px;">
392
  <a href="javascript:void(0);" onclick="ShortPixel.removeBulkMsg(this)" style='color: #c32525;'>&#10006;</a>
@@ -760,6 +782,7 @@ class ShortPixelView {
760
  $createWebp = ($settings->createWebp ? 'checked' : '');
761
  $autoMediaLibrary = ($settings->autoMediaLibrary ? 'checked' : '');
762
  $optimizeRetina = ($settings->optimizeRetina ? 'checked' : '');
 
763
  ?>
764
  <div class="wp-shortpixel-options">
765
  <?php if(!$this->ctrl->getVerifiedKey()) { ?>
@@ -881,6 +904,12 @@ class ShortPixelView {
881
  </p>
882
  </td>
883
  </tr>
 
 
 
 
 
 
884
  <tr>
885
  <th scope="row"><label for="authentication"><?php _e('HTTP AUTH credentials','shortpixel-image-optimiser');?></label></th>
886
  <td>
@@ -951,9 +980,13 @@ class ShortPixelView {
951
  <tr>
952
  <th scope="row" bgcolor="#ffffff"><label for="apiQuota"><?php _e('Your monthly plan','shortpixel-image-optimiser');?>:</label></th>
953
  <td bgcolor="#ffffff">
954
- <?php printf(__('%s/month, renews in %s days, on %s ( <a href="https://shortpixel.com/login/%s" target="_blank">Need More? See the options available</a> )','shortpixel-image-optimiser'),
955
- $quotaData['APICallsQuota'], floor(30 + (strtotime($quotaData['APILastRenewalDate']) - time()) / 86400),
956
- date('M d, Y', strtotime($quotaData['APILastRenewalDate']. ' + 30 days')), $this->ctrl->getApiKey());?><br/>
 
 
 
 
957
  <?php printf(__('<a href="https://shortpixel.com/login/%s/tell-a-friend" target="_blank">Join our friend referral system</a> to win more credits. For each user that joins, you receive +100 images credits/month.','shortpixel-image-optimiser'),
958
  $this->ctrl->getApiKey());?>
959
  </td>
@@ -992,7 +1025,9 @@ class ShortPixelView {
992
  <th scope="row"><label for="sizeBackup"><?php _e('Original images are stored in a backup folder. Your backup folder size is now:','shortpixel-image-optimiser');?></label></th>
993
  <td>
994
  <form action="" method="POST">
995
- <?php echo($backupFolderSize);?>
 
 
996
  <input type="submit" style="margin-left: 15px; vertical-align: middle;" class="button button-secondary" name="emptyBackup" value="<?php _e('Empty backups','shortpixel-image-optimiser');?>"/>
997
  </form>
998
  </td>
@@ -1053,9 +1088,18 @@ class ShortPixelView {
1053
  case 'imgOptimized':
1054
  $successText = $this->getSuccessText($data['percent'],$data['bonus'],$data['type'],$data['thumbsOpt'],$data['thumbsTotal'], $data['retinasOpt']);
1055
  if($extended) {
 
 
 
 
 
 
 
 
1056
  $successText .= ($data['webpCount'] ? "<br>+" . $data['webpCount'] . __(" WebP images", 'shortpixel-image-optimiser') : "")
1057
  . "<br>EXIF: " . ($data['exifKept'] ? __('kept','shortpixel-image-optimiser') : __('removed','shortpixel-image-optimiser'))
1058
- . "<br>" . __("Optimized on", 'shortpixel-image-optimiser') . ": " . $data['date'];
 
1059
  }
1060
  $this->renderListCell($id, $data['showActions'],
1061
  !$data['thumbsOpt'] && $data['thumbsTotal'], $data['thumbsTotal'], $data['backup'], $data['type'], $successText);
16
  public function displayQuotaExceededAlert($quotaData, $averageCompression = false, $recheck = false)
17
  { ?>
18
  <br/>
19
+ <div class="wrap sp-quota-exceeded-alert" id="short-pixel-notice-exceed">
20
  <?php if($averageCompression) { ?>
21
  <div style="float:right; margin-top: 10px">
22
+ <div class="bulk-progress-indicator" style="height: 110px">
23
+ <div style="margin-bottom:5px"><?php _e('Average image<br>reduction so far:','shortpixel-image-optimiser');?></div>
24
  <div id="sp-avg-optimization"><input type="text" id="sp-avg-optimization-dial" value="<?php echo("" . round($averageCompression))?>" class="dial"></div>
25
  <script>
26
  jQuery(function() {
36
  echo('<span style="color: red">' . __('You have no available image credits. If you just bought a package, please note that sometimes it takes a few minutes for the payment confirmation to be sent to us by the payment processor.','shortpixel-image-optimiser') . '</span><br>');
37
  }
38
  printf(__('The plugin has optimized <strong>%s images</strong> and stopped because it reached the available quota limit.','shortpixel-image-optimiser'),
39
+ number_format(max(0, $quotaData['APICallsMadeNumeric'] + $quotaData['APICallsMadeOneTimeNumeric'])));?>
40
  <?php if($quotaData['totalProcessedFiles'] < $quotaData['totalFiles']) { ?>
41
  <?php
42
  printf(__('<strong>%s images and %s thumbnails</strong> are not yet optimized by ShortPixel.','shortpixel-image-optimiser'),
43
+ number_format(max(0, $quotaData['mainFiles'] - $quotaData['mainProcessedFiles'])),
44
+ number_format(max(0, ($quotaData['totalFiles'] - $quotaData['mainFiles']) - ($quotaData['totalProcessedFiles'] - $quotaData['mainProcessedFiles'])))); ?>
45
  <?php } ?></p>
46
  <div> <!-- style='float:right;margin-top:20px;'> -->
47
  <a class='button button-primary' href='https://shortpixel.com/login/<?php echo($this->ctrl->getApiKey());?>' target='_blank'><?php _e('Upgrade','shortpixel-image-optimiser');?></a>
68
  <?php
69
  }
70
 
71
+ public static function displayActivationNotice($when = 'activate', $extra = '') {
72
+ $extraStyle = $when == 'compat' ? "style='border-left: 4px solid#ff0000;'" : '';
73
+ ?>
74
+ <div class='notice notice-warning' id='short-pixel-notice-<?php echo($when);?>' <?php echo($extraStyle);?>>
75
  <?php if($when != 'activate') { ?>
76
  <div style="float:right;"><a href="javascript:dismissShortPixelNotice('<?php echo($when);?>')" class="button" style="margin-top:10px;"><?php _e('Dismiss','shortpixel-image-optimiser');?></a></div>
77
  <?php } ?>
78
+ <h3><?php
79
+ if($when == 'compat') {_e('Warning','shortpixel-image-optimiser'); echo(' - ');}
80
+ _e('ShortPixel Image Optimizer','shortpixel-image-optimiser');?></h3> <?php
81
  switch($when) {
82
  case '2h' :
83
  _e("Action needed. Please <a href='https://shortpixel.com/wp-apikey' target='_blank'>get your API key</a> to activate your ShortPixel plugin.",'shortpixel-image-optimiser') . "<BR><BR>";
88
  case 'activate':
89
  self::displayApiKeyAlert();
90
  break;
91
+ case 'compat' :
92
+ _e("Using ShortPixel while other image optimization plugins are active can lead to unpredictable results. We recommend to deactivate the following plugin(s): ",'shortpixel-image-optimiser');
93
+ echo('<ul>');
94
+ foreach($extra as $plugin) {
95
+ echo('<li class="sp-conflict-plugins-list"><strong>' . $plugin['name'] . '</strong>');
96
+ echo('<a href="' . wp_nonce_url( admin_url( 'admin-post.php?action=shortpixel_deactivate_plugin&plugin=' . urlencode( $plugin['path'] ) ), 'sp_deactivate_plugin_nonce' ) . '" class="button">'
97
+ . __( 'Deactivate', 'shortpixel_image_optimiser' ) . '</a>');
98
+ }
99
+ echo("</ul>");
100
+ break;
101
  }
102
  ?>
103
  </div>
302
  $todo = $reopt = false;
303
  if($quotaData['totalProcessedFiles'] < $quotaData['totalFiles']) {
304
  $todo = true;
305
+ $mainNotProcessed = max(0, $quotaData['mainFiles'] - $quotaData['mainProcessedFiles']);
306
+ $thumbsNotProcessed = max(0, ($quotaData['totalFiles'] - $quotaData['mainFiles']) - ($quotaData['totalProcessedFiles'] - $quotaData['mainProcessedFiles']));
307
  ?>
308
  <p>
309
  <?php
394
  </div>
395
  <?php } ?>
396
 
397
+ <div class="sp-floating-block sp-notice bulk-notices-parent">
398
  <div class="bulk-notice-container">
399
  <div class="bulk-notice-msg bulk-lengthy">
400
  <img src="<?php echo(plugins_url( 'shortpixel-image-optimiser/res/img/loading-dark-big.gif' ));?>">
401
  <?php _e('Lengthy operation in progress:','shortpixel-image-optimiser');?><br>
402
  <?php _e('Optimizing image','shortpixel-image-optimiser');?> <a href="#" data-href="<?php echo(get_admin_url());?>/post.php?post=__ID__&action=edit" target="_blank">placeholder.png</a>
403
  </div>
404
+ <div class="bulk-notice-msg bulk-maintenance">
405
+ <img src="<?php echo(plugins_url( 'shortpixel-image-optimiser/res/img/loading-dark-big.gif' ));?>">
406
+ <?php _e("The ShortPixel API is in maintenance mode. Please don't close this window. The bulk will resume automatically as soon as the API is back online.",'shortpixel-image-optimiser');?>
407
+ </div>
408
+ <div class="bulk-notice-msg bulk-queue-full">
409
+ <img src="<?php echo(plugins_url( 'shortpixel-image-optimiser/res/img/loading-dark-big.gif' ));?>">
410
+ <?php _e("Too many images processing simultaneously for your site, automatically retrying in 1 min. Please don't close this window.",'shortpixel-image-optimiser');?>
411
+ </div>
412
  <div class="bulk-notice-msg bulk-error" id="bulk-error-template">
413
  <div style="float: right; margin-top: -4px; margin-right: -8px;">
414
  <a href="javascript:void(0);" onclick="ShortPixel.removeBulkMsg(this)" style='color: #c32525;'>&#10006;</a>
782
  $createWebp = ($settings->createWebp ? 'checked' : '');
783
  $autoMediaLibrary = ($settings->autoMediaLibrary ? 'checked' : '');
784
  $optimizeRetina = ($settings->optimizeRetina ? 'checked' : '');
785
+ $optimizePdfs = ($settings->optimizePdfs ? 'checked' : '');
786
  ?>
787
  <div class="wp-shortpixel-options">
788
  <?php if(!$this->ctrl->getVerifiedKey()) { ?>
904
  </p>
905
  </td>
906
  </tr>
907
+ <tr>
908
+ <th scope="row"><label for="optimizePdfs"><?php _e('Optimize PDFs','shortpixel-image-optimiser');?></label></th>
909
+ <td>
910
+ <input name="optimizePdfs" type="checkbox" id="optimizePdfs" <?php echo( $optimizePdfs );?>> <?php _e('Optimize PDF documents.','shortpixel-image-optimiser');?>
911
+ </td>
912
+ </tr>
913
  <tr>
914
  <th scope="row"><label for="authentication"><?php _e('HTTP AUTH credentials','shortpixel-image-optimiser');?></label></th>
915
  <td>
980
  <tr>
981
  <th scope="row" bgcolor="#ffffff"><label for="apiQuota"><?php _e('Your monthly plan','shortpixel-image-optimiser');?>:</label></th>
982
  <td bgcolor="#ffffff">
983
+ <?php
984
+ $DateNow = time();
985
+ $DateSubscription = strtotime($quotaData['APILastRenewalDate']);
986
+ $DaysToReset = 30 - ((($DateNow - $DateSubscription) / 84600) % 30);
987
+ printf(__('%s/month, renews in %s days, on %s ( <a href="https://shortpixel.com/login/%s" target="_blank">Need More? See the options available</a> )','shortpixel-image-optimiser'),
988
+ $quotaData['APICallsQuota'], $DaysToReset,
989
+ date('M d, Y', strtotime(date('M d, Y') . ' + ' . $DaysToReset . ' days')), $this->ctrl->getApiKey());?><br/>
990
  <?php printf(__('<a href="https://shortpixel.com/login/%s/tell-a-friend" target="_blank">Join our friend referral system</a> to win more credits. For each user that joins, you receive +100 images credits/month.','shortpixel-image-optimiser'),
991
  $this->ctrl->getApiKey());?>
992
  </td>
1025
  <th scope="row"><label for="sizeBackup"><?php _e('Original images are stored in a backup folder. Your backup folder size is now:','shortpixel-image-optimiser');?></label></th>
1026
  <td>
1027
  <form action="" method="POST">
1028
+ <?php if ($backupFolderSize === null) { ?>
1029
+ <span id='backup-folder-size'>Calculating...</span>
1030
+ <?php } else { echo($backupFolderSize); }?>
1031
  <input type="submit" style="margin-left: 15px; vertical-align: middle;" class="button button-secondary" name="emptyBackup" value="<?php _e('Empty backups','shortpixel-image-optimiser');?>"/>
1032
  </form>
1033
  </td>
1088
  case 'imgOptimized':
1089
  $successText = $this->getSuccessText($data['percent'],$data['bonus'],$data['type'],$data['thumbsOpt'],$data['thumbsTotal'], $data['retinasOpt']);
1090
  if($extended) {
1091
+ $missingThumbs = '';
1092
+ if(count($data['thumbsMissing'])) {
1093
+ $missingThumbs .= "<br><span style='font-weight: bold;'>" . __("Missing thumbs:", 'shortpixel-image-optimiser');
1094
+ foreach($data['thumbsMissing'] as $miss) {
1095
+ $missingThumbs .= "<br> &#8226; " . $miss;
1096
+ }
1097
+ $missingThumbs .= '</span>';
1098
+ }
1099
  $successText .= ($data['webpCount'] ? "<br>+" . $data['webpCount'] . __(" WebP images", 'shortpixel-image-optimiser') : "")
1100
  . "<br>EXIF: " . ($data['exifKept'] ? __('kept','shortpixel-image-optimiser') : __('removed','shortpixel-image-optimiser'))
1101
+ . "<br>" . __("Optimized on", 'shortpixel-image-optimiser') . ": " . $data['date']
1102
+ . $missingThumbs;
1103
  }
1104
  $this->renderListCell($id, $data['showActions'],
1105
  !$data['thumbsOpt'] && $data['thumbsTotal'], $data['thumbsTotal'], $data['backup'], $data['type'], $successText);
class/wp-shortpixel-settings.php CHANGED
@@ -35,6 +35,7 @@ class WPShortPixelSettings {
35
  'siteAuthUser' => 'wp-short-pixel-site-auth-user',
36
  'siteAuthPass' => 'wp-short-pixel-site-auth-pass',
37
  'autoMediaLibrary' => 'wp-short-pixel-auto-media-library',
 
38
 
39
  //optimize other images than the ones in Media Library
40
  'includeNextGen' => 'wp-short-pixel-include-next-gen',
@@ -100,6 +101,7 @@ class WPShortPixelSettings {
100
  // the following lines practically set defaults for options if they're not set
101
  self::getOpt('wp-short-pixel-auto-media-library', 1);
102
  self::getOpt('wp-short-pixel-optimize-retina', 1);
 
103
  self::getOpt( 'wp-short-pixel-fileCount', 0);
104
  self::getOpt( 'wp-short-pixel-thumbnail-count', 0);//amount of optimized thumbnails
105
  self::getOpt( 'wp-short-pixel-files-under-5-percent', 0);//amount of optimized thumbnails
@@ -131,6 +133,12 @@ class WPShortPixelSettings {
131
  }
132
  update_option( 'wp-short-pixel-activation-date', time());
133
  delete_option( 'wp-short-pixel-bulk-last-status');
 
 
 
 
 
 
134
  }
135
 
136
  public static function onDeactivate() {
35
  'siteAuthUser' => 'wp-short-pixel-site-auth-user',
36
  'siteAuthPass' => 'wp-short-pixel-site-auth-pass',
37
  'autoMediaLibrary' => 'wp-short-pixel-auto-media-library',
38
+ 'optimizePdfs' => 'wp-short-pixel-optimize-pdfs',
39
 
40
  //optimize other images than the ones in Media Library
41
  'includeNextGen' => 'wp-short-pixel-include-next-gen',
101
  // the following lines practically set defaults for options if they're not set
102
  self::getOpt('wp-short-pixel-auto-media-library', 1);
103
  self::getOpt('wp-short-pixel-optimize-retina', 1);
104
+ self::getOpt('wp-short-pixel-optimize-pdfs', 1);
105
  self::getOpt( 'wp-short-pixel-fileCount', 0);
106
  self::getOpt( 'wp-short-pixel-thumbnail-count', 0);//amount of optimized thumbnails
107
  self::getOpt( 'wp-short-pixel-files-under-5-percent', 0);//amount of optimized thumbnails
133
  }
134
  update_option( 'wp-short-pixel-activation-date', time());
135
  delete_option( 'wp-short-pixel-bulk-last-status');
136
+ $dismissed = get_option('wp-short-pixel-dismissed-notices', array());
137
+ if(isset($dismissed['compat'])) {
138
+ unset($dismissed['compat']);
139
+ update_option('wp-short-pixel-dismissed-notices', $dismissed);
140
+ }
141
+
142
  }
143
 
144
  public static function onDeactivate() {
readme.txt CHANGED
@@ -1,11 +1,11 @@
1
  === ShortPixel Image Optimizer ===
2
 
3
- Contributors: AlexSP
4
  Tags: image optimizer, image optimization, compress pdf, compress jpeg, compress png, image compression, wp smush, compress images, optimize images, shrink jpeg, optimize photos, tinypng
5
 
6
  Requires at least: 3.2.0
7
  Tested up to: 4.7
8
- Stable tag: 4.2.5
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -18,6 +18,8 @@ Speed up your website and boost your SEO by compressing old & new images and PDF
18
  Increase your website's SEO ranking, number of visitors and ultimately your sales by optimizing any image or PDF document on your website.
19
  ShortPixel is an easy to use, lightweight, install-and-forget-about-it <a rel="friend" href="https://shortpixel.com" target="_blank">image optimization</a> plugin that can compress all your past images and PDF documents with a single click. New images are automatically resized/rescaled and optimized on the fly, in the background.
20
 
 
 
21
  This plugin uses minimal resources and works well with any shared, cloud, VPS or dedicated web hosting. It can optimize any image you have on your website even the images that aren't listed in Media Library like those in galleries like NextGEN or added directly via FTP!
22
 
23
  Both lossy and lossless image compression is available for the most common image types (JPG, PNG, GIF and WebP) plus PDF files. Optimized images mean better user experience, better PageSpeed Insights or GTmetrix results, better Google PageRank and more visitors.
@@ -53,10 +55,6 @@ Make an instant <a rel="friend" href="http://shortpixel.com/image-compression-te
53
  * 40 days optimization report with all image details and overall statistics
54
  * **free optimization credits for non-profits**, <a href="https://shortpixel.com/contact" target="_blank">contact us</a> for details
55
 
56
- **Eager to test how it works?**
57
- Give it a spin on a <a href="https://addendio.com/try-plugin/?slug=shortpixel-image-optimiser
58
- " target="_blank">test environment</a> (thanks addendio!)
59
-
60
  **How much it costs?**
61
  ShortPixel comes with 100 free credits/month and additional credits can be bought with as little as $4.99 for 5,000 image credits.
62
  Check out <a rel="friend" href="https://shortpixel.com/pricing" target="_blank">our prices</a>
@@ -98,73 +96,100 @@ Let's get ShortPixel plugin running on your WordPress website:
98
 
99
  == Frequently Asked Questions ==
100
 
101
- = What happens to the existing images, when installing the ShortPixel plugin? =
102
-
103
- Just installing the plugin won’t start the optimization process on existing images. To begin optimizing the images previously loaded on your website, you should:
104
-
105
- * Go to **Media Library**, and select which of the existing images you want to optimize.
106
 
107
- OR
 
 
108
 
109
- * Use the **Bulk ShortPixel** option, to automatically optimize all your previous library.
 
 
110
 
111
- = Should I pick lossy or lossless optimization? =
 
 
112
 
113
- This depends on your compression needs. **Lossy** has a better compression rate than lossless compression. The resulting image is not 100% identical with the original. Works well for photos taken with your camera.
 
 
114
 
115
- With **lossless** compression, the shrunk image will be indistinguishable from the original, and smaller in size. Use this when you do not want to loose any of the original image's details. Works best for technical drawings, clip art and comics.
 
 
116
 
117
- For more information about the difference read the <a href="http://en.wikipedia.org/wiki/Lossy_compression#Lossy_and_lossless_compression" target="_blank">Wiki article</a> on the lossy/lossless difference.
118
-
119
- = Why do I need an API key? =
120
 
121
- ShortPixel Image Optimizer uses automated processes to crunch images. The ShortPixel API integrates in the dashboard of your WordPress website and processes both old and new images automatically. You can also use the same API, multiple times, in your own applications, the <a href="https://shortpixel.com/api-docs">Documentation API</a> shows you how.
 
 
122
 
123
- = Where do I get my API key? =
 
 
124
 
125
- To get your API key, you must <a href="https://shortpixel.com/wp-apikey">Sign up to ShortPixel</a>. You will receive your personal API key in a confirmation email to the address you provided. Use your API key to activate ShortPixel plugin in the 'Plugins' menu in WordPress.
 
126
 
127
- = Where do I use my API key? =
 
128
 
129
- You use the API key in the ShortPixel plugin Settings (don’t forget to click Save Settings). The same API key can be used on multiple websites/blogs.
 
130
 
131
  = How do I activate the API key on a multisite? =
 
 
 
 
 
132
 
133
- You have to activate the plugin in the network admin and then activate it manually on each individual site in the multisite. Once you have done that, the Settings menu appears and you can add the API key for each individual site.
134
-
135
- As an alternative, you can edit wp-config.php and add this line
136
- define('SHORTPIXEL_API_KEY', 'APIKEY')
137
- where 'APIKEY' is the API Key received upon sign up.
138
-
139
- If configured that way, the API key will be used for all the sites of the multisite but will only be visible on the main site's Settings page, being hidden for the rest of the sites.
140
 
141
- = How does Bulk Optimization work? =
 
 
142
 
143
- The Bulk option makes ShortPixel optimize all your images at once (not one by one). You can do this in the Media > Bulk ShortPixel section by clicking on the **Compress all your images** button.
 
144
 
145
- The batch optimization may work slower, depending on your existing image gallery. Please be patient and do not close the Wordpress admin while you are rolling the Bulk Processing on your media gallery.
 
 
146
 
147
- = Are my images safe? =
 
 
148
 
149
- Yes, privacy is guaranteed. The ShortPixel encryption process doesn't allow anyone to view your photos.
 
 
150
 
151
- = What happens with my original images after they have been processed with ShortPixel? =
 
152
 
153
- If you didn't make any changes in the plugin Settings and you left the 'Image backup' option checked, the originals will be located in a backup folder at:
 
 
 
154
 
155
- /wp-content/uploads/ShortpixelBackups
 
 
 
 
156
 
157
- After optimization, if you want to switch back to a certain original image, hit Restore backup in the Media Library. If you are happy with the optimized images, you can deactivate saving the backups in the plugin Settings.
 
 
158
 
159
-
160
-
161
- = What types of formats can be optimized? =
162
-
163
- For now, ShortPixel supports JPEG, PNG, PDF and GIF formats. Animated GIFs and thumbnails are also optimized. Additional formats are scheduled for optimization in the future.
164
-
165
- = Will ShortPixel work if my website is using CloudFare? =
166
-
167
- Yes, the image processing happens without interfering with the CloudFare protection. The ShortPixel and CloudFare plugins are also compatible.
168
 
169
  = I’m stuck. What do I do? =
170
 
@@ -172,15 +197,15 @@ The ShortPixel team is here to help. <a href="https://shortpixel.com/contact">Co
172
 
173
  == Screenshots ==
174
 
175
- 1. Activate your API key in the plugin Settings. (Settings>ShortPixel)
176
 
177
- 2. Check out the main settings after API key activated. (Settings>ShortPixel)
178
 
179
- 3. Tweak it using Advanced settings. (Settings>ShortPixel)
180
 
181
- 4. Compress all your past images with one click. (Media>Bulk ShortPixel)
182
 
183
- 5. Check the progress of your bulk optimization process. (Media>Bulk ShortPixel)
184
 
185
  6. Check your stats: number of processed files, saved space, average compression, saved bandwidth, remaining images. (Settings>ShortPixel)
186
 
@@ -192,6 +217,20 @@ The ShortPixel team is here to help. <a href="https://shortpixel.com/contact">Co
192
 
193
  == Changelog ==
194
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  = 4.2.5 =
196
 
197
  * fix for PHP7.1 fatal error when encountering corrupt metadata (wp_get_attachment_metadata returning empty string)
1
  === ShortPixel Image Optimizer ===
2
 
3
+ Contributors: ShortPixel
4
  Tags: image optimizer, image optimization, compress pdf, compress jpeg, compress png, image compression, wp smush, compress images, optimize images, shrink jpeg, optimize photos, tinypng
5
 
6
  Requires at least: 3.2.0
7
  Tested up to: 4.7
8
+ Stable tag: 4.2.6
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
18
  Increase your website's SEO ranking, number of visitors and ultimately your sales by optimizing any image or PDF document on your website.
19
  ShortPixel is an easy to use, lightweight, install-and-forget-about-it <a rel="friend" href="https://shortpixel.com" target="_blank">image optimization</a> plugin that can compress all your past images and PDF documents with a single click. New images are automatically resized/rescaled and optimized on the fly, in the background.
20
 
21
+ **Ready for a quick DEMO? <a href="https://addendio.com/try-plugin/?slug=shortpixel-image-optimiser" target="_blank">Test here.</a>**
22
+
23
  This plugin uses minimal resources and works well with any shared, cloud, VPS or dedicated web hosting. It can optimize any image you have on your website even the images that aren't listed in Media Library like those in galleries like NextGEN or added directly via FTP!
24
 
25
  Both lossy and lossless image compression is available for the most common image types (JPG, PNG, GIF and WebP) plus PDF files. Optimized images mean better user experience, better PageSpeed Insights or GTmetrix results, better Google PageRank and more visitors.
55
  * 40 days optimization report with all image details and overall statistics
56
  * **free optimization credits for non-profits**, <a href="https://shortpixel.com/contact" target="_blank">contact us</a> for details
57
 
 
 
 
 
58
  **How much it costs?**
59
  ShortPixel comes with 100 free credits/month and additional credits can be bought with as little as $4.99 for 5,000 image credits.
60
  Check out <a rel="friend" href="https://shortpixel.com/pricing" target="_blank">our prices</a>
96
 
97
  == Frequently Asked Questions ==
98
 
99
+ = Can I use the same API Key on multiple web sites? =
100
+ Yes, you can.
101
+ As long as you have available credits you can use a single API Key on as many websites as you wish!
 
 
102
 
103
+ = Can I test/use the plugin for free? =
104
+ Yes you can.
105
+ We offer 100 free image optimization credits each month. Exceeding the monthly free quota will pause the optimization process till the quota is reset or extended by buying one of our plans.
106
 
107
+ = Can I optimize images that aren't in Media Library? =
108
+ Absolutely.
109
+ You can actually optimize any image you have on your site regardless of its place. You just need to add - in the Advanced section of the ShortPixel Settings - the folders where the images you want to optimize are located and ShortPixel will work its magic and do the rest.
110
 
111
+ = Can I optimize both past and new images? =
112
+ Sure!
113
+ You can optimize all your past/current images using our "Bulk ShortPixel" page in your Media with a single click.
114
 
115
+ = A credit = an optimized image? =
116
+ Yes, that is correct.
117
+ But please note that usually an image in Media Library has 3, 5 or more associated thumbs. Each optimized thumb requires a credit. In the rare cases when ShortPixel does not optimize the image (lossy) with at least 5%, the credit will not be consumed, though.
118
 
119
+ = Can I restore my images? What happens with the originals? =
120
+ If you choose the "Image backup" option in Settings/ShortPixel then the original version of any optimized image or PDF will be saved in the backup folder.
121
+ The original image is necessary if you want to restore an image or if you want to convert an image from lossy to lossless or viceversa.
122
 
123
+ = What types of formats can be optimized? =
124
+ ShortPixel optimizes JPEG, PNG, GIF and PDF type of files.
 
125
 
126
+ = Do you have one-time plans? =
127
+ Yes we do.
128
+ The credits that come with our <a href="https://shortpixel.com/plans" rel="friend">one-time plans</a> never expire. Yummy! :-)
129
 
130
+ = What happens to my existing images? =
131
+ Your existing images are replaced with the optimized ones.
132
+ If you choose the backup option then the originals will be saved in a separate folder so you can restore them should you ever need/want to do that.
133
 
134
+ = How does the plugin work? =
135
+ Our light-weight plugin sends the original images to our Image Optimization Cloud where they are compressed. ShortPixel then downloads the optimized images and the unoptimized originals are replaced with the optimized versions.
136
 
137
+ = Do you optimize the images in cloud? =
138
+ Yes, all the images processsed by ShortPixel are optimized in the Cloud. This takes the load off of your server and allows us to produce the best results.
139
 
140
+ = What payment methods are accepted? =
141
+ We accept payments via PayPal and card.
142
 
143
  = How do I activate the API key on a multisite? =
144
+ You have to activate the plugin in the network admin and then activate it manually on each individual site in the multisite. Once you have done that, the Settings menu appears and you can add the API key for each individual site.
145
+ As an alternative, you can edit wp-config.php and add this line
146
+ define(‘SHORTPIXEL_API_KEY’, ‘APIKEY’)
147
+ where ‘APIKEY’ is the API Key received upon sign up.
148
+ If configured that way, the API key will be used for all the sites of the multisite but will only be visible on the main site’s Settings page, being hidden for the rest of the sites.
149
 
150
+ = How much is a credit? =
151
+ A credit is used each time ShortPixel optimizes an image or thumbnail by at least 5%. If we're not able to optimize an image or thumbnail by at least 5% then no credit will be used :-)
152
+ Please also note that usually images in your Media Library have 3-5 thumbs associated and a credit will be used for each featured image or associated thumbnail that is optimized.
 
 
 
 
153
 
154
+ = Why shall I use a wordpress plugin and not an offline tool? =
155
+ Because ShortPixel algorithms were perfected while optimizing over a hundred million real-life images.
156
+ ShortPixel not only offers the best compression for JPEG, PNG, GIF and PDF files but it also saves you a lot of time. You just install it on your site and then ShortPixel will take care that all the images on your site are immediately optimized after upload.
157
 
158
+ = Does optimizing images affect my ALT tags? =
159
+ No, ShortPixel only optimizes images, it won't touch anything else like your HTML/CSS.
160
 
161
+ = If I stop using ShortPixel will my images remain optimized? =
162
+ Absolutely!
163
+ Once optimized the images will remain optimized unless you explicitly choose to restore them. But why would you do that? :-)
164
 
165
+ = Do I have to pay monthly or one time? =
166
+ We have both options available.
167
+ One-time credits never expire are a bit more expensive. Check out our prices <a href="https://shortpixel.com/pricing" rel="friend">here</a>
168
 
169
+ = When can I cancel a monthly plan? =
170
+ Whenever you want.
171
+ The credits you still have available for the current billing period will still be available until the end of the billing period. At the end of it, you won't be billed again and the plan will be reset to the free plan.
172
 
173
+ = When credits expire? =
174
+ Monthly credits expire after 30 days while one-time credits never expire.
175
 
176
+ = Do you have an API? =
177
+ Yes, we have several APIs and tools.
178
+ You can learn more about it here:
179
+ <a href="https://shortpixel.com/api-tools">https://shortpixel.com/api-tools</a>
180
 
181
+ = Can I use ShortPixel WP plugin on a localhost installation? =
182
+ Unfortunately not :-(
183
+ But you can use either our command line tool or our web tool
184
+ <a href="https://shortpixel.com/web-tool-docs">https://shortpixel.com/web-tool-docs</a>
185
+ <a href="https://shortpixel.com/cli-docs">https://shortpixel.com/cli-docs</a>
186
 
187
+ = How does resizing work? =
188
+ If you choose the option to resize your images then the featured image can be resized to a predefined size while keeping its aspect and proportions intact. The associated thumbs won't be resized
189
+ Using this option you can safely upload original images safely without needing to apply any pre-processing to make them smaller.
190
 
191
+ = Will ShortPixel work if my website is using CloudFare? =
192
+ Absolutely! Sometimes you'll need to make sure you whitelist some IPs, just <a href="http://shortpixel.com/contact">contact us</a> and we'll assist you with that.
 
 
 
 
 
 
 
193
 
194
  = I’m stuck. What do I do? =
195
 
197
 
198
  == Screenshots ==
199
 
200
+ 1. Bulk optimization running. (Media>Bulk ShortPixel)
201
 
202
+ 2. Activate your API key in the plugin Settings. (Settings>ShortPixel)
203
 
204
+ 3. Check out the main settings after API key activated. (Settings>ShortPixel)
205
 
206
+ 4. Tweak it using Advanced settings. (Settings>ShortPixel)
207
 
208
+ 5. Compress all your past images with one click. (Media>Bulk ShortPixel)
209
 
210
  6. Check your stats: number of processed files, saved space, average compression, saved bandwidth, remaining images. (Settings>ShortPixel)
211
 
217
 
218
  == Changelog ==
219
 
220
+ = 4.2.6 =
221
+
222
+ * add the webp files as thumbs to the sizes array in metadata so they are automatically used by themes that use srcset.
223
+ * add option to optimize PDFs or not.
224
+ * seamless integration with WP/LR Sync plugin.
225
+ * gracefully ignore missing thumbs on disk when doing the optimization - just mark them as missing in the metadata.
226
+ * gracefully add Media Library files that are present on disk but not present in the image metadata (sizes array).
227
+ * option to dismiss the top toolbar ShortPixel alert when quota expired.
228
+ * compute the backup folder size asynchronously in order to speed up the settings page.
229
+ * editors/authors now are able to optimize/restore images from the Media Library list.
230
+ * handle internationalized domain names (punycode encoded).
231
+ * reset failed images from Custom Media when user launches a reprocessing of the images from Bulk.
232
+ * bugfixes
233
+
234
  = 4.2.5 =
235
 
236
  * fix for PHP7.1 fatal error when encountering corrupt metadata (wp_get_attachment_metadata returning empty string)
res/css/short-pixel.css CHANGED
@@ -34,6 +34,14 @@ div.fb-like {
34
  .sp-notice-success {
35
  border-left-color: #46b450;
36
  }
 
 
 
 
 
 
 
 
37
  div.short-pixel-bulk-page input.dial {
38
  font-size: 16px !important;
39
  }
@@ -72,6 +80,7 @@ div.sp-quota-exceeded-alert .bulk-progress-indicator {
72
  div.wrap.short-pixel-bulk-page .bulk-notice-container {
73
  margin-top: 15px;
74
  position: absolute;
 
75
  }
76
  div.wrap.short-pixel-bulk-page .bulk-notice-container .bulk-notice-msg {
77
  text-align: center;
@@ -83,8 +92,14 @@ div.wrap.short-pixel-bulk-page .bulk-notice-container .bulk-notice-msg {
83
  padding: 7px 10px 10px;
84
  display: none;
85
  max-width: 600px;
 
86
  }
87
  div.wrap.short-pixel-bulk-page .bulk-notice-container .bulk-notice-msg.bulk-error {
 
 
 
 
 
88
  border: 1px solid #c32525;
89
  background-color: #ff969d;
90
  }
@@ -379,9 +394,11 @@ li.shortpixel-hide {
379
  .sp-floating-block.bulk-slider-container {
380
  display: none;
381
  }
382
- .sp-floating-block.notice.bulk-notices-parent {
383
  padding: 0;
384
  margin: 0;
 
 
385
  }
386
  .bulk-slider-container {
387
  margin-top: 20px;
@@ -580,3 +597,8 @@ section#tab-resources p {
580
  width: 97%;
581
  }
582
  }
 
 
 
 
 
34
  .sp-notice-success {
35
  border-left-color: #46b450;
36
  }
37
+ li.sp-conflict-plugins-list {
38
+ line-height: 28px;
39
+ list-style: disc;
40
+ margin-left: 20px;
41
+ }
42
+ li.sp-conflict-plugins-list a.button {
43
+ margin-left: 10px;
44
+ }
45
  div.short-pixel-bulk-page input.dial {
46
  font-size: 16px !important;
47
  }
80
  div.wrap.short-pixel-bulk-page .bulk-notice-container {
81
  margin-top: 15px;
82
  position: absolute;
83
+ width: 500px;
84
  }
85
  div.wrap.short-pixel-bulk-page .bulk-notice-container .bulk-notice-msg {
86
  text-align: center;
92
  padding: 7px 10px 10px;
93
  display: none;
94
  max-width: 600px;
95
+ margin-right:20px;
96
  }
97
  div.wrap.short-pixel-bulk-page .bulk-notice-container .bulk-notice-msg.bulk-error {
98
+ border: 1px solid #b5914d;
99
+ background-color: #ffe996;
100
+ margin-right:20px;
101
+ }
102
+ div.wrap.short-pixel-bulk-page .bulk-notice-container .bulk-notice-msg.bulk-error.bulk-error-fatal {
103
  border: 1px solid #c32525;
104
  background-color: #ff969d;
105
  }
394
  .sp-floating-block.bulk-slider-container {
395
  display: none;
396
  }
397
+ .sp-floating-block.sp-notice.bulk-notices-parent {
398
  padding: 0;
399
  margin: 0;
400
+ float: right;
401
+ margin-right: 500px !important;
402
  }
403
  .bulk-slider-container {
404
  margin-top: 20px;
597
  width: 97%;
598
  }
599
  }
600
+
601
+ /*workaround for bad style inline css in the plugin <<WooCommerce Remove /product & /product-category>> that's causing conflict*/
602
+ .sp-tabs h2:before{
603
+ content:none;
604
+ }
res/js/punycode.js ADDED
@@ -0,0 +1,448 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ /** ShortPixel: Changed punycode to sp_punycode in order to prevent possible conflicts with other WP plugins that use punycode
4
+ * Also moved all the functions inside the sp_punycode const for the same reason and changed const's to let's */
5
+
6
+ var sp_punycode = function(){
7
+
8
+ /** Highest positive signed 32-bit float value */
9
+ var maxInt = 2147483647; // aka. 0x7FFFFFFF or 2^31-1
10
+
11
+ /** Bootstring parameters */
12
+ var base = 36;
13
+ var tMin = 1;
14
+ var tMax = 26;
15
+ var skew = 38;
16
+ var damp = 700;
17
+ var initialBias = 72;
18
+ var initialN = 128; // 0x80
19
+ var delimiter = '-'; // '\x2D'
20
+
21
+ /** Regular expressions */
22
+ var regexPunycode = /^xn--/;
23
+ var regexNonASCII = /[^\0-\x7E]/; // non-ASCII chars
24
+ var regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g; // RFC 3490 separators
25
+
26
+ /** Error messages */
27
+ var errors = {
28
+ 'overflow': 'Overflow: input needs wider integers to process',
29
+ 'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
30
+ 'invalid-input': 'Invalid input'
31
+ };
32
+
33
+ /** Convenience shortcuts */
34
+ var baseMinusTMin = base - tMin;
35
+ var floor = Math.floor;
36
+ var stringFromCharCode = String.fromCharCode;
37
+
38
+ /*--------------------------------------------------------------------------*/
39
+
40
+ /**
41
+ * A generic error utility function.
42
+ * @private
43
+ * @param {String} type The error type.
44
+ * @returns {Error} Throws a `RangeError` with the applicable error message.
45
+ */
46
+ function error(type) {
47
+ throw new RangeError(errors[type]);
48
+ }
49
+
50
+ /**
51
+ * A generic `Array#map` utility function.
52
+ * @private
53
+ * @param {Array} array The array to iterate over.
54
+ * @param {Function} callback The function that gets called for every array
55
+ * item.
56
+ * @returns {Array} A new array of values returned by the callback function.
57
+ */
58
+ function map(array, fn) {
59
+ var result = [];
60
+ var length = array.length;
61
+ while (length--) {
62
+ result[length] = fn(array[length]);
63
+ }
64
+ return result;
65
+ }
66
+
67
+ /**
68
+ * A simple `Array#map`-like wrapper to work with domain name strings or email
69
+ * addresses.
70
+ * @private
71
+ * @param {String} domain The domain name or email address.
72
+ * @param {Function} callback The function that gets called for every
73
+ * character.
74
+ * @returns {Array} A new string of characters returned by the callback
75
+ * function.
76
+ */
77
+ function mapDomain(string, fn) {
78
+ var parts = string.split('@');
79
+ var result = '';
80
+ if (parts.length > 1) {
81
+ // In email addresses, only the domain name should be punycoded. Leave
82
+ // the local part (i.e. everything up to `@`) intact.
83
+ result = parts[0] + '@';
84
+ string = parts[1];
85
+ }
86
+ // Avoid `split(regex)` for IE8 compatibility. See #17.
87
+ string = string.replace(regexSeparators, '\x2E');
88
+ var labels = string.split('.');
89
+ var encoded = map(labels, fn).join('.');
90
+ return result + encoded;
91
+ }
92
+
93
+ /**
94
+ * Creates an array containing the numeric code points of each Unicode
95
+ * character in the string. While JavaScript uses UCS-2 internally,
96
+ * this function will convert a pair of surrogate halves (each of which
97
+ * UCS-2 exposes as separate characters) into a single code point,
98
+ * matching UTF-16.
99
+ * @see `punycode.ucs2.encode`
100
+ * @see <https://mathiasbynens.be/notes/javascript-encoding>
101
+ * @memberOf punycode.ucs2
102
+ * @name decode
103
+ * @param {String} string The Unicode input string (UCS-2).
104
+ * @returns {Array} The new array of code points.
105
+ */
106
+ function ucs2decode(string) {
107
+ var output = [];
108
+ var counter = 0;
109
+ var length = string.length;
110
+ while (counter < length) {
111
+ var value = string.charCodeAt(counter++);
112
+ if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
113
+ // It's a high surrogate, and there is a next character.
114
+ var extra = string.charCodeAt(counter++);
115
+ if ((extra & 0xFC00) == 0xDC00) { // Low surrogate.
116
+ output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
117
+ } else {
118
+ // It's an unmatched surrogate; only append this code unit, in case the
119
+ // next code unit is the high surrogate of a surrogate pair.
120
+ output.push(value);
121
+ counter--;
122
+ }
123
+ } else {
124
+ output.push(value);
125
+ }
126
+ }
127
+ return output;
128
+ }
129
+
130
+ /**
131
+ * Creates a string based on an array of numeric code points.
132
+ * @see `punycode.ucs2.decode`
133
+ * @memberOf punycode.ucs2
134
+ * @name encode
135
+ * @param {Array} codePoints The array of numeric code points.
136
+ * @returns {String} The new Unicode string (UCS-2).
137
+ */
138
+ //const ucs2encode = array => String.fromCodePoint(...array);
139
+ var ucs2encode = array => String.fromCodePoint.prototype.apply(null, array);
140
+
141
+ /**
142
+ * Converts a basic code point into a digit/integer.
143
+ * @see `digitToBasic()`
144
+ * @private
145
+ * @param {Number} codePoint The basic numeric code point value.
146
+ * @returns {Number} The numeric value of a basic code point (for use in
147
+ * representing integers) in the range `0` to `base - 1`, or `base` if
148
+ * the code point does not represent a value.
149
+ */
150
+ var basicToDigit = function(codePoint) {
151
+ if (codePoint - 0x30 < 0x0A) {
152
+ return codePoint - 0x16;
153
+ }
154
+ if (codePoint - 0x41 < 0x1A) {
155
+ return codePoint - 0x41;
156
+ }
157
+ if (codePoint - 0x61 < 0x1A) {
158
+ return codePoint - 0x61;
159
+ }
160
+ return base;
161
+ };
162
+
163
+ /**
164
+ * Converts a digit/integer into a basic code point.
165
+ * @see `basicToDigit()`
166
+ * @private
167
+ * @param {Number} digit The numeric value of a basic code point.
168
+ * @returns {Number} The basic code point whose value (when used for
169
+ * representing integers) is `digit`, which needs to be in the range
170
+ * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
171
+ * used; else, the lowercase form is used. The behavior is undefined
172
+ * if `flag` is non-zero and `digit` has no uppercase form.
173
+ */
174
+ var digitToBasic = function(digit, flag) {
175
+ // 0..25 map to ASCII a..z or A..Z
176
+ // 26..35 map to ASCII 0..9
177
+ return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
178
+ };
179
+
180
+ /**
181
+ * Bias adaptation function as per section 3.4 of RFC 3492.
182
+ * https://tools.ietf.org/html/rfc3492#section-3.4
183
+ * @private
184
+ */
185
+ var adapt = function(delta, numPoints, firstTime) {
186
+ var k = 0;
187
+ delta = firstTime ? floor(delta / damp) : delta >> 1;
188
+ delta += floor(delta / numPoints);
189
+ for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
190
+ delta = floor(delta / baseMinusTMin);
191
+ }
192
+ return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
193
+ };
194
+
195
+ /**
196
+ * Converts a Punycode string of ASCII-only symbols to a string of Unicode
197
+ * symbols.
198
+ * @memberOf punycode
199
+ * @param {String} input The Punycode string of ASCII-only symbols.
200
+ * @returns {String} The resulting string of Unicode symbols.
201
+ */
202
+ var decode = function(input) {
203
+ // Don't use UCS-2.
204
+ var output = [];
205
+ var inputLength = input.length;
206
+ var i = 0;
207
+ var n = initialN;
208
+ var bias = initialBias;
209
+
210
+ // Handle the basic code points: let `basic` be the number of input code
211
+ // points before the last delimiter, or `0` if there is none, then copy
212
+ // the first basic code points to the output.
213
+
214
+ var basic = input.lastIndexOf(delimiter);
215
+ if (basic < 0) {
216
+ basic = 0;
217
+ }
218
+
219
+ for (var j = 0; j < basic; ++j) {
220
+ // if it's not a basic code point
221
+ if (input.charCodeAt(j) >= 0x80) {
222
+ error('not-basic');
223
+ }
224
+ output.push(input.charCodeAt(j));
225
+ }
226
+
227
+ // Main decoding loop: start just after the last delimiter if any basic code
228
+ // points were copied; start at the beginning otherwise.
229
+
230
+ for (var index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
231
+
232
+ // `index` is the index of the next character to be consumed.
233
+ // Decode a generalized variable-length integer into `delta`,
234
+ // which gets added to `i`. The overflow checking is easier
235
+ // if we increase `i` as we go, then subtract off its starting
236
+ // value at the end to obtain `delta`.
237
+ var oldi = i;
238
+ for (var w = 1, k = base; /* no condition */; k += base) {
239
+
240
+ if (index >= inputLength) {
241
+ error('invalid-input');
242
+ }
243
+
244
+ var digit = basicToDigit(input.charCodeAt(index++));
245
+
246
+ if (digit >= base || digit > floor((maxInt - i) / w)) {
247
+ error('overflow');
248
+ }
249
+
250
+ i += digit * w;
251
+ var t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
252
+
253
+ if (digit < t) {
254
+ break;
255
+ }
256
+
257
+ var baseMinusT = base - t;
258
+ if (w > floor(maxInt / baseMinusT)) {
259
+ error('overflow');
260
+ }
261
+
262
+ w *= baseMinusT;
263
+
264
+ }
265
+
266
+ var out = output.length + 1;
267
+ bias = adapt(i - oldi, out, oldi == 0);
268
+
269
+ // `i` was supposed to wrap around from `out` to `0`,
270
+ // incrementing `n` each time, so we'll fix that now:
271
+ if (floor(i / out) > maxInt - n) {
272
+ error('overflow');
273
+ }
274
+
275
+ n += floor(i / out);
276
+ i %= out;
277
+
278
+ // Insert `n` at position `i` of the output.
279
+ output.splice(i++, 0, n);
280
+
281
+ }
282
+
283
+ //return String.fromCodePoint(...output);
284
+ return String.fromCodePoint.prototype.apply(null, output);
285
+ };
286
+
287
+ /**
288
+ * Converts a string of Unicode symbols (e.g. a domain name label) to a
289
+ * Punycode string of ASCII-only symbols.
290
+ * @memberOf punycode
291
+ * @param {String} input The string of Unicode symbols.
292
+ * @returns {String} The resulting Punycode string of ASCII-only symbols.
293
+ */
294
+ const encode = function(input) {
295
+ var output = [];
296
+
297
+ // Convert the input in UCS-2 to an array of Unicode code points.
298
+ input = ucs2decode(input);
299
+
300
+ // Cache the length.
301
+ var inputLength = input.length;
302
+
303
+ // Initialize the state.
304
+ var n = initialN;
305
+ var delta = 0;
306
+ var bias = initialBias;
307
+
308
+ // Handle the basic code points.
309
+ for (var currentValue of input) {
310
+ if (currentValue < 0x80) {
311
+ output.push(stringFromCharCode(currentValue));
312
+ }
313
+ }
314
+
315
+ var basicLength = output.length;
316
+ var handledCPCount = basicLength;
317
+
318
+ // `handledCPCount` is the number of code points that have been handled;
319
+ // `basicLength` is the number of basic code points.
320
+
321
+ // Finish the basic string with a delimiter unless it's empty.
322
+ if (basicLength) {
323
+ output.push(delimiter);
324
+ }
325
+
326
+ // Main encoding loop:
327
+ while (handledCPCount < inputLength) {
328
+
329
+ // All non-basic code points < n have been handled already. Find the next
330
+ // larger one:
331
+ var m = maxInt;
332
+ for (var currentValue of input) {
333
+ if (currentValue >= n && currentValue < m) {
334
+ m = currentValue;
335
+ }
336
+ }
337
+
338
+ // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
339
+ // but guard against overflow.
340
+ var handledCPCountPlusOne = handledCPCount + 1;
341
+ if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
342
+ error('overflow');
343
+ }
344
+
345
+ delta += (m - n) * handledCPCountPlusOne;
346
+ n = m;
347
+
348
+ for (var currentValue of input) {
349
+ if (currentValue < n && ++delta > maxInt) {
350
+ error('overflow');
351
+ }
352
+ if (currentValue == n) {
353
+ // Represent delta as a generalized variable-length integer.
354
+ var q = delta;
355
+ for (var k = base; /* no condition */; k += base) {
356
+ var t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
357
+ if (q < t) {
358
+ break;
359
+ }
360
+ var qMinusT = q - t;
361
+ var baseMinusT = base - t;
362
+ output.push(
363
+ stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
364
+ );
365
+ q = floor(qMinusT / baseMinusT);
366
+ }
367
+
368
+ output.push(stringFromCharCode(digitToBasic(q, 0)));
369
+ bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
370
+ delta = 0;
371
+ ++handledCPCount;
372
+ }
373
+ }
374
+
375
+ ++delta;
376
+ ++n;
377
+
378
+ }
379
+ return output.join('');
380
+ };
381
+
382
+ /**
383
+ * Converts a Punycode string representing a domain name or an email address
384
+ * to Unicode. Only the Punycoded parts of the input will be converted, i.e.
385
+ * it doesn't matter if you call it on a string that has already been
386
+ * converted to Unicode.
387
+ * @memberOf punycode
388
+ * @param {String} input The Punycoded domain name or email address to
389
+ * convert to Unicode.
390
+ * @returns {String} The Unicode representation of the given Punycode
391
+ * string.
392
+ */
393
+ var toUnicode = function(input) {
394
+ return mapDomain(input, function(string) {
395
+ return regexPunycode.test(string)
396
+ ? decode(string.slice(4).toLowerCase())
397
+ : string;
398
+ });
399
+ };
400
+
401
+ /**
402
+ * Converts a Unicode string representing a domain name or an email address to
403
+ * Punycode. Only the non-ASCII parts of the domain name will be converted,
404
+ * i.e. it doesn't matter if you call it with a domain that's already in
405
+ * ASCII.
406
+ * @memberOf punycode
407
+ * @param {String} input The domain name or email address to convert, as a
408
+ * Unicode string.
409
+ * @returns {String} The Punycode representation of the given domain name or
410
+ * email address.
411
+ */
412
+ var toASCII = function(input) {
413
+ return mapDomain(input, function(string) {
414
+ return regexNonASCII.test(string)
415
+ ? 'xn--' + encode(string)
416
+ : string;
417
+ });
418
+ };
419
+
420
+ /*--------------------------------------------------------------------------*/
421
+
422
+ /** Define the public API */
423
+ return {
424
+ /**
425
+ * A string representing the current Punycode.js version number.
426
+ * @memberOf punycode
427
+ * @type String
428
+ */
429
+ version: '2.1.0',
430
+ /**
431
+ * An object of methods to convert from JavaScript's internal character
432
+ * representation (UCS-2) to Unicode code points, and back.
433
+ * @see <https://mathiasbynens.be/notes/javascript-encoding>
434
+ * @memberOf punycode
435
+ * @type Object
436
+ */
437
+ ucs2: {
438
+ 'decode': ucs2decode,
439
+ 'encode': ucs2encode
440
+ },
441
+ decode: decode,
442
+ encode: encode,
443
+ toASCII: toASCII,
444
+ toUnicode: toUnicode
445
+ }
446
+ }();
447
+
448
+ //module.exports = sp_punycode;
res/js/short-pixel.js CHANGED
@@ -11,6 +11,10 @@ jQuery(document).ready(function($){
11
 
12
  ShortPixel.setOptions(ShortPixelConstants);
13
 
 
 
 
 
14
  if( ShortPixel.MEDIA_ALERT == 'todo' && jQuery('div.media-frame.mode-grid').length > 0) {
15
  //the media table is not in the list mode, alert the user
16
  jQuery('div.media-frame.mode-grid').before('<div id="short-pixel-media-alert" class="notice notice-warning"><p>'
@@ -135,7 +139,7 @@ var ShortPixel = function() {
135
  }
136
 
137
  function adjustSettingsTabsHeight(){
138
- var sectionHeight = jQuery('section#tab-settings .wp-shortpixel-options').height() + 60;
139
  sectionHeight = Math.max(sectionHeight, jQuery('section#tab-adv-settings .wp-shortpixel-options').height() + 20);
140
  sectionHeight = Math.max(sectionHeight, jQuery('section#tab-resources .area1').height() + 60);
141
  jQuery('#shortpixel-settings-tabs').css('height', sectionHeight);
@@ -164,7 +168,7 @@ var ShortPixel = function() {
164
  }
165
 
166
  function successMsg(id, percent, type, thumbsCount, retinasCount) {
167
- return (percent > 0 ? "<div class='sp-column-info'>" + _spTr.reducedBy + " <span class='percent'><strong>" + percent + "%</strong></span> " : "")
168
  + (percent > 0 && percent < 5 ? "<br>" : '')
169
  + (percent < 5 ? _spTr.bonusProcessing : '')
170
  + (type.length > 0 ? " ("+type+")" : "")
@@ -218,7 +222,7 @@ var ShortPixel = function() {
218
  console.log("Invalid response from server (Error: " + msg + "). Retrying pass " + (ShortPixel.retries + 1) + "...");
219
  setTimeout(checkBulkProgress, 5000);
220
  } else {
221
- ShortPixel.bulkShowError(-1,"Invalid response from server received 6 times. Please retry later by reloading this page, or contact support. (Error: " + msg + ")", "");
222
  console.log("Invalid response from server 6 times. Giving up.");
223
  }
224
  }
@@ -238,6 +242,21 @@ var ShortPixel = function() {
238
  return browseResponse;
239
  }
240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  function initFolderSelector() {
242
  jQuery(".select-folder-button").click(function(){
243
  jQuery(".sp-folder-picker-shade").css("display", "block");
@@ -277,13 +296,22 @@ var ShortPixel = function() {
277
  }
278
 
279
  notice.css("display", "block");
280
-
281
  }
282
 
283
  function bulkHideLengthyMsg(){
284
  jQuery(".bulk-notice-msg.bulk-lengthy").css("display", "none");
285
  }
286
 
 
 
 
 
 
 
 
 
 
 
287
  function bulkShowError(id, msg, fileName, customLink) {
288
  var noticeTpl = jQuery("#bulk-error-template");
289
  if(noticeTpl.length == 0) return;
@@ -291,8 +319,12 @@ var ShortPixel = function() {
291
  notice.attr("id", "bulk-error-" + id);
292
  if(id == -1) {
293
  jQuery("span.sp-err-title", notice).remove();
 
 
 
 
294
  }
295
- jQuery("span.sp-err-content", notice).text(msg);
296
  var link = jQuery("a.sp-post-link", notice);
297
  if(customLink) {
298
  link.attr("href", customLink);
@@ -335,8 +367,11 @@ var ShortPixel = function() {
335
  retry : retry,
336
  initFolderSelector : initFolderSelector,
337
  browseContent : browseContent,
 
338
  bulkShowLengthyMsg : bulkShowLengthyMsg,
339
  bulkHideLengthyMsg : bulkHideLengthyMsg,
 
 
340
  bulkShowError : bulkShowError,
341
  removeBulkMsg : removeBulkMsg,
342
  isCustomImageId : isCustomImageId,
@@ -407,9 +442,29 @@ function checkQuotaExceededAlert() {
407
  * calls itself until receives an Empty queue message
408
  */
409
  function checkBulkProgress() {
410
- //the replace stands for malformed urls on some sites, like wp-admin//upload.php which are accepted by the browser.
411
- var url = window.location.href.toLowerCase().replace(/\/\//g , "/");
412
- var adminUrl = ShortPixel.WP_ADMIN_URL.toLowerCase().replace(/\/\//g , "/");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
  if( url.search(adminUrl + "upload.php") < 0
414
  && url.search(adminUrl + "edit.php") < 0
415
  && url.search(adminUrl + "edit-tags.php") < 0
@@ -517,6 +572,7 @@ function checkBulkProcessingCallApi(){
517
  case ShortPixel.STATUS_SUCCESS:
518
  if(isBulkPage) {
519
  ShortPixel.bulkHideLengthyMsg();
 
520
  }
521
  var percent = data["PercentImprovement"];
522
 
@@ -572,8 +628,16 @@ function checkBulkProcessingCallApi(){
572
  }
573
  setTimeout(checkBulkProgress, 5000);
574
  break;
 
 
 
 
 
 
 
 
575
  default:
576
- ShortPixel.retry(e.message);
577
  break;
578
  }
579
  }
@@ -657,6 +721,19 @@ function optimizeThumbs(id) {
657
  });
658
  }
659
 
 
 
 
 
 
 
 
 
 
 
 
 
 
660
  function dismissShortPixelNotice(id) {
661
  jQuery("#short-pixel-notice-" + id).hide();
662
  var data = { action : 'shortpixel_dismiss_notice',
11
 
12
  ShortPixel.setOptions(ShortPixelConstants);
13
 
14
+ if(jQuery('#backup-folder-size').length) {
15
+ jQuery('#backup-folder-size').html(ShortPixel.getBackupSize());
16
+ }
17
+
18
  if( ShortPixel.MEDIA_ALERT == 'todo' && jQuery('div.media-frame.mode-grid').length > 0) {
19
  //the media table is not in the list mode, alert the user
20
  jQuery('div.media-frame.mode-grid').before('<div id="short-pixel-media-alert" class="notice notice-warning"><p>'
139
  }
140
 
141
  function adjustSettingsTabsHeight(){
142
+ var sectionHeight = jQuery('section#tab-settings .wp-shortpixel-options').height() + 90;
143
  sectionHeight = Math.max(sectionHeight, jQuery('section#tab-adv-settings .wp-shortpixel-options').height() + 20);
144
  sectionHeight = Math.max(sectionHeight, jQuery('section#tab-resources .area1').height() + 60);
145
  jQuery('#shortpixel-settings-tabs').css('height', sectionHeight);
168
  }
169
 
170
  function successMsg(id, percent, type, thumbsCount, retinasCount) {
171
+ return (percent > 0 ? "<div class='sp-column-info'>" + _spTr.reducedBy + " <strong><span class='percent'>" + percent + "%</span></strong> " : "")
172
  + (percent > 0 && percent < 5 ? "<br>" : '')
173
  + (percent < 5 ? _spTr.bonusProcessing : '')
174
  + (type.length > 0 ? " ("+type+")" : "")
222
  console.log("Invalid response from server (Error: " + msg + "). Retrying pass " + (ShortPixel.retries + 1) + "...");
223
  setTimeout(checkBulkProgress, 5000);
224
  } else {
225
+ ShortPixel.bulkShowError(-1,"Invalid response from server received 6 times. Please retry later by reloading this page, or <a href='https://shortpixel.com/contact' target='_blank'>contact support</a>. (Error: " + msg + ")", "");
226
  console.log("Invalid response from server 6 times. Giving up.");
227
  }
228
  }
242
  return browseResponse;
243
  }
244
 
245
+ function getBackupSize() {
246
+ var browseData = { 'action': 'shortpixel_get_backup_size'};
247
+ var browseResponse = "";
248
+ jQuery.ajax({
249
+ type: "POST",
250
+ url: ShortPixel.AJAX_URL,
251
+ data: browseData,
252
+ success: function(response) {
253
+ browseResponse = response;
254
+ },
255
+ async: false
256
+ });
257
+ return browseResponse;
258
+ }
259
+
260
  function initFolderSelector() {
261
  jQuery(".select-folder-button").click(function(){
262
  jQuery(".sp-folder-picker-shade").css("display", "block");
296
  }
297
 
298
  notice.css("display", "block");
 
299
  }
300
 
301
  function bulkHideLengthyMsg(){
302
  jQuery(".bulk-notice-msg.bulk-lengthy").css("display", "none");
303
  }
304
 
305
+ function bulkShowMaintenanceMsg(type){
306
+ var notice = jQuery(".bulk-notice-msg.bulk-" + type);
307
+ if(notice.length == 0) return;
308
+ notice.css("display", "block");
309
+ }
310
+
311
+ function bulkHideMaintenanceMsg(type){
312
+ jQuery(".bulk-notice-msg.bulk-" + type).css("display", "none");
313
+ }
314
+
315
  function bulkShowError(id, msg, fileName, customLink) {
316
  var noticeTpl = jQuery("#bulk-error-template");
317
  if(noticeTpl.length == 0) return;
319
  notice.attr("id", "bulk-error-" + id);
320
  if(id == -1) {
321
  jQuery("span.sp-err-title", notice).remove();
322
+ notice.addClass("bulk-error-fatal");
323
+ } else {
324
+ jQuery("img", notice).remove();
325
+ jQuery("#bulk-error-" . id).remove();
326
  }
327
+ jQuery("span.sp-err-content", notice).html(msg);
328
  var link = jQuery("a.sp-post-link", notice);
329
  if(customLink) {
330
  link.attr("href", customLink);
367
  retry : retry,
368
  initFolderSelector : initFolderSelector,
369
  browseContent : browseContent,
370
+ getBackupSize : getBackupSize,
371
  bulkShowLengthyMsg : bulkShowLengthyMsg,
372
  bulkHideLengthyMsg : bulkHideLengthyMsg,
373
+ bulkShowMaintenanceMsg : bulkShowMaintenanceMsg,
374
+ bulkHideMaintenanceMsg : bulkHideMaintenanceMsg,
375
  bulkShowError : bulkShowError,
376
  removeBulkMsg : removeBulkMsg,
377
  isCustomImageId : isCustomImageId,
442
  * calls itself until receives an Empty queue message
443
  */
444
  function checkBulkProgress() {
445
+ //the replace stands for malformed urls on some sites, like wp-admin//upload.php which are accepted by the browser.
446
+ //using a replacer function to avoid replacing the first occurence (https:// ...)
447
+ var replacer = function(match) {
448
+ if(!first) {
449
+ first = true;
450
+ return match;
451
+ }
452
+ return '/';
453
+ };
454
+
455
+ var first = false; //arm replacer
456
+ var url = window.location.href.toLowerCase().replace(/\/\//g , replacer);
457
+
458
+ first = false; //rearm replacer
459
+ var adminUrl = ShortPixel.WP_ADMIN_URL.toLowerCase().replace(/\/\//g , replacer);
460
+
461
+ //handle possible Punycode domain names.
462
+ if(url.search(adminUrl) < 0) {
463
+ var parser = document.createElement('a');
464
+ parser.href = url;
465
+ url = url.replace(parser.protocol + '//' + parser.hostname, parser.protocol + '//' + parser.hostname.split('.').map(function(part) {return sp_punycode.toASCII(part)}).join('.'));
466
+ }
467
+
468
  if( url.search(adminUrl + "upload.php") < 0
469
  && url.search(adminUrl + "edit.php") < 0
470
  && url.search(adminUrl + "edit-tags.php") < 0
572
  case ShortPixel.STATUS_SUCCESS:
573
  if(isBulkPage) {
574
  ShortPixel.bulkHideLengthyMsg();
575
+ ShortPixel.bulkHideMaintenanceMsg();
576
  }
577
  var percent = data["PercentImprovement"];
578
 
628
  }
629
  setTimeout(checkBulkProgress, 5000);
630
  break;
631
+ case ShortPixel.STATUS_MAINTENANCE:
632
+ ShortPixel.bulkShowMaintenanceMsg('maintenance');
633
+ setTimeout(checkBulkProgress, 60000);
634
+ break;
635
+ case ShortPixel.STATUS_QUEUE_FULL:
636
+ ShortPixel.bulkShowMaintenanceMsg('queue-full');
637
+ setTimeout(checkBulkProgress, 60000);
638
+ break;
639
  default:
640
+ ShortPixel.retry("Unknown status " + data["Status"] + ". Retrying...");
641
  break;
642
  }
643
  }
721
  });
722
  }
723
 
724
+ function dismissShortPixelNoticeExceed(e) {
725
+ jQuery("#wp-admin-bar-shortpixel_processing").hide();
726
+ var data = { action : 'shortpixel_dismiss_notice',
727
+ notice_id: 'exceed'};
728
+ jQuery.get(ShortPixel.AJAX_URL, data, function(response) {
729
+ data = JSON.parse(response);
730
+ if(data["Status"] == ShortPixel.STATUS_SUCCESS) {
731
+ console.log("dismissed");
732
+ }
733
+ });
734
+ e.preventDefault();
735
+ }
736
+
737
  function dismissShortPixelNotice(id) {
738
  jQuery("#short-pixel-notice-" + id).hide();
739
  var data = { action : 'shortpixel_dismiss_notice',
shortpixel_api.php CHANGED
@@ -14,12 +14,15 @@ class ShortPixelAPI {
14
  const STATUS_NOT_FOUND = -5;
15
  const STATUS_NO_KEY = -6;
16
  const STATUS_RETRY = -7;
 
 
17
 
18
  const ERR_FILE_NOT_FOUND = -2;
19
  const ERR_TIMEOUT = -3;
20
  const ERR_SAVE = -4;
21
  const ERR_SAVE_BKP = -5;
22
  const ERR_INCORRECT_FILE_SIZE = -6;
 
23
 
24
  private $_settings;
25
  private $_maxAttempts = 10;
@@ -66,19 +69,19 @@ class ShortPixelAPI {
66
  'body' => json_encode($requestParameters),
67
  'cookies' => array()
68
  );
 
69
  //add this explicitely only for https, otherwise (for http) it slows down the request
70
  if($this->_settings->httpProto !== 'https') {
71
  unset($arguments['sslverify']);
72
  }
73
-
74
- WpShortPixel::log("Calling API with params : " . json_encode($arguments));
75
 
76
  $response = wp_remote_post($this->_apiEndPoint, $arguments );
77
 
78
  //only if $Blocking is true analyze the response
79
  if ( $Blocking )
80
  {
81
- WpShortPixel::log("API response : " . json_encode($response));
82
 
83
  //die(var_dump(array('URL: ' => $this->_apiEndPoint, '<br><br>REQUEST:' => $arguments, '<br><br>RESPONSE: ' => $response )));
84
  //there was an error, save this error inside file's SP optimization field
@@ -138,7 +141,7 @@ class ShortPixelAPI {
138
  */
139
  private function processImageRecursive($URLs, $PATHs, $itemHandler = null, $startTime = 0)
140
  {
141
- //#$meta = wp_get_attachment_metadata($ID);
142
 
143
  $PATHs = self::CheckAndFixImagePaths($PATHs);//check for images to make sure they exist on disk
144
  if ( $PATHs === false || isset($PATHs['error'])) {
@@ -183,7 +186,9 @@ class ShortPixelAPI {
183
  $meta = $itemHandler->getMeta();
184
  $compressionType = $meta->getCompressionType() !== null ? $meta->getCompressionType() : $this->_settings->compressionType;
185
  $response = $this->doRequests($URLs, true, $itemHandler, $compressionType);//send requests to API
186
-
 
 
187
  if($response['response']['code'] != 200)//response <> 200 -> there was an error apparently?
188
  return array("Status" => self::STATUS_FAIL, "Message" => __('There was an error and your request was not processed.','shortpixel-image-optimiser'));
189
 
@@ -237,7 +242,7 @@ class ShortPixelAPI {
237
 
238
  if(!isset($APIresponse['Status'])) {
239
  WpShortPixel::log("API Response Status unfound : " . json_encode($APIresponse));
240
- return array("Status" => self::STATUS_FAIL, "Message" => __('Unecognized API response. Please contact support.','shortpixel-image-optimiser'));
241
  } else {
242
  switch($APIresponse['Status']->Code)
243
  {
@@ -246,6 +251,10 @@ class ShortPixelAPI {
246
  $this->_settings->quotaExceeded = 1;
247
  return array("Status" => self::STATUS_QUOTA_EXCEEDED, "Message" => __('Quota exceeded.','shortpixel-image-optimiser'));
248
  break;
 
 
 
 
249
  }
250
 
251
  //sometimes the response array can be different
@@ -333,6 +342,7 @@ class ShortPixelAPI {
333
  @unlink($tempFile);
334
  $returnMessage = array(
335
  "Status" => self::STATUS_ERROR,
 
336
  "Message" => __('Error downloading file','shortpixel-image-optimiser') . " ({$fileData->$fileType}) " . $tempFile->get_error_message());
337
  }
338
  //check response so that download is OK
@@ -341,10 +351,13 @@ class ShortPixelAPI {
341
  @unlink($tempFile);
342
  $returnMessage = array(
343
  "Status" => self::STATUS_ERROR,
 
344
  "Message" => sprintf(__('Error downloading file - incorrect file size (downloaded: %s, correct: %s )','shortpixel-image-optimiser'),$size, $correctFileSize));
345
  }
346
  elseif (!file_exists($tempFile)) {
347
- $returnMessage = array("Status" => self::STATUS_ERROR, "Message" => __('Unable to locate downloaded file','shortpixel-image-optimiser') . " " . $tempFile);
 
 
348
  }
349
  return $returnMessage;
350
  }
@@ -386,7 +399,7 @@ class ShortPixelAPI {
386
  $tempFiles[$counter] = $downloadResult;
387
  }
388
  else {
389
- return array("Status" => $downloadResult['Status'], "Message" => $downloadResult['Message']);
390
  }
391
 
392
  }
@@ -397,9 +410,11 @@ class ShortPixelAPI {
397
  }
398
 
399
  //figure out in what SubDir files should land
400
- $fullSubDir = str_replace(wp_normalize_path(get_home_path()), "", wp_normalize_path(dirname($itemHandler->getMeta()->getPath()))) . '/';
401
- $SubDir = ShortPixelMetaFacade::returnSubDir($itemHandler->getMeta()->getPath(), $itemHandler->getType());
402
-
 
 
403
  //if backup is enabled - we try to save the images
404
  if( $this->_settings->backupImages )
405
  {
@@ -423,7 +438,7 @@ class ShortPixelAPI {
423
  foreach ( $destination as $fileID => $filePATH )
424
  {
425
  if ( !file_exists($filePATH) )
426
- {
427
  if ( !@copy($source[$fileID], $filePATH) )
428
  {//file couldn't be saved in backup folder
429
  $msg = sprintf(__('Cannot save file <i>%s</i> in backup directory','shortpixel-image-optimiser'),self::MB_basename($source[$fileID]));
@@ -447,6 +462,8 @@ class ShortPixelAPI {
447
  $width = $height = null;
448
  $resize = $this->_settings->resizeImages;
449
  $retinas = 0;
 
 
450
 
451
  if ( !empty($tempFiles) )
452
  {
@@ -456,12 +473,20 @@ class ShortPixelAPI {
456
  if(!is_array($tempFile)) continue;
457
 
458
  $targetFile = $PATHs[$tempFileID];
459
-
 
 
 
 
 
 
460
  if($tempFile['Status'] == self::STATUS_SUCCESS) { //if it's unchanged it will still be in the array but only for WebP (handled below)
461
  $tempFilePATH = $tempFile["Message"];
462
  if ( file_exists($tempFilePATH) && file_exists($PATHs[$tempFileID]) && is_writable($PATHs[$tempFileID]) ) {
463
  copy($tempFilePATH, $targetFile);
464
- $retinas += ShortPixelMetaFacade::isRetina($targetFile) ? 1 : 0;
 
 
465
  if($resize && $itemHandler->getMeta()->getPath() == $targetFile) { //this is the main image
466
  $size = getimagesize($PATHs[$tempFileID]);
467
  $width = $size[0];
@@ -490,6 +515,10 @@ class ShortPixelAPI {
490
  if(file_exists($tempWebpFilePATH)) {
491
  $targetWebPFile = dirname($targetFile) . '/' . basename($targetFile, '.' . pathinfo($targetFile, PATHINFO_EXTENSION)) . ".webp";
492
  copy($tempWebpFilePATH, $targetWebPFile);
 
 
 
 
493
  }
494
  @unlink($tempWebpFilePATH);
495
  }
@@ -526,12 +555,13 @@ class ShortPixelAPI {
526
  : "Couldn't compute thumbs optimization percent. Main image: " . $percentImprovement);
527
  WPShortPixel::log("HANDLE SUCCESS: Image optimization: ".$meta->getMessage());
528
  $meta->setCompressionType($compressionType);
529
- $meta->setCompressedSize(filesize($meta->getPath()));
530
  $meta->setKeepExif($this->_settings->keepExif);
531
  $meta->setTsOptimized(date("Y-m-d H:i:s"));
532
- $meta->setThumbsOpt(($meta->getThumbsTodo() || $this->_settings->processThumbnails) ? count($meta->getThumbs()) : 0);
533
  $meta->setRetinasOpt($retinas);
534
  $meta->setThumbsTodo(false);
 
535
  if($width && $height) {
536
  $meta->setActualWidth($width);
537
  $meta->setActualHeight($height);
@@ -556,10 +586,10 @@ class ShortPixelAPI {
556
  * @param string $Path
557
  * @return string
558
  */
559
- static public function MB_basename($Path){
560
  $Separator = " qq ";
561
  $Path = preg_replace("/[^ ]/u", $Separator."\$0".$Separator, $Path);
562
- $Base = basename($Path);
563
  $Base = str_replace($Separator, "", $Base);
564
  return $Base;
565
  }
@@ -587,8 +617,13 @@ class ShortPixelAPI {
587
  if (file_exists($NewFile)) {
588
  $PATHs[$Id] = $NewFile;
589
  } else {
590
- $missingFiles[] = $File;
591
- $ErrorCount++;
 
 
 
 
 
592
  }
593
  }
594
  }
14
  const STATUS_NOT_FOUND = -5;
15
  const STATUS_NO_KEY = -6;
16
  const STATUS_RETRY = -7;
17
+ const STATUS_QUEUE_FULL = -404;
18
+ const STATUS_MAINTENANCE = -500;
19
 
20
  const ERR_FILE_NOT_FOUND = -2;
21
  const ERR_TIMEOUT = -3;
22
  const ERR_SAVE = -4;
23
  const ERR_SAVE_BKP = -5;
24
  const ERR_INCORRECT_FILE_SIZE = -6;
25
+ const ERR_DOWNLOAD = -7;
26
 
27
  private $_settings;
28
  private $_maxAttempts = 10;
69
  'body' => json_encode($requestParameters),
70
  'cookies' => array()
71
  );
72
+ //die(var_dump($requestParameters));
73
  //add this explicitely only for https, otherwise (for http) it slows down the request
74
  if($this->_settings->httpProto !== 'https') {
75
  unset($arguments['sslverify']);
76
  }
77
+ //WpShortPixel::log("Calling API with params : " . json_encode($arguments));
 
78
 
79
  $response = wp_remote_post($this->_apiEndPoint, $arguments );
80
 
81
  //only if $Blocking is true analyze the response
82
  if ( $Blocking )
83
  {
84
+ //WpShortPixel::log("API response : " . json_encode($response));
85
 
86
  //die(var_dump(array('URL: ' => $this->_apiEndPoint, '<br><br>REQUEST:' => $arguments, '<br><br>RESPONSE: ' => $response )));
87
  //there was an error, save this error inside file's SP optimization field
141
  */
142
  private function processImageRecursive($URLs, $PATHs, $itemHandler = null, $startTime = 0)
143
  {
144
+ //WPShortPixel::log("processImageRecursive ID: " . $itemHandler->getId() . " PATHs: " . json_encode($PATHs));
145
 
146
  $PATHs = self::CheckAndFixImagePaths($PATHs);//check for images to make sure they exist on disk
147
  if ( $PATHs === false || isset($PATHs['error'])) {
186
  $meta = $itemHandler->getMeta();
187
  $compressionType = $meta->getCompressionType() !== null ? $meta->getCompressionType() : $this->_settings->compressionType;
188
  $response = $this->doRequests($URLs, true, $itemHandler, $compressionType);//send requests to API
189
+
190
+ //die(var_dump($response));
191
+
192
  if($response['response']['code'] != 200)//response <> 200 -> there was an error apparently?
193
  return array("Status" => self::STATUS_FAIL, "Message" => __('There was an error and your request was not processed.','shortpixel-image-optimiser'));
194
 
242
 
243
  if(!isset($APIresponse['Status'])) {
244
  WpShortPixel::log("API Response Status unfound : " . json_encode($APIresponse));
245
+ return array("Status" => self::STATUS_FAIL, "Message" => __('Unrecognized API response. Please contact support.','shortpixel-image-optimiser'));
246
  } else {
247
  switch($APIresponse['Status']->Code)
248
  {
251
  $this->_settings->quotaExceeded = 1;
252
  return array("Status" => self::STATUS_QUOTA_EXCEEDED, "Message" => __('Quota exceeded.','shortpixel-image-optimiser'));
253
  break;
254
+ case -404:
255
+ return array("Status" => self::STATUS_QUEUE_FULL, "Message" => $APIresponse['Status']->Message);
256
+ case -500:
257
+ return array("Status" => self::STATUS_MAINTENANCE, "Message" => $APIresponse['Status']->Message);
258
  }
259
 
260
  //sometimes the response array can be different
342
  @unlink($tempFile);
343
  $returnMessage = array(
344
  "Status" => self::STATUS_ERROR,
345
+ "Code" => self::ERR_DOWNLOAD,
346
  "Message" => __('Error downloading file','shortpixel-image-optimiser') . " ({$fileData->$fileType}) " . $tempFile->get_error_message());
347
  }
348
  //check response so that download is OK
351
  @unlink($tempFile);
352
  $returnMessage = array(
353
  "Status" => self::STATUS_ERROR,
354
+ "Code" => self::ERR_INCORRECT_FILE_SIZE,
355
  "Message" => sprintf(__('Error downloading file - incorrect file size (downloaded: %s, correct: %s )','shortpixel-image-optimiser'),$size, $correctFileSize));
356
  }
357
  elseif (!file_exists($tempFile)) {
358
+ $returnMessage = array("Status" => self::STATUS_ERROR,
359
+ "Code" => self::ERR_FILE_NOT_FOUND,
360
+ "Message" => __('Unable to locate downloaded file','shortpixel-image-optimiser') . " " . $tempFile);
361
  }
362
  return $returnMessage;
363
  }
399
  $tempFiles[$counter] = $downloadResult;
400
  }
401
  else {
402
+ return array("Status" => $downloadResult['Status'], "Code" => $downloadResult['Code'], "Message" => $downloadResult['Message']);
403
  }
404
 
405
  }
410
  }
411
 
412
  //figure out in what SubDir files should land
413
+ //$fullSubDir = str_replace(wp_normalize_path(get_home_path()), "", wp_normalize_path(dirname($itemHandler->getMeta()->getPath()))) . '/';
414
+ //$SubDir = ShortPixelMetaFacade::returnSubDir($itemHandler->getMeta()->getPath(), $itemHandler->getType());
415
+ $mainPath = $itemHandler->getMeta()->getPath();
416
+ $fullSubDir = ShortPixelMetaFacade::returnSubDir($mainPath, $itemHandler->getType());
417
+
418
  //if backup is enabled - we try to save the images
419
  if( $this->_settings->backupImages )
420
  {
438
  foreach ( $destination as $fileID => $filePATH )
439
  {
440
  if ( !file_exists($filePATH) )
441
+ {
442
  if ( !@copy($source[$fileID], $filePATH) )
443
  {//file couldn't be saved in backup folder
444
  $msg = sprintf(__('Cannot save file <i>%s</i> in backup directory','shortpixel-image-optimiser'),self::MB_basename($source[$fileID]));
462
  $width = $height = null;
463
  $resize = $this->_settings->resizeImages;
464
  $retinas = 0;
465
+ $thumbsOpt = 0;
466
+ $webpSizes = array();
467
 
468
  if ( !empty($tempFiles) )
469
  {
473
  if(!is_array($tempFile)) continue;
474
 
475
  $targetFile = $PATHs[$tempFileID];
476
+ $isRetina = ShortPixelMetaFacade::isRetina($targetFile);
477
+
478
+ if( ($tempFile['Status'] == self::STATUS_UNCHANGED || $tempFile['Status'] == self::STATUS_SUCCESS) && !$isRetina
479
+ && $targetFile !== $mainPath) {
480
+ $thumbsOpt++;
481
+ }
482
+
483
  if($tempFile['Status'] == self::STATUS_SUCCESS) { //if it's unchanged it will still be in the array but only for WebP (handled below)
484
  $tempFilePATH = $tempFile["Message"];
485
  if ( file_exists($tempFilePATH) && file_exists($PATHs[$tempFileID]) && is_writable($PATHs[$tempFileID]) ) {
486
  copy($tempFilePATH, $targetFile);
487
+ if(ShortPixelMetaFacade::isRetina($targetFile)) {
488
+ $retinas ++;
489
+ }
490
  if($resize && $itemHandler->getMeta()->getPath() == $targetFile) { //this is the main image
491
  $size = getimagesize($PATHs[$tempFileID]);
492
  $width = $size[0];
515
  if(file_exists($tempWebpFilePATH)) {
516
  $targetWebPFile = dirname($targetFile) . '/' . basename($targetFile, '.' . pathinfo($targetFile, PATHINFO_EXTENSION)) . ".webp";
517
  copy($tempWebpFilePATH, $targetWebPFile);
518
+ $webpSize = $itemHandler->getWebpSizeMeta($targetFile);
519
+ if($webpSize) {
520
+ $webpSizes[$webpSize['key']] = $webpSize['val'];
521
+ }
522
  }
523
  @unlink($tempWebpFilePATH);
524
  }
555
  : "Couldn't compute thumbs optimization percent. Main image: " . $percentImprovement);
556
  WPShortPixel::log("HANDLE SUCCESS: Image optimization: ".$meta->getMessage());
557
  $meta->setCompressionType($compressionType);
558
+ $meta->setCompressedSize(@filesize($meta->getPath()));
559
  $meta->setKeepExif($this->_settings->keepExif);
560
  $meta->setTsOptimized(date("Y-m-d H:i:s"));
561
+ $meta->setThumbsOpt(($meta->getThumbsTodo() || $this->_settings->processThumbnails) ? $thumbsOpt : 0);
562
  $meta->setRetinasOpt($retinas);
563
  $meta->setThumbsTodo(false);
564
+ //* Not yet as it doesn't seem to work... */$meta->addThumbs($webpSizes);
565
  if($width && $height) {
566
  $meta->setActualWidth($width);
567
  $meta->setActualHeight($height);
586
  * @param string $Path
587
  * @return string
588
  */
589
+ static public function MB_basename($Path, $suffix = false){
590
  $Separator = " qq ";
591
  $Path = preg_replace("/[^ ]/u", $Separator."\$0".$Separator, $Path);
592
+ $Base = basename($Path, $suffix);
593
  $Base = str_replace($Separator, "", $Base);
594
  return $Base;
595
  }
617
  if (file_exists($NewFile)) {
618
  $PATHs[$Id] = $NewFile;
619
  } else {
620
+ $NewFile = $uploadDir['basedir'] . "/" . $File;
621
+ if (file_exists($NewFile)) {
622
+ $PATHs[$Id] = $NewFile;
623
+ } else {
624
+ $missingFiles[] = $File;
625
+ $ErrorCount++;
626
+ }
627
  }
628
  }
629
  }
wp-shortpixel.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: ShortPixel Image Optimizer
4
  * Plugin URI: https://shortpixel.com/
5
  * Description: ShortPixel optimizes images automatically, while guarding the quality of your images. Check your <a href="options-general.php?page=wp-shortpixel" target="_blank">Settings &gt; ShortPixel</a> page on how to start optimizing your image library and make your website load faster.
6
- * Version: 4.2.5
7
  * Author: ShortPixel
8
  * Author URI: https://shortpixel.com
9
  * Text Domain: shortpixel-image-optimiser
@@ -12,9 +12,11 @@
12
 
13
  define('SP_RESET_ON_ACTIVATE', false); //if true TODO set false
14
 
 
 
15
  define('SP_AFFILIATE_CODE', '');
16
 
17
- define('PLUGIN_VERSION', "4.2.5");
18
  define('SP_MAX_TIMEOUT', 10);
19
  define('SP_VALIDATE_MAX_TIMEOUT', 15);
20
  define('SP_BACKUP', 'ShortpixelBackups');
@@ -76,6 +78,10 @@ class WPShortPixel {
76
  $this->prioQ = new ShortPixelQueue($this, $this->_settings);
77
  $this->view = new ShortPixelView($this);
78
 
 
 
 
 
79
  define('QUOTA_EXCEEDED', $this->view->getQuotaExceededHTML());
80
 
81
  $this->setDefaultViewModeList();//set default mode as list. only @ first run
@@ -103,6 +109,9 @@ class WPShortPixel {
103
  // hook on the NextGen gallery list update
104
  add_action('ngg_update_addgallery_page', array( &$this, 'addNextGenGalleriesToCustom'));
105
  }
 
 
 
106
 
107
  //custom hook
108
  add_action( 'shortpixel-optimize-now', array( &$this, 'optimizeNowHook' ), 10, 1);
@@ -113,6 +122,7 @@ class WPShortPixel {
113
  add_action( 'admin_menu', array( &$this, 'registerAdminPage' ) );
114
 
115
  add_action('wp_ajax_shortpixel_browse_content', array(&$this, 'browseContent'));
 
116
 
117
  add_action( 'delete_attachment', array( &$this, 'handleDeleteAttachmentInBackup' ) );
118
  add_action( 'load-upload.php', array( &$this, 'handleCustomBulk'));
@@ -126,6 +136,8 @@ class WPShortPixel {
126
 
127
  //toolbar notifications
128
  add_action( 'admin_bar_menu', array( &$this, 'toolbar_shortpixel_processing'), 999 );
 
 
129
  }
130
 
131
  //automatic optimization
@@ -139,7 +151,7 @@ class WPShortPixel {
139
  add_action('admin_action_shortpixel_check_quota', array(&$this, 'handleCheckQuota'));
140
  //This adds the constants used in PHP to be available also in JS
141
  add_action( 'admin_footer', array( &$this, 'shortPixelJS') );
142
-
143
  if($this->_settings->frontBootstrap) {
144
  //also need to have it in the front footer then
145
  add_action( 'wp_footer', array( &$this, 'shortPixelJS') );
@@ -195,11 +207,34 @@ class WPShortPixel {
195
  ShortPixelQueue::resetBulk();
196
  ShortPixelQueue::resetPrio();
197
  WPShortPixelSettings::onDeactivate();
198
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
  public function displayAdminNotices() {
 
201
  if(!$this->_settings->verifiedKey) {
202
- $dismissed = $this->_settings->dismissedNotices ? $this->_settings->dismissedNotices : array();
203
  $now = time();
204
  $act = $this->_settings->activationDate ? $this->_settings->activationDate : $now;
205
  if($this->_settings->activationNotice && $this->_settings->redirectedSettings >= 2) {
@@ -212,6 +247,12 @@ class WPShortPixel {
212
  ShortPixelView::displayActivationNotice('3d');
213
  }
214
  }
 
 
 
 
 
 
215
  }
216
 
217
  public function dismissAdminNotice() {
@@ -264,6 +305,8 @@ class WPShortPixel {
264
  STATUS_SKIP: <?php echo ShortPixelAPI::STATUS_SKIP; ?>,
265
  STATUS_NO_KEY: <?php echo ShortPixelAPI::STATUS_NO_KEY; ?>,
266
  STATUS_RETRY: <?php echo ShortPixelAPI::STATUS_RETRY; ?>,
 
 
267
  WP_PLUGIN_URL: '<?php echo plugins_url( '', __FILE__ ); ?>',
268
  WP_ADMIN_URL: '<?php echo admin_url(); ?>',
269
  API_KEY: "<?php echo $this->_settings->apiKey; ?>",
@@ -278,7 +321,7 @@ class WPShortPixel {
278
  $jsTranslation = array(
279
  'optimizeWithSP' => __( 'Optimize with ShortPixel', 'shortpixel-image-optimiser' ),
280
  'changeMLToListMode' => __( 'In order to access the ShortPixel Optimization actions and info, please change to {0}List View{1}List View{2}Dismiss{3}', 'shortpixel-image-optimiser' ),
281
- 'alertOnlyAppliesToNewImages' => __( 'This type of optimization will apply to new uploaded images.\nImages that were already processed will not be re-optimized unless you restart the bulk process.', 'shortpixel-image-optimiser' ),
282
  'areYouSureStopOptimizing' => __( 'Are you sure you want to stop optimizing the folder {0}?', 'shortpixel-image-optimiser' ),
283
  'reducedBy' => __( 'Reduced by', 'shortpixel-image-optimiser' ),
284
  'bonusProcessing' => __( 'Bonus processing', 'shortpixel-image-optimiser' ),
@@ -301,12 +344,14 @@ class WPShortPixel {
301
 
302
  wp_enqueue_script('jquery.knob.js', plugins_url('/res/js/jquery.knob.js',__FILE__) );
303
  wp_enqueue_script('jquery.tooltip.js', plugins_url('/res/js/jquery.tooltip.js',__FILE__) );
 
304
  }
305
 
306
  function toolbar_shortpixel_processing( $wp_admin_bar ) {
307
 
308
  $extraClasses = " shortpixel-hide";
309
  /*translators: toolbar icon tooltip*/
 
310
  $tooltip = __('ShortPixel optimizing...','shortpixel-image-optimiser');
311
  $icon = "shortpixel.png";
312
  $successLink = $link = current_user_can( 'edit_others_posts')? 'upload.php?page=wp-short-pixel-bulk' : 'upload.php';
@@ -314,10 +359,12 @@ class WPShortPixel {
314
  if($this->prioQ->processing()) {
315
  $extraClasses = " shortpixel-processing";
316
  }
317
- if($this->_settings->quotaExceeded) {
318
  $extraClasses = " shortpixel-alert shortpixel-quota-exceeded";
319
  /*translators: toolbar icon tooltip*/
320
- $tooltip = __('ShortPixel quota exceeded. Click for details.','shortpixel-image-optimiser');
 
 
321
  //$link = "http://shortpixel.com/login/" . $this->_settings->apiKey;
322
  $link = "options-general.php?page=wp-shortpixel";
323
  //$blank = '_blank';
@@ -331,12 +378,27 @@ class WPShortPixel {
331
 
332
  $args = array(
333
  'id' => 'shortpixel_processing',
334
- 'title' => '<div title="' . $tooltip . '" ><img src="'
335
  . plugins_url( 'res/img/'.$icon, __FILE__ ) . '" success-url="' . $successLink . '"><span class="shp-alert">!</span></div>',
336
  'href' => $link,
337
  'meta' => array('target'=> $blank, 'class' => 'shortpixel-toolbar-processing' . $extraClasses)
338
  );
339
  $wp_admin_bar->add_node( $args );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  }
341
 
342
  public function handleCustomBulk() {
@@ -432,7 +494,7 @@ class WPShortPixel {
432
  $folder = new ShortPixelFolder(array("path" => $galleryPath));
433
  $folderMsg = $this->spMetaDao->saveFolder($folder);
434
  $folderId = $folder->getId();
435
- self::log("NG Image Upload: created folder from path $galleryPath : Folder info: " . json_encode($folder));
436
  }
437
  $pathParts = explode('/', trim($imageFsPath));
438
  //Add the main image
@@ -550,7 +612,7 @@ class WPShortPixel {
550
  //daca n-am adaugat niciuna pana acum, n-are sens sa mai selectez zona asta de id-uri in bulk-ul asta.
551
  $leapStart = $this->prioQ->getStartBulkId();
552
  $crtStartQueryID = $startQueryID = $itemMetaData->post_id - 1; //decrement it so we don't select it again
553
- $res = WpShortPixelMediaLbraryAdapter::countAllProcessableFiles($leapStart, $crtStartQueryID);
554
  $skippedAlreadyProcessed += $res["mainProcessedFiles"] - $res["mainProc".($this->getCompressionType() == 1 ? "Lossy" : "Lossless")."Files"];
555
  $this->prioQ->setStartBulkId($startQueryID);
556
  } else {
@@ -595,7 +657,7 @@ class WPShortPixel {
595
  }
596
 
597
  self::log("HIP: 0 Priority Queue: ".json_encode($this->prioQ->get()));
598
- self::log("HIP: 0 Skipped: ".json_encode($this->prioQ->getSkipped()));
599
 
600
  //1: get 3 ids to process. Take them with priority from the queue
601
  $ids = $this->getFromPrioAndCheck();
@@ -603,8 +665,17 @@ class WPShortPixel {
603
  if($this->prioQ->bulkRunning()) {
604
  $res = $this->getBulkItemsFromDb();
605
  $bulkItems = $res['items'];
 
606
  if($bulkItems){
607
- $ids = array_merge ($ids, $bulkItems);
 
 
 
 
 
 
 
 
608
  }
609
  }
610
  }
@@ -620,8 +691,8 @@ class WPShortPixel {
620
  $ids = array_merge($ids, array_map(array('ShortPixelMetaFacade', 'getNewFromRow'), $customIds));
621
  }
622
  }
623
- // var_dump($ids);
624
- // die("za stop 2");
625
 
626
  self::log("HIP: 1 Prio Queue: ".json_encode($this->prioQ->get()));
627
  self::log("HIP: 1 Selected IDs count: ".count($ids));
@@ -631,8 +702,14 @@ class WPShortPixel {
631
  $itemHandler = $ids[$i];
632
  $tmpMeta = $itemHandler->getMeta();
633
  $compType = ($tmpMeta->getCompressionType() !== null ? $tmpMeta->getCompressionType() : $this->_settings->compressionType);
634
- try {
 
635
  $URLsAndPATHs = $this->sendToProcessing($itemHandler, $compType, $tmpMeta->getThumbsTodo());
 
 
 
 
 
636
  } catch(Exception $e) { // Exception("Post metadata is corrupt (No attachment URL)")
637
  $meta = $itemHandler->setError(ShortPixelAPI::ERR_FILE_NOT_FOUND, $e->getMessage());
638
  unset($ids[$i]);
@@ -646,9 +723,11 @@ class WPShortPixel {
646
  if ($ids === false || count( $ids ) == 0 ){
647
  //if searching, than the script is searching for not processed items and found none yet, should be relaunced
648
  if(isset($res['searching']) && $res['searching']) {
649
- die(json_encode(array("Status" => ShortPixelAPI::STATUS_RETRY, "Message" => 'Searching images to optimize... ' . $this->prioQ->getStartBulkId() . '->' . $this->prioQ->getStopBulkId() )));
 
650
  }
651
  //in this case the queue is really empty
 
652
  $bulkEverRan = $this->prioQ->stopBulk();
653
  $avg = $this->getAverageCompression();
654
  $fileCount = $this->_settings->fileCount;
@@ -668,6 +747,7 @@ class WPShortPixel {
668
  $itemHandler = $ids[0];
669
  $itemId = $itemHandler->getQueuedId();
670
  $result = $this->_apiInterface->processImage($firstUrlAndPaths['URLs'], $firstUrlAndPaths['PATHs'], $itemHandler);
 
671
  $result["ImageID"] = $itemId;
672
  $meta = $itemHandler->getMeta();
673
  $result["Filename"] = basename($meta->getPath());
@@ -681,13 +761,14 @@ class WPShortPixel {
681
  //remove also from the failed list if it failed in the past
682
  $prio = $this->prioQ->removeFromFailed($itemId);
683
  $result["Type"] = $meta->getCompressionType() !== null ? ShortPixelAPI::getCompressionTypeName($meta->getCompressionType()) : '';
684
- $result["ThumbsTotal"] = $meta->getThumbs() && is_array($meta->getThumbs()) ? count($meta->getThumbs()): 0;
 
685
  $result["ThumbsCount"] = $meta->getThumbsOpt()
686
  ? $meta->getThumbsOpt() //below is the fallback for old optimized images that don't have thumbsOpt
687
  : ($this->_settings->processThumbnails ? $result["ThumbsTotal"] : 0);
688
 
689
  $result["RetinasCount"] = $meta->getRetinasOpt();
690
- $result["BackupEnabled"] = ($this->getBackupFolder($meta->getPath()) ? true : false);//$this->_settings->backupImages;
691
 
692
  if(!$prio && $itemId <= $this->prioQ->getStartBulkId()) {
693
  $this->advanceBulk($itemId, $result);
@@ -728,7 +809,7 @@ class WPShortPixel {
728
  $bkThumb = $backupUrl . $urlBkPath . $thumb;
729
  }
730
  if(strlen($thumb)) {
731
- $uploadsUrl = home_url() . '/';
732
  $urlPath = ShortPixelMetaFacade::returnSubDir($meta->getPath(), ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE);
733
  //$urlPath = implode("/", array_slice($filePath, 0, count($filePath) - 1));
734
  $thumb = $uploadsUrl . $urlPath . $thumb;
@@ -768,9 +849,12 @@ class WPShortPixel {
768
  }
769
  $result["Status"] = ShortPixelAPI::STATUS_SKIP;
770
  $result["Message"] .= __(' Retry limit reached. Skipping file ID ','shortpixel-image-optimiser') . $itemId . ".";
771
- $itemHandler->setError(ShortPixelAPI::ERR_INCORRECT_FILE_SIZE, $result["Message"] );
772
  }
773
  else {
 
 
 
774
  $itemHandler->incrementRetries();
775
  }
776
  }
@@ -790,6 +874,12 @@ class WPShortPixel {
790
  $result["CustomImageLink"] = ShortPixelMetaFacade::getHomeUrl() . $meta->getWebPath();
791
  }
792
  }
 
 
 
 
 
 
793
  elseif ($this->prioQ->isPrio($itemId) && $result["Status"] == ShortPixelAPI::STATUS_QUOTA_EXCEEDED) {
794
  if(!$this->prioQ->skippedCount()) {
795
  $this->prioQ->reverse(); //for the first prio item with quota exceeded, revert the prio queue as probably the bottom ones were processed
@@ -836,11 +926,66 @@ class WPShortPixel {
836
  $result["BulkMsg"] = $this->bulkProgressMessage($deltaBulkPercent, $minutesRemaining);
837
  }
838
 
 
 
839
  private function sendToProcessing($itemHandler, $compressionType = false, $onlyThumbs = false) {
840
  $URLsAndPATHs = $this->getURLsAndPATHs($itemHandler, NULL, $onlyThumbs);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
841
  //echo("URLS: "); die(var_dump($URLsAndPATHs));
842
  $this->_apiInterface->doRequests($URLsAndPATHs['URLs'], false, $itemHandler,
843
- $compressionType === false ? $this->_settings->compressionType : $compressionType);//send a request, do NOT wait for response
844
  $itemHandler->setWaitingProcessing();
845
  //$meta = wp_get_attachment_metadata($ID);
846
  //$meta['ShortPixel']['WaitingProcessing'] = true;
@@ -868,9 +1013,6 @@ class WPShortPixel {
868
  case "C-":
869
  throw new Exception("HandleManualOptimization for custom images not implemented");
870
  default:
871
- if($cleanup) {
872
- WpShortPixelMediaLbraryAdapter::fixWPMediaMetaMissingThumbs($imageId);
873
- }
874
  $this->optimizeNowHook(intval($imageId));
875
  break;
876
  }
@@ -897,6 +1039,19 @@ class WPShortPixel {
897
  die(json_encode($ret));
898
  }
899
 
 
 
 
 
 
 
 
 
 
 
 
 
 
900
  //save error in file's meta data
901
  public function handleError($ID, $result)
902
  {
@@ -906,10 +1061,21 @@ class WPShortPixel {
906
  }
907
 
908
  public function getBackupFolder($file) {
909
- $file = realpath($file); //found cases when $file contains for example /wp/../wp-content - clean it up
 
 
910
  $fileExtension = strtolower(substr($file,strrpos($file,".")+1));
911
  $SubDir = ShortPixelMetaFacade::returnSubDir($file, ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE);
 
912
 
 
 
 
 
 
 
 
 
913
  //sometimes the month of original file and backup can differ
914
  if ( !file_exists(SP_BACKUP_FOLDER . '/' . $SubDir . ShortPixelAPI::MB_basename($file)) ) {
915
  $SubDir = date("Y") . "/" . date("m") . "/";
@@ -920,8 +1086,27 @@ class WPShortPixel {
920
  return SP_BACKUP_FOLDER . '/' . $SubDir;
921
  }
922
 
 
 
 
 
 
 
 
 
 
 
 
 
923
  protected function setFilePerms($file) {
924
  //die(getenv('USERNAME') ? getenv('USERNAME') : getenv('USER'));
 
 
 
 
 
 
 
925
  $perms = @fileperms($file);
926
  if(!($perms & 0x0100) || !($perms & 0x0080)) {
927
  if(!@chmod($file, $perms | 0x0100 | 0x0080)) {
@@ -932,41 +1117,55 @@ class WPShortPixel {
932
  }
933
 
934
  protected function doRestore($attachmentID, $meta = null) {
935
- $file = get_attached_file($attachmentID);
936
  if(!$meta) {
937
  $meta = wp_get_attachment_metadata($attachmentID);
938
  }
939
  $pathInfo = pathinfo($file);
940
 
941
- $bkFolder = $this->getBackupFolder($file);
942
- $bkNewFile = str_replace(dirname(SP_BACKUP_FOLDER), SP_BACKUP_FOLDER, $file);
943
- $bkFile = $bkFolder . ShortPixelAPI::MB_basename($file);
944
-
945
- if(file_exists($bkNewFile)) {
946
- $bkFile = $bkNewFile;
947
- }
948
-
949
  //first check if the file is readable by the current user - otherwise it will be unaccessible for the web browser
950
  // - collect the thumbs paths in the process
951
- if(! $this->setFilePerms($bkFile) ) return false;
 
 
 
 
 
 
 
952
  $thumbsPaths = array();
953
  if( !empty($meta['file']) && is_array($meta["sizes"]) ) {
954
  foreach($meta["sizes"] as $size => $imageData) {
955
- $source = $bkFolder . $imageData['file'];
 
956
  if(!file_exists($source)) continue; // if thumbs were not optimized, then the backups will not be there.
957
- $thumbsPaths[$source] = $pathInfo['dirname'] . '/' . $imageData['file'];
958
- if(! $this->setFilePerms($source)) return false;
 
 
 
959
  }
960
  }
961
-
 
 
 
962
  if($bkFolder) {
963
  try {
964
  //main file
965
- $this->renameWithRetina($bkFile, $file);
 
 
966
  //getSize to update meta if image was resized by ShortPixel
967
- $size = getimagesize($file);
968
- $width = $size[0];
969
- $height = $size[1];
 
 
 
970
 
971
  //overwriting thumbnails
972
  foreach($thumbsPaths as $source => $destination) {
@@ -986,7 +1185,9 @@ class WPShortPixel {
986
  }
987
  wp_update_attachment_metadata($ID, $crtMeta);
988
  }
989
-
 
 
990
  } catch(Exception $e) {
991
  //what to do, what to do?
992
  return false;
@@ -1076,7 +1277,7 @@ class WPShortPixel {
1076
  $meta = wp_get_attachment_metadata($ID);
1077
  //die(var_dump($meta));
1078
  if( isset($meta['ShortPixelImprovement'])
1079
- && isset($meta['sizes']) && count($meta['sizes'])
1080
  && ( !isset($meta['ShortPixel']['thumbsOpt']) || $meta['ShortPixel']['thumbsOpt'] == 0)) { //optimized without thumbs, thumbs exist
1081
  $meta['ShortPixel']['thumbsTodo'] = true;
1082
  wp_update_attachment_metadata($ID, $meta);
@@ -1127,6 +1328,15 @@ class WPShortPixel {
1127
  }
1128
  }
1129
  }
 
 
 
 
 
 
 
 
 
1130
 
1131
  public function checkQuotaAndAlert($quotaData = null, $recheck = false) {
1132
  if(!$quotaData) {
@@ -1136,7 +1346,7 @@ class WPShortPixel {
1136
  return $quotaData;
1137
  }
1138
  //$tempus = microtime(true);
1139
- $imageCount = WpShortPixelMediaLbraryAdapter::countAllProcessableFiles();
1140
 
1141
  $this->_settings->currentTotalFiles = $imageCount['totalFiles'];
1142
 
@@ -1161,7 +1371,7 @@ class WPShortPixel {
1161
 
1162
  ?><script>var shortPixelQuotaExceeded = 0;</script><?php
1163
  }
1164
- else {
1165
  $this->view->displayQuotaExceededAlert($quotaData, self::getAverageCompression(), $recheck);
1166
  ?><script>var shortPixelQuotaExceeded = 1;</script><?php
1167
  }
@@ -1209,7 +1419,7 @@ class WPShortPixel {
1209
  <?php
1210
  $customMediaListTable->display();
1211
  //push to the processing list the pending ones, just in case
1212
- $count = $this->spMetaDao->getCustomMetaCount();
1213
  foreach ($items as $item) {
1214
  if($item->status == 1){
1215
  $this->prioQ->push(ShortPixelMetaFacade::queuedId(ShortPixelMetaFacade::CUSTOM_TYPE, $item->id));
@@ -1261,6 +1471,11 @@ class WPShortPixel {
1261
  } else {
1262
  $this->_settings->processThumbnails = 0;
1263
  }
 
 
 
 
 
1264
  $this->prioQ->startBulk();
1265
  $this->_settings->customBulkPaused = 0;
1266
  self::log("BULK: Start: " . $this->prioQ->getStartBulkId() . ", stop: " . $this->prioQ->getStopBulkId() . " PrioQ: "
@@ -1356,15 +1571,17 @@ class WPShortPixel {
1356
 
1357
 
1358
  //parse all images and set the right flag that the image has no backup
 
1359
  foreach($attachments as $attachment)
1360
  {
1361
- if(self::isProcessable(get_attached_file($attachment->ID)) == false) continue;
1362
 
1363
  $meta = wp_get_attachment_metadata($attachment->ID);
1364
  $meta['ShortPixel']['NoBackup'] = true;
1365
  wp_update_attachment_metadata($attachment->ID, $meta);
1366
  }
1367
-
 
1368
  //delete the actual files on disk
1369
  $this->deleteDir(SP_BACKUP_FOLDER);//call a recursive function to empty files and sub-dirs in backup dir
1370
  }
@@ -1373,9 +1590,16 @@ class WPShortPixel {
1373
  public function backupFolderIsEmpty() {
1374
  return count(scandir(SP_BACKUP_FOLDER)) > 2 ? false : true;
1375
  }
 
 
 
 
 
 
 
1376
 
1377
  public function browseContent() {
1378
- if ( !current_user_can( 'manage_options' ) ) {
1379
  wp_die(__('You do not have sufficient permissions to access this page.','shortpixel-image-optimiser'));
1380
  }
1381
 
@@ -1608,6 +1832,7 @@ class WPShortPixel {
1608
 
1609
  $this->_settings->createWebp = (isset($_POST['createWebp']) ? 1: 0);
1610
  $this->_settings->optimizeRetina = (isset($_POST['optimizeRetina']) ? 1: 0);
 
1611
  $this->_settings->frontBootstrap = (isset($_POST['frontBootstrap']) ? 1: 0);
1612
  $this->_settings->autoMediaLibrary = (isset($_POST['autoMediaLibrary']) ? 1: 0);
1613
 
@@ -1661,18 +1886,17 @@ class WPShortPixel {
1661
  if (is_numeric($quotaData['APICallsQuota'])) {
1662
  $quotaData['APICallsQuota'] .= "/month";
1663
  }
1664
- $backupFolderSize = self::formatBytes(self::folderSize(SP_BACKUP_FOLDER));
1665
  $remainingImages = $quotaData['APICallsRemaining'];
1666
  $remainingImages = ( $remainingImages < 0 ) ? 0 : number_format($remainingImages);
1667
  $totalCallsMade = number_format($quotaData['APICallsMadeNumeric'] + $quotaData['APICallsMadeOneTimeNumeric']);
1668
 
1669
- $resources = wp_remote_get($this->_settings->httpProto . "://shortpixel.com/resources-frag");
1670
  if(is_wp_error( $resources )) {
1671
  $resources = array();
1672
  }
1673
  $this->view->displaySettings($showApiKey, $editApiKey,
1674
  $quotaData, $notice, $resources, $averageCompression, $savedSpace, $savedBandwidth, $remainingImages,
1675
- $totalCallsMade, $fileCount, $backupFolderSize, $customFolders,
1676
  $folderMsg, $folderMsg ? $addedFolder : false, isset($_POST['saveAdv']));
1677
  } else {
1678
  $this->view->displaySettings($showApiKey, $editApiKey, $quotaData, $notice);
@@ -1730,7 +1954,7 @@ class WPShortPixel {
1730
  if($validate) {
1731
  $args['body']['DomainCheck'] = get_site_url();
1732
  $args['body']['Info'] = get_bloginfo('version') . '|' . phpversion();
1733
- $imageCount = WpShortPixelMediaLbraryAdapter::countAllProcessableFiles();
1734
  $args['body']['ImagesCount'] = $imageCount['mainFiles'];
1735
  $args['body']['ThumbsCount'] = $imageCount['totalFiles'] - $imageCount['mainFiles'];
1736
  $argsStr .= "&DomainCheck={$args['body']['DomainCheck']}&Info={$args['body']['Info']}&ImagesCount={$imageCount['mainFiles']}&ThumbsCount={$args['body']['ThumbsCount']}";
@@ -1814,7 +2038,7 @@ class WPShortPixel {
1814
  }
1815
 
1816
  if ( ( $data->APICallsMade + $data->APICallsMadeOneTime ) < ( $data->APICallsQuota + $data->APICallsQuotaOneTime ) ) //reset quota exceeded flag -> user is allowed to process more images.
1817
- $this->_settings->quotaExceeded = 0;
1818
  else
1819
  $this->_settings->quotaExceeded = 1;//activate quota limiting
1820
 
@@ -1839,16 +2063,31 @@ class WPShortPixel {
1839
  "DomainCheck" => (isset($data->DomainCheck) ? $data->DomainCheck : null)
1840
  );
1841
  }
 
 
 
 
 
 
 
 
 
1842
 
1843
  public function generateCustomColumn( $column_name, $id, $extended = false ) {
1844
  if( 'wp-shortPixel' == $column_name ) {
1845
 
 
 
 
 
 
 
 
1846
  $data = wp_get_attachment_metadata($id);
1847
- $file = get_attached_file($id);
1848
- $fileExtension = strtolower(substr($file,strrpos($file,".")+1));
1849
  $invalidKey = !$this->_settings->verifiedKey;
1850
  $quotaExceeded = $this->_settings->quotaExceeded;
1851
- $renderData = array("id" => $id, "showActions" => current_user_can( 'manage_options' ));
1852
 
1853
  if($invalidKey) { //invalid key - let the user first register and only then
1854
  $renderData['status'] = 'invalidKey';
@@ -1874,27 +2113,30 @@ class WPShortPixel {
1874
  }
1875
 
1876
  if(is_numeric($data['ShortPixelImprovement'])) { //already optimized
 
 
1877
  $renderData['status'] = $fileExtension == "pdf" ? 'pdfOptimized' : 'imgOptimized';
1878
  $renderData['percent'] = $data['ShortPixelImprovement'];
1879
  $renderData['bonus'] = ($data['ShortPixelImprovement'] < 5);
1880
- $renderData['backup'] = $this->getBackupFolder(get_attached_file($id)) ;
1881
  $renderData['type'] = isset($data['ShortPixel']['type']) ? $data['ShortPixel']['type'] : '';
1882
- $sizes = isset($data['sizes']) ? count($data['sizes']) : 0;
1883
- $renderData['thumbsTotal'] = $sizes;
1884
- $renderData['thumbsOpt'] = isset($data['ShortPixel']['thumbsOpt']) ? $data['ShortPixel']['thumbsOpt'] : $sizes;
1885
  $renderData['retinasOpt'] = isset($data['ShortPixel']['retinasOpt']) ? $data['ShortPixel']['retinasOpt'] : null;
1886
  $renderData['exifKept'] = isset($data['ShortPixel']['exifKept']) ? $data['ShortPixel']['exifKept'] : null;
1887
  $renderData['date'] = isset($data['ShortPixel']['date']) ? $data['ShortPixel']['date'] : null;
1888
  $renderData['quotaExceeded'] = $quotaExceeded;
1889
  $webP = 0;
1890
  if($extended) {
1891
- if(file_exists(dirname($file) . '/' . basename($file, '.'.pathinfo($file, PATHINFO_EXTENSION)) . '.webp' )){
1892
  $webP++;
1893
  }
1894
  if(isset($data['sizes'])) {
1895
- foreach($data['sizes'] as $size) {
 
1896
  $sizeName = $size['file'];
1897
- if(file_exists(dirname($file) . '/' . basename($sizeName, '.'.pathinfo($sizeName, PATHINFO_EXTENSION)) . '.webp' )){
1898
  $webP++;
1899
  }
1900
  }
@@ -1903,7 +2145,12 @@ class WPShortPixel {
1903
  $renderData['webpCount'] = $webP;
1904
  }
1905
  elseif($data['ShortPixelImprovement'] == __('Optimization N/A','shortpixel-image-optimiser')) { //We don't optimize this
1906
- $renderData['status'] = 'n/a';
 
 
 
 
 
1907
  }
1908
  elseif(isset($meta['ShortPixel']['BulkProcessing'])) { //Scheduled to bulk.
1909
  $renderData['status'] = $quotaExceeded ? 'quotaExceeded' : 'optimizeNow';
@@ -1932,7 +2179,7 @@ class WPShortPixel {
1932
  }
1933
  else { //finally
1934
  $renderData['status'] = $quotaExceeded ? 'quotaExceeded' : 'optimizeNow';
1935
- $sizes = isset($data['sizes']) ? count($data['sizes']) : 0;
1936
  $renderData['thumbsTotal'] = $sizes;
1937
  $renderData['message'] = ($fileExtension == "pdf" ? 'PDF' : 'Image') . ' not processed.';
1938
  }
@@ -2050,12 +2297,13 @@ class WPShortPixel {
2050
 
2051
  static public function isProcessable($ID) {
2052
  $path = get_attached_file($ID);//get the full file PATH
2053
- return self::isProcessablePath($path);
2054
  }
2055
 
2056
  static public function isProcessablePath($path) {
2057
  $pathParts = pathinfo($path);
2058
- if( isset($pathParts['extension']) && in_array(strtolower($pathParts['extension']), self::$PROCESSABLE_EXTENSIONS)) {
 
2059
  return true;
2060
  } else {
2061
  return false;
3
  * Plugin Name: ShortPixel Image Optimizer
4
  * Plugin URI: https://shortpixel.com/
5
  * Description: ShortPixel optimizes images automatically, while guarding the quality of your images. Check your <a href="options-general.php?page=wp-shortpixel" target="_blank">Settings &gt; ShortPixel</a> page on how to start optimizing your image library and make your website load faster.
6
+ * Version: 4.2.6
7
  * Author: ShortPixel
8
  * Author URI: https://shortpixel.com
9
  * Text Domain: shortpixel-image-optimiser
12
 
13
  define('SP_RESET_ON_ACTIVATE', false); //if true TODO set false
14
 
15
+ //define('SHORTPIXEL_DEBUG', true);
16
+
17
  define('SP_AFFILIATE_CODE', '');
18
 
19
+ define('PLUGIN_VERSION', "4.2.6");
20
  define('SP_MAX_TIMEOUT', 10);
21
  define('SP_VALIDATE_MAX_TIMEOUT', 15);
22
  define('SP_BACKUP', 'ShortpixelBackups');
78
  $this->prioQ = new ShortPixelQueue($this, $this->_settings);
79
  $this->view = new ShortPixelView($this);
80
 
81
+ if(!$this->_settings->optimizePdfs) {
82
+ self::$PROCESSABLE_EXTENSIONS = array_diff(self::$PROCESSABLE_EXTENSIONS, array('pdf'));
83
+ }
84
+
85
  define('QUOTA_EXCEEDED', $this->view->getQuotaExceededHTML());
86
 
87
  $this->setDefaultViewModeList();//set default mode as list. only @ first run
109
  // hook on the NextGen gallery list update
110
  add_action('ngg_update_addgallery_page', array( &$this, 'addNextGenGalleriesToCustom'));
111
  }
112
+
113
+ // integration with WP/LR Sync plugin
114
+ add_action( 'wplr_update_media', array( &$this, 'onWpLrUpdateMedia' ), 10, 2);
115
 
116
  //custom hook
117
  add_action( 'shortpixel-optimize-now', array( &$this, 'optimizeNowHook' ), 10, 1);
122
  add_action( 'admin_menu', array( &$this, 'registerAdminPage' ) );
123
 
124
  add_action('wp_ajax_shortpixel_browse_content', array(&$this, 'browseContent'));
125
+ add_action('wp_ajax_shortpixel_get_backup_size', array(&$this, 'getBackupSize'));
126
 
127
  add_action( 'delete_attachment', array( &$this, 'handleDeleteAttachmentInBackup' ) );
128
  add_action( 'load-upload.php', array( &$this, 'handleCustomBulk'));
136
 
137
  //toolbar notifications
138
  add_action( 'admin_bar_menu', array( &$this, 'toolbar_shortpixel_processing'), 999 );
139
+ //deactivate plugin
140
+ add_action( 'admin_post_shortpixel_deactivate_plugin', array(&$this, 'deactivatePlugin'));
141
  }
142
 
143
  //automatic optimization
151
  add_action('admin_action_shortpixel_check_quota', array(&$this, 'handleCheckQuota'));
152
  //This adds the constants used in PHP to be available also in JS
153
  add_action( 'admin_footer', array( &$this, 'shortPixelJS') );
154
+
155
  if($this->_settings->frontBootstrap) {
156
  //also need to have it in the front footer then
157
  add_action( 'wp_footer', array( &$this, 'shortPixelJS') );
207
  ShortPixelQueue::resetBulk();
208
  ShortPixelQueue::resetPrio();
209
  WPShortPixelSettings::onDeactivate();
210
+ }
211
+
212
+ public static function getConflictingPlugins() {
213
+ $conflictPlugins = array(
214
+ 'WP Smush - Image Optimization' => 'wp-smushit/wp-smush.php',
215
+ 'Imagify Image Optimizer' => 'imagify/imagify.php',
216
+ 'Compress JPEG & PNG images (TinyPNG)' => 'tiny-compress-images/tiny-compress-images.php',
217
+ 'Kraken.io Image Optimizer' => 'kraken-image-optimizer/kraken.php',
218
+ 'Optimus - WordPress Image Optimizer' => 'optimus/optimus.php',
219
+ 'EWWW Image Optimizer' => 'ewww-image-optimizer/ewww-image-optimizer.php',
220
+ 'ImageRecycle pdf & image compression' => 'imagerecycle-pdf-image-compression/wp-image-recycle.php',
221
+ 'CheetahO Image Optimizer' => 'cheetaho-image-optimizer/cheetaho.php',
222
+ 'Zara 4 Image Compression' => 'zara-4/zara-4.php',
223
+ 'Prizm Image' => 'prizm-image/wp-prizmimage.php',
224
+ 'CW Image Optimizer' => 'cw-image-optimizer/cw-image-optimizer.php'
225
+ );
226
+ $found = array();
227
+ foreach($conflictPlugins as $name => $path) {
228
+ if(is_plugin_active($path)) {
229
+ $found[] = array('name' => $name, 'path' => $path);
230
+ }
231
+ }
232
+ return $found;
233
+ }
234
 
235
  public function displayAdminNotices() {
236
+ $dismissed = $this->_settings->dismissedNotices ? $this->_settings->dismissedNotices : array();
237
  if(!$this->_settings->verifiedKey) {
 
238
  $now = time();
239
  $act = $this->_settings->activationDate ? $this->_settings->activationDate : $now;
240
  if($this->_settings->activationNotice && $this->_settings->redirectedSettings >= 2) {
247
  ShortPixelView::displayActivationNotice('3d');
248
  }
249
  }
250
+ if(!isset($dismissed['compat'])) {
251
+ $conflictPlugins = self::getConflictingPlugins();
252
+ if(count($conflictPlugins)) {
253
+ ShortPixelView::displayActivationNotice('compat', $conflictPlugins);
254
+ }
255
+ }
256
  }
257
 
258
  public function dismissAdminNotice() {
305
  STATUS_SKIP: <?php echo ShortPixelAPI::STATUS_SKIP; ?>,
306
  STATUS_NO_KEY: <?php echo ShortPixelAPI::STATUS_NO_KEY; ?>,
307
  STATUS_RETRY: <?php echo ShortPixelAPI::STATUS_RETRY; ?>,
308
+ STATUS_QUEUE_FULL: <?php echo ShortPixelAPI::STATUS_QUEUE_FULL; ?>,
309
+ STATUS_MAINTENANCE: <?php echo ShortPixelAPI::STATUS_MAINTENANCE; ?>,
310
  WP_PLUGIN_URL: '<?php echo plugins_url( '', __FILE__ ); ?>',
311
  WP_ADMIN_URL: '<?php echo admin_url(); ?>',
312
  API_KEY: "<?php echo $this->_settings->apiKey; ?>",
321
  $jsTranslation = array(
322
  'optimizeWithSP' => __( 'Optimize with ShortPixel', 'shortpixel-image-optimiser' ),
323
  'changeMLToListMode' => __( 'In order to access the ShortPixel Optimization actions and info, please change to {0}List View{1}List View{2}Dismiss{3}', 'shortpixel-image-optimiser' ),
324
+ 'alertOnlyAppliesToNewImages' => __( 'This type of optimization will apply to new uploaded images. Images that were already processed will not be re-optimized unless you restart the bulk process.', 'shortpixel-image-optimiser' ),
325
  'areYouSureStopOptimizing' => __( 'Are you sure you want to stop optimizing the folder {0}?', 'shortpixel-image-optimiser' ),
326
  'reducedBy' => __( 'Reduced by', 'shortpixel-image-optimiser' ),
327
  'bonusProcessing' => __( 'Bonus processing', 'shortpixel-image-optimiser' ),
344
 
345
  wp_enqueue_script('jquery.knob.js', plugins_url('/res/js/jquery.knob.js',__FILE__) );
346
  wp_enqueue_script('jquery.tooltip.js', plugins_url('/res/js/jquery.tooltip.js',__FILE__) );
347
+ wp_enqueue_script('punycode.js', plugins_url('/res/js/punycode.js',__FILE__) );
348
  }
349
 
350
  function toolbar_shortpixel_processing( $wp_admin_bar ) {
351
 
352
  $extraClasses = " shortpixel-hide";
353
  /*translators: toolbar icon tooltip*/
354
+ $id = 'short-pixel-notice-toolbar';
355
  $tooltip = __('ShortPixel optimizing...','shortpixel-image-optimiser');
356
  $icon = "shortpixel.png";
357
  $successLink = $link = current_user_can( 'edit_others_posts')? 'upload.php?page=wp-short-pixel-bulk' : 'upload.php';
359
  if($this->prioQ->processing()) {
360
  $extraClasses = " shortpixel-processing";
361
  }
362
+ if($this->_settings->quotaExceeded && !isset($this->_settings->dismissedNotices['exceed'])) {
363
  $extraClasses = " shortpixel-alert shortpixel-quota-exceeded";
364
  /*translators: toolbar icon tooltip*/
365
+ $id = 'short-pixel-notice-exceed';
366
+ $tooltip = '';
367
+ $exceedTooltip = __('ShortPixel quota exceeded. Click for details.','shortpixel-image-optimiser');
368
  //$link = "http://shortpixel.com/login/" . $this->_settings->apiKey;
369
  $link = "options-general.php?page=wp-shortpixel";
370
  //$blank = '_blank';
378
 
379
  $args = array(
380
  'id' => 'shortpixel_processing',
381
+ 'title' => '<div id="' . $id . '" title="' . $tooltip . '" ><img src="'
382
  . plugins_url( 'res/img/'.$icon, __FILE__ ) . '" success-url="' . $successLink . '"><span class="shp-alert">!</span></div>',
383
  'href' => $link,
384
  'meta' => array('target'=> $blank, 'class' => 'shortpixel-toolbar-processing' . $extraClasses)
385
  );
386
  $wp_admin_bar->add_node( $args );
387
+ if($this->_settings->quotaExceeded && !isset($this->_settings->dismissedNotices['exceed'])) {
388
+ $wp_admin_bar->add_node( array(
389
+ 'id' => 'shortpixel_processing-title',
390
+ 'parent' => 'shortpixel_processing',
391
+ 'title' => $exceedTooltip,
392
+ 'href' => $link
393
+ ));
394
+ $wp_admin_bar->add_node( array(
395
+ 'id' => 'shortpixel_processing-dismiss',
396
+ 'parent' => 'shortpixel_processing',
397
+ 'title' => '<div style="text-align: right;">Dismiss</div>',
398
+ 'href' => "#",
399
+ 'meta' => array('onclick'=> 'dismissShortPixelNoticeExceed(event)')
400
+ ));
401
+ }
402
  }
403
 
404
  public function handleCustomBulk() {
494
  $folder = new ShortPixelFolder(array("path" => $galleryPath));
495
  $folderMsg = $this->spMetaDao->saveFolder($folder);
496
  $folderId = $folder->getId();
497
+ //self::log("NG Image Upload: created folder from path $galleryPath : Folder info: " . json_encode($folder));
498
  }
499
  $pathParts = explode('/', trim($imageFsPath));
500
  //Add the main image
612
  //daca n-am adaugat niciuna pana acum, n-are sens sa mai selectez zona asta de id-uri in bulk-ul asta.
613
  $leapStart = $this->prioQ->getStartBulkId();
614
  $crtStartQueryID = $startQueryID = $itemMetaData->post_id - 1; //decrement it so we don't select it again
615
+ $res = WpShortPixelMediaLbraryAdapter::countAllProcessableFiles($this->_settings->optimizePdfs, $leapStart, $crtStartQueryID);
616
  $skippedAlreadyProcessed += $res["mainProcessedFiles"] - $res["mainProc".($this->getCompressionType() == 1 ? "Lossy" : "Lossless")."Files"];
617
  $this->prioQ->setStartBulkId($startQueryID);
618
  } else {
657
  }
658
 
659
  self::log("HIP: 0 Priority Queue: ".json_encode($this->prioQ->get()));
660
+ //self::log("HIP: 0 Bulk running? " . $this->prioQ->bulkRunning() . " START " . $this->_settings->startBulkId . " STOP " . $this->_settings->stopBulkId);
661
 
662
  //1: get 3 ids to process. Take them with priority from the queue
663
  $ids = $this->getFromPrioAndCheck();
665
  if($this->prioQ->bulkRunning()) {
666
  $res = $this->getBulkItemsFromDb();
667
  $bulkItems = $res['items'];
668
+ //merge them into the $ids array based on the ID (the same ID could be in prio also)
669
  if($bulkItems){
670
+ foreach($bulkItems as $bi) {
671
+ $add = true;
672
+ foreach($ids as $pi) {
673
+ if($pi->getType() == ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE && $bi->getId() == $pi->getId()) {
674
+ $add = false;
675
+ }
676
+ }
677
+ $ids[] = $bi;
678
+ }
679
  }
680
  }
681
  }
691
  $ids = array_merge($ids, array_map(array('ShortPixelMetaFacade', 'getNewFromRow'), $customIds));
692
  }
693
  }
694
+ //var_dump($ids);
695
+ //die("za stop 2");
696
 
697
  self::log("HIP: 1 Prio Queue: ".json_encode($this->prioQ->get()));
698
  self::log("HIP: 1 Selected IDs count: ".count($ids));
702
  $itemHandler = $ids[$i];
703
  $tmpMeta = $itemHandler->getMeta();
704
  $compType = ($tmpMeta->getCompressionType() !== null ? $tmpMeta->getCompressionType() : $this->_settings->compressionType);
705
+ try {
706
+ self::log("HIP: 1 sendToProcessing: ".$itemHandler->getId());
707
  $URLsAndPATHs = $this->sendToProcessing($itemHandler, $compType, $tmpMeta->getThumbsTodo());
708
+ //make sure it gets processed even if the user stops the bulk or the bulk skips somehow
709
+ $this->prioQ->enqueue($itemHandler->getId()); //this adds it to the end of the queue instead as first as push does
710
+ if(!count($URLsAndPATHs['PATHs'])) {
711
+ throw new Exception(__('Image files are missing.','shortpixel-image-optimiser'));
712
+ }
713
  } catch(Exception $e) { // Exception("Post metadata is corrupt (No attachment URL)")
714
  $meta = $itemHandler->setError(ShortPixelAPI::ERR_FILE_NOT_FOUND, $e->getMessage());
715
  unset($ids[$i]);
723
  if ($ids === false || count( $ids ) == 0 ){
724
  //if searching, than the script is searching for not processed items and found none yet, should be relaunced
725
  if(isset($res['searching']) && $res['searching']) {
726
+ die(json_encode(array("Status" => ShortPixelAPI::STATUS_RETRY,
727
+ "Message" => __('Searching images to optimize... ','shortpixel-image-optimiser') . $this->prioQ->getStartBulkId() . '->' . $this->prioQ->getStopBulkId() )));
728
  }
729
  //in this case the queue is really empty
730
+ self::log("HIP: 1 STOP BULK");
731
  $bulkEverRan = $this->prioQ->stopBulk();
732
  $avg = $this->getAverageCompression();
733
  $fileCount = $this->_settings->fileCount;
747
  $itemHandler = $ids[0];
748
  $itemId = $itemHandler->getQueuedId();
749
  $result = $this->_apiInterface->processImage($firstUrlAndPaths['URLs'], $firstUrlAndPaths['PATHs'], $itemHandler);
750
+
751
  $result["ImageID"] = $itemId;
752
  $meta = $itemHandler->getMeta();
753
  $result["Filename"] = basename($meta->getPath());
761
  //remove also from the failed list if it failed in the past
762
  $prio = $this->prioQ->removeFromFailed($itemId);
763
  $result["Type"] = $meta->getCompressionType() !== null ? ShortPixelAPI::getCompressionTypeName($meta->getCompressionType()) : '';
764
+ $result["ThumbsTotal"] = $meta->getThumbs() && is_array($meta->getThumbs()) ? WpShortPixelMediaLbraryAdapter::countNonWebpSizes($meta->getThumbs()): 0;
765
+ $result["ThumbsTotal"] -= count($meta->getThumbsMissing());
766
  $result["ThumbsCount"] = $meta->getThumbsOpt()
767
  ? $meta->getThumbsOpt() //below is the fallback for old optimized images that don't have thumbsOpt
768
  : ($this->_settings->processThumbnails ? $result["ThumbsTotal"] : 0);
769
 
770
  $result["RetinasCount"] = $meta->getRetinasOpt();
771
+ $result["BackupEnabled"] = ($this->getBackupFolderAny($meta->getPath(), $meta->getThumbs()) ? true : false);//$this->_settings->backupImages;
772
 
773
  if(!$prio && $itemId <= $this->prioQ->getStartBulkId()) {
774
  $this->advanceBulk($itemId, $result);
809
  $bkThumb = $backupUrl . $urlBkPath . $thumb;
810
  }
811
  if(strlen($thumb)) {
812
+ $uploadsUrl = ShortPixelMetaFacade::getHomeUrl2();
813
  $urlPath = ShortPixelMetaFacade::returnSubDir($meta->getPath(), ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE);
814
  //$urlPath = implode("/", array_slice($filePath, 0, count($filePath) - 1));
815
  $thumb = $uploadsUrl . $urlPath . $thumb;
849
  }
850
  $result["Status"] = ShortPixelAPI::STATUS_SKIP;
851
  $result["Message"] .= __(' Retry limit reached. Skipping file ID ','shortpixel-image-optimiser') . $itemId . ".";
852
+ $itemHandler->setError(isset($result['Code']) ? $result['Code'] : ShortPixelAPI::ERR_INCORRECT_FILE_SIZE, $result["Message"] );
853
  }
854
  else {
855
+ if(isset($result['Code'])) {
856
+ $itemHandler->setError($result['Code'], $result["Message"] );
857
+ }
858
  $itemHandler->incrementRetries();
859
  }
860
  }
874
  $result["CustomImageLink"] = ShortPixelMetaFacade::getHomeUrl() . $meta->getWebPath();
875
  }
876
  }
877
+ elseif($result["Status"] == ShortPixelAPI::STATUS_QUEUE_FULL) {
878
+ //nimic?
879
+ }
880
+ elseif($result["Status"] == ShortPixelAPI::STATUS_MAINTENANCE) {
881
+ //nimic?
882
+ }
883
  elseif ($this->prioQ->isPrio($itemId) && $result["Status"] == ShortPixelAPI::STATUS_QUOTA_EXCEEDED) {
884
  if(!$this->prioQ->skippedCount()) {
885
  $this->prioQ->reverse(); //for the first prio item with quota exceeded, revert the prio queue as probably the bottom ones were processed
926
  $result["BulkMsg"] = $this->bulkProgressMessage($deltaBulkPercent, $minutesRemaining);
927
  }
928
 
929
+
930
+
931
  private function sendToProcessing($itemHandler, $compressionType = false, $onlyThumbs = false) {
932
  $URLsAndPATHs = $this->getURLsAndPATHs($itemHandler, NULL, $onlyThumbs);
933
+
934
+ $meta = $itemHandler->getMeta();
935
+ //find thumbs that are not listed in the metadata and add them in the sizes array
936
+ if($itemHandler->getType() == ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE) {
937
+ $mainFile = $meta->getPath();
938
+
939
+ $foundThumbs = @glob(WpShortPixelMediaLbraryAdapter::thumbsSearchPattern($mainFile));
940
+ //first identify which thumbs are not in the sizes
941
+ $sizes = $meta->getThumbs();
942
+ foreach($foundThumbs as $id => $found) {
943
+ //get the mime-type from one of the thumbs metas
944
+ foreach($sizes as $size) {
945
+ if(pathinfo($mainFile, PATHINFO_EXTENSION) !== pathinfo($size['file'], PATHINFO_EXTENSION)){
946
+ continue;
947
+ }
948
+ $mimeType = $size['mime-type'];
949
+ if($size['file'] === ShortPixelAPI::MB_basename($found)) {
950
+ $foundThumbs[$id] = false;
951
+ }
952
+ }
953
+ }
954
+ // add the unfound ones to the sizes array
955
+ $ind = 1;
956
+ while (isset($sizes[ShortPixelMeta::FOUND_THUMB_PREFIX . str_pad("".$ind, 2, '0', STR_PAD_LEFT)])) $ind++;
957
+ $start = $ind;
958
+ foreach($foundThumbs as $found) {
959
+ if($found !== false) {
960
+ $size = getimagesize($found);
961
+ $sizes[ShortPixelMeta::FOUND_THUMB_PREFIX . str_pad("".$ind, 2, '0', STR_PAD_LEFT)]= array( // it's a file that has no corresponding thumb so it's the WEBP for the main file
962
+ 'file' => ShortPixelAPI::MB_basename($found),
963
+ 'width' => $size[0],
964
+ 'height' => $size[1],
965
+ 'mime-type' => $mimeType
966
+ );
967
+ $ind++;
968
+ }
969
+ }
970
+ if($ind > $start) { // at least one thumbnail added, update
971
+ $meta->setThumbs($sizes);
972
+ $itemHandler->updateMeta($meta);
973
+ $URLsAndPATHs = $this->getURLsAndPATHs($itemHandler, NULL, $onlyThumbs);
974
+ }
975
+ }
976
+
977
+ //find any missing thumbs files and mark them as such
978
+ if( isset($URLsAndPATHs['sizesMissing']) && count($URLsAndPATHs['sizesMissing'])
979
+ && (null === $meta->getThumbsMissing() || count(array_diff_key($meta->getThumbsMissing(), array_merge($URLsAndPATHs['sizesMissing'], $meta->getThumbsMissing()))))) {
980
+ //fix missing thumbs in the metadata before sending to processing
981
+ $meta->setThumbsMissing($URLsAndPATHs['sizesMissing']);
982
+ $itemHandler->updateMeta();
983
+ }
984
+ //die(var_dump($itemHandler));
985
+ $refresh = $meta->getStatus() === ShortPixelAPI::ERR_INCORRECT_FILE_SIZE;
986
  //echo("URLS: "); die(var_dump($URLsAndPATHs));
987
  $this->_apiInterface->doRequests($URLsAndPATHs['URLs'], false, $itemHandler,
988
+ $compressionType === false ? $this->_settings->compressionType : $compressionType, $refresh);//send a request, do NOT wait for response
989
  $itemHandler->setWaitingProcessing();
990
  //$meta = wp_get_attachment_metadata($ID);
991
  //$meta['ShortPixel']['WaitingProcessing'] = true;
1013
  case "C-":
1014
  throw new Exception("HandleManualOptimization for custom images not implemented");
1015
  default:
 
 
 
1016
  $this->optimizeNowHook(intval($imageId));
1017
  break;
1018
  }
1039
  die(json_encode($ret));
1040
  }
1041
 
1042
+ //WP/LR Sync plugin integration
1043
+ public function onWpLrUpdateMedia($imageId, $galleryIdsUnused) {
1044
+ $meta = wp_get_attachment_metadata($imageId);
1045
+ if(is_array($meta)) {
1046
+ unset($meta['ShortPixel']);
1047
+ $meta['ShortPixel'] = array();
1048
+ $meta['ShortPixel']['WaitingProcessing'] = true;
1049
+ $this->prioQ->push($imageId);
1050
+ wp_update_attachment_metadata($imageId, $meta);
1051
+ }
1052
+ }
1053
+
1054
+
1055
  //save error in file's meta data
1056
  public function handleError($ID, $result)
1057
  {
1061
  }
1062
 
1063
  public function getBackupFolder($file) {
1064
+ if(realpath($file)) {
1065
+ $file = realpath($file); //found cases when $file contains for example /wp/../wp-content - clean it up
1066
+ }
1067
  $fileExtension = strtolower(substr($file,strrpos($file,".")+1));
1068
  $SubDir = ShortPixelMetaFacade::returnSubDir($file, ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE);
1069
+ $SubDirOld = ShortPixelMetaFacade::returnSubDirOld($file, ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE);
1070
 
1071
+ if ( !file_exists(SP_BACKUP_FOLDER . '/' . $SubDir . ShortPixelAPI::MB_basename($file))
1072
+ && !file_exists(SP_BACKUP_FOLDER . '/' . date("Y") . "/" . date("m") . "/" . ShortPixelAPI::MB_basename($file)) ) {
1073
+ $SubDir = $SubDirOld; //maybe the folder was saved with the old method that returned the full path if the wp-content was not inside the root of the site.
1074
+ }
1075
+ if ( !file_exists(SP_BACKUP_FOLDER . '/' . $SubDir . ShortPixelAPI::MB_basename($file))
1076
+ && !file_exists(SP_BACKUP_FOLDER . '/' . date("Y") . "/" . date("m") . "/" . ShortPixelAPI::MB_basename($file)) ) {
1077
+ $SubDir = trailingslashit(substr(dirname($file), 1)); //try this too
1078
+ }
1079
  //sometimes the month of original file and backup can differ
1080
  if ( !file_exists(SP_BACKUP_FOLDER . '/' . $SubDir . ShortPixelAPI::MB_basename($file)) ) {
1081
  $SubDir = date("Y") . "/" . date("m") . "/";
1086
  return SP_BACKUP_FOLDER . '/' . $SubDir;
1087
  }
1088
 
1089
+ public function getBackupFolderAny($file, $thumbs) {
1090
+ if(!file_exists($file)) {
1091
+ //try with the thumbnails
1092
+ if(isset($thumbs)) foreach($thumbs as $size) {
1093
+ $backup = $this->getBackupFolder(trailingslashit(dirname($file)) . $size['file']);
1094
+ if($backup) return $backup;
1095
+ }
1096
+ } else {
1097
+ return $this->getBackupFolder($file);
1098
+ }
1099
+ }
1100
+
1101
  protected function setFilePerms($file) {
1102
  //die(getenv('USERNAME') ? getenv('USERNAME') : getenv('USER'));
1103
+ if(strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
1104
+ //on *nix platforms check also the owner
1105
+ $owner = fileowner($file);
1106
+ if($owner !== false && $owner != posix_getuid()) { //files with changed owner
1107
+ return false;
1108
+ }
1109
+ }
1110
  $perms = @fileperms($file);
1111
  if(!($perms & 0x0100) || !($perms & 0x0080)) {
1112
  if(!@chmod($file, $perms | 0x0100 | 0x0080)) {
1117
  }
1118
 
1119
  protected function doRestore($attachmentID, $meta = null) {
1120
+ $file = $origFile = get_attached_file($attachmentID);
1121
  if(!$meta) {
1122
  $meta = wp_get_attachment_metadata($attachmentID);
1123
  }
1124
  $pathInfo = pathinfo($file);
1125
 
1126
+ $bkFolder = $this->getBackupFolderAny($file, $meta["sizes"]);
1127
+ $bkFile = trailingslashit($bkFolder) . ShortPixelAPI::MB_basename($file);
1128
+
 
 
 
 
 
1129
  //first check if the file is readable by the current user - otherwise it will be unaccessible for the web browser
1130
  // - collect the thumbs paths in the process
1131
+ $bkCount = 0; $main = false;
1132
+ if(file_exists($bkFile)) {
1133
+ if(!$this->setFilePerms($bkFile) || (file_exists($file) && !$this->setFilePerms($file)) ) {
1134
+ return false;
1135
+ }
1136
+ $bkCount++;
1137
+ $main = true;
1138
+ }
1139
  $thumbsPaths = array();
1140
  if( !empty($meta['file']) && is_array($meta["sizes"]) ) {
1141
  foreach($meta["sizes"] as $size => $imageData) {
1142
+ $dest = $pathInfo['dirname'] . '/' . $imageData['file'];
1143
+ $source = trailingslashit($bkFolder) . $imageData['file'];
1144
  if(!file_exists($source)) continue; // if thumbs were not optimized, then the backups will not be there.
1145
+ if(!$this->setFilePerms($source) || (file_exists($dest) && !$this->setFilePerms($dest))) {
1146
+ return false;
1147
+ }
1148
+ $bkCount++;
1149
+ $thumbsPaths[$source] = $dest;
1150
  }
1151
  }
1152
+ if(!$bkCount) {
1153
+ return false;
1154
+ }
1155
+
1156
  if($bkFolder) {
1157
  try {
1158
  //main file
1159
+ if($main) {
1160
+ $this->renameWithRetina($bkFile, $file);
1161
+ }
1162
  //getSize to update meta if image was resized by ShortPixel
1163
+ $width = false;
1164
+ if(file_exists($file)) {
1165
+ $size = getimagesize($file);
1166
+ $width = $size[0];
1167
+ $height = $size[1];
1168
+ }
1169
 
1170
  //overwriting thumbnails
1171
  foreach($thumbsPaths as $source => $destination) {
1185
  }
1186
  wp_update_attachment_metadata($ID, $crtMeta);
1187
  }
1188
+ unset($meta["ShortPixelImprovement"]);
1189
+ unset($meta['ShortPixel']);
1190
+
1191
  } catch(Exception $e) {
1192
  //what to do, what to do?
1193
  return false;
1277
  $meta = wp_get_attachment_metadata($ID);
1278
  //die(var_dump($meta));
1279
  if( isset($meta['ShortPixelImprovement'])
1280
+ && isset($meta['sizes']) && WpShortPixelMediaLbraryAdapter::countNonWebpSizes($meta['sizes'])
1281
  && ( !isset($meta['ShortPixel']['thumbsOpt']) || $meta['ShortPixel']['thumbsOpt'] == 0)) { //optimized without thumbs, thumbs exist
1282
  $meta['ShortPixel']['thumbsTodo'] = true;
1283
  wp_update_attachment_metadata($ID, $meta);
1328
  }
1329
  }
1330
  }
1331
+
1332
+ public function deactivatePlugin() {
1333
+ if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'sp_deactivate_plugin_nonce' ) ) {
1334
+ wp_nonce_ays( '' );
1335
+ }
1336
+ deactivate_plugins( $_GET['plugin'] );
1337
+ wp_safe_redirect( wp_get_referer() );
1338
+ die();
1339
+ }
1340
 
1341
  public function checkQuotaAndAlert($quotaData = null, $recheck = false) {
1342
  if(!$quotaData) {
1346
  return $quotaData;
1347
  }
1348
  //$tempus = microtime(true);
1349
+ $imageCount = WpShortPixelMediaLbraryAdapter::countAllProcessableFiles($this->_settings->optimizePdfs);
1350
 
1351
  $this->_settings->currentTotalFiles = $imageCount['totalFiles'];
1352
 
1371
 
1372
  ?><script>var shortPixelQuotaExceeded = 0;</script><?php
1373
  }
1374
+ else {
1375
  $this->view->displayQuotaExceededAlert($quotaData, self::getAverageCompression(), $recheck);
1376
  ?><script>var shortPixelQuotaExceeded = 1;</script><?php
1377
  }
1419
  <?php
1420
  $customMediaListTable->display();
1421
  //push to the processing list the pending ones, just in case
1422
+ //$count = $this->spMetaDao->getCustomMetaCount();
1423
  foreach ($items as $item) {
1424
  if($item->status == 1){
1425
  $this->prioQ->push(ShortPixelMetaFacade::queuedId(ShortPixelMetaFacade::CUSTOM_TYPE, $item->id));
1471
  } else {
1472
  $this->_settings->processThumbnails = 0;
1473
  }
1474
+ //clean the custom files errors in order to process them again
1475
+ if($this->_settings->hasCustomFolders) {
1476
+ $this->spMetaDao->resetFailed();
1477
+ }
1478
+
1479
  $this->prioQ->startBulk();
1480
  $this->_settings->customBulkPaused = 0;
1481
  self::log("BULK: Start: " . $this->prioQ->getStartBulkId() . ", stop: " . $this->prioQ->getStopBulkId() . " PrioQ: "
1571
 
1572
 
1573
  //parse all images and set the right flag that the image has no backup
1574
+ /* this is obsolete as the backup exists decision is taken on verfication of the actual backup files
1575
  foreach($attachments as $attachment)
1576
  {
1577
+ if(self::isProcessable($attachment->ID) == false) continue;
1578
 
1579
  $meta = wp_get_attachment_metadata($attachment->ID);
1580
  $meta['ShortPixel']['NoBackup'] = true;
1581
  wp_update_attachment_metadata($attachment->ID, $meta);
1582
  }
1583
+ */
1584
+
1585
  //delete the actual files on disk
1586
  $this->deleteDir(SP_BACKUP_FOLDER);//call a recursive function to empty files and sub-dirs in backup dir
1587
  }
1590
  public function backupFolderIsEmpty() {
1591
  return count(scandir(SP_BACKUP_FOLDER)) > 2 ? false : true;
1592
  }
1593
+
1594
+ public function getBackupSize() {
1595
+ if ( !current_user_can( 'manage_options' ) ) {
1596
+ wp_die(__('You do not have sufficient permissions to access this page.','shortpixel-image-optimiser'));
1597
+ }
1598
+ die(self::formatBytes(self::folderSize(SP_BACKUP_FOLDER)));
1599
+ }
1600
 
1601
  public function browseContent() {
1602
+ if ( !current_user_can( 'manage_options' ) ) {
1603
  wp_die(__('You do not have sufficient permissions to access this page.','shortpixel-image-optimiser'));
1604
  }
1605
 
1832
 
1833
  $this->_settings->createWebp = (isset($_POST['createWebp']) ? 1: 0);
1834
  $this->_settings->optimizeRetina = (isset($_POST['optimizeRetina']) ? 1: 0);
1835
+ $this->_settings->optimizePdfs = (isset($_POST['optimizePdfs']) ? 1: 0);
1836
  $this->_settings->frontBootstrap = (isset($_POST['frontBootstrap']) ? 1: 0);
1837
  $this->_settings->autoMediaLibrary = (isset($_POST['autoMediaLibrary']) ? 1: 0);
1838
 
1886
  if (is_numeric($quotaData['APICallsQuota'])) {
1887
  $quotaData['APICallsQuota'] .= "/month";
1888
  }
 
1889
  $remainingImages = $quotaData['APICallsRemaining'];
1890
  $remainingImages = ( $remainingImages < 0 ) ? 0 : number_format($remainingImages);
1891
  $totalCallsMade = number_format($quotaData['APICallsMadeNumeric'] + $quotaData['APICallsMadeOneTimeNumeric']);
1892
 
1893
+ $resources = wp_remote_post($this->_settings->httpProto . "://shortpixel.com/resources-frag");
1894
  if(is_wp_error( $resources )) {
1895
  $resources = array();
1896
  }
1897
  $this->view->displaySettings($showApiKey, $editApiKey,
1898
  $quotaData, $notice, $resources, $averageCompression, $savedSpace, $savedBandwidth, $remainingImages,
1899
+ $totalCallsMade, $fileCount, null /*folder size now on AJAX*/, $customFolders,
1900
  $folderMsg, $folderMsg ? $addedFolder : false, isset($_POST['saveAdv']));
1901
  } else {
1902
  $this->view->displaySettings($showApiKey, $editApiKey, $quotaData, $notice);
1954
  if($validate) {
1955
  $args['body']['DomainCheck'] = get_site_url();
1956
  $args['body']['Info'] = get_bloginfo('version') . '|' . phpversion();
1957
+ $imageCount = WpShortPixelMediaLbraryAdapter::countAllProcessableFiles($this->_settings->optimizePdfs);
1958
  $args['body']['ImagesCount'] = $imageCount['mainFiles'];
1959
  $args['body']['ThumbsCount'] = $imageCount['totalFiles'] - $imageCount['mainFiles'];
1960
  $argsStr .= "&DomainCheck={$args['body']['DomainCheck']}&Info={$args['body']['Info']}&ImagesCount={$imageCount['mainFiles']}&ThumbsCount={$args['body']['ThumbsCount']}";
2038
  }
2039
 
2040
  if ( ( $data->APICallsMade + $data->APICallsMadeOneTime ) < ( $data->APICallsQuota + $data->APICallsQuotaOneTime ) ) //reset quota exceeded flag -> user is allowed to process more images.
2041
+ $this->resetQuotaExceeded();
2042
  else
2043
  $this->_settings->quotaExceeded = 1;//activate quota limiting
2044
 
2063
  "DomainCheck" => (isset($data->DomainCheck) ? $data->DomainCheck : null)
2064
  );
2065
  }
2066
+
2067
+ public function resetQuotaExceeded() {
2068
+ if( $this->_settings->quotaExceeded == 1) {
2069
+ $dismissed = $this->_settings->dismissedNotices;
2070
+ unset($dismissed['exceed']);
2071
+ $this->_settings->dismissedNotices = $dismissed;
2072
+ }
2073
+ $this->_settings->quotaExceeded = 0;
2074
+ }
2075
 
2076
  public function generateCustomColumn( $column_name, $id, $extended = false ) {
2077
  if( 'wp-shortPixel' == $column_name ) {
2078
 
2079
+ $file = get_attached_file($id);
2080
+ if(!self::isProcessablePath($file)) {
2081
+ $renderData['status'] = 'n/a';
2082
+ $this->view->renderCustomColumn($id, $renderData, $extended);
2083
+ return;
2084
+ }
2085
+
2086
  $data = wp_get_attachment_metadata($id);
2087
+ $fileExtension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
 
2088
  $invalidKey = !$this->_settings->verifiedKey;
2089
  $quotaExceeded = $this->_settings->quotaExceeded;
2090
+ $renderData = array("id" => $id, "showActions" => (current_user_can( 'manage_options' ) || current_user_can( 'upload_files' ) || current_user_can( 'edit_posts' )));
2091
 
2092
  if($invalidKey) { //invalid key - let the user first register and only then
2093
  $renderData['status'] = 'invalidKey';
2113
  }
2114
 
2115
  if(is_numeric($data['ShortPixelImprovement'])) { //already optimized
2116
+ $sizesCount = isset($data['sizes']) ? WpShortPixelMediaLbraryAdapter::countNonWebpSizes($data['sizes']) : 0;
2117
+
2118
  $renderData['status'] = $fileExtension == "pdf" ? 'pdfOptimized' : 'imgOptimized';
2119
  $renderData['percent'] = $data['ShortPixelImprovement'];
2120
  $renderData['bonus'] = ($data['ShortPixelImprovement'] < 5);
2121
+ $renderData['backup'] = $this->getBackupFolderAny(get_attached_file($id), $sizesCount? $data['sizes'] : array());
2122
  $renderData['type'] = isset($data['ShortPixel']['type']) ? $data['ShortPixel']['type'] : '';
2123
+ $renderData['thumbsTotal'] = $sizesCount;
2124
+ $renderData['thumbsOpt'] = isset($data['ShortPixel']['thumbsOpt']) ? $data['ShortPixel']['thumbsOpt'] : $sizesCount;
2125
+ $renderData['thumbsMissing'] = isset($data['ShortPixel']['thumbsMissing']) ? $data['ShortPixel']['thumbsMissing'] : array();
2126
  $renderData['retinasOpt'] = isset($data['ShortPixel']['retinasOpt']) ? $data['ShortPixel']['retinasOpt'] : null;
2127
  $renderData['exifKept'] = isset($data['ShortPixel']['exifKept']) ? $data['ShortPixel']['exifKept'] : null;
2128
  $renderData['date'] = isset($data['ShortPixel']['date']) ? $data['ShortPixel']['date'] : null;
2129
  $renderData['quotaExceeded'] = $quotaExceeded;
2130
  $webP = 0;
2131
  if($extended) {
2132
+ if(file_exists(dirname($file) . '/' . basename($file, '.'.$fileExtension) . '.webp' )){
2133
  $webP++;
2134
  }
2135
  if(isset($data['sizes'])) {
2136
+ foreach($data['sizes'] as $key => $size) {
2137
+ if (strpos($key, ShortPixelMeta::WEBP_THUMB_PREFIX) === 0) continue;
2138
  $sizeName = $size['file'];
2139
+ if(file_exists(dirname($file) . '/' . basename($sizeName, '.'.$fileExtension) . '.webp' )){
2140
  $webP++;
2141
  }
2142
  }
2145
  $renderData['webpCount'] = $webP;
2146
  }
2147
  elseif($data['ShortPixelImprovement'] == __('Optimization N/A','shortpixel-image-optimiser')) { //We don't optimize this
2148
+ if('pdf' === $fileExtension && $this->_settings->optimizePdfs) {
2149
+ $renderData['status'] = $quotaExceeded ? 'quotaExceeded' : 'optimizeNow';
2150
+ $renderData['message'] = 'PDF not processed.';
2151
+ } else {
2152
+ $renderData['status'] = 'n/a';
2153
+ }
2154
  }
2155
  elseif(isset($meta['ShortPixel']['BulkProcessing'])) { //Scheduled to bulk.
2156
  $renderData['status'] = $quotaExceeded ? 'quotaExceeded' : 'optimizeNow';
2179
  }
2180
  else { //finally
2181
  $renderData['status'] = $quotaExceeded ? 'quotaExceeded' : 'optimizeNow';
2182
+ $sizes = isset($data['sizes']) ? WpShortPixelMediaLbraryAdapter::countNonWebpSizes($data['sizes']) : 0;
2183
  $renderData['thumbsTotal'] = $sizes;
2184
  $renderData['message'] = ($fileExtension == "pdf" ? 'PDF' : 'Image') . ' not processed.';
2185
  }
2297
 
2298
  static public function isProcessable($ID) {
2299
  $path = get_attached_file($ID);//get the full file PATH
2300
+ return $path ? self::isProcessablePath($path) : false;
2301
  }
2302
 
2303
  static public function isProcessablePath($path) {
2304
  $pathParts = pathinfo($path);
2305
+ $ext = $pathParts['extension'];
2306
+ if( isset($ext) && in_array(strtolower($ext), self::$PROCESSABLE_EXTENSIONS)) {
2307
  return true;
2308
  } else {
2309
  return false;