ShortPixel Image Optimizer - Version 2.0.0

Version Description

  • SP plugin uses API v2 and the processing speed is significantly improved
Download this release

Release Info

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

Code changes from version 1.6.10 to 2.0.0

Files changed (3) hide show
  1. readme.txt +5 -1
  2. shortpixel_api.php +191 -170
  3. wp-shortpixel.php +610 -558
readme.txt CHANGED
@@ -4,7 +4,7 @@ Contributors: AlexSP
4
  Tags: picture, optimization, image editor, pngout, upload speed, shortpixel, compression, jpegmini, webp, lossless, cwebp, media, tinypng, jpegtran,image, image optimisation, shrink, picture, photo, optimize photos, compress, performance, tinypng, crunch, pngquant, attachment, optimize, pictures,fast, images, image files, image quality, lossy, upload, kraken, resize, seo, smushit, optipng, kraken image optimizer, ewww, photo optimization, gifsicle, image optimizer, images, krakenio, png, gmagick, image optimize
5
  Requires at least: 3.0.0 or higher
6
  Tested up to: 4.1
7
- Stable tag: 1.6.10
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -107,6 +107,10 @@ The ShortPixel team is here to help. <a href="https://shortpixel.com/contact">Co
107
 
108
  == Changelog ==
109
 
 
 
 
 
110
  = 1.6.10 =
111
 
112
  * Corrected a bug affecting option saving for some of the users.
4
  Tags: picture, optimization, image editor, pngout, upload speed, shortpixel, compression, jpegmini, webp, lossless, cwebp, media, tinypng, jpegtran,image, image optimisation, shrink, picture, photo, optimize photos, compress, performance, tinypng, crunch, pngquant, attachment, optimize, pictures,fast, images, image files, image quality, lossy, upload, kraken, resize, seo, smushit, optipng, kraken image optimizer, ewww, photo optimization, gifsicle, image optimizer, images, krakenio, png, gmagick, image optimize
5
  Requires at least: 3.0.0 or higher
6
  Tested up to: 4.1
7
+ Stable tag: 2.0.0
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
107
 
108
  == Changelog ==
109
 
110
+ = 2.0.0 =
111
+
112
+ * SP plugin uses API v2 and the processing speed is significantly improved
113
+
114
  = 1.6.10 =
115
 
116
  * Corrected a bug affecting option saving for some of the users.
shortpixel_api.php CHANGED
@@ -1,208 +1,229 @@
1
  <?php
2
  if ( !function_exists( 'download_url' ) ) {
3
- require_once( ABSPATH . 'wp-admin/includes/file.php' );
4
  }
5
 
6
  class shortpixel_api {
7
 
8
- private $_apiKey = '';
9
- private $_compressionType = '';
10
- private $_maxAttempts = 10;
11
- private $_apiEndPoint = 'https://api.shortpixel.com/v1/reducer.php';
12
 
13
- public function setCompressionType($compressionType) {
14
- $this->_compressionType = $compressionType;
15
- }
16
-
17
- public function getCompressionType() {
18
- return $this->_compressionType;
19
- }
20
 
21
- public function setApiKey($apiKey) {
22
- $this->_apiKey = $apiKey;
23
- }
24
 
25
- public function getApiKey() {
26
- return $this->_apiKey;
27
- }
28
 
29
- public function __construct($apiKey, $compressionType) {
30
- $this->_apiKey = $apiKey;
31
- $this->setCompressionType($compressionType);
32
 
33
- add_action('processImageAction', array(&$this, 'processImageAction'), 10, 4);
34
- }
 
35
 
36
- public function processImageAction($url, $filePath, $ID, $time) {
37
- $this->processImage($url, $filePath, $ID, $time);
38
- }
39
 
40
- public function doRequests($url, $filePath, $ID = null) {
41
- $requestURL = $this->_apiEndPoint . '?key=' . $this->_apiKey . '&lossy=' . $this->_compressionType . '&url=';
42
- $requestURL = $requestURL . urlencode($url);
43
 
44
- $args = array('timeout'=> SP_MAX_TIMEOUT, 'sslverify' => false);
45
- $response = wp_remote_get($requestURL, $args);
46
 
47
- if(is_object($response) && get_class($response) == 'WP_Error') {
48
- return false;
49
- }
50
-
51
- return $response;
52
  }
53
 
54
- public function parseResponse($response) {
55
- $data = $response['body'];
56
- $data = str_replace('Warning: Division by zero in /usr/local/important/web/api.shortpixel.com/lib/functions.php on line 33', '', $data);
57
- $data = $this->parseJSON($data);
58
- return $data;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
60
 
61
- //handles the processing of the image using the ShortPixel API
62
- public function processImage($url, $filePath, $ID = null, $startTime = 0) {
63
- if($startTime == 0) { $startTime = time(); }
64
- if(time() - $startTime > MAX_EXECUTION_TIME) {
65
- $meta = wp_get_attachment_metadata($ID);
66
- $meta['ShortPixelImprovement'] = 'Could not determine compression';
67
- unset($meta['ShortPixel']['WaitingProcessing']);
68
- wp_update_attachment_metadata($ID, $meta);
69
- return 'Could not determine compression';
70
- }
71
-
72
- $response = $this->doRequests($url, $filePath, $ID);
73
-
74
- if(!$response) return $response;
75
 
76
- if($response['response']['code'] != 200) {
77
- WPShortPixel::log("Response 200 OK");
78
- printf('Web service did not respond. Please try again later.');
79
- return false;
80
- }
81
-
82
- $data = $this->parseResponse($response);
83
-
84
- switch($data->Status->Code) {
85
- case 1:
86
- //handle image has been scheduled
87
- sleep(1);
88
- return $this->processImage($url, $filePath, $ID, $startTime);
89
- break;
90
- case 2:
91
- //handle image has been processed
92
- $this->handleSuccess($data, $url, $filePath, $ID);
93
- break;
94
- case -16:
95
- return 'Quota exceeded</br>';
96
- case -17:
97
- return 'Wrong API Key</br>';
98
- case -302:
99
- return 'Images does not exists</br>';
100
- default:
101
- //handle error
102
- return $data->Status->Message;
103
- }
104
 
105
- return $data;
 
 
 
106
  }
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
- public function handleSuccess($callData, $url, $filePath, $ID) {
110
-
111
- if($this->_compressionType) {
112
- //lossy
113
- $correctFileSize = $callData->LossySize;
114
- $tempFile = download_url(urldecode($callData->LossyURL));
115
- if(is_wp_error( $tempFile )) {
116
- $tempFile = download_url(str_replace('https://', 'http://', urldecode($callData->LossyURL)));
117
- }
118
- } else {
119
- //lossless
120
- $correctFileSize = $callData->LoselessSize;
121
- $tempFile = download_url(urldecode($callData->LosslessURL));
122
- if(is_wp_error( $tempFile )) {
123
- $tempFile = download_url(str_replace('https://', 'http://', urldecode($callData->LosslessURL)));
124
- }
125
- }
 
 
 
 
126
 
127
- if ( is_wp_error( $tempFile ) ) {
128
- @unlink($tempFile);
129
- return sprintf("Error downloading file (%s)", $tempFile->get_error_message());
130
- die;
131
- }
132
 
133
- //check response so that download is OK
134
- if(filesize($tempFile) != $correctFileSize) {
135
- return sprintf("Error downloading file - incorrect file size");
136
- die;
137
- }
138
 
139
- if (!file_exists($tempFile)) {
140
- return sprintf("Unable to locate downloaded file (%s)", $tempFile);
141
- die;
142
- }
143
 
144
- //if backup is enabled
145
- if(get_option('wp-short-backup_images')) {
146
 
147
- if(!file_exists(SP_BACKUP_FOLDER) && !mkdir(SP_BACKUP_FOLDER, 0777, true)) {
148
- return sprintf("Backup folder does not exist and it could not be created");
149
- }
150
 
151
- $source = $filePath;
152
- $destination = SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . basename($source);
153
 
154
- if(is_writable(SP_BACKUP_FOLDER)) {
155
- if(!file_exists($destination)) {
156
- @copy($source, $destination);
157
- }
158
- } else {
159
- return sprintf("Backup folder exists but is not writable");
160
- }
161
  }
 
 
 
 
162
 
163
- @unlink( $filePath );
164
- $success = @rename( $tempFile, $filePath );
165
-
166
- if (!$success) {
167
- $copySuccess = copy($tempFile, $filePath);
168
- unlink($tempFile);
169
- }
170
 
171
- if($success || $copySuccess) {
172
- //update statistics
173
- if(isset($callData->LossySize)) {
174
- $savedSpace = $callData->OriginalSize - $callData->LossySize;
175
- } else {
176
- $savedSpace = $callData->OriginalSize - $callData->LoselessSize;
177
- }
178
-
179
- update_option(
180
- 'wp-short-pixel-savedSpace',
181
- get_option('wp-short-pixel-savedSpace') + $savedSpace
182
- );
183
- $averageCompression = get_option('wp-short-pixel-averageCompression') * get_option('wp-short-pixel-fileCount');
184
- $averageCompression += $callData->PercentImprovement;
185
- $averageCompression = $averageCompression / (get_option('wp-short-pixel-fileCount') + 1);
186
- update_option('wp-short-pixel-averageCompression', $averageCompression);
187
- update_option('wp-short-pixel-fileCount', get_option('wp-short-pixel-fileCount')+1);
188
-
189
- //update metadata
190
- if(isset($ID)) {
191
- $meta = wp_get_attachment_metadata($ID);
192
- $meta['ShortPixelImprovement'] = $callData->PercentImprovement;
193
- wp_update_attachment_metadata($ID, $meta);
194
- }
195
- }
196
  }
197
 
198
- public function parseJSON($data) {
199
- if ( function_exists('json_decode') ) {
200
- $data = json_decode( $data );
201
- } else {
202
- require_once( 'JSON/JSON.php' );
203
- $json = new Services_JSON( );
204
- $data = $json->decode( $data );
205
- }
206
- return $data;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  }
 
 
208
  }
1
  <?php
2
  if ( !function_exists( 'download_url' ) ) {
3
+ require_once( ABSPATH . 'wp-admin/includes/file.php' );
4
  }
5
 
6
  class shortpixel_api {
7
 
8
+ private $_apiKey = '';
9
+ private $_compressionType = '';
10
+ private $_maxAttempts = 10;
11
+ private $_apiEndPoint = 'https://api.shortpixel.com/v2/reducer.php';
12
 
13
+ public function setCompressionType($compressionType) {
14
+ $this->_compressionType = $compressionType;
15
+ }
 
 
 
 
16
 
17
+ public function getCompressionType() {
18
+ return $this->_compressionType;
19
+ }
20
 
21
+ public function setApiKey($apiKey) {
22
+ $this->_apiKey = $apiKey;
23
+ }
24
 
25
+ public function getApiKey() {
26
+ return $this->_apiKey;
27
+ }
28
 
29
+ public function __construct($apiKey, $compressionType) {
30
+ $this->_apiKey = $apiKey;
31
+ $this->setCompressionType($compressionType);
32
 
33
+ add_action('processImageAction', array(&$this, 'processImageAction'), 10, 4);
34
+ }
 
35
 
36
+ public function processImageAction($url, $filePath, $ID, $time) {
37
+ $this->processImage($url, $filePath, $ID, $time);
38
+ }
39
 
40
+ public function doRequests($url, $filePath, $ID = null) {
41
+ $response = $this->doBulkRequest(array($url), true);
42
 
43
+ if(is_object($response) && get_class($response) == 'WP_Error') {
44
+ return false;
 
 
 
45
  }
46
 
47
+ return $response;
48
+ }
49
+
50
+ public function doBulkRequest($imageList = array(), $blocking = false) {
51
+ if(!is_array($imageList)) return false;
52
+
53
+ $requestParameters = array(
54
+ 'key' => $this->_apiKey,
55
+ 'lossy' => $this->_compressionType,
56
+ 'urllist' => $imageList
57
+ );
58
+
59
+ $response = wp_remote_post($this->_apiEndPoint, array(
60
+ 'method' => 'POST',
61
+ 'timeout' => 45,
62
+ 'redirection' => 5,
63
+ 'httpversion' => '1.0',
64
+ 'blocking' => $blocking,
65
+ 'headers' => array(),
66
+ 'body' => json_encode($requestParameters),
67
+ 'cookies' => array()
68
+ ));
69
+
70
+ return $response;
71
+
72
+ }
73
+
74
+ public function parseResponse($response) {
75
+ $data = $response['body'];
76
+ $data = str_replace('Warning: Division by zero in /usr/local/important/web/api.shortpixel.com/lib/functions.php on line 33', '', $data);
77
+ $data = $this->parseJSON($data);
78
+ return $data;
79
+ }
80
+
81
+ //handles the processing of the image using the ShortPixel API
82
+ public function processImage($url, $filePath, $ID = null, $startTime = 0) {
83
+ if($startTime == 0) { $startTime = time(); }
84
+ if(time() - $startTime > MAX_EXECUTION_TIME) {
85
+ $meta = wp_get_attachment_metadata($ID);
86
+ $meta['ShortPixelImprovement'] = 'Could not determine compression';
87
+ unset($meta['ShortPixel']['WaitingProcessing']);
88
+ wp_update_attachment_metadata($ID, $meta);
89
+ return 'Could not determine compression';
90
  }
91
 
92
+ $response = $this->doRequests($url, $filePath, $ID);
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
+ if(!$response) return $response;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
+ if($response['response']['code'] != 200) {
97
+ WPShortPixel::log("Response 200 OK");
98
+ printf('Web service did not respond. Please try again later.');
99
+ return false;
100
  }
101
 
102
+ $data = $this->parseResponse($response);
103
+ $data = $data[0];
104
+
105
+ switch($data->Status->Code) {
106
+ case 1:
107
+ //handle image has been scheduled
108
+ sleep(1);
109
+ return $this->processImage($url, $filePath, $ID, $startTime);
110
+ break;
111
+ case 2:
112
+ //handle image has been processed
113
+ $this->handleSuccess($data, $url, $filePath, $ID);
114
+ break;
115
+ case -16:
116
+ return 'Quota exceeded</br>';
117
+ case -17:
118
+ return 'Wrong API Key</br>';
119
+ case -302:
120
+ return 'Images does not exists</br>';
121
+ default:
122
+ //handle error
123
+ return $data->Status->Message;
124
+ }
125
 
126
+ return $data;
127
+ }
128
+
129
+
130
+ public function handleSuccess($callData, $url, $filePath, $ID) {
131
+
132
+ if($this->_compressionType) {
133
+ //lossy
134
+ $correctFileSize = $callData->LossySize;
135
+ $tempFile = download_url(urldecode($callData->LossyURL));
136
+ if(is_wp_error( $tempFile )) {
137
+ $tempFile = download_url(str_replace('https://', 'http://', urldecode($callData->LossyURL)));
138
+ }
139
+ } else {
140
+ //lossless
141
+ $correctFileSize = $callData->LoselessSize;
142
+ $tempFile = download_url(urldecode($callData->LosslessURL));
143
+ if(is_wp_error( $tempFile )) {
144
+ $tempFile = download_url(str_replace('https://', 'http://', urldecode($callData->LosslessURL)));
145
+ }
146
+ }
147
 
148
+ if ( is_wp_error( $tempFile ) ) {
149
+ @unlink($tempFile);
150
+ return sprintf("Error downloading file (%s)", $tempFile->get_error_message());
151
+ die;
152
+ }
153
 
154
+ //check response so that download is OK
155
+ if(filesize($tempFile) != $correctFileSize) {
156
+ return sprintf("Error downloading file - incorrect file size");
157
+ die;
158
+ }
159
 
160
+ if (!file_exists($tempFile)) {
161
+ return sprintf("Unable to locate downloaded file (%s)", $tempFile);
162
+ die;
163
+ }
164
 
165
+ //if backup is enabled
166
+ if(get_option('wp-short-backup_images')) {
167
 
168
+ if(!file_exists(SP_BACKUP_FOLDER) && !mkdir(SP_BACKUP_FOLDER, 0777, true)) {
169
+ return sprintf("Backup folder does not exist and it could not be created");
170
+ }
171
 
172
+ $source = $filePath;
173
+ $destination = SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . basename($source);
174
 
175
+ if(is_writable(SP_BACKUP_FOLDER)) {
176
+ if(!file_exists($destination)) {
177
+ @copy($source, $destination);
 
 
 
 
178
  }
179
+ } else {
180
+ return sprintf("Backup folder exists but is not writable");
181
+ }
182
+ }
183
 
184
+ @unlink( $filePath );
185
+ $success = @rename( $tempFile, $filePath );
 
 
 
 
 
186
 
187
+ if (!$success) {
188
+ $copySuccess = copy($tempFile, $filePath);
189
+ unlink($tempFile);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  }
191
 
192
+ if($success || $copySuccess) {
193
+ //update statistics
194
+ if(isset($callData->LossySize)) {
195
+ $savedSpace = $callData->OriginalSize - $callData->LossySize;
196
+ } else {
197
+ $savedSpace = $callData->OriginalSize - $callData->LoselessSize;
198
+ }
199
+
200
+ update_option(
201
+ 'wp-short-pixel-savedSpace',
202
+ get_option('wp-short-pixel-savedSpace') + $savedSpace
203
+ );
204
+ $averageCompression = get_option('wp-short-pixel-averageCompression') * get_option('wp-short-pixel-fileCount');
205
+ $averageCompression += $callData->PercentImprovement;
206
+ $averageCompression = $averageCompression / (get_option('wp-short-pixel-fileCount') + 1);
207
+ update_option('wp-short-pixel-averageCompression', $averageCompression);
208
+ update_option('wp-short-pixel-fileCount', get_option('wp-short-pixel-fileCount')+1);
209
+
210
+ //update metadata
211
+ if(isset($ID)) {
212
+ $meta = wp_get_attachment_metadata($ID);
213
+ $meta['ShortPixelImprovement'] = $callData->PercentImprovement;
214
+ wp_update_attachment_metadata($ID, $meta);
215
+ }
216
+ }
217
+ }
218
+
219
+ public function parseJSON($data) {
220
+ if ( function_exists('json_decode') ) {
221
+ $data = json_decode( $data );
222
+ } else {
223
+ require_once( 'JSON/JSON.php' );
224
+ $json = new Services_JSON( );
225
+ $data = $json->decode( $data );
226
  }
227
+ return $data;
228
+ }
229
  }
wp-shortpixel.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: ShortPixel Image Optimiser
4
  * Plugin URI: https://shortpixel.com/
5
  * Description: ShortPixel is an image compression tool that helps improve your website performance. The plugin optimises images automatically using both lossy and lossless compression. Resulting, smaller, images are no different in quality from the original. To install: 1) Click the "Activate" link to the left of this description. 2) <a href="https://shortpixel.com/wp-apikey" target="_blank">Free Sign up</a> for your unique API Key . 3) Check your email for your API key. 4) Use your API key to activate ShortPixel plugin in the 'Plugins' menu in WordPress. 5) Done!
6
- * Version: 1.6.10
7
  * Author: ShortPixel
8
  * Author URI: https://shortpixel.com
9
  */
@@ -14,426 +14,456 @@ require_once( ABSPATH . 'wp-admin/includes/image.php' );
14
  define('SP_DEBUG', false);
15
  define('SP_LOG', false);
16
  define('SP_MAX_TIMEOUT', 10);
17
- define('SP_BACKUP_FOLDER', WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'ShortpixelBackups');
18
  define('MUST_HAVE_KEY', true);
19
  define('BATCH_SIZE', 1);
20
  define('MAX_EXECUTION_TIME', 30); //in seconds
21
 
22
  class WPShortPixel {
23
 
24
- private $_apiInterface = null;
25
- private $_apiKey = '';
26
- private $_compressionType = 1;
27
- private $_processThumbnails = 1;
28
- private $_backupImages = 1;
29
- private $_verifiedKey = false;
30
 
31
- public function __construct() {
32
- $this->populateOptions();
33
 
34
- $this->_apiInterface = new shortpixel_api($this->_apiKey, $this->_compressionType);
35
 
36
- //add hook for image upload processing
37
- add_filter( 'wp_generate_attachment_metadata', array( &$this, 'handleImageUpload' ), 10, 2 );
38
- add_filter( 'manage_media_columns', array( &$this, 'columns' ) );
39
- add_filter( 'plugin_action_links_' . plugin_basename(__FILE__), array(&$this, 'generatePluginLinks'));
40
 
41
- //add_action( 'admin_footer', array(&$this, 'handleImageProcessing'));
42
- add_action( 'manage_media_custom_column', array( &$this, 'generateCustomColumn' ), 10, 2 );
43
 
44
- //add settings page
45
- add_action( 'admin_menu', array( &$this, 'registerSettingsPage' ) );
46
- add_action( 'admin_menu', array( &$this, 'registerAdminPage' ) );
47
- add_action( 'delete_attachment', array( &$this, 'handleDeleteAttachmentInBackup' ) );
48
 
49
- //automatic optimization
50
- add_action( 'admin_footer', array( &$this, 'my_action_javascript') );
51
- add_action( 'wp_ajax_my_action', array( &$this, 'handleImageProcessing') );
52
 
53
- //manual optimization
54
- add_action('admin_action_shortpixel_manual_optimize', array(&$this, 'handleManualOptimization'));
55
- //backup restore
56
- add_action('admin_action_shortpixel_restore_backup', array(&$this, 'handleRestoreBackup'));
57
 
58
- //bulk processing in media library
59
- //add_action('load-upload.php', array(&$this, 'wp_load_admin_js'));
60
- //add_action('admin_enqueue_scripts', array(&$this, 'bulkOptimizeActionHandler'));
61
- }
62
-
63
- public function populateOptions() {
64
 
65
- if(get_option('wp-short-pixel-apiKey') !== false) {
66
- $this->_apiKey = get_option('wp-short-pixel-apiKey');
67
- } else {
68
- add_option( 'wp-short-pixel-apiKey', '', '', 'yes' );
69
- }
70
 
71
- if(get_option('wp-short-pixel-verifiedKey') !== false) {
72
- $this->_verifiedKey = get_option('wp-short-pixel-verifiedKey');
73
- }
74
-
75
- if(get_option('wp-short-pixel-compression') !== false) {
76
- $this->_compressionType = get_option('wp-short-pixel-compression');
77
- } else {
78
- add_option('wp-short-pixel-compression', $this->_compressionType, '', 'yes');
79
- }
80
 
81
- if(get_option('wp-short-process_thumbnails') !== false) {
82
- $this->_processThumbnails = get_option('wp-short-process_thumbnails');
83
- } else {
84
- add_option('wp-short-process_thumbnails', $this->_processThumbnails, '', 'yes' );
85
- }
86
 
87
- if(get_option('wp-short-backup_images') !== false) {
88
- $this->_backupImages = get_option('wp-short-backup_images');
89
- } else {
90
- add_option('wp-short-backup_images', $this->_backupImages, '', 'yes' );
91
- }
92
 
93
- if(get_option('wp-short-pixel-fileCount') === false) {
94
- add_option( 'wp-short-pixel-fileCount', 0, '', 'yes' );
95
- }
 
 
96
 
97
- if(get_option('wp-short-pixel-savedSpace') === false) {
98
- add_option( 'wp-short-pixel-savedSpace', 0, '', 'yes' );
99
- }
 
 
100
 
101
- if(get_option('wp-short-pixel-averageCompression') === false) {
102
- add_option( 'wp-short-pixel-averageCompression', 0, '', 'yes' );
103
- }
104
  }
105
 
106
- static function log($message) {
107
- if(SP_DEBUG) {
108
- echo "{$message}</br>";
109
- }
110
  }
111
 
112
- function my_action_javascript() { ?>
113
- <script type="text/javascript" >
114
- jQuery(document).ready(sendRequest());
115
- function sendRequest() {
116
- var data = { 'action': 'my_action' };
117
- // since 2.8 ajaxurl is always defined in the admin header and points to admin-ajax.php
118
- jQuery.post(ajaxurl, data, function(response) {
119
- if(response.search('Empty queue') >= 0 || response.search('Error processing image') >= 0) {
120
- console.log('Queue is empty');
121
- } else {
122
- console.log('Server response: ' + response);
123
- sendRequest();
124
- }
125
- });
126
- }
127
- </script> <?php
128
- }
129
-
130
- function wp_load_admin_js() {
131
- add_action('admin_print_footer_scripts', array(&$this, 'add_bulk_actions_via_javascript'));
132
- }
133
-
134
- function add_bulk_actions_via_javascript() {
135
- ?>
136
- <script type="text/javascript">
137
- jQuery(document).ready(function($){
138
- $('select[name^="action"] option:last-child').before('<option value="2">Bulk Optimize</option>');
139
- });
140
- </script>
141
- <?php }
142
-
143
- //handling older
144
- public function WPShortPixel() {
145
- $this->__construct();
146
- }
147
-
148
- public function handleImageUpload($meta, $ID = null) {
149
- if(MUST_HAVE_KEY && $this->_verifiedKey) {
150
- self::log("Processing image id {$ID}");
151
- $url = wp_get_attachment_url($ID);
152
- $path = get_attached_file($ID);
153
- if(self::isImage($path) != false) {
154
- $this->_apiInterface->doRequests($url, $path, $ID);
155
-
156
- //send request for thumbs as well
157
- if($this->_processThumbnails && !empty($meta['sizes'])) {
158
- foreach($meta['sizes'] as $thumbnailInfo) {
159
- $thumbURL = str_replace(basename($url), $thumbnailInfo['file'], $url);
160
- $thumbPath = str_replace(basename($path), $thumbnailInfo['file'], $path);
161
- $this->_apiInterface->doRequests($thumbURL, $thumbPath);
162
- }
163
- }
164
- } else {
165
- $meta['ShortPixelImprovement'] = 'File is not an image';
166
- return $meta;
167
- }
168
- } else {
169
 
170
- }
171
- $meta['ShortPixel']['WaitingProcessing'] = true;
172
- return $meta;
173
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
- public function handleImageProcessing($ID = null) {
176
- if(MUST_HAVE_KEY && $this->_verifiedKey == false) {
177
- echo "Missing API Key";
178
- die();
179
- }
 
 
 
 
 
180
 
181
- //query database for first found entry that needs processing
182
- global $wpdb;
183
- $qry = "SELECT * FROM " . $wpdb->prefix . "postmeta
184
  WHERE meta_value LIKE '%\"WaitingProcessing\";b:1;%'
185
  OR meta_value LIKE '%\"BulkProcessing\";b:1;%'
186
  LIMIT " . BATCH_SIZE;
187
- $idList = $wpdb->get_results($qry);
188
-
189
- if(empty($idList)) { echo 'Empty queue'; die; }
190
-
191
- foreach($idList as $post) {
192
- $ID = $post->post_id;
193
- $imageURL = wp_get_attachment_url($ID);
194
- $imagePath = get_attached_file($ID);
195
- $meta = wp_get_attachment_metadata($ID);
196
-
197
- //check if image is public
198
- if(wp_remote_retrieve_response_code($imageURL) > 400) {
199
- if(isset($meta['ShortPixel']['BulkProcessing'])) { unset($meta['ShortPixel']['BulkProcessing']); }
200
- if(isset($met['ShortPixel']['WaitingProcessing'])) { unset($meta['ShortPixel']['WaitingProcessing']); }
201
- wp_update_attachment_metadata($ID, $meta);
202
- die;
203
- }
204
-
205
- $result = $this->_apiInterface->processImage($imageURL, $imagePath, $ID);
206
-
207
- if(is_string($result)) {
208
- if(isset($meta['ShortPixel']['BulkProcessing'])) { unset($meta['ShortPixel']['BulkProcessing']); }
209
- if(isset($meta['ShortPixel']['WaitingProcessing'])) { unset($meta['ShortPixel']['WaitingProcessing']); }
210
- $meta['ShortPixelImprovement'] = $result;
211
- wp_update_attachment_metadata($ID, $meta);
212
- echo "Error processing image: " . $result;
213
- die;
214
- }
215
-
216
- $processThumbnails = get_option('wp-short-process_thumbnails');
217
-
218
- //handle the rest of the thumbnails generated by WP
219
- if($processThumbnails && $result && !empty($meta['sizes'])) {
220
- foreach($meta['sizes'] as $thumbnailInfo) {
221
- $thumbURL = str_replace(basename($imagePath), $thumbnailInfo['file'], $imageURL);
222
- $thumbPath = str_replace(basename($imagePath), $thumbnailInfo['file'], $imagePath);
223
- $this->_apiInterface->processImage($thumbURL, $thumbPath);
224
- }
225
- }
226
-
227
- unset($meta['ShortPixel']['WaitingProcessing']);
228
-
229
- if(isset($meta['ShortPixel']['BulkProcessing'])) {
230
- unset($meta['ShortPixel']['BulkProcessing']);
231
- }
232
-
233
- $meta['ShortPixelImprovement'] = $result->PercentImprovement;
234
-
235
- wp_update_attachment_metadata($ID, $meta);
236
- echo "Processing done succesfully for image #{$ID}";
237
- }
238
 
239
- die();
240
- }
241
 
242
- public function handleManualOptimization() {
243
- $attachmentID = intval($_GET['attachment_ID']);
 
 
 
244
 
245
- $url = wp_get_attachment_url($attachmentID);
246
- $filePath = get_attached_file($attachmentID);
247
- $meta = wp_get_attachment_metadata($attachmentID);
 
 
 
 
248
 
249
- $result = $this->_apiInterface->processImage($url, $filePath, $attachmentID);
250
 
251
- $processThumbnails = get_option('wp-short-process_thumbnails');
 
 
 
 
 
 
 
252
 
253
- //handle the rest of the thumbnails generated by WP
254
- if($processThumbnails && $result && !empty($meta['sizes'])) {
255
- foreach($meta['sizes'] as $thumbnailInfo) {
256
- $thumbURL = str_replace(basename($filePath), $thumbnailInfo['file'], $filePath);
257
- $thumbPath = str_replace(basename($filePath), $thumbnailInfo['file'], $filePath);
258
- $this->_apiInterface->processImage($thumbURL, $thumbPath);
259
- }
 
260
  }
 
261
 
262
- // store the referring webpage location
263
- $sendback = wp_get_referer();
264
- // sanitize the referring webpage location
265
- $sendback = preg_replace('|[^a-z0-9-~+_.?#=&;,/:]|i', '', $sendback);
266
- // send the user back where they came from
267
- wp_redirect($sendback);
268
- // we are done,
269
- }
270
-
271
- public function handleRestoreBackup() {
272
- $attachmentID = intval($_GET['attachment_ID']);
273
-
274
- $uploadFilePath = get_attached_file($attachmentID);
275
- $meta = wp_get_attachment_metadata($attachmentID);
276
- $pathInfo = pathinfo($uploadFilePath);
277
-
278
- try {
279
- //main file
280
- @rename(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . basename($uploadFilePath), $uploadFilePath);
281
- //overwriting thumbnails
282
- if(is_array($meta["sizes"])) {
283
- foreach($meta["sizes"] as $size => $imageData) {
284
- $source = SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $imageData['file'];
285
- $destination = $pathInfo['dirname'] . DIRECTORY_SEPARATOR . $imageData['file'];
286
- @rename($source, $destination);
287
- }
288
- }
289
-
290
- unset($meta["ShortPixelImprovement"]);
291
- wp_update_attachment_metadata($attachmentID, $meta);
292
 
293
- } catch(Exception $e) {
294
- //what to do, what to do?
295
- }
296
- // store the referring webpage location
297
- $sendback = wp_get_referer();
298
- // sanitize the referring webpage location
299
- $sendback = preg_replace('|[^a-z0-9-~+_.?#=&;,/:]|i', '', $sendback);
300
- // send the user back where they came from
301
- wp_redirect($sendback);
302
- // we are done
303
- }
304
-
305
-
306
- public function handleDeleteAttachmentInBackup($ID) {
307
- $uploadFilePath = get_attached_file($ID);
308
- $meta = wp_get_attachment_metadata($ID);
309
- if(self::isImage($uploadFilePath) != false) {
310
- try {
311
- //main file
312
- @unlink(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . basename($uploadFilePath));
313
- //overwriting thumbnails
314
- foreach($meta["sizes"] as $size => $imageData) {
315
- @unlink(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $imageData['file']);
316
- }
317
- } catch(Exception $e) {
318
- //what to do, what to do?
319
- }
320
- }
321
  }
322
 
323
- public function bulkOptimizeActionHandler($hook) {
324
- if($hook == 'upload.php') {
325
- if($_GET['action'] == 2) {
326
- if(!empty($_GET['media'])) {
327
- $imageLog = array();
328
- //remove all ShortPixel data from metadata
329
- foreach($_GET['media'] as $attachmentID) {
330
- $meta = wp_get_attachment_metadata($attachmentID);
331
- $meta['ShortPixel']['BulkProcessing'] = true;
332
- unset($meta['ShortPixelImprovement']);
333
- wp_update_attachment_metadata($attachmentID, $meta);
334
- $imageLog[$attachmentID] = false;
335
- }
336
- update_option('bulkProcessingLog', $imageLog);
337
- }
338
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  }
 
340
  }
 
 
 
 
 
341
 
342
- public function registerSettingsPage() {
343
- add_options_page( 'ShortPixel Settings', 'ShortPixel', 'manage_options', 'wp-shortpixel', array($this, 'renderSettingsMenu'));
 
 
 
 
 
 
 
 
 
344
  }
345
 
346
- function registerAdminPage( ) {
347
- add_media_page( 'ShortPixel Bulk Process', 'Bulk ShortPixel', 'edit_others_posts', 'wp-short-pixel-bulk', array( &$this, 'bulkProcesss' ) );
 
 
 
 
 
 
 
 
 
 
 
348
  }
349
 
350
- public function bulkProcesss() {
351
- echo '<h1>Bulk Image Optimisation by ShortPixel</h1>';
 
352
 
353
- if(MUST_HAVE_KEY && $this->_verifiedKey == false) {
354
- echo "<p>In order to start processing your images, you need to validate your API key in the ShortPixel Settings. If you don’t have an API Key, you can get one delivered to your inbox.</p>";
355
- echo "<p>Don’t have an API Key yet? Get it now at <a href=\"https://shortpixel.com/wp-apikey\" target=\"_blank\">www.ShortPixel.com</a>, for free.</p>";
356
- return;
357
- }
358
 
359
- $attachments = null;
360
- $attachments = get_posts( array(
361
- 'numberposts' => -1,
362
- 'post_type' => 'attachment',
363
- 'post_mime_type' => 'image'
364
- ));
365
-
366
- if($_GET['cancel']) {
367
- foreach($attachments as $attachment) {
368
- $meta = wp_get_attachment_metadata($attachment->ID);
369
- if(isset($meta['ShortPixel']['BulkProcessing'])) unset($meta['ShortPixel']['BulkProcessing']);
370
- wp_update_attachment_metadata($attachment->ID, $meta);
371
- }
372
  }
373
 
374
- if($_POST["bulkProcess"]) {
375
- //remove all ShortPixel data from metadata
376
- foreach($attachments as $attachment) {
377
- if(self::isImage(get_attached_file($attachment->ID)) == false) continue;
378
- $meta = wp_get_attachment_metadata($attachment->ID);
379
- $meta['ShortPixel']['BulkProcessing'] = true;
380
- wp_update_attachment_metadata($attachment->ID, $meta);
381
- }
382
 
383
- update_option('bulkProcessingStatus', 'running');
 
 
 
 
 
 
 
 
384
  }
 
 
 
 
 
 
 
 
385
 
386
- global $wpdb;
387
- $qry = "SELECT * FROM " . $wpdb->prefix . "postmeta
388
  WHERE meta_value LIKE '%\"BulkProcessing\";b:1;%'";
389
- $idList = $wpdb->get_results($qry);
390
 
391
- if(!empty($idList)) {
392
- if(is_array($idList)) {
393
- echo "<p>
394
  Bulk optimisation has started. This process will take some time, depending on the number of images in your library. <BR>Do not worry about the slow speed, it is a necessary measure in order not to interfere with the normal functioning of your site.<BR><BR>
395
  This is a brief estimation of the bulk processing times:<BR>
396
- 1 to 100 images < 1 hour <BR>
397
- 100 to 500 images < 4 hours<BR>
398
- 500 to 1000 images < 8 hours<BR>
399
- over 1000 images > 12 hours or more<BR><BR>
400
 
401
  The latest status of the processing will be displayed here every 30 seconds.<BR>
402
  In the meantime, you can continue using the admin as usual.<BR>
403
  However, <b>you musn’t close the WordPress admin</b>, or the bulk processing will stop.
404
  </p>";
405
- echo '
406
  <script type="text/javascript" >
407
  var bulkProcessingRunning = true;
408
  </script>
409
  ';
410
 
411
- $imagesLeft = count($idList);
412
- $totalImages = count($attachments);
413
 
414
- echo "<p>{$imagesLeft} out of {$totalImages} images left to process.</p>";
415
 
416
- echo '
417
  <a class="button button-secondary" href="' . get_admin_url() . 'upload.php">Media Library</a>
418
  <a class="button button-secondary" href="' . get_admin_url() . 'upload.php?page=wp-short-pixel-bulk&cancel=1">Cancel Processing</a>
419
  ';
420
- }
421
- } else {
422
- $bulkProcessingStatus = get_option('bulkProcessingStatus');
423
- if(isset($bulkProcessingStatus) && $bulkProcessingStatus == 'running') {
424
- echo "<p>Bulk optimisation was successful. ShortPixel has finished optimising all your images.</p>
425
  <p>Go to the ShortPixel <a href='" . get_admin_url() . "options-general.php?page=wp-shortpixel#facts'>Facts &amp; Figures</a> and see your website's optimised stats (in Settings > ShortPixel). </p>";
426
- delete_option('bulkProcessingStatus');
427
- }
428
- echo $this->getBulkProcessingForm(count($attachments));
429
- echo '
430
  <script type="text/javascript" >
431
  var bulkProcessingRunning = false;
432
  </script>
433
  ';
434
- }
435
 
436
- echo '
437
  <script type="text/javascript" >
438
  jQuery(document).ready(function() {
439
  if(bulkProcessingRunning) {
@@ -447,77 +477,77 @@ class WPShortPixel {
447
  });
448
  </script>
449
  ';
450
- }
451
 
452
- public function renderSettingsMenu() {
453
- if ( !current_user_can( 'manage_options' ) ) {
454
- wp_die('You do not have sufficient permissions to access this page.');
455
- }
456
- echo '<h1>ShortPixel Image Optimiser Settings</h1>';
457
- echo '<p>
458
  <a href="https://shortpixel.com">ShortPixel.com</a> |
459
  <a href="https://wordpress.org/plugins/shortpixel-image-optimiser/installation/">Installation </a> |
460
  <a href="https://wordpress.org/support/plugin/shortpixel-image-optimiser">Support </a>
461
  </p>';
462
- echo '<p>New images uploaded to the Media Library will be optimized automatically.<br/>If you have existing images you would like to optimize, you can use the <a href="' . get_admin_url() . 'upload.php?page=wp-short-pixel-bulk">Bulk Optimisation Tool</a>.</p>';
463
-
464
- $noticeHTML = "<br/><div style=\"background-color: #fff; border-left: 4px solid %s; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); padding: 1px 12px;\"><p>%s</p></div>";
465
-
466
- if(isset($_POST['submit']) || isset($_POST['validate'])) {
467
- //handle API Key - common for submit and validate
468
- $_POST['key'] = trim($_POST['key']);
469
- $validityData = $this->getQuotaInformation($_POST['key'], true);
470
-
471
- $this->_apiKey = $_POST['key'];
472
- $this->_apiInterface->setApiKey($this->_apiKey);
473
- update_option('wp-short-pixel-apiKey', $_POST['key']);
474
- if($validityData['APIKeyValid']) {
475
- if(isset($_POST['validate'])) {
476
- //display notification
477
- printf($noticeHTML, '#7ad03a', 'API Key valid!');
478
- }
479
- update_option('wp-short-pixel-verifiedKey', true);
480
- $this->_verifiedKey = true;
481
- } else {
482
- if(isset($_POST['validate'])) {
483
- //display notification
484
- printf($noticeHTML, '#dd3d36', $validityData["Message"]);
485
- }
486
- update_option('wp-short-pixel-verifiedKey', false);
487
- $this->_verifiedKey = false;
488
- }
489
-
490
- //if save button - we process the rest of the form elements
491
- if(isset($_POST['submit'])) {
492
- update_option('wp-short-pixel-compression', $_POST['compressionType']);
493
- $this->_compressionType = $_POST['compressionType'];
494
- $this->_apiInterface->setCompressionType($this->_compressionType);
495
- if(isset($_POST['thumbnails'])) { $this->_processThumbnails = 1; } else { $this->_processThumbnails = 0; }
496
- if(isset($_POST['backupImages'])) { $this->_backupImages = 1; } else { $this->_backupImages = 0; }
497
- update_option('wp-short-process_thumbnails', $this->_processThumbnails);
498
- update_option('wp-short-backup_images', $this->_backupImages);
499
- }
500
- }
501
 
502
- if(isset($_POST['emptyBackup'])) {
503
- if(file_exists(SP_BACKUP_FOLDER)) {
504
- $files = scandir(SP_BACKUP_FOLDER);
505
- $cleanPath = rtrim(SP_BACKUP_FOLDER, '/'). '/';
506
- foreach($files as $t) {
507
- if ( $t != "." && $t != "..") {
508
- unlink(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $t);
509
- }
510
- }
511
- }
512
  }
 
 
513
 
514
- $checked = '';
515
- if($this->_processThumbnails) { $checked = 'checked'; }
516
 
517
- $checkedBackupImages = '';
518
- if($this->_backupImages) { $checkedBackupImages = 'checked'; }
519
 
520
- $formHTML = <<< HTML
521
  <form name='wp_shortpixel_options' action='' method='post' id='wp_shortpixel_options'>
522
  <table class="form-table">
523
  <tbody><tr>
@@ -528,13 +558,13 @@ class WPShortPixel {
528
  </tr>
529
  HTML;
530
 
531
- if(!$this->_verifiedKey) {
532
- //if invalid key we display the link to the API Key
533
- $formHTML .= '<tr><td style="padding-left: 0px;" colspan="2">Don’t have an API Key? <a href="https://shortpixel.com/wp-apikey" target="_blank">Sign up, it’s free.</a></td></tr>';
534
- $formHTML .= '</form>';
535
- } else {
536
- //if valid key we display the rest of the options
537
- $formHTML .= <<< HTML
538
  <tr><th scope="row">
539
  <label for="compressionType">Compression type: <span title="
540
  Lossy compression: 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.
@@ -543,15 +573,15 @@ Lossless compression: the shrunk image will be identical with the original and s
543
  </th><td>
544
  HTML;
545
 
546
- if($this->_compressionType == 1) {
547
- $formHTML .= '<input type="radio" name="compressionType" value="1" checked>Lossy</br></br>';
548
- $formHTML .= '<input type="radio" name="compressionType" value="0" >Lossless';
549
- } else {
550
- $formHTML .= '<input type="radio" name="compressionType" value="1">Lossy</br></br>';
551
- $formHTML .= '<input type="radio" name="compressionType" value="0" checked>Lossless';
552
- }
553
 
554
- $formHTML .= <<<HTML
555
  </td>
556
  </tr>
557
  <tr>
@@ -586,19 +616,19 @@ for(var i = 0; i < rad.length; i++) {
586
  }
587
  </script>
588
  HTML;
589
- }
590
 
591
- echo $formHTML;
592
 
593
- if($this->_verifiedKey) {
594
- $fileCount = get_option('wp-short-pixel-fileCount');
595
- $savedSpace = self::formatBytes(get_option('wp-short-pixel-savedSpace'),2);
596
- $averageCompression = round(get_option('wp-short-pixel-averageCompression'),2);
597
- $savedBandwidth = self::formatBytes(get_option('wp-short-pixel-savedSpace') * 1000,2);
598
- $quotaData = $this->getQuotaInformation();
599
- $backupFolderSize = self::formatBytes(self::folderSize(SP_BACKUP_FOLDER));
600
 
601
- $statHTML = <<< HTML
602
  <a id="facts"></a>
603
  <h3>ShortPixel Facts & Figures</h3>
604
  <table class="form-table">
@@ -627,8 +657,8 @@ HTML;
627
  <td>$averageCompression%</td>
628
  </tr>
629
  HTML;
630
- if($this->_backupImages) {
631
- $statHTML .= <<< HTML
632
  <form action="" method="POST">
633
  <tr>
634
  <th scope="row"><label for="sizeBackup">Backup folder size:</label></th>
@@ -639,18 +669,18 @@ HTML;
639
  </tr>
640
  </form>
641
  HTML;
642
- }
643
 
644
- $statHTML .= <<< HTML
645
  </tbody></table>
646
  <p>* Saved bandwidth is calculated at 100,000 impressions/image</p>
647
  HTML;
648
- echo $statHTML;
649
- }
650
  }
 
651
 
652
- public function getBulkProcessingForm($imageCount) {
653
- return <<< HTML
654
  </br>
655
  Currently, you have {$imageCount} images in your library. </br>
656
  </br>
@@ -658,164 +688,186 @@ Currently, you have {$imageCount} images in your library. </br>
658
  <input type="submit" name="bulkProcess" id="bulkProcess" class="button button-primary" value="Compress all your images">
659
  </form>
660
  HTML;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
661
  }
662
 
 
663
 
664
- public function getQuotaInformation($apiKey = null, $appendUserAgent = false) {
 
 
665
 
666
- if(is_null($apiKey)) { $apiKey = $this->_apiKey; }
 
 
 
 
667
 
668
- $requestURL = 'https://api.shortpixel.com/v1/api-status.php';
669
- $args = array('timeout'=> SP_MAX_TIMEOUT,
670
- 'sslverify' => false,
671
- 'body' => array('key' => $apiKey)
672
- );
673
 
674
- if($appendUserAgent) {
675
- $args['body']['useragent'] = urlencode($_SERVER['HTTP_USER_AGENT']);
676
- }
677
 
678
- $response = wp_remote_post($requestURL, $args);
 
679
 
680
- if(is_wp_error( $response )) {
681
- $response = wp_remote_get(str_replace('https://', 'http://', $requestURL), $args);
682
- }
683
 
684
- $defaultData = array(
685
- "APIKeyValid" => false,
686
- "Message" => 'API Key could not be validated. Could not connect Shortpixel service.',
687
- "APICallsMade" => 'Information unavailable. Please check your API key.',
688
- "APICallsQuota" => 'Information unavailable. Please check your API key.');
689
 
690
- if(is_object($response) && get_class($response) == 'WP_Error') {
691
- return $defaultData;
692
- }
 
 
693
 
694
- if($response['response']['code'] != 200) {
695
- return $defaultData;
696
- }
697
 
698
- $data = $response['body'];
699
- $data = $this->parseJSON($data);
700
 
701
- if(empty($data)) { return $defaultData; }
 
 
702
 
703
- if($data->Status->Code != 2) {
704
- $defaultData['Message'] = $data->Status->Message;
705
- return $defaultData;
 
706
  }
707
 
708
- return array(
709
- "APIKeyValid" => true,
710
- "APICallsMade" => number_format($data->APICallsMade) . ' images',
711
- "APICallsQuota" => number_format($data->APICallsQuota) . ' images'
712
- );
713
-
714
-
715
- }
716
-
717
- public function generateCustomColumn( $column_name, $id ) {
718
- if( 'wp-shortPixel' == $column_name ) {
719
- $data = wp_get_attachment_metadata($id);
720
-
721
- if ( isset( $data['ShortPixelImprovement'] ) ) {
722
- if(isset($meta['ShortPixel']['BulkProcessing'])) {
723
- print 'Waiting for bulk processing';
724
- return;
725
- }
726
-
727
- print $data['ShortPixelImprovement'];
728
- if(is_numeric($data['ShortPixelImprovement'])) {
729
- print '%';
730
- print " | <a href=\"admin.php?action=shortpixel_restore_backup&amp;attachment_ID={$id}\">Restore backup</a>";
731
- return;
732
- }
733
- } elseif(isset($data['ShortPixel']['WaitingProcessing'])) {
734
- print 'Image waiting to be processed';
735
- return;
736
- } else {
737
- if ( wp_attachment_is_image( $id ) ) {
738
- print 'Image not processed';
739
- print " | <a href=\"admin.php?action=shortpixel_manual_optimize&amp;attachment_ID={$id}\">Optimize now</a>";
740
- return;
741
- }
742
- }
743
  }
 
 
 
 
 
 
 
 
 
 
744
  }
745
-
746
- public function columns( $defaults ) {
747
- $defaults['wp-shortPixel'] = 'ShortPixel Compression';
748
- return $defaults;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
749
  }
 
 
750
 
751
- public function generatePluginLinks($links) {
752
- $in = '<a href="options-general.php?page=wp-shortpixel">Settings</a>';
753
- array_unshift($links, $in);
754
- return $links;
755
- }
756
 
757
- public function parseJSON($data) {
758
- if ( function_exists('json_decode') ) {
759
- $data = json_decode( $data );
760
- } else {
761
- require_once( 'JSON/JSON.php' );
762
- $json = new Services_JSON( );
763
- $data = $json->decode( $data );
764
- }
765
- return $data;
766
- }
767
 
 
768
 
769
- static public function formatBytes($bytes, $precision = 2) {
770
- $units = array('B', 'KB', 'MB', 'GB', 'TB');
771
 
772
- $bytes = max($bytes, 0);
773
- $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
774
- $pow = min($pow, count($units) - 1);
 
 
 
 
 
 
 
 
775
 
776
- $bytes /= pow(1024, $pow);
777
 
778
- return round($bytes, $precision) . ' ' . $units[$pow];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
779
  }
 
 
780
 
781
- static public function isImage($path) {
782
- if(function_exists('exif_imagetype')) {
783
- return exif_imagetype($path);
784
- } else {
785
- $pathParts = pathinfo($path);
786
- if(in_array($pathParts['extension'], array('jpg', 'jpeg', 'gif', 'png', 'bmp'))) {
787
- return true;
788
- } else {
789
- return false;
790
- }
791
- }
792
 
 
 
 
 
 
793
  }
794
 
795
- static public function folderSize($path) {
796
- $total_size = 0;
797
- if(file_exists($path)) {
798
- $files = scandir($path);
799
- } else {
800
- return $total_size;
801
- }
802
- $cleanPath = rtrim($path, '/'). '/';
803
- foreach($files as $t) {
804
- if ($t<>"." && $t<>"..") {
805
- $currentFile = $cleanPath . $t;
806
- if (is_dir($currentFile)) {
807
- $size = foldersize($currentFile);
808
- $total_size += $size;
809
- }
810
- else {
811
- $size = filesize($currentFile);
812
- $total_size += $size;
813
- }
814
- }
815
- }
816
- return $total_size;
817
  }
818
 
 
 
 
819
 
820
  }
821
 
3
  * Plugin Name: ShortPixel Image Optimiser
4
  * Plugin URI: https://shortpixel.com/
5
  * Description: ShortPixel is an image compression tool that helps improve your website performance. The plugin optimises images automatically using both lossy and lossless compression. Resulting, smaller, images are no different in quality from the original. To install: 1) Click the "Activate" link to the left of this description. 2) <a href="https://shortpixel.com/wp-apikey" target="_blank">Free Sign up</a> for your unique API Key . 3) Check your email for your API key. 4) Use your API key to activate ShortPixel plugin in the 'Plugins' menu in WordPress. 5) Done!
6
+ * Version: 2.0.0
7
  * Author: ShortPixel
8
  * Author URI: https://shortpixel.com
9
  */
14
  define('SP_DEBUG', false);
15
  define('SP_LOG', false);
16
  define('SP_MAX_TIMEOUT', 10);
17
+ define('SP_BACKUP_FOLDER', WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'ShortpixelBackups');
18
  define('MUST_HAVE_KEY', true);
19
  define('BATCH_SIZE', 1);
20
  define('MAX_EXECUTION_TIME', 30); //in seconds
21
 
22
  class WPShortPixel {
23
 
24
+ private $_apiInterface = null;
25
+ private $_apiKey = '';
26
+ private $_compressionType = 1;
27
+ private $_processThumbnails = 1;
28
+ private $_backupImages = 1;
29
+ private $_verifiedKey = false;
30
 
31
+ public function __construct() {
32
+ $this->populateOptions();
33
 
34
+ $this->_apiInterface = new shortpixel_api($this->_apiKey, $this->_compressionType);
35
 
36
+ //add hook for image upload processing
37
+ add_filter( 'wp_generate_attachment_metadata', array( &$this, 'handleImageUpload' ), 10, 2 );
38
+ add_filter( 'manage_media_columns', array( &$this, 'columns' ) );
39
+ add_filter( 'plugin_action_links_' . plugin_basename(__FILE__), array(&$this, 'generatePluginLinks'));
40
 
41
+ //add_action( 'admin_footer', array(&$this, 'handleImageProcessing'));
42
+ add_action( 'manage_media_custom_column', array( &$this, 'generateCustomColumn' ), 10, 2 );
43
 
44
+ //add settings page
45
+ add_action( 'admin_menu', array( &$this, 'registerSettingsPage' ) );
46
+ add_action( 'admin_menu', array( &$this, 'registerAdminPage' ) );
47
+ add_action( 'delete_attachment', array( &$this, 'handleDeleteAttachmentInBackup' ) );
48
 
49
+ //automatic optimization
50
+ add_action( 'admin_footer', array( &$this, 'my_action_javascript') );
51
+ add_action( 'wp_ajax_my_action', array( &$this, 'handleImageProcessing') );
52
 
53
+ //manual optimization
54
+ add_action('admin_action_shortpixel_manual_optimize', array(&$this, 'handleManualOptimization'));
55
+ //backup restore
56
+ add_action('admin_action_shortpixel_restore_backup', array(&$this, 'handleRestoreBackup'));
57
 
58
+ $this->migrateBackupFolder();
59
+ }
 
 
 
 
60
 
61
+ public function populateOptions() {
 
 
 
 
62
 
63
+ if(get_option('wp-short-pixel-apiKey') !== false) {
64
+ $this->_apiKey = get_option('wp-short-pixel-apiKey');
65
+ } else {
66
+ add_option( 'wp-short-pixel-apiKey', '', '', 'yes' );
67
+ }
 
 
 
 
68
 
69
+ if(get_option('wp-short-pixel-verifiedKey') !== false) {
70
+ $this->_verifiedKey = get_option('wp-short-pixel-verifiedKey');
71
+ }
 
 
72
 
73
+ if(get_option('wp-short-pixel-compression') !== false) {
74
+ $this->_compressionType = get_option('wp-short-pixel-compression');
75
+ } else {
76
+ add_option('wp-short-pixel-compression', $this->_compressionType, '', 'yes');
77
+ }
78
 
79
+ if(get_option('wp-short-process_thumbnails') !== false) {
80
+ $this->_processThumbnails = get_option('wp-short-process_thumbnails');
81
+ } else {
82
+ add_option('wp-short-process_thumbnails', $this->_processThumbnails, '', 'yes' );
83
+ }
84
 
85
+ if(get_option('wp-short-backup_images') !== false) {
86
+ $this->_backupImages = get_option('wp-short-backup_images');
87
+ } else {
88
+ add_option('wp-short-backup_images', $this->_backupImages, '', 'yes' );
89
+ }
90
 
91
+ if(get_option('wp-short-pixel-fileCount') === false) {
92
+ add_option( 'wp-short-pixel-fileCount', 0, '', 'yes' );
 
93
  }
94
 
95
+ if(get_option('wp-short-pixel-savedSpace') === false) {
96
+ add_option( 'wp-short-pixel-savedSpace', 0, '', 'yes' );
 
 
97
  }
98
 
99
+ if(get_option('wp-short-pixel-averageCompression') === false) {
100
+ add_option( 'wp-short-pixel-averageCompression', 0, '', 'yes' );
101
+ }
102
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
+ static function log($message) {
105
+ if(SP_DEBUG) {
106
+ echo "{$message}</br>";
107
  }
108
+ }
109
+
110
+ function my_action_javascript() { ?>
111
+ <script type="text/javascript" >
112
+ jQuery(document).ready(sendRequest());
113
+ function sendRequest() {
114
+ var data = { 'action': 'my_action' };
115
+ // since 2.8 ajaxurl is always defined in the admin header and points to admin-ajax.php
116
+ jQuery.post(ajaxurl, data, function(response) {
117
+ if(response.search('Empty queue') >= 0 || response.search('Error processing image') >= 0) {
118
+ console.log('Queue is empty');
119
+ } else {
120
+ console.log('Server response: ' + response);
121
+ sendRequest();
122
+ }
123
+ });
124
+ }
125
+ </script> <?php
126
+ }
127
+
128
+ function wp_load_admin_js() {
129
+ add_action('admin_print_footer_scripts', array(&$this, 'add_bulk_actions_via_javascript'));
130
+ }
131
+
132
+ function add_bulk_actions_via_javascript() {
133
+ ?>
134
+ <script type="text/javascript">
135
+ jQuery(document).ready(function($){
136
+ $('select[name^="action"] option:last-child').before('<option value="2">Bulk Optimize</option>');
137
+ });
138
+ </script>
139
+ <?php }
140
+
141
+ //handling older
142
+ public function WPShortPixel() {
143
+ $this->__construct();
144
+ }
145
+
146
+ public function handleImageUpload($meta, $ID = null) {
147
+ if(MUST_HAVE_KEY && $this->_verifiedKey) {
148
+ self::log("Processing image id {$ID}");
149
+ $url = wp_get_attachment_url($ID);
150
+ $path = get_attached_file($ID);
151
+ if(self::isImage($path) != false) {
152
+ $this->_apiInterface->doRequests($url, $path, $ID);
153
+
154
+ //send request for thumbs as well
155
+ if($this->_processThumbnails && !empty($meta['sizes'])) {
156
+ foreach($meta['sizes'] as $thumbnailInfo) {
157
+ $thumbURL = str_replace(basename($url), $thumbnailInfo['file'], $url);
158
+ $thumbPath = str_replace(basename($path), $thumbnailInfo['file'], $path);
159
+ $this->_apiInterface->doRequests($thumbURL, $thumbPath);
160
+ }
161
+ }
162
+ } else {
163
+ $meta['ShortPixelImprovement'] = 'File is not an image';
164
+ return $meta;
165
+ }
166
+ } else {
167
 
168
+ }
169
+ $meta['ShortPixel']['WaitingProcessing'] = true;
170
+ return $meta;
171
+ }
172
+
173
+ public function handleImageProcessing($ID = null) {
174
+ if(MUST_HAVE_KEY && $this->_verifiedKey == false) {
175
+ echo "Missing API Key";
176
+ die();
177
+ }
178
 
179
+ //query database for first found entry that needs processing
180
+ global $wpdb;
181
+ $qry = "SELECT * FROM " . $wpdb->prefix . "postmeta
182
  WHERE meta_value LIKE '%\"WaitingProcessing\";b:1;%'
183
  OR meta_value LIKE '%\"BulkProcessing\";b:1;%'
184
  LIMIT " . BATCH_SIZE;
185
+ $idList = $wpdb->get_results($qry);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
 
187
+ if(empty($idList)) { echo 'Empty queue'; die; }
 
188
 
189
+ foreach($idList as $post) {
190
+ $ID = $post->post_id;
191
+ $imageURL = wp_get_attachment_url($ID);
192
+ $imagePath = get_attached_file($ID);
193
+ $meta = wp_get_attachment_metadata($ID);
194
 
195
+ //check if image is public
196
+ if(wp_remote_retrieve_response_code($imageURL) > 400) {
197
+ if(isset($meta['ShortPixel']['BulkProcessing'])) { unset($meta['ShortPixel']['BulkProcessing']); }
198
+ if(isset($met['ShortPixel']['WaitingProcessing'])) { unset($meta['ShortPixel']['WaitingProcessing']); }
199
+ wp_update_attachment_metadata($ID, $meta);
200
+ die;
201
+ }
202
 
203
+ $result = $this->_apiInterface->processImage($imageURL, $imagePath, $ID);
204
 
205
+ if(is_string($result)) {
206
+ if(isset($meta['ShortPixel']['BulkProcessing'])) { unset($meta['ShortPixel']['BulkProcessing']); }
207
+ if(isset($meta['ShortPixel']['WaitingProcessing'])) { unset($meta['ShortPixel']['WaitingProcessing']); }
208
+ $meta['ShortPixelImprovement'] = $result;
209
+ wp_update_attachment_metadata($ID, $meta);
210
+ echo "Error processing image: " . $result;
211
+ die;
212
+ }
213
 
214
+ $processThumbnails = get_option('wp-short-process_thumbnails');
215
+
216
+ //handle the rest of the thumbnails generated by WP
217
+ if($processThumbnails && $result && !empty($meta['sizes'])) {
218
+ foreach($meta['sizes'] as $thumbnailInfo) {
219
+ $thumbURL = str_replace(basename($imagePath), $thumbnailInfo['file'], $imageURL);
220
+ $thumbPath = str_replace(basename($imagePath), $thumbnailInfo['file'], $imagePath);
221
+ $this->_apiInterface->processImage($thumbURL, $thumbPath);
222
  }
223
+ }
224
 
225
+ unset($meta['ShortPixel']['WaitingProcessing']);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
+ if(isset($meta['ShortPixel']['BulkProcessing'])) {
228
+ unset($meta['ShortPixel']['BulkProcessing']);
229
+ }
230
+
231
+ $meta['ShortPixelImprovement'] = $result->PercentImprovement;
232
+
233
+ wp_update_attachment_metadata($ID, $meta);
234
+
235
+ echo "Processing done succesfully for image #{$ID}";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  }
237
 
238
+ die();
239
+ }
240
+
241
+ public function handleManualOptimization() {
242
+ $attachmentID = intval($_GET['attachment_ID']);
243
+
244
+ $url = wp_get_attachment_url($attachmentID);
245
+ $filePath = get_attached_file($attachmentID);
246
+ $meta = wp_get_attachment_metadata($attachmentID);
247
+
248
+ $result = $this->_apiInterface->processImage($url, $filePath, $attachmentID);
249
+
250
+ $processThumbnails = get_option('wp-short-process_thumbnails');
251
+
252
+ //handle the rest of the thumbnails generated by WP
253
+ if($processThumbnails && $result && !empty($meta['sizes'])) {
254
+ foreach($meta['sizes'] as $thumbnailInfo) {
255
+ $thumbURL = str_replace(basename($filePath), $thumbnailInfo['file'], $filePath);
256
+ $thumbPath = str_replace(basename($filePath), $thumbnailInfo['file'], $filePath);
257
+ $this->_apiInterface->processImage($thumbURL, $thumbPath);
258
+ }
259
+ }
260
+
261
+ // store the referring webpage location
262
+ $sendback = wp_get_referer();
263
+ // sanitize the referring webpage location
264
+ $sendback = preg_replace('|[^a-z0-9-~+_.?#=&;,/:]|i', '', $sendback);
265
+ // send the user back where they came from
266
+ wp_redirect($sendback);
267
+ // we are done,
268
+ }
269
+
270
+ public function handleRestoreBackup() {
271
+ $attachmentID = intval($_GET['attachment_ID']);
272
+
273
+ $uploadFilePath = get_attached_file($attachmentID);
274
+ $meta = wp_get_attachment_metadata($attachmentID);
275
+ $pathInfo = pathinfo($uploadFilePath);
276
+
277
+ try {
278
+ //main file
279
+ @rename(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . basename($uploadFilePath), $uploadFilePath);
280
+ //overwriting thumbnails
281
+ if(is_array($meta["sizes"])) {
282
+ foreach($meta["sizes"] as $size => $imageData) {
283
+ $source = SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $imageData['file'];
284
+ $destination = $pathInfo['dirname'] . DIRECTORY_SEPARATOR . $imageData['file'];
285
+ @rename($source, $destination);
286
+ }
287
+ }
288
+
289
+ unset($meta["ShortPixelImprovement"]);
290
+ wp_update_attachment_metadata($attachmentID, $meta);
291
+
292
+ } catch(Exception $e) {
293
+ //what to do, what to do?
294
+ }
295
+ // store the referring webpage location
296
+ $sendback = wp_get_referer();
297
+ // sanitize the referring webpage location
298
+ $sendback = preg_replace('|[^a-z0-9-~+_.?#=&;,/:]|i', '', $sendback);
299
+ // send the user back where they came from
300
+ wp_redirect($sendback);
301
+ // we are done
302
+ }
303
+
304
+
305
+ public function handleDeleteAttachmentInBackup($ID) {
306
+ $uploadFilePath = get_attached_file($ID);
307
+ $meta = wp_get_attachment_metadata($ID);
308
+ if(self::isImage($uploadFilePath) != false) {
309
+ try {
310
+ //main file
311
+ @unlink(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . basename($uploadFilePath));
312
+ //overwriting thumbnails
313
+ foreach($meta["sizes"] as $size => $imageData) {
314
+ @unlink(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $imageData['file']);
315
+ }
316
+ } catch(Exception $e) {
317
+ //what to do, what to do?
318
+ }
319
+ }
320
+ }
321
+
322
+ public function bulkOptimizeActionHandler($hook) {
323
+ if($hook == 'upload.php') {
324
+ if($_GET['action'] == 2) {
325
+ if(!empty($_GET['media'])) {
326
+ $imageLog = array();
327
+ //remove all ShortPixel data from metadata
328
+ foreach($_GET['media'] as $attachmentID) {
329
+ $meta = wp_get_attachment_metadata($attachmentID);
330
+ $meta['ShortPixel']['BulkProcessing'] = true;
331
+ unset($meta['ShortPixelImprovement']);
332
+ wp_update_attachment_metadata($attachmentID, $meta);
333
+ $imageLog[$attachmentID] = false;
334
+ }
335
+ update_option('bulkProcessingLog', $imageLog);
336
  }
337
+ }
338
  }
339
+ }
340
+
341
+ public function registerSettingsPage() {
342
+ add_options_page( 'ShortPixel Settings', 'ShortPixel', 'manage_options', 'wp-shortpixel', array($this, 'renderSettingsMenu'));
343
+ }
344
 
345
+ function registerAdminPage( ) {
346
+ add_media_page( 'ShortPixel Bulk Process', 'Bulk ShortPixel', 'edit_others_posts', 'wp-short-pixel-bulk', array( &$this, 'bulkProcesss' ) );
347
+ }
348
+
349
+ public function bulkProcesss() {
350
+ echo '<h1>Bulk Image Optimisation by ShortPixel</h1>';
351
+
352
+ if(MUST_HAVE_KEY && $this->_verifiedKey == false) {
353
+ echo "<p>In order to start processing your images, you need to validate your API key in the ShortPixel Settings. If you don’t have an API Key, you can get one delivered to your inbox.</p>";
354
+ echo "<p>Don’t have an API Key yet? Get it now at <a href=\"https://shortpixel.com/wp-apikey\" target=\"_blank\">www.ShortPixel.com</a>, for free.</p>";
355
+ return;
356
  }
357
 
358
+ $attachments = null;
359
+ $attachments = get_posts( array(
360
+ 'numberposts' => -1,
361
+ 'post_type' => 'attachment',
362
+ 'post_mime_type' => 'image'
363
+ ));
364
+
365
+ if($_GET['cancel']) {
366
+ foreach($attachments as $attachment) {
367
+ $meta = wp_get_attachment_metadata($attachment->ID);
368
+ if(isset($meta['ShortPixel']['BulkProcessing'])) unset($meta['ShortPixel']['BulkProcessing']);
369
+ wp_update_attachment_metadata($attachment->ID, $meta);
370
+ }
371
  }
372
 
373
+ if($_POST["bulkProcess"]) {
374
+ //remove all ShortPixel data from metadata
375
+ $imageList = array();
376
 
377
+ foreach($attachments as $attachment) {
378
+ if(self::isImage(get_attached_file($attachment->ID)) == false) continue;
 
 
 
379
 
380
+ //prepare bulk call for processing
381
+ $imagePath = wp_get_attachment_url($attachment->ID);
382
+ $imageList[] = $imagePath;
383
+
384
+ $meta = wp_get_attachment_metadata($attachment->ID);
385
+
386
+ if(isset($meta['sizes'])) {
387
+ foreach($meta['sizes'] as $thumbnailData) {
388
+ $thumbPath = substr($imagePath, 0, strrpos($imagePath, '/')) . '/' . $thumbnailData["file"];
389
+ $imageList[] = $thumbPath;
390
+ }
 
 
391
  }
392
 
393
+ $meta['ShortPixel']['BulkProcessing'] = true;
394
+ wp_update_attachment_metadata($attachment->ID, $meta);
395
+ }
 
 
 
 
 
396
 
397
+ if(count($imageList) > 100) {
398
+ $batchList = array();
399
+ foreach($imageList as $image) {
400
+ $batchList[] = $image;
401
+
402
+ if(count($batchList) == 100) {
403
+ $this->_apiInterface->doBulkRequest($batchList);
404
+ $batchList = array();
405
+ }
406
  }
407
+ } else {
408
+ $this->_apiInterface->doBulkRequest($imageList);
409
+ }
410
+
411
+
412
+
413
+ update_option('bulkProcessingStatus', 'running');
414
+ }
415
 
416
+ global $wpdb;
417
+ $qry = "SELECT * FROM " . $wpdb->prefix . "postmeta
418
  WHERE meta_value LIKE '%\"BulkProcessing\";b:1;%'";
419
+ $idList = $wpdb->get_results($qry);
420
 
421
+ if(!empty($idList)) {
422
+ if(is_array($idList)) {
423
+ echo "<p>
424
  Bulk optimisation has started. This process will take some time, depending on the number of images in your library. <BR>Do not worry about the slow speed, it is a necessary measure in order not to interfere with the normal functioning of your site.<BR><BR>
425
  This is a brief estimation of the bulk processing times:<BR>
426
+ 1 to 100 images < 20 min <BR>
427
+ 100 to 500 images < 2 hour<BR>
428
+ 500 to 1000 images < 4 hours<BR>
429
+ over 1000 images > 4 hours or more<BR><BR>
430
 
431
  The latest status of the processing will be displayed here every 30 seconds.<BR>
432
  In the meantime, you can continue using the admin as usual.<BR>
433
  However, <b>you musn’t close the WordPress admin</b>, or the bulk processing will stop.
434
  </p>";
435
+ echo '
436
  <script type="text/javascript" >
437
  var bulkProcessingRunning = true;
438
  </script>
439
  ';
440
 
441
+ $imagesLeft = count($idList);
442
+ $totalImages = count($attachments);
443
 
444
+ echo "<p>{$imagesLeft} out of {$totalImages} images left to process.</p>";
445
 
446
+ echo '
447
  <a class="button button-secondary" href="' . get_admin_url() . 'upload.php">Media Library</a>
448
  <a class="button button-secondary" href="' . get_admin_url() . 'upload.php?page=wp-short-pixel-bulk&cancel=1">Cancel Processing</a>
449
  ';
450
+ }
451
+ } else {
452
+ $bulkProcessingStatus = get_option('bulkProcessingStatus');
453
+ if(isset($bulkProcessingStatus) && $bulkProcessingStatus == 'running') {
454
+ echo "<p>Bulk optimisation was successful. ShortPixel has finished optimising all your images.</p>
455
  <p>Go to the ShortPixel <a href='" . get_admin_url() . "options-general.php?page=wp-shortpixel#facts'>Facts &amp; Figures</a> and see your website's optimised stats (in Settings > ShortPixel). </p>";
456
+ delete_option('bulkProcessingStatus');
457
+ }
458
+ echo $this->getBulkProcessingForm(count($attachments));
459
+ echo '
460
  <script type="text/javascript" >
461
  var bulkProcessingRunning = false;
462
  </script>
463
  ';
464
+ }
465
 
466
+ echo '
467
  <script type="text/javascript" >
468
  jQuery(document).ready(function() {
469
  if(bulkProcessingRunning) {
477
  });
478
  </script>
479
  ';
480
+ }
481
 
482
+ public function renderSettingsMenu() {
483
+ if ( !current_user_can( 'manage_options' ) ) {
484
+ wp_die('You do not have sufficient permissions to access this page.');
485
+ }
486
+ echo '<h1>ShortPixel Image Optimiser Settings</h1>';
487
+ echo '<p>
488
  <a href="https://shortpixel.com">ShortPixel.com</a> |
489
  <a href="https://wordpress.org/plugins/shortpixel-image-optimiser/installation/">Installation </a> |
490
  <a href="https://wordpress.org/support/plugin/shortpixel-image-optimiser">Support </a>
491
  </p>';
492
+ echo '<p>New images uploaded to the Media Library will be optimized automatically.<br/>If you have existing images you would like to optimize, you can use the <a href="' . get_admin_url() . 'upload.php?page=wp-short-pixel-bulk">Bulk Optimisation Tool</a>.</p>';
493
+
494
+ $noticeHTML = "<br/><div style=\"background-color: #fff; border-left: 4px solid %s; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); padding: 1px 12px;\"><p>%s</p></div>";
495
+
496
+ if(isset($_POST['submit']) || isset($_POST['validate'])) {
497
+ //handle API Key - common for submit and validate
498
+ $_POST['key'] = trim($_POST['key']);
499
+ $validityData = $this->getQuotaInformation($_POST['key'], true);
500
+
501
+ $this->_apiKey = $_POST['key'];
502
+ $this->_apiInterface->setApiKey($this->_apiKey);
503
+ update_option('wp-short-pixel-apiKey', $_POST['key']);
504
+ if($validityData['APIKeyValid']) {
505
+ if(isset($_POST['validate'])) {
506
+ //display notification
507
+ printf($noticeHTML, '#7ad03a', 'API Key valid!');
508
+ }
509
+ update_option('wp-short-pixel-verifiedKey', true);
510
+ $this->_verifiedKey = true;
511
+ } else {
512
+ if(isset($_POST['validate'])) {
513
+ //display notification
514
+ printf($noticeHTML, '#dd3d36', $validityData["Message"]);
515
+ }
516
+ update_option('wp-short-pixel-verifiedKey', false);
517
+ $this->_verifiedKey = false;
518
+ }
519
+
520
+ //if save button - we process the rest of the form elements
521
+ if(isset($_POST['submit'])) {
522
+ update_option('wp-short-pixel-compression', $_POST['compressionType']);
523
+ $this->_compressionType = $_POST['compressionType'];
524
+ $this->_apiInterface->setCompressionType($this->_compressionType);
525
+ if(isset($_POST['thumbnails'])) { $this->_processThumbnails = 1; } else { $this->_processThumbnails = 0; }
526
+ if(isset($_POST['backupImages'])) { $this->_backupImages = 1; } else { $this->_backupImages = 0; }
527
+ update_option('wp-short-process_thumbnails', $this->_processThumbnails);
528
+ update_option('wp-short-backup_images', $this->_backupImages);
529
+ }
530
+ }
531
 
532
+ if(isset($_POST['emptyBackup'])) {
533
+ if(file_exists(SP_BACKUP_FOLDER)) {
534
+ $files = scandir(SP_BACKUP_FOLDER);
535
+ $cleanPath = rtrim(SP_BACKUP_FOLDER, '/'). '/';
536
+ foreach($files as $t) {
537
+ if ( $t != "." && $t != "..") {
538
+ unlink(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $t);
539
+ }
 
 
540
  }
541
+ }
542
+ }
543
 
544
+ $checked = '';
545
+ if($this->_processThumbnails) { $checked = 'checked'; }
546
 
547
+ $checkedBackupImages = '';
548
+ if($this->_backupImages) { $checkedBackupImages = 'checked'; }
549
 
550
+ $formHTML = <<< HTML
551
  <form name='wp_shortpixel_options' action='' method='post' id='wp_shortpixel_options'>
552
  <table class="form-table">
553
  <tbody><tr>
558
  </tr>
559
  HTML;
560
 
561
+ if(!$this->_verifiedKey) {
562
+ //if invalid key we display the link to the API Key
563
+ $formHTML .= '<tr><td style="padding-left: 0px;" colspan="2">Don’t have an API Key? <a href="https://shortpixel.com/wp-apikey" target="_blank">Sign up, it’s free.</a></td></tr>';
564
+ $formHTML .= '</form>';
565
+ } else {
566
+ //if valid key we display the rest of the options
567
+ $formHTML .= <<< HTML
568
  <tr><th scope="row">
569
  <label for="compressionType">Compression type: <span title="
570
  Lossy compression: 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.
573
  </th><td>
574
  HTML;
575
 
576
+ if($this->_compressionType == 1) {
577
+ $formHTML .= '<input type="radio" name="compressionType" value="1" checked>Lossy</br></br>';
578
+ $formHTML .= '<input type="radio" name="compressionType" value="0" >Lossless';
579
+ } else {
580
+ $formHTML .= '<input type="radio" name="compressionType" value="1">Lossy</br></br>';
581
+ $formHTML .= '<input type="radio" name="compressionType" value="0" checked>Lossless';
582
+ }
583
 
584
+ $formHTML .= <<<HTML
585
  </td>
586
  </tr>
587
  <tr>
616
  }
617
  </script>
618
  HTML;
619
+ }
620
 
621
+ echo $formHTML;
622
 
623
+ if($this->_verifiedKey) {
624
+ $fileCount = get_option('wp-short-pixel-fileCount');
625
+ $savedSpace = self::formatBytes(get_option('wp-short-pixel-savedSpace'),2);
626
+ $averageCompression = round(get_option('wp-short-pixel-averageCompression'),2);
627
+ $savedBandwidth = self::formatBytes(get_option('wp-short-pixel-savedSpace') * 1000,2);
628
+ $quotaData = $this->getQuotaInformation();
629
+ $backupFolderSize = self::formatBytes(self::folderSize(SP_BACKUP_FOLDER));
630
 
631
+ $statHTML = <<< HTML
632
  <a id="facts"></a>
633
  <h3>ShortPixel Facts & Figures</h3>
634
  <table class="form-table">
657
  <td>$averageCompression%</td>
658
  </tr>
659
  HTML;
660
+ if($this->_backupImages) {
661
+ $statHTML .= <<< HTML
662
  <form action="" method="POST">
663
  <tr>
664
  <th scope="row"><label for="sizeBackup">Backup folder size:</label></th>
669
  </tr>
670
  </form>
671
  HTML;
672
+ }
673
 
674
+ $statHTML .= <<< HTML
675
  </tbody></table>
676
  <p>* Saved bandwidth is calculated at 100,000 impressions/image</p>
677
  HTML;
678
+ echo $statHTML;
 
679
  }
680
+ }
681
 
682
+ public function getBulkProcessingForm($imageCount) {
683
+ return <<< HTML
684
  </br>
685
  Currently, you have {$imageCount} images in your library. </br>
686
  </br>
688
  <input type="submit" name="bulkProcess" id="bulkProcess" class="button button-primary" value="Compress all your images">
689
  </form>
690
  HTML;
691
+ }
692
+
693
+
694
+ public function getQuotaInformation($apiKey = null, $appendUserAgent = false) {
695
+
696
+ if(is_null($apiKey)) { $apiKey = $this->_apiKey; }
697
+
698
+ $requestURL = 'https://api.shortpixel.com/v2/api-status.php';
699
+ $args = array('timeout'=> SP_MAX_TIMEOUT,
700
+ 'sslverify' => false,
701
+ 'body' => array('key' => $apiKey)
702
+ );
703
+
704
+ if($appendUserAgent) {
705
+ $args['body']['useragent'] = urlencode($_SERVER['HTTP_USER_AGENT']);
706
  }
707
 
708
+ $response = wp_remote_post($requestURL, $args);
709
 
710
+ if(is_wp_error( $response )) {
711
+ $response = wp_remote_get(str_replace('https://', 'http://', $requestURL), $args);
712
+ }
713
 
714
+ $defaultData = array(
715
+ "APIKeyValid" => false,
716
+ "Message" => 'API Key could not be validated. Could not connect Shortpixel service.',
717
+ "APICallsMade" => 'Information unavailable. Please check your API key.',
718
+ "APICallsQuota" => 'Information unavailable. Please check your API key.');
719
 
720
+ if(is_object($response) && get_class($response) == 'WP_Error') {
721
+ return $defaultData;
722
+ }
 
 
723
 
724
+ if($response['response']['code'] != 200) {
725
+ return $defaultData;
726
+ }
727
 
728
+ $data = $response['body'];
729
+ $data = $this->parseJSON($data);
730
 
731
+ if(empty($data)) { return $defaultData; }
 
 
732
 
733
+ if($data->Status->Code != 2) {
734
+ $defaultData['Message'] = $data->Status->Message;
735
+ return $defaultData;
736
+ }
 
737
 
738
+ return array(
739
+ "APIKeyValid" => true,
740
+ "APICallsMade" => number_format($data->APICallsMade) . ' images',
741
+ "APICallsQuota" => number_format($data->APICallsQuota) . ' images'
742
+ );
743
 
 
 
 
744
 
745
+ }
 
746
 
747
+ public function generateCustomColumn( $column_name, $id ) {
748
+ if( 'wp-shortPixel' == $column_name ) {
749
+ $data = wp_get_attachment_metadata($id);
750
 
751
+ if ( isset( $data['ShortPixelImprovement'] ) ) {
752
+ if(isset($meta['ShortPixel']['BulkProcessing'])) {
753
+ print 'Waiting for bulk processing';
754
+ return;
755
  }
756
 
757
+ print $data['ShortPixelImprovement'];
758
+ if(is_numeric($data['ShortPixelImprovement'])) {
759
+ print '%';
760
+ print " | <a href=\"admin.php?action=shortpixel_restore_backup&amp;attachment_ID={$id}\">Restore backup</a>";
761
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
762
  }
763
+ } elseif(isset($data['ShortPixel']['WaitingProcessing'])) {
764
+ print 'Image waiting to be processed';
765
+ return;
766
+ } else {
767
+ if ( wp_attachment_is_image( $id ) ) {
768
+ print 'Image not processed';
769
+ print " | <a href=\"admin.php?action=shortpixel_manual_optimize&amp;attachment_ID={$id}\">Optimize now</a>";
770
+ return;
771
+ }
772
+ }
773
  }
774
+ }
775
+
776
+ public function columns( $defaults ) {
777
+ $defaults['wp-shortPixel'] = 'ShortPixel Compression';
778
+ return $defaults;
779
+ }
780
+
781
+ public function generatePluginLinks($links) {
782
+ $in = '<a href="options-general.php?page=wp-shortpixel">Settings</a>';
783
+ array_unshift($links, $in);
784
+ return $links;
785
+ }
786
+
787
+ public function parseJSON($data) {
788
+ if ( function_exists('json_decode') ) {
789
+ $data = json_decode( $data );
790
+ } else {
791
+ require_once( 'JSON/JSON.php' );
792
+ $json = new Services_JSON( );
793
+ $data = $json->decode( $data );
794
  }
795
+ return $data;
796
+ }
797
 
 
 
 
 
 
798
 
799
+ static public function formatBytes($bytes, $precision = 2) {
800
+ $units = array('B', 'KB', 'MB', 'GB', 'TB');
801
+
802
+ $bytes = max($bytes, 0);
803
+ $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
804
+ $pow = min($pow, count($units) - 1);
 
 
 
 
805
 
806
+ $bytes /= pow(1024, $pow);
807
 
808
+ return round($bytes, $precision) . ' ' . $units[$pow];
809
+ }
810
 
811
+ static public function isImage($path) {
812
+ if(function_exists('exif_imagetype')) {
813
+ return exif_imagetype($path);
814
+ } else {
815
+ $pathParts = pathinfo($path);
816
+ if(in_array($pathParts['extension'], array('jpg', 'jpeg', 'gif', 'png', 'bmp'))) {
817
+ return true;
818
+ } else {
819
+ return false;
820
+ }
821
+ }
822
 
823
+ }
824
 
825
+ static public function folderSize($path) {
826
+ $total_size = 0;
827
+ if(file_exists($path)) {
828
+ $files = scandir($path);
829
+ } else {
830
+ return $total_size;
831
+ }
832
+ $cleanPath = rtrim($path, '/'). '/';
833
+ foreach($files as $t) {
834
+ if ($t<>"." && $t<>"..") {
835
+ $currentFile = $cleanPath . $t;
836
+ if (is_dir($currentFile)) {
837
+ $size = foldersize($currentFile);
838
+ $total_size += $size;
839
+ }
840
+ else {
841
+ $size = filesize($currentFile);
842
+ $total_size += $size;
843
+ }
844
+ }
845
  }
846
+ return $total_size;
847
+ }
848
 
849
+ public function migrateBackupFolder() {
850
+ $oldBackupFolder = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'ShortpixelBackups';
 
 
 
 
 
 
 
 
 
851
 
852
+ if(!file_exists($oldBackupFolder)) return; //if old backup folder does not exist then there is nothing to do
853
+
854
+ if(!file_exists(SP_BACKUP_FOLDER)) {
855
+ //we check that the backup folder exists, if not we create it so we can copy into it
856
+ if(!mkdir(SP_BACKUP_FOLDER, 0777, true)) return;
857
  }
858
 
859
+ $scannedDirectory = array_diff(scandir($oldBackupFolder), array('..', '.'));
860
+ foreach($scannedDirectory as $file) {
861
+ @rename($oldBackupFolder.DIRECTORY_SEPARATOR.$file, SP_BACKUP_FOLDER.DIRECTORY_SEPARATOR.$file);
862
+ }
863
+ $scannedDirectory = array_diff(scandir($oldBackupFolder), array('..', '.'));
864
+ if(empty($scannedDirectory)) {
865
+ @rmdir($oldBackupFolder);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
866
  }
867
 
868
+ return;
869
+ }
870
+
871
 
872
  }
873