ShortPixel Image Optimizer - Version 4.0.0

Version Description

  • Custom folders optimization
  • NextGen galleries optimization
Download this release

Release Info

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

Code changes from version 3.3.8 to 4.0.0

Files changed (66) hide show
  1. class/db/shortpixel-custom-meta-dao.php +421 -0
  2. class/db/shortpixel-db.php +9 -0
  3. class/db/shortpixel-meta-facade.php +344 -0
  4. class/db/shortpixel-nextgen-adapter.php +33 -0
  5. class/db/wp-shortpixel-db.php +76 -0
  6. class/db/wp-shortpixel-media-library-adapter.php +165 -0
  7. class/model/shortpixel-entity.php +19 -0
  8. class/model/shortpixel-folder.php +198 -0
  9. class/model/shortpixel-meta.php +251 -0
  10. class/model/sp-file-rights-exception.php +6 -0
  11. class/shortpixel-tools.php +42 -0
  12. class/view/shortpixel-list-table.php +180 -0
  13. class/view/shortpixel_view.php +971 -0
  14. readme.txt +18 -25
  15. {css → res/css}/short-pixel.css +188 -13
  16. res/css/sp-file-tree.css +259 -0
  17. {img → res/img}/arrow.png +0 -0
  18. res/img/exclamation-big.png +0 -0
  19. res/img/file-tree/application.png +0 -0
  20. res/img/file-tree/code.png +0 -0
  21. res/img/file-tree/css.png +0 -0
  22. res/img/file-tree/db.png +0 -0
  23. res/img/file-tree/directory-lock.png +0 -0
  24. res/img/file-tree/directory.png +0 -0
  25. res/img/file-tree/doc.png +0 -0
  26. res/img/file-tree/file-lock.png +0 -0
  27. res/img/file-tree/file.png +0 -0
  28. res/img/file-tree/film.png +0 -0
  29. res/img/file-tree/flash.png +0 -0
  30. res/img/file-tree/folder_open.png +0 -0
  31. res/img/file-tree/html.png +0 -0
  32. res/img/file-tree/java.png +0 -0
  33. res/img/file-tree/linux.png +0 -0
  34. res/img/file-tree/music.png +0 -0
  35. res/img/file-tree/pdf.png +0 -0
  36. res/img/file-tree/php.png +0 -0
  37. res/img/file-tree/picture.png +0 -0
  38. res/img/file-tree/ppt.png +0 -0
  39. res/img/file-tree/psd.png +0 -0
  40. res/img/file-tree/ruby.png +0 -0
  41. res/img/file-tree/script.png +0 -0
  42. res/img/file-tree/spinner.gif +0 -0
  43. res/img/file-tree/txt.png +0 -0
  44. res/img/file-tree/xls.png +0 -0
  45. res/img/file-tree/zip.png +0 -0
  46. res/img/info-icon.png +0 -0
  47. {img → res/img}/loading-dark-big.gif +0 -0
  48. {img → res/img}/loading-dark.gif +0 -0
  49. {img → res/img}/loading.gif +0 -0
  50. res/img/logo-pdf.png +0 -0
  51. res/img/robo-slider.png +0 -0
  52. {img → res/img}/shortpixel-alert.png +0 -0
  53. {img → res/img}/shortpixel.png +0 -0
  54. {img → res/img}/slider.png +0 -0
  55. {img → res/img}/stars.png +0 -0
  56. res/js/jquery.knob.js +805 -0
  57. res/js/jquery.tooltip.js +207 -0
  58. {js → res/js}/short-pixel.js +238 -65
  59. res/js/sp-file-tree.js +213 -0
  60. shortpixel-debug.php +47 -0
  61. shortpixel_api.php +271 -200
  62. shortpixel_queue.php +142 -131
  63. shortpixel_view.php +1 -1
  64. wp-shortpixel-req.php +36 -0
  65. wp-shortpixel-settings.php +55 -16
  66. wp-shortpixel.php +840 -507
class/db/shortpixel-custom-meta-dao.php ADDED
@@ -0,0 +1,421 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ShortPixelCustomMetaDao {
4
+ const META_VERSION = 1;
5
+ private $db;
6
+
7
+ private static $fields = array(
8
+ ShortPixelMeta::TABLE_SUFFIX => array(
9
+ "folder_id" => "d",
10
+ "path" => "s",
11
+ "name" => "s",
12
+ "compression_type" => "d",
13
+ "compressed_size" => "d",
14
+ "keep_exif" => "d",
15
+ "cmyk2rgb" => "d",
16
+ "resize" => "d",
17
+ "resize_width" => "d",
18
+ "resize_height" => "d",
19
+ "backup" => "d",
20
+ "status" => "d",
21
+ "retries" => "d",
22
+ "message" => "s",
23
+ "ext_meta_id" => "d" //this is nggPid for now
24
+ ),
25
+ ShortPixelFolder::TABLE_SUFFIX => array(
26
+ "path" => "s",
27
+ "file_count" => "d",
28
+ "status" => "d",
29
+ "ts_updated" => "s",
30
+ "ts_created" => "s",
31
+ )
32
+ );
33
+
34
+ public function __construct($db) {
35
+ $this->db = $db;
36
+ }
37
+
38
+ public static function getCreateFolderTableSQL($tablePrefix, $charsetCollate) {
39
+ return "CREATE TABLE {$tablePrefix}shortpixel_folders (
40
+ id mediumint(9) NOT NULL AUTO_INCREMENT,
41
+ path varchar(512),
42
+ name varchar(64),
43
+ path_md5 char(32),
44
+ file_count int,
45
+ status tinyint DEFAULT 0,
46
+ ts_updated timestamp,
47
+ ts_created timestamp,
48
+ UNIQUE KEY id (id)
49
+ ) $charsetCollate;";
50
+ // UNIQUE INDEX spf_path_md5 (path_md5)
51
+ }
52
+
53
+ public static function getCreateMetaTableSQL($tablePrefix, $charsetCollate) {
54
+ return "CREATE TABLE {$tablePrefix}shortpixel_meta (
55
+ id mediumint(10) NOT NULL AUTO_INCREMENT,
56
+ folder_id mediumint(9) NOT NULL,
57
+ ext_meta_id int(10),
58
+ path varchar(512),
59
+ name varchar(64),
60
+ path_md5 char(32),
61
+ compressed_size int(10) NOT NULL DEFAULT 0,
62
+ compression_type tinyint,
63
+ keep_exif tinyint,
64
+ cmyk2rgb tinyint,
65
+ resize tinyint,
66
+ resize_width smallint,
67
+ resize_height smallint,
68
+ backup tinyint,
69
+ status tinyint DEFAULT 0,
70
+ retries tinyint DEFAULT 0,
71
+ message varchar(255),
72
+ ts_added timestamp,
73
+ ts_optimized timestamp,
74
+ UNIQUE KEY sp_id (id)
75
+ ) $charsetCollate;";
76
+ //UNIQUE INDEX sp_path_md5 (path_md5),
77
+ //FOREIGN KEY fk_shortpixel_meta_folder(folder_id) REFERENCES {$tablePrefix}shortpixel_folders(id)
78
+ }
79
+
80
+ private function addIfMissing($type, $table, $key, $field, $fkTable = null, $fkField = null) {
81
+ $hasIndexSql = "select count(*) hasIndex from information_schema.statistics where table_name = '%s' and index_name = '%s' and table_schema = database()";
82
+ $createIndexSql = "ALTER TABLE %s ADD UNIQUE INDEX %s (%s)";
83
+ $createFkSql = "ALTER TABLE %s ADD FOREIGN KEY %s(%s) REFERENCES %s(%s)";
84
+ $hasIndex = $this->db->query(sprintf($hasIndexSql, $table, $key));
85
+ if($hasIndex[0]->hasIndex == 0){
86
+ if($type == "UNIQUE INDEX"){
87
+ $this->db->query(sprintf($createIndexSql, $table, $key, $field));
88
+ } else {
89
+ $this->db->query(sprintf($createFkSql, $table, $key, $field, $fkTable, $fkField));
90
+ }
91
+ return true;
92
+ }
93
+ return false;
94
+ }
95
+
96
+ public function tablesExist() {
97
+ $hasTablesSql = "SELECT COUNT(1) tableCount FROM information_schema.tables WHERE table_schema='{$wpdb->dbname}' "
98
+ . "AND table_name='{$wpdb->prefix}shortpixel_meta' OR table_name='{$wpdb->prefix}shortpixel_folders'";
99
+ $hasTables = $this->db->query(sprintf($this->db->getPrefix()."shortpixel_meta", "fk_shortpixel_meta_folder"));
100
+ if($hasTables[0]->tableCount == 2){
101
+ return true;
102
+ }
103
+ return false;
104
+
105
+ }
106
+
107
+ public function createUpdateShortPixelTables() {
108
+ $res = $this->db->createUpdateSchema(array(
109
+ self::getCreateFolderTableSQL($this->db->getPrefix(), $this->db->getCharsetCollate()),
110
+ self::getCreateMetaTableSQL($this->db->getPrefix(), $this->db->getCharsetCollate())
111
+ ));
112
+ // Set up indexes, not handled well by WP DBDelta
113
+ $this->addIfMissing("UNIQUE INDEX", $this->db->getPrefix()."shortpixel_folders", "spf_path_md5", "path_md5");
114
+ $this->addIfMissing("UNIQUE INDEX", $this->db->getPrefix()."shortpixel_meta", "sp_path_md5", "path_md5");
115
+ $this->addIfMissing("FOREIGN KEY", $this->db->getPrefix()."shortpixel_meta", "fk_shortpixel_meta_folder", "folder_id",
116
+ $this->db->getPrefix()."shortpixel_folders", "id");
117
+ }
118
+
119
+ public function getFolders($deleted = false) {
120
+ $sql = "SELECT * FROM {$this->db->getPrefix()}shortpixel_folders" . ($deleted ? "" : " WHERE status <> -1");
121
+ $rows = $this->db->query($sql);
122
+ $folders = array();
123
+ foreach($rows as $row) {
124
+ $folders[] = new ShortPixelFolder($row);
125
+ }
126
+ return $folders;
127
+ }
128
+
129
+ public function getFolder($path, $deleted = false) {
130
+ $sql = "SELECT * FROM {$this->db->getPrefix()}shortpixel_folders" . ($deleted ? "" : " WHERE path = %s AND status <> -1");
131
+ $rows = $this->db->query($sql, array($path));
132
+ $folders = array();
133
+ foreach($rows as $row) {
134
+ return new ShortPixelFolder($row);
135
+ }
136
+ return false;
137
+ }
138
+
139
+ public function hasFoldersTable() {
140
+ global $wpdb;
141
+ $foldersTable = $wpdb->get_results("SELECT COUNT(1) hasFoldersTable FROM information_schema.tables WHERE table_schema='{$wpdb->dbname}' AND table_name='{$wpdb->prefix}shortpixel_folders'");
142
+ if(isset($foldersTable[0]->hasFoldersTable) && $foldersTable[0]->hasFoldersTable > 0) {
143
+ return true;
144
+ }
145
+ return false;
146
+ }
147
+
148
+ public function addFolder($folder, $fileCount = 0) {
149
+ //$sql = "INSERT INTO {$this->db->getPrefix()}shortpixel_folders (path, file_count, ts_created) values (%s, %d, now())";
150
+ //$this->db->query($sql, array($folder, $fileCount));
151
+ return $this->db->insert($this->db->getPrefix().'shortpixel_folders',
152
+ array("path" => $folder, "path_md5" => md5($folder), "file_count" => $fileCount, "ts_updated" => date("Y-m-d H:i:s")),
153
+ array("path" => "%s", "path_md5" => "%s", "file_count" => "%d", "ts_updated" => "%s"));
154
+ }
155
+
156
+ public function updateFolder($folder, $newPath, $status = 0, $fileCount = 0) {
157
+ $sql = "UPDATE {$this->db->getPrefix()}shortpixel_folders SET path = %s, path_md5 = %s, file_count = %d, ts_updated = %s, status = %d WHERE path = %s";
158
+ $this->db->query($sql, array($newPath, md5($newPath), $fileCount, date("Y-m-d H:i:s"), $status, $folder));
159
+ $sql2 = "SELECT id FROM {$this->db->getPrefix()}shortpixel_folders WHERE path = %s";
160
+ $res = $this->db->query($sql2, array($folder));
161
+ if(is_array($res)) {
162
+ return $res[0]->id;
163
+ }
164
+ else return -1;
165
+ }
166
+
167
+ public function removeFolder($folderPath) {
168
+ $sql = "SELECT id FROM {$this->db->getPrefix()}shortpixel_folders WHERE path = %s";
169
+ $row = $this->db->query($sql, array($folderPath));
170
+ if(!isset($row[0]->id)) return false;
171
+ $id = $row[0]->id;
172
+ $sql = "UPDATE {$this->db->getPrefix()}shortpixel_folders SET status = -1 WHERE id = %d";
173
+ $this->db->query($sql, array($id));
174
+
175
+ $this->db->hideErrors();
176
+ $sql = "DELETE FROM {$this->db->getPrefix()}shortpixel_meta WHERE folder_id = %d AND status <> 1 AND status <> 2";
177
+ @$this->db->query($sql, array($id));
178
+ $sql = "DELETE FROM {$this->db->getPrefix()}shortpixel_folders WHERE path = %s";
179
+ @$this->db->query($sql, array($folderPath));
180
+ $this->db->restoreErrors();
181
+ }
182
+
183
+ public function newFolderFromPath($path, $uploadPath, $rootPath) {
184
+ WpShortPixelDb::checkCustomTables(); // check if custom tables are created, if not, create them
185
+ $addedFolder = ShortPixelFolder::checkFolder($path, $uploadPath);
186
+ if($this->getFolder($addedFolder)) {
187
+ return "Folder already added.";
188
+ }
189
+ if(strpos($addedFolder, $rootPath) !== 0) {
190
+
191
+ return( "The $addedFolder folder cannot be processed as it's not inside the root path of your website.");
192
+ }
193
+ $folder = new ShortPixelFolder(array("path" => $addedFolder));
194
+ try {
195
+ $folder->setFileCount($folder->countFiles());
196
+ } catch(SpFileRightsException $ex) {
197
+ return $ex->getMessage();
198
+ }
199
+ if(ShortPixelMetaFacade::isMediaSubfolder($folder->getPath())) {
200
+ return "This folder contains Media Library images. To optimize Media Library images please go to <a href='upload.php?mode=list'>Media Library list view</a> or to <a href='upload.php?page=wp-short-pixel-bulk'>SortPixel Bulk page</a>.";
201
+ }
202
+ $folderMsg = $this->saveFolder($folder);
203
+ if(!$folder->getId()) {
204
+ throw new Exception("Inserted folder doesn't have an ID!");
205
+ }
206
+ //die(var_dump($folder));
207
+ if(!$folderMsg) {
208
+ $fileList = $folder->getFileList();
209
+ $this->batchInsertImages($fileList, $folder->getId());
210
+ }
211
+ return $folderMsg;
212
+
213
+ }
214
+ /**
215
+ *
216
+ * @param type $path
217
+ * @return false if saved OK, error message otherwise.
218
+ */
219
+ public function saveFolder(&$folder) {
220
+ $addedPath = $folder->getPath();
221
+ if($addedPath) {
222
+ //first check if it does contain the Backups Folder - we don't allow that
223
+ if(ShortPixelFolder::checkFolderIsSubfolder(SP_BACKUP_FOLDER, $addedPath)) {
224
+ return "This folder contains the ShortPixel Backups. Please select a different folder.";
225
+ }
226
+ $customFolderPaths = array_map(function($item){return $item->getPath();}, $this->getFolders());
227
+ $allFolders = $this->getFolders(true);
228
+ $customAllFolderPaths = array_map(function($item){return $item->getPath();}, $allFolders);
229
+ $parent = ShortPixelFolder::checkFolderIsSubfolder($addedPath, $customFolderPaths);
230
+ if(!$parent){
231
+ $sub = ShortPixelFolder::checkFolderIsParent($addedPath, $customAllFolderPaths);
232
+ if($sub) {
233
+ $id = $this->updateFolder($sub, $addedPath, 0, $folder->getFileCount());
234
+ } else {
235
+ $id = $this->addFolder($addedPath, $folder->getFileCount());
236
+ }
237
+ $folder->setId($id);
238
+ return false;
239
+ } else {
240
+ foreach($allFolders as $fld) {
241
+ if($fld->getPath() == $parent) {
242
+ $folder->setPath($parent);
243
+ $folder->setId($fld->getId());
244
+ }
245
+ }
246
+ //var_dump($allFolders);
247
+ return "Folder already included in {$parent}.";
248
+ }
249
+ } else {
250
+ return "Folder does not exist.";
251
+ }
252
+ }
253
+
254
+ protected function metaToParams($meta) {
255
+ $params = $types = array();
256
+ foreach(self::$fields[ShortPixelMeta::TABLE_SUFFIX] as $key=>$type) {
257
+ $getter = "get" . ShortPixelTools::snakeToCamel($key);
258
+ if(!method_exists($meta, $getter)) {
259
+ throw new Exception("Bad fields list in ShortPixelCustomMetaDao::metaToParams");
260
+ }
261
+ $val = $meta->$getter();
262
+ if($val !== null) {
263
+ $params[$key] = $val;
264
+ $types[] = "%" . $type;
265
+ }
266
+ }
267
+ return (object)array("params" => $params, "types" => $types);
268
+ }
269
+ public function addImage($meta) {
270
+ $p = $this->metaToParams($meta);
271
+ $id = $this->db->insert($this->db->getPrefix().'shortpixel_meta', $p->params, $p->types);
272
+ return $id;
273
+ }
274
+
275
+ public function batchInsertImages($pathsFile, $folderId) {
276
+ $pathsFileHandle = fopen($pathsFile, 'r');
277
+
278
+ $values = ''; $inserted = 0;
279
+ $sql = "INSERT IGNORE INTO {$this->db->getPrefix()}shortpixel_meta(folder_id, path, name, path_md5, status) VALUES ";
280
+ for ($i = 0; ($path = fgets($pathsFileHandle)) !== false; $i++) {
281
+ $pathParts = explode(DIRECTORY_SEPARATOR, trim($path));
282
+ $namePrep = $this->db->prepare("%s",$pathParts[count($pathParts) - 1]);
283
+ $values .= (strlen($values) ? ", ": "") . "(" . $folderId . ", ". $this->db->prepare("%s", trim($path)) . ", ". $namePrep .", '". md5($path) ."', 0)";
284
+ if($i % 1000 == 999) {
285
+ $id = $this->db->query($sql . $values);
286
+ $values = '';
287
+ $inserted++;
288
+ }
289
+ }
290
+ if($values) {
291
+ $id = $this->db->query($sql . $values);
292
+ }
293
+ fclose($pathsFileHandle);
294
+ unlink($pathsFile);
295
+ return $inserted;
296
+ }
297
+
298
+ public function getPaginatedMetas($hasNextGen, $count, $page, $orderby = false, $order = false) {
299
+ $sql = "SELECT sm.id, sm.name, sm.path folder, "
300
+ . ($hasNextGen ? "CASE WHEN ng.gid IS NOT NULL THEN 'NextGen' ELSE 'Custom' END media_type, " : "'Custom' media_type, ")
301
+ . "sm.status, sm.compression_type, sm.keep_exif, sm.cmyk2rgb, sm.resize, sm.resize_width, sm.resize_height, sm.message "
302
+ . "FROM {$this->db->getPrefix()}shortpixel_meta sm "
303
+ . "INNER JOIN {$this->db->getPrefix()}shortpixel_folders sf on sm.folder_id = sf.id "
304
+ . ($hasNextGen ? "LEFT JOIN {$this->db->getPrefix()}ngg_gallery ng on sf.path = ng.path " : " ")
305
+ . "WHERE sf.status <> -1 "
306
+ . ($orderby ? "ORDER BY $orderby $order " : "")
307
+ . "LIMIT $count OFFSET " . ($page - 1) * $count;
308
+
309
+ //die($sql);
310
+ return $this->db->query($sql);
311
+ }
312
+
313
+ public function getPendingMetas($count) {
314
+ return $this->db->query("SELECT sm.id from {$this->db->getPrefix()}shortpixel_meta sm "
315
+ . "INNER JOIN {$this->db->getPrefix()}shortpixel_folders sf on sm.folder_id = sf.id "
316
+ . "WHERE sf.status <> -1 AND ( sm.status = 0 OR sm.status = 1 OR (sm.status <> 2 AND sm.retries < 3)) "
317
+ . "ORDER BY ts_added DESC LIMIT $count");
318
+ }
319
+
320
+ public function getFolderOptimizationStatus($folderId) {
321
+ $res = $this->db->query("SELECT SUM(CASE WHEN sm.status = 2 THEN 1 ELSE 0 END) Optimized, SUM(CASE WHEN sm.status = 1 THEN 1 ELSE 0 END) Pending, "
322
+ . "SUM(CASE WHEN sm.status = 0 THEN 1 ELSE 0 END) Waiting, SUM(CASE WHEN sm.status < 0 THEN 1 ELSE 0 END) Failed, count(*) Total "
323
+ . "FROM {$this->db->getPrefix()}shortpixel_meta sm "
324
+ . "INNER JOIN {$this->db->getPrefix()}shortpixel_folders sf on sm.folder_id = sf.id "
325
+ . "WHERE sf.id = $folderId");
326
+ return $res[0];
327
+ }
328
+
329
+ public function getPendingMetaCount() {
330
+ $res = $this->db->query("SELECT COUNT(sm.id) recCount from {$this->db->getPrefix()}shortpixel_meta sm "
331
+ . "INNER JOIN {$this->db->getPrefix()}shortpixel_folders sf on sm.folder_id = sf.id "
332
+ . "WHERE sf.status <> -1 AND ( sm.status = 0 OR sm.status = 1 )");
333
+ return isset($res[0]->recCount) ? $res[0]->recCount : null;
334
+ }
335
+
336
+ public function getCustomMetaCount() {
337
+ $sql = "SELECT COUNT(sm.id) recCount FROM {$this->db->getPrefix()}shortpixel_meta sm "
338
+ . "INNER JOIN {$this->db->getPrefix()}shortpixel_folders sf on sm.folder_id = sf.id "
339
+ . "WHERE sf.status <> -1";
340
+ $res = $this->db->query($sql);
341
+ return isset($res[0]->recCount) ? $res[0]->recCount : 0;
342
+ }
343
+
344
+ public function getMeta($id, $deleted = false) {
345
+ $sql = "SELECT * FROM {$this->db->getPrefix()}shortpixel_meta WHERE id = %d " . ($deleted ? "" : " AND status <> -1");
346
+ $rows = $this->db->query($sql, array($id));
347
+ foreach($rows as $row) {
348
+ $meta = new ShortPixelMeta($row);
349
+ if($meta->getPath()) {
350
+ $meta->setWebPath(ShortPixelMetaFacade::filenameToContentRelative($meta->getPath()));
351
+ }
352
+ //die(var_dump($meta)."ZA META");
353
+ return $meta;
354
+ }
355
+ return null;
356
+ }
357
+
358
+ public function getMetaForPath($path, $deleted = false) {
359
+ $sql = "SELECT * FROM {$this->db->getPrefix()}shortpixel_meta WHERE path = %s " . ($deleted ? "" : " AND status <> -1");
360
+ $rows = $this->db->query($sql, array($path));
361
+ foreach($rows as $row) {
362
+ return new ShortPixelMeta($row);
363
+ }
364
+ return null;
365
+ }
366
+
367
+ public function update($meta) {
368
+ $metaClass = get_class($meta);
369
+ $sql = "UPDATE {$this->db->getPrefix()}shortpixel_" . $metaClass::TABLE_SUFFIX . " SET ";
370
+ foreach(self::$fields[$metaClass::TABLE_SUFFIX] as $field => $type) {
371
+ $getter = "get" . ShortPixelTools::snakeToCamel($field);
372
+ $val = $meta->$getter();
373
+ if($meta->$getter() !== null) {
374
+ $sql .= " {$field} = %{$type},"; $params[] = $val;
375
+ }
376
+ }
377
+
378
+ if(substr($sql, -1) != ',') {
379
+ return; //no fields added;
380
+ }
381
+
382
+ $sql = rtrim($sql, ",");
383
+ $sql .= " WHERE id = %d";
384
+ $params[] = $meta->getId();
385
+ $this->db->query($sql, $params);
386
+ }
387
+
388
+ public function delete($meta) {
389
+ $metaClass = get_class($meta);
390
+ $sql = "DELETE FROM {$this->db->getPrefix()}shortpixel_" . $metaClass::TABLE_SUFFIX . " WHERE id = %d";
391
+ $this->db->query($sql, array($meta->getId()));
392
+ }
393
+
394
+ public function countAllProcessableFiles() {
395
+ $sql = "SELECT count(*) totalFiles, sum(CASE WHEN status = 2 THEN 1 ELSE 0 END) totalProcessedFiles,"
396
+ ." sum(CASE WHEN status = 2 AND compression_type = 1 THEN 1 ELSE 0 END) totalProcLossyFiles,"
397
+ ." sum(CASE WHEN status = 2 AND compression_type = 0 THEN 1 ELSE 0 END) totalProcLosslessFiles"
398
+ ." FROM {$this->db->getPrefix()}shortpixel_meta WHERE status <> -1";
399
+ $rows = $this->db->query($sql);
400
+
401
+ $filesWithErrors = array();
402
+ $sql = "SELECT id, name, path, message FROM {$this->db->getPrefix()}shortpixel_meta WHERE status < -1 AND retries >= 3 LIMIT 30";
403
+ $failRows = $this->db->query($sql);
404
+ $filesWithErrors = array();
405
+ foreach($failRows as $failLine) {
406
+ $filesWithErrors['C-' . $failLine->id] = array('Name' => $failLine->name, 'Message' => $failLine->message, 'Path' => $failLine->path);
407
+ }
408
+
409
+ return array("totalFiles" => $rows[0]->totalFiles, "mainFiles" => $rows[0]->totalFiles,
410
+ "totalProcessedFiles" => $rows[0]->totalProcessedFiles, "mainProcessedFiles" => $rows[0]->totalProcessedFiles,
411
+ "totalProcLossyFiles" => $rows[0]->totalProcLossyFiles, "mainProcLossyFiles" => $rows[0]->totalProcLossyFiles,
412
+ "totalProcLosslessFiles" => $rows[0]->totalProcLosslessFiles, "mainProcLosslessFiles" => $rows[0]->totalProcLosslessFiles,
413
+ "totalCustomFiles" => $rows[0]->totalFiles, "mainCustomFiles" => $rows[0]->totalFiles,
414
+ "totalProcessedCustomFiles" => $rows[0]->totalProcessedFiles, "mainProcessedCustomFiles" => $rows[0]->totalProcessedFiles,
415
+ "totalProcLossyCustomFiles" => $rows[0]->totalProcLossyFiles, "mainProcLossyCustomFiles" => $rows[0]->totalProcLossyFiles,
416
+ "totalProcLosslessCustomFiles" => $rows[0]->totalProcLosslessFiles, "mainProcLosslessCustomFiles" => $rows[0]->totalProcLosslessFiles,
417
+ "filesWithErrors" => $filesWithErrors
418
+ );
419
+
420
+ }
421
+ }
class/db/shortpixel-db.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ interface ShortPixelDb {
4
+
5
+ public static function createUpdateSchema($tableDefinitions);
6
+ public function getPrefix();
7
+ public function query($sql);
8
+ public function getCharsetCollate();
9
+ }
class/db/shortpixel-meta-facade.php ADDED
@@ -0,0 +1,344 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ShortPixelMetaFacade {
4
+ const MEDIA_LIBRARY_TYPE = 1;
5
+ const CUSTOM_TYPE = 2;
6
+
7
+ private $ID;
8
+ private $type;
9
+ private $meta;
10
+ private $spMetaDao;
11
+ private $rawMeta;
12
+
13
+ public function __construct($ID) {
14
+ if(strpos($ID, 'C-') === 0) {
15
+ $this->ID = substr($ID, 2);
16
+ $this->type = self::CUSTOM_TYPE;
17
+ } else {
18
+ $this->ID = $ID;
19
+ $this->type = self::MEDIA_LIBRARY_TYPE;
20
+ }
21
+ $this->spMetaDao = new ShortPixelCustomMetaDao(new WpShortPixelDb());
22
+ }
23
+
24
+ function setRawMeta($rawMeta) {
25
+ if($this->type == self::MEDIA_LIBRARY_TYPE) {
26
+ $this->rawMeta = $rawMeta;
27
+ $this->meta = self::rawMetaToMeta($this->ID, $rawMeta);
28
+ }
29
+ }
30
+
31
+ function getMeta($refresh = false) {
32
+ if($refresh || !isset($this->meta)) {
33
+ if($this->type == self::CUSTOM_TYPE) {
34
+ $this->meta = $this->spMetaDao->getMeta($this->ID);
35
+ } else {
36
+ $rawMeta = wp_get_attachment_metadata($this->ID);
37
+ $this->meta = self::rawMetaToMeta($this->ID, $rawMeta);
38
+ $this->rawMeta = $rawMeta;
39
+ }
40
+ }
41
+ return $this->meta;
42
+ }
43
+
44
+ private static function rawMetaToMeta($ID, $rawMeta) {
45
+ return new ShortPixelMeta(array(
46
+ "id" => $ID,
47
+ "path" => get_attached_file($ID),
48
+ "webPath" => (isset($rawMeta["file"]) ? $rawMeta["file"] : null),
49
+ "thumbs" => (isset($rawMeta["sizes"]) ? $rawMeta["sizes"] : array()),
50
+ "message" =>(isset($rawMeta["ShortPixelImprovement"]) ? $rawMeta["ShortPixelImprovement"] : null),
51
+ "compressionType" =>(isset($rawMeta["ShortPixel"]["type"]) ? ($rawMeta["ShortPixel"]["type"] == "lossy" ? 1 : 0) : null),
52
+ "thumbsOpt" =>(isset($rawMeta["ShortPixel"]["thumbsOpt"]) ? $rawMeta["ShortPixel"]["thumbsOpt"] : null),
53
+ "thumbsTodo" =>(isset($rawMeta["ShortPixel"]["thumbsTodo"]) ? $rawMeta["ShortPixel"]["thumbsTodo"] : false),
54
+ "backup" => !isset($rawMeta['ShortPixel']['NoBackup']),
55
+ "status" => (!isset($rawMeta["ShortPixel"]) ? 0
56
+ : (isset($rawMeta["ShortPixelImprovement"]) && is_numeric($rawMeta["ShortPixelImprovement"]) ? 2
57
+ : (isset($rawMeta["ShortPixel"]["WaitingProcessing"]) ? 1
58
+ : -500))),
59
+ "retries" =>(isset($rawMeta["ShortPixel"]["retries"]) ? $rawMeta["ShortPixel"]["retries"] : 0),
60
+ ));
61
+ }
62
+
63
+ function check() {
64
+ if($this->type == self::CUSTOM_TYPE) {
65
+ $this->meta = $this->spMetaDao->getMeta($this->ID);
66
+ return $this->meta;
67
+ } else {
68
+ return wp_get_attachment_url($this->ID);
69
+ }
70
+ }
71
+
72
+ function updateMeta($newMeta = null) {
73
+ if($newMeta) {
74
+ $this->meta = $newMeta;
75
+ }
76
+ if($this->type == self::CUSTOM_TYPE) {
77
+ $this->spMetaDao->update($this->meta);
78
+ if($this->meta->getExtMetaId()) {
79
+ ShortPixelNextGenAdapter::updateImageSize($this->meta->getExtMetaId(), $this->meta->getPath());
80
+ }
81
+ }
82
+ elseif($this->type == ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE) {
83
+ //TODO vezi daca nu mai bine mutam asta in Facade - acolo unde actualizam meta, in updateMeta
84
+ $duplicates = ShortPixelMetaFacade::getWPMLDuplicates($this->ID);
85
+ foreach($duplicates as $_ID) {
86
+ $rawMeta = wp_get_attachment_metadata($_ID);
87
+ if(!is_array($rawMeta)) {
88
+ $rawMeta = array("previous_meta" => $rawMeta, 'ShortPixel' => array());
89
+ }
90
+ $rawMeta['ShortPixel']['type'] = ($newMeta->getCompressionType() == 1 ? "lossy" : "lossless");
91
+ $rawMeta['ShortPixel']['exifKept'] = $newMeta->getKeepExif();
92
+ $rawMeta['ShortPixel']['date'] = date("Y-m-d", strtotime($newMeta->getTsOptimized()));
93
+ //thumbs were processed if settings or if they were explicitely requested
94
+ $rawMeta['ShortPixel']['thumbsOpt'] = $newMeta->getThumbsOpt();
95
+ //if thumbsTodo - this means there was an explicit request to process thumbs for an image that was previously processed without
96
+ // don't update the ShortPixelImprovement ratio as this is only calculated based on main image
97
+ if($newMeta->getThumbsTodo()) {
98
+ $rawMeta['ShortPixel']['thumbsTodo'] = true;
99
+ } else {
100
+ $rawMeta['ShortPixelImprovement'] = "".round($newMeta->getImprovementPercent(),2);
101
+ unset($rawMeta['ShortPixel']['thumbsTodo']);
102
+ }
103
+ if($newMeta->getActualWidth() && $newMeta->getActualHeight()) {
104
+ $rawMeta['width'] = $newMeta->getActualWidth();
105
+ $rawMeta['height'] = $newMeta->getActualHeight();
106
+ }
107
+ if(!$newMeta->getBackup()) {
108
+ $rawMeta['ShortPixel']['NoBackup'] = true;
109
+ }
110
+ if($newMeta->getStatus() !== 1) {
111
+ unset($rawMeta['ShortPixel']['WaitingProcessing']);
112
+ }
113
+ wp_update_attachment_metadata($this->ID, $rawMeta);
114
+ }
115
+ }
116
+ }
117
+
118
+ function deleteMeta() {
119
+ if($this->type == self::CUSTOM_TYPE) {
120
+ throw new Exception("Not implemented 1");
121
+ } else {
122
+ unset($this->rawMeta['ShortPixel']);
123
+ wp_update_attachment_metadata($this->ID, $this->rawMeta);
124
+ }
125
+ }
126
+
127
+ function incrementRetries($count = 1) {
128
+ if($this->type == self::CUSTOM_TYPE) {
129
+ $this->meta->setRetries($this->meta->getRetries() + $count);
130
+ $this->updateMeta();
131
+ } else {
132
+ $this->rawMeta['ShortPixel']['Retries'] = isset($this->rawMeta['ShortPixel']['Retries']) ? $this->rawMeta['ShortPixel']['Retries'] + $count : $count;
133
+ $this->meta->setRetries($this->rawMeta['ShortPixel']['Retries']);
134
+ wp_update_attachment_metadata($this->ID, $this->rawMeta);
135
+ }
136
+ }
137
+
138
+ function setWaitingProcessing($status = true) {
139
+ if($status) {
140
+ $this->meta->setStatus(1);
141
+ }
142
+ if($this->type == self::CUSTOM_TYPE) {
143
+ $this->updateMeta();
144
+ } else {
145
+ if($status) {
146
+ $this->rawMeta['ShortPixel']['WaitingProcessing'] = true;
147
+ } else {
148
+ unset($this->rawMeta['ShortPixel']['WaitingProcessing']);
149
+ }
150
+ wp_update_attachment_metadata($this->ID, $this->rawMeta);
151
+ }
152
+ }
153
+
154
+ function setError($errorCode, $errorMessage) {
155
+ $this->meta->setMessage('Error: <i>' . $errorMessage . '</i>');
156
+ $this->meta->setStatus($errorCode);
157
+ if($this->type == self::CUSTOM_TYPE) {
158
+ if($errorCode == ShortPixelAPI::ERR_FILE_NOT_FOUND) {
159
+ $this->spMetaDao->delete($this->meta);
160
+ } else {
161
+ $this->spMetaDao->update($this->meta);
162
+ }
163
+ } else {
164
+ $this->rawMeta['ShortPixelImprovement'] = $this->meta->getMessage();
165
+ unset($this->rawMeta['ShortPixel']['WaitingProcessing']);
166
+ wp_update_attachment_metadata($this->ID, $this->rawMeta);
167
+ }
168
+ }
169
+
170
+ public function getURLsAndPATHs($processThumbnails, $onlyThumbs = false) {
171
+ if($this->type == self::CUSTOM_TYPE) {
172
+ $meta = $this->getMeta();
173
+ $urlList[] = str_replace(get_home_path(), network_site_url("/"), $meta->getPath());
174
+ $filePaths[] = $meta->getPath();
175
+ return array("URLs" => $urlList, "PATHs" => $filePaths);
176
+ } else {
177
+ if ( !parse_url(WP_CONTENT_URL, PHP_URL_SCHEME) ) {//no absolute URLs used -> we implement a hack
178
+ $url = get_site_url() . wp_get_attachment_url($this->ID);//get the file URL
179
+ }
180
+ else {
181
+ $url = wp_get_attachment_url($this->ID);//get the file URL
182
+ }
183
+ $urlList[] = $url;
184
+ $path = get_attached_file($this->ID);//get the full file PATH
185
+ $filePath[] = $path;
186
+
187
+ $meta = $this->getMeta();
188
+ $sizes = $meta->getThumbs();
189
+
190
+ //it is NOT a PDF file and thumbs are processable
191
+ if ( strtolower(substr($filePath[0],strrpos($filePath[0], ".")+1)) != "pdf"
192
+ && ($processThumbnails || $onlyThumbs)
193
+ && count($sizes))
194
+ {
195
+ foreach( $sizes as $thumbnailInfo ) {
196
+ $urlList[] = str_replace(ShortPixelAPI::MB_basename($urlList[0]), $thumbnailInfo['file'], $url);
197
+ $filePath[] = str_replace(ShortPixelAPI::MB_basename($filePath[0]), $thumbnailInfo['file'], $path);
198
+ }
199
+ }
200
+ if(!count($sizes)) {
201
+ WPShortPixel::log("getURLsAndPATHs: no meta sizes for ID " . $this->ID . " : " . json_encode($this->rawMeta));
202
+ }
203
+
204
+ if($onlyThumbs) { //remove the main image
205
+ array_shift($urlList);
206
+ array_shift($filePath);
207
+ }
208
+ }
209
+ return array("URLs" => $urlList, "PATHs" => $filePath);
210
+ }
211
+
212
+ public static function getWPMLDuplicates( $id ) {
213
+ global $wpdb;
214
+
215
+ $parentId = get_post_meta ($id, '_icl_lang_duplicate_of', true );
216
+ if($parentId) $id = $parentId;
217
+
218
+ $duplicates = $wpdb->get_col( $wpdb->prepare( "
219
+ SELECT pm.post_id FROM {$wpdb->postmeta} pm
220
+ WHERE pm.meta_value = %s AND pm.meta_key = '_icl_lang_duplicate_of'
221
+ ", $id ) );
222
+
223
+ if(!in_array($id, $duplicates)) $duplicates[] = $id;
224
+
225
+ $transTable = $wpdb->get_results("SELECT COUNT(1) hasTransTable FROM information_schema.tables WHERE table_schema='{$wpdb->dbname}' AND table_name='{$wpdb->prefix}icl_translations'");
226
+ if(isset($transTable[0]->hasTransTable) && $transTable[0]->hasTransTable > 0) {
227
+ $transGroupId = $wpdb->get_results("SELECT trid FROM {$wpdb->prefix}icl_translations WHERE element_id = {$id}");
228
+ if(count($transGroupId)) {
229
+ $transGroup = $wpdb->get_results("SELECT element_id FROM {$wpdb->prefix}icl_translations WHERE trid = " . $transGroupId[0]->trid);
230
+ foreach($transGroup as $trans) {
231
+ $duplicates[] = $trans->element_id;
232
+ }
233
+ }
234
+ }
235
+ return array_unique($duplicates);
236
+ }
237
+
238
+ public static function pathToWebPath($path) {
239
+ //$upl = wp_upload_dir();
240
+ //return str_replace($upl["basedir"], $upl["baseurl"], $path);
241
+ return str_replace(get_home_path(), site_url()."/", $path);
242
+ }
243
+
244
+ public static function pathToContentRelative($path) {
245
+ //$upl = wp_upload_dir();
246
+ $pathParts = explode(DIRECTORY_SEPARATOR, $path);
247
+ unset($pathParts[count($pathParts) - 1]);
248
+ $path = implode(DIRECTORY_SEPARATOR, $pathParts);
249
+ return str_replace(WP_CONTENT_DIR . "/", "", $path);
250
+ }
251
+
252
+ public static function filenameToContentRelative($path) {
253
+ return str_replace(WP_CONTENT_DIR . DIRECTORY_SEPARATOR, "", $path);
254
+ }
255
+
256
+ public static function getMaxMediaId() {
257
+ global $wpdb;
258
+ $queryMax = "SELECT max(post_id) as QueryID FROM " . $wpdb->prefix . "postmeta";
259
+ $resultQuery = $wpdb->get_results($queryMax);
260
+ return $resultQuery[0]->QueryID;
261
+ }
262
+
263
+ public static function getMinMediaId() {
264
+ global $wpdb;
265
+ $queryMax = "SELECT min(post_id) as QueryID FROM " . $wpdb->prefix . "postmeta";
266
+ $resultQuery = $wpdb->get_results($queryMax);
267
+ return $resultQuery[0]->QueryID;
268
+ }
269
+
270
+ public static function isCustomQueuedId($id) {
271
+ return substr($id, 0, 2) == "C-";
272
+ }
273
+
274
+ public static function stripQueuedIdType($id) {
275
+ return substr($id, 2);
276
+ }
277
+
278
+ public function getQueuedId() {
279
+ return self::queuedId($this->type, $this->ID);
280
+ }
281
+
282
+ public static function queuedId($type, $id) {
283
+ return ($type == self::CUSTOM_TYPE ? "C-" : "") . $id;
284
+ }
285
+
286
+ function getId() {
287
+ return $this->ID;
288
+ }
289
+
290
+ function getType() {
291
+ return $this->type;
292
+ }
293
+
294
+ function setId($ID) {
295
+ $this->ID = $ID;
296
+ }
297
+
298
+ function setType($type) {
299
+ $this->type = $type;
300
+ }
301
+
302
+ function getRawMeta() {
303
+ return $this->rawMeta;
304
+ }
305
+
306
+ /**
307
+ * return subdir for that particular attached file - if it's media library then last 3 path items, otherwise substract the uploads path
308
+ * Has trailing directory separator (/)
309
+ * @param type $file
310
+ * @return string
311
+ */
312
+ static public function returnSubDir($file, $type)
313
+ {
314
+ if(strstr($file, WP_CONTENT_DIR . DIRECTORY_SEPARATOR)) {
315
+ $path = str_replace( WP_CONTENT_DIR . DIRECTORY_SEPARATOR, "", $file);
316
+ } else {
317
+ $path = (substr($file, 1));
318
+ }
319
+ $pathArr = explode(DIRECTORY_SEPARATOR, $path);
320
+ unset($pathArr[count($pathArr) - 1]);
321
+ return implode(DIRECTORY_SEPARATOR, $pathArr) . DIRECTORY_SEPARATOR;
322
+ }
323
+
324
+ public static function isMediaSubfolder($path) {
325
+ $uploadDir = wp_upload_dir();
326
+ $uploadBase = $uploadDir["basedir"];
327
+ $uploadPath = $uploadDir["path"];
328
+ //contains the current media upload path
329
+ if(ShortPixelFolder::checkFolderIsSubfolder($uploadPath, $path)) {
330
+ return true;
331
+ }
332
+ //contains one of the year subfolders of the media library
333
+ if(strpos($path, $uploadPath) == 0) {
334
+ $pathArr = explode(DIRECTORY_SEPARATOR, str_replace($uploadBase . DIRECTORY_SEPARATOR, "", $path));
335
+ if( count($pathArr) >= 1
336
+ && is_numeric($pathArr[0]) && $pathArr[0] > 1900 && $pathArr[0] < 2100 //contains the year subfolder
337
+ && ( count($pathArr) == 1 //if there is another subfolder then it's the month subfolder
338
+ || (is_numeric($pathArr[1]) && $pathArr[1] > 0 && $pathArr[1] < 13) )) {
339
+ return true;
340
+ }
341
+ }
342
+ return false;
343
+ }
344
+ }
class/db/shortpixel-nextgen-adapter.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ShortPixelNextGenAdapter {
4
+ public static function getGalleries () {
5
+ global $wpdb;
6
+ return array_map(function($item){return $item->path;}, $wpdb->get_results("SELECT path FROM {$wpdb->prefix}ngg_gallery"));
7
+
8
+ }
9
+
10
+ public static function hasNextGen() {
11
+ global $wpdb;
12
+ $nggTable = $wpdb->get_results("SELECT COUNT(1) hasNggTable FROM information_schema.tables WHERE table_schema='{$wpdb->dbname}' AND table_name='{$wpdb->prefix}ngg_gallery'");
13
+ if(isset($nggTable[0]->hasNggTable) && $nggTable[0]->hasNggTable > 0) {
14
+ return true;
15
+ }
16
+ return false;
17
+ }
18
+
19
+ public static function getImageAbspath($image) {
20
+ $storage = C_Gallery_Storage::get_instance();
21
+ return $storage->get_image_abspath($image);
22
+ }
23
+
24
+ public static function updateImageSize($nggId, $path) {
25
+ $mapper = C_Image_Mapper::get_instance();
26
+ $image = $mapper->find($nggId);
27
+ $dimensions = getimagesize(self::getImageAbspath($image));
28
+ $size_meta = array('width' => $dimensions[0], 'height' => $dimensions[1]);
29
+ $image->meta_data = array_merge($image->meta_data, $size_meta);
30
+ $image->meta_data['full'] = $size_meta;
31
+ $mapper->save($image);
32
+ }
33
+ }
class/db/wp-shortpixel-db.php ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class WpShortPixelDb implements ShortPixelDb {
4
+
5
+ protected $prefix;
6
+ protected $defaultShowErrors;
7
+
8
+ public function __construct($prefix = null) {
9
+ $this->prefix = $prefix;
10
+ }
11
+
12
+ public static function createUpdateSchema($tableDefinitions) {
13
+ require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
14
+ $res = array();
15
+ foreach($tableDefinitions as $tableDef) {
16
+ array_merge($res, dbDelta( $tableDef ));
17
+ }
18
+ return $res;
19
+ }
20
+
21
+ public static function checkCustomTables() {
22
+ global $wpdb;
23
+ if(function_exists("is_multisite") && is_multisite()) {
24
+ $sites = wp_get_sites();
25
+ foreach($sites as $site) {
26
+ $prefix = $wpdb->get_blog_prefix($site['blog_id']);
27
+ $spMetaDao = new ShortPixelCustomMetaDao(new WpShortPixelDb($prefix));
28
+ $spMetaDao->createUpdateShortPixelTables();
29
+ }
30
+
31
+ } else {
32
+ $spMetaDao = new ShortPixelCustomMetaDao(new WpShortPixelDb());
33
+ $spMetaDao->createUpdateShortPixelTables();
34
+ }
35
+ }
36
+
37
+ public function getCharsetCollate() {
38
+ global $wpdb;
39
+ return $wpdb->get_charset_collate();
40
+ }
41
+
42
+ public function getPrefix() {
43
+ global $wpdb;
44
+ return $this->prefix ? $this->prefix : $wpdb->prefix;
45
+ }
46
+
47
+ public function query($sql, $params = false) {
48
+ global $wpdb;
49
+ if($params) {
50
+ $sql = $wpdb->prepare($sql, $params);
51
+ }
52
+ return $wpdb->get_results($sql);
53
+ }
54
+
55
+ public function insert($table, $params, $format = null) {
56
+ global $wpdb;
57
+ $wpdb->insert($table, $params, $format);
58
+ return $wpdb->insert_id;
59
+ }
60
+
61
+ public function prepare($query, $args) {
62
+ global $wpdb;
63
+ return $wpdb->prepare($query, $args);
64
+ }
65
+
66
+ public function hideErrors() {
67
+ global $wpdb;
68
+ $this->defaultShowErrors = $wpdb->show_errors;
69
+ $wpdb->show_errors = false;
70
+ }
71
+
72
+ public function restoreErrors() {
73
+ global $wpdb;
74
+ $wpdb->show_errors = $this->defaultShowErrors;
75
+ }
76
+ }
class/db/wp-shortpixel-media-library-adapter.php ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
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 =
10
+ $procLossyMainFiles = $procLossyTotalFiles = $procLosslessMainFiles = $procLosslessTotalFiles = $procUndefMainFiles = $procUndefTotalFiles = $mainUnprocessedThumbs = 0;
11
+ $filesMap = $processedFilesMap = array();
12
+ $limit = self::getOptimalChunkSize();
13
+ $pointer = 0;
14
+ $filesWithErrors = array();
15
+
16
+ //count all the files, main and thumbs
17
+ while ( 1 ) {
18
+ $ids = self::getPostIdsChunk($minId, $maxId, $pointer, $limit);
19
+ if($ids === null) {
20
+ break; //we parsed all the results
21
+ }
22
+ elseif(count($ids) == 0) {
23
+ $pointer += $limit;
24
+ continue;
25
+ }
26
+
27
+ $filesList= $wpdb->get_results("SELECT * FROM " . $wpdb->prefix . "postmeta
28
+ WHERE post_id IN (" . implode(',', $ids) . ")
29
+ AND ( meta_key = '_wp_attached_file' OR meta_key = '_wp_attachment_metadata' )");
30
+
31
+ foreach ( $filesList as $file )
32
+ {
33
+ if ( $file->meta_key == "_wp_attached_file" )
34
+ {//count pdf files only
35
+ $extension = substr($file->meta_value, strrpos($file->meta_value,".") + 1 );
36
+ if ( $extension == "pdf" && !isset($filesMap[$file->meta_value]))
37
+ {
38
+ $totalFiles++;
39
+ $mainFiles++;
40
+ $filesMap[$file->meta_value] = 1;
41
+ }
42
+ }
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
+ {
55
+ $totalFiles++;
56
+ $mainFiles++;
57
+ $filesMap[$attachment['file']] = 1;
58
+ }
59
+ }
60
+ //processed
61
+ if (isset($attachment['ShortPixelImprovement'])
62
+ && ($attachment['ShortPixelImprovement'] > 0 || $attachment['ShortPixelImprovement'] === 0.0 || $attachment['ShortPixelImprovement'] === "0")
63
+ //for PDFs there is no file field so just let it pass.
64
+ && (!isset($attachment['file']) || !isset($processedFilesMap[$attachment['file']])) ) {
65
+
66
+ //add main file to counts
67
+ $processedMainFiles++;
68
+ $processedTotalFiles++;
69
+ $type = isset($attachment['ShortPixel']['type']) ? $attachment['ShortPixel']['type'] : null;
70
+ if($type == 'lossy') {
71
+ $procLossyMainFiles++;
72
+ $procLossyTotalFiles++;
73
+ } elseif($type == 'lossless') {
74
+ $procLosslessMainFiles++;
75
+ $procLosslessTotalFiles++;
76
+ } else {
77
+ $procUndefMainFiles++;
78
+ $procUndefTotalFiles++;
79
+ }
80
+
81
+ //get the thumbs processed for that attachment
82
+ $thumbs = $allThumbs = 0;
83
+ if ( isset($attachment['ShortPixel']['thumbsOpt']) ) {
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
+
93
+ //increment with thumbs processed
94
+ $processedTotalFiles += $thumbs;
95
+ if($type == 'lossy') {
96
+ $procLossyTotalFiles += $thumbs;
97
+ } else {
98
+ $procLosslessTotalFiles += $thumbs;
99
+ }
100
+
101
+ if ( isset($attachment['file']) ) {
102
+ $processedFilesMap[$attachment['file']] = 1;
103
+ }
104
+ }
105
+ elseif($isProcessable && isset($attachment['ShortPixelImprovement']) && count($filesWithErrors) < 50) {
106
+ $filePath = explode("/", $attachment["file"]);
107
+ $name = is_array($filePath)? $filePath[count($filePath) - 1] : $file->post_id;
108
+ $filesWithErrors[$file->post_id] = array('Name' => $name, 'Message' => $attachment['ShortPixelImprovement']);
109
+ }
110
+
111
+ }
112
+ }
113
+ unset($filesList);
114
+ $pointer += $limit;
115
+
116
+ }//end while
117
+
118
+ return array("totalFiles" => $totalFiles, "mainFiles" => $mainFiles,
119
+ "totalProcessedFiles" => $processedTotalFiles, "mainProcessedFiles" => $processedMainFiles,
120
+ "totalProcLossyFiles" => $procLossyTotalFiles, "mainProcLossyFiles" => $procLossyMainFiles,
121
+ "totalProcLosslessFiles" => $procLosslessTotalFiles, "mainProcLosslessFiles" => $procLosslessMainFiles,
122
+ "totalMlFiles" => $totalFiles, "mainMlFiles" => $mainFiles,
123
+ "totalProcessedMlFiles" => $processedTotalFiles, "mainProcessedMlFiles" => $processedMainFiles,
124
+ "totalProcLossyMlFiles" => $procLossyTotalFiles, "mainProcLossyMlFiles" => $procLossyMainFiles,
125
+ "totalProcLosslessMlFiles" => $procLosslessTotalFiles, "mainProcLosslessMlFiles" => $procLosslessMainFiles,
126
+ "totalProcUndefMlFiles" => $procUndefTotalFiles, "mainProcUndefMlFiles" => $procUndefMainFiles,
127
+ "mainUnprocessedThumbs" => $mainUnprocessedThumbs,
128
+ "filesWithErrors" => $filesWithErrors
129
+ );
130
+ }
131
+
132
+ protected static function getOptimalChunkSize() {
133
+ global $wpdb;
134
+ $cnt = $wpdb->get_results("SELECT count(*) posts FROM " . $wpdb->prefix . "posts");
135
+ $posts = isset($cnt) && count($cnt) > 0 ? $cnt[0]->posts : 0;
136
+ if($posts > 100000) {
137
+ return 20000;
138
+ } elseif ($posts > 50000) {
139
+ return 5000;
140
+ } elseif($posts > 10000) {
141
+ return 2000;
142
+ } else {
143
+ return 500;
144
+ }
145
+ }
146
+
147
+ protected static function getPostIdsChunk($minId, $maxId, $pointer, $limit) {
148
+ global $wpdb;
149
+
150
+ $ids = array();
151
+ $idList = $wpdb->get_results("SELECT ID, post_mime_type FROM " . $wpdb->prefix . "posts
152
+ WHERE ( ID <= $maxId AND ID > $minId )
153
+ LIMIT $pointer,$limit");
154
+ if ( empty($idList) ) {
155
+ return null;
156
+ }
157
+ foreach($idList as $item) {
158
+ if($item->post_mime_type != '') {
159
+ $ids[] = $item->ID;
160
+ }
161
+ }
162
+ return $ids;
163
+ }
164
+
165
+ }
class/model/shortpixel-entity.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ShortPixelEntity{
4
+ public function __construct($data) {
5
+ if(is_object($data)) {
6
+ $dataArr = get_object_vars($data);
7
+ } elseif(is_array($data)) {
8
+ $dataArr = $data;
9
+ } else {
10
+ return;
11
+ }
12
+ foreach($dataArr as $key => $val) {
13
+ $setter = 'set' . ShortPixelTools::snakeToCamel($key);
14
+ if(method_exists($this, $setter)) {
15
+ $this->$setter($val);
16
+ }
17
+ }
18
+ }
19
+ }
class/model/shortpixel-folder.php ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ShortPixelFolder extends ShortPixelEntity{
4
+ const META_VERSION = 1;
5
+
6
+ protected $id;
7
+ protected $path;
8
+ protected $type;
9
+ protected $status;
10
+ protected $fileCount;
11
+ protected $tsCreated;
12
+ protected $tsUpdated;
13
+
14
+ const TABLE_SUFFIX = 'folders';
15
+
16
+ public function __construct($data) {
17
+ parent::__construct($data);
18
+ }
19
+
20
+ public static function checkFolder($folder, $base) {
21
+ if(substr($folder, 0, 1) !== DIRECTORY_SEPARATOR) {
22
+ $folder = DIRECTORY_SEPARATOR . $folder;
23
+ }
24
+ if(is_dir($folder)) {
25
+ return realpath($folder);
26
+ } elseif(is_dir($base . $folder)) {
27
+ return realpath($base . $folder);
28
+ }
29
+ return false;
30
+ }
31
+
32
+ /**
33
+ * returns the first from parents that is a parent folder of $folder
34
+ * @param type $folder
35
+ * @param type $parents
36
+ * @return parent if found, false otherwise
37
+ */
38
+ public static function checkFolderIsSubfolder($folder, $parents) {
39
+ if(!is_array($parents)) {
40
+ $parents = array($parents);
41
+ }
42
+ foreach($parents as $parent) {
43
+ if(strpos($folder, $parent) === 0 && (strlen($parent) == strlen($folder) || substr($folder, strlen($parent), 1) == DIRECTORY_SEPARATOR)) {
44
+ return $parent;
45
+ }
46
+ }
47
+ return false;
48
+ }
49
+
50
+ /**
51
+ * finds the first from the subfolders that has the folder as parent
52
+ * @param type $folder
53
+ * @param type $subfolders
54
+ * @return subfolder if found, false otherwise
55
+ */
56
+ public static function checkFolderIsParent($folder, $subfolders) {
57
+ if(!is_array($subfolders)) {
58
+ $subfolders[] = $subfolders;
59
+ }
60
+ foreach($subfolders as $sub) {
61
+ if(strpos($sub, $folder) === 0 && (strlen($folder) == strlen($sub) || substr($sub, strlen($folder) - 1, 1) == DIRECTORY_SEPARATOR)) {
62
+ return $sub;
63
+ }
64
+ }
65
+ return false;
66
+ }
67
+
68
+ public function countFiles($path = null) {
69
+ $size = 0;
70
+ $path = $path ? $path : $this->getPath();
71
+ if($path == null) {
72
+ return 0;
73
+ }
74
+ $ignore = array('.','..');
75
+ if(!is_writable($path)) {
76
+ throw new SpFileRightsException("Folder " . $path . " is not writeable. Please check permissions and try again.");
77
+ }
78
+ $files = scandir($path);
79
+ foreach($files as $t) {
80
+ $tpath = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $t;
81
+ if(in_array($t, $ignore)) continue;
82
+ if (is_dir($tpath)) {
83
+ $size += $this->countFiles($tpath);
84
+ } elseif(in_array(pathinfo($t, PATHINFO_EXTENSION), WPShortPixel::$PROCESSABLE_EXTENSIONS)) {
85
+ $size++;
86
+ }
87
+ }
88
+ return $size;
89
+ }
90
+
91
+ public function getFileList($onlyNewerThan = 0) {
92
+ $upl = wp_upload_dir();
93
+ $fileListPath = tempnam($upl["basedir"] . DIRECTORY_SEPARATOR, 'sp_');
94
+ $fileHandle = fopen($fileListPath, 'w+');
95
+ self::getFileListRecursive($this->getPath(), $fileHandle, $onlyNewerThan);
96
+ fclose($fileHandle);
97
+ return $fileListPath;
98
+ }
99
+
100
+ protected static function getFileListRecursive($path, $fileHandle, $onlyNewerThan) {
101
+ $ignore = array('.','..');
102
+ $files = scandir($path);
103
+ $add = (filemtime($path) > $onlyNewerThan);
104
+ /*
105
+ if($add && $onlyNewerThan) {
106
+ echo("<br> FOLDER UPDATED: " . $path);
107
+ }
108
+ */
109
+ foreach($files as $t) {
110
+ if(in_array($t, $ignore)) continue;
111
+ $tpath = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $t;
112
+ if (is_dir($tpath)) {
113
+ self::getFileListRecursive($tpath, $fileHandle, $onlyNewerThan);
114
+ } elseif($add && in_array(pathinfo($t, PATHINFO_EXTENSION), WPShortPixel::$PROCESSABLE_EXTENSIONS)) {
115
+ fwrite($fileHandle, $tpath . "\n");
116
+ }
117
+ }
118
+ }
119
+
120
+ public function getFolderContentsChangeDate() {
121
+ return self::getFolderContentsChangeDateRecursive($this->getPath(), 0, time($this->getTsUpdated()));
122
+ }
123
+
124
+ protected static function getFolderContentsChangeDateRecursive($path, $mtime, $refMtime) {
125
+ $ignore = array('.','..');
126
+ if(!is_writable($path)) {
127
+ throw new SpFileRightsException("Folder " . $path . " is not writeable. Please check permissions and try again.");
128
+ }
129
+ $files = scandir($path);
130
+ $mtime = max($mtime, filemtime($path));
131
+ foreach($files as $t) {
132
+ if(in_array($t, $ignore)) continue;
133
+ $tpath = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $t;
134
+ if (is_dir($tpath)) {
135
+ $mtime = max($mtime, filemtime($tpath));
136
+ self::getFolderContentsChangeDateRecursive($tpath, $mtime, $refMtime);
137
+ }
138
+ }
139
+ return $mtime;
140
+ }
141
+
142
+ function getId() {
143
+ return $this->id;
144
+ }
145
+
146
+ function getPath() {
147
+ return $this->path;
148
+ }
149
+
150
+ function getTsCreated() {
151
+ return $this->tsCreated;
152
+ }
153
+
154
+ function getTsUpdated() {
155
+ return $this->tsUpdated;
156
+ }
157
+
158
+ function setId($id) {
159
+ $this->id = $id;
160
+ }
161
+
162
+ function setPath($path) {
163
+ $this->path = $path;
164
+ }
165
+
166
+ function getType() {
167
+ return $this->type;
168
+ }
169
+
170
+ function setType($type) {
171
+ $this->type = $type;
172
+ }
173
+
174
+ function getStatus() {
175
+ return $this->status;
176
+ }
177
+
178
+ function setStatus($status) {
179
+ $this->status = $status;
180
+ }
181
+
182
+ function getFileCount() {
183
+ return $this->fileCount;
184
+ }
185
+
186
+ function setFileCount($fileCount) {
187
+ $this->fileCount = $fileCount;
188
+ }
189
+
190
+ function setTsCreated($tsCreated) {
191
+ $this->tsCreated = $tsCreated;
192
+ }
193
+
194
+ function setTsUpdated($tsUpdated) {
195
+ $this->tsUpdated = $tsUpdated;
196
+ }
197
+
198
+ }
class/model/shortpixel-meta.php ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ShortPixelMeta extends ShortPixelEntity{
4
+ const META_VERSION = 1;
5
+
6
+ protected $id;
7
+ protected $folderId;
8
+ protected $extMetaId;
9
+ protected $path;
10
+ protected $name;
11
+ protected $webPath;
12
+ protected $compressionType;
13
+ protected $compressedSize;
14
+ protected $thumbsOpt;
15
+ protected $thumbsTodo;
16
+ protected $keepExif;
17
+ protected $cmyk2rgb;
18
+ protected $resize;
19
+ protected $resizeWidth;
20
+ protected $resizeHeight;
21
+ protected $actualWidth;
22
+ protected $actualHeight;
23
+ protected $backup;
24
+ protected $status; //0 waiting, 1 pending, 2 success, -x errors
25
+ protected $retries;
26
+ protected $message;
27
+ protected $tsAdded;
28
+ protected $tsOptimized;
29
+ protected $thumbs;
30
+
31
+ const TABLE_SUFFIX = 'meta';
32
+
33
+ public function __construct($data = array()) {
34
+ parent::__construct($data);
35
+ }
36
+
37
+ /**
38
+ * @return meta string to be embedded into the image
39
+ */
40
+ public function getCompressedMeta() {
41
+ //To be implemented
42
+ return base64_encode("Not now John.");
43
+ }
44
+
45
+ function getImprovementPercent() {
46
+ if(is_numeric($this->message)) {
47
+ return round($this->message,2);
48
+ }
49
+ return 0;
50
+ }
51
+
52
+ function getId() {
53
+ return $this->id;
54
+ }
55
+
56
+ function getPath() {
57
+ return $this->path;
58
+ }
59
+ function getWebPath() {
60
+ return $this->webPath;
61
+ }
62
+
63
+ function setWebPath($webPath) {
64
+ $this->webPath = $webPath;
65
+ }
66
+
67
+ function getPathMd5() {
68
+ return md5($this->path);
69
+ }
70
+
71
+ function getFolderId() {
72
+ return $this->folderId;
73
+ }
74
+
75
+ function setFolderId($folderId) {
76
+ $this->folderId = $folderId;
77
+ }
78
+
79
+ function getExtMetaId() {
80
+ return $this->extMetaId;
81
+ }
82
+
83
+ function setExtMetaId($extMetaId) {
84
+ $this->extMetaId = $extMetaId;
85
+ }
86
+
87
+ function getCompressionType() {
88
+ return $this->compressionType;
89
+ }
90
+
91
+ function getKeepExif() {
92
+ return $this->keepExif;
93
+ }
94
+
95
+ function getBackup() {
96
+ return $this->backup;
97
+ }
98
+
99
+ function getTsOptimized() {
100
+ return $this->tsOptimized;
101
+ }
102
+
103
+ function setId($id) {
104
+ $this->id = $id;
105
+ }
106
+
107
+ function setPath($path) {
108
+ $this->path = $path;
109
+ }
110
+
111
+ function getName() {
112
+ return $this->name;
113
+ }
114
+
115
+ function getCompressedSize() {
116
+ return $this->compressedSize;
117
+ }
118
+
119
+ function setName($name) {
120
+ $this->name = $name;
121
+ }
122
+
123
+ function setCompressedSize($compressedSize) {
124
+ $this->compressedSize = $compressedSize;
125
+ }
126
+
127
+ function setCompressionType($compressionType) {
128
+ $this->compressionType = $compressionType;
129
+ }
130
+
131
+ function getThumbsOpt() {
132
+ return $this->thumbsOpt;
133
+ }
134
+
135
+ function setThumbsOpt($thumbsOpt) {
136
+ $this->thumbsOpt = $thumbsOpt;
137
+ }
138
+
139
+ function getThumbsTodo() {
140
+ return $this->thumbsTodo;
141
+ }
142
+
143
+ function setThumbsTodo($thumbsTodo) {
144
+ $this->thumbsTodo = $thumbsTodo;
145
+ }
146
+
147
+ function setKeepExif($keepExif) {
148
+ $this->keepExif = $keepExif;
149
+ }
150
+
151
+ function setBackup($backup) {
152
+ $this->backup = $backup;
153
+ }
154
+
155
+ function setTsOptimized($tsOptimized) {
156
+ $this->tsOptimized = $tsOptimized;
157
+ }
158
+
159
+ function getTsAdded() {
160
+ return $this->tsAdded;
161
+ }
162
+
163
+ function setTsAdded($tsAdded) {
164
+ $this->tsAdded = $tsAdded;
165
+ }
166
+
167
+ function getCmyk2rgb() {
168
+ return $this->cmyk2rgb;
169
+ }
170
+
171
+ function getResize() {
172
+ return $this->resize;
173
+ }
174
+
175
+ function getResizeWidth() {
176
+ return $this->resizeWidth;
177
+ }
178
+
179
+ function getResizeHeight() {
180
+ return $this->resizeHeight;
181
+ }
182
+
183
+ function getActualWidth() {
184
+ return $this->actualWidth;
185
+ }
186
+
187
+ function getActualHeight() {
188
+ return $this->actualHeight;
189
+ }
190
+
191
+ function setActualWidth($actualWidth) {
192
+ $this->actualWidth = $actualWidth;
193
+ }
194
+
195
+ function setActualHeight($actualHeight) {
196
+ $this->actualHeight = $actualHeight;
197
+ }
198
+
199
+ function setCmyk2rgb($cmyk2rgb) {
200
+ $this->cmyk2rgb = $cmyk2rgb;
201
+ }
202
+
203
+ function setResize($resize) {
204
+ $this->resize = $resize;
205
+ }
206
+
207
+ function setResizeWidth($resizeWidth) {
208
+ $this->resizeWidth = $resizeWidth;
209
+ }
210
+
211
+ function setResizeHeight($resizeHeight) {
212
+ $this->resizeHeight = $resizeHeight;
213
+ }
214
+
215
+ function getStatus() {
216
+ return $this->status;
217
+ }
218
+
219
+ function getMessage() {
220
+ return $this->message;
221
+ }
222
+
223
+ function getRetries() {
224
+ return $this->retries;
225
+ }
226
+
227
+ function setRetries($retries) {
228
+ $this->retries = $retries;
229
+ }
230
+
231
+ function setStatus($status) {
232
+ $this->status = $status;
233
+ }
234
+
235
+ function setMessage($message) {
236
+ $this->message = $message;
237
+ }
238
+
239
+ function getType() {
240
+ return (isset($this->folderId) ? ShortPixelMetaFacade::CUSTOM_TYPE : ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE);
241
+ }
242
+
243
+ function getThumbs() {
244
+ return $this->thumbs;
245
+ }
246
+
247
+ function setThumbs($thumbs) {
248
+ $this->thumbs = $thumbs;
249
+ }
250
+
251
+ }
class/model/sp-file-rights-exception.php ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class SpFileRightsException extends Exception {
4
+
5
+ }
6
+
class/shortpixel-tools.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ShortPixelTools {
4
+ public static function parseJSON($data) {
5
+ if ( function_exists('json_decode') ) {
6
+ $data = json_decode( $data );
7
+ } else {
8
+ require_once( 'JSON/JSON.php' );
9
+ $json = new Services_JSON( );
10
+ $data = $json->decode( $data );
11
+ }
12
+ return $data;
13
+ }
14
+
15
+ public static function snakeToCamel($snake_case) {
16
+ return str_replace(' ', '', ucwords(str_replace('_', ' ', $snake_case)));
17
+ }
18
+
19
+ public static function requestIsFrontendAjax()
20
+ {
21
+ $script_filename = isset($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : '';
22
+
23
+ //Try to figure out if frontend AJAX request... If we are DOING_AJAX; let's look closer
24
+ if((defined('DOING_AJAX') && DOING_AJAX))
25
+ {
26
+ //From wp-includes/functions.php, wp_get_referer() function.
27
+ //Required to fix: https://core.trac.wordpress.org/ticket/25294
28
+ $ref = '';
29
+ if ( ! empty( $_REQUEST['_wp_http_referer'] ) ) {
30
+ $ref = wp_unslash( $_REQUEST['_wp_http_referer'] );
31
+ } elseif ( ! empty( $_SERVER['HTTP_REFERER'] ) ) {
32
+ $ref = wp_unslash( $_SERVER['HTTP_REFERER'] );
33
+ }
34
+ //If referer does not contain admin URL and we are using the admin-ajax.php endpoint, this is likely a frontend AJAX request
35
+ if(((strpos($ref, admin_url()) === false) && (basename($script_filename) === 'admin-ajax.php')))
36
+ return true;
37
+ }
38
+
39
+ //If no checks triggered, we end up here - not an AJAX request.
40
+ return false;
41
+ }
42
+ }
class/view/shortpixel-list-table.php ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if( ! class_exists( 'WP_List_Table' ) ) {
4
+ require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
5
+ }
6
+
7
+ class ShortPixelListTable extends WP_List_Table {
8
+
9
+ protected $ctrl;
10
+ protected $spMetaDao;
11
+ protected $hasNextGen;
12
+
13
+ public function __construct($ctrl, $spMetaDao, $hasNextGen) {
14
+ parent::__construct( [
15
+ 'singular' => 'Image', //singular name of the listed records
16
+ 'plural' => 'Images', //plural name of the listed records
17
+ 'ajax' => false //should this table support ajax?
18
+ ] );
19
+ $this->ctrl = $ctrl;
20
+ $this->spMetaDao = $spMetaDao;
21
+ $this->hasNextGen = $hasNextGen;
22
+ }
23
+
24
+ // define the columns to display, the syntax is 'internal name' => 'display name'
25
+ function get_columns() {
26
+ $columns = array();
27
+
28
+ //pe viitor. $columns['cb'] = '<input type="checkbox" />';
29
+ $columns['name'] = 'Filename';
30
+ $columns['folder'] = 'Folder';
31
+ $columns['media_type'] = 'Type';
32
+ $columns['status'] = 'Status';
33
+ $columns['options'] = 'Options';
34
+ //$columns = apply_filters('shortpixel_list_columns', $columns);
35
+
36
+ return $columns;
37
+ }
38
+
39
+ function column_cb( $item ) {
40
+ return sprintf('<input type="checkbox" name="bulk-optimize[]" value="%s" />', $item->id);
41
+ }
42
+
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
+ $actions = array();
49
+ if($item->status <= 1) {
50
+ // create the image web path
51
+ $actions = [
52
+ 'optimize' => sprintf( '<a href="?page=%s&action=%s&image=%s&_wpnonce=%s">' . ($item->status == 0 ? 'Optimize' : 'Retry') . '</a>',
53
+ esc_attr( $_REQUEST['page'] ), 'optimize', absint( $item->id ), wp_create_nonce( 'sp_optimize_image' ) )
54
+ ];
55
+ }
56
+ $url = ShortPixelMetaFacade::pathToWebPath($item->folder);
57
+ $actions['view'] = sprintf( '<a href="%s" target="_blank">View</a>', $url );
58
+ $title = $title . $this->row_actions( $actions );
59
+ return $title;
60
+ case 'folder':
61
+ return ShortPixelMetaFacade::pathToContentRelative($item->folder);
62
+ case 'status':
63
+ switch($item->status) {
64
+ case 2: $msg = 0 + $item->message == 0 ? "Bonus processing" : "Reduced by " . $item->message . "%" . (0 + $item->message < 5 ? "<br>Bonus processing." : "");
65
+ break;
66
+ case 1: $msg = "Pending";
67
+ break;
68
+ case 0: $msg = "Waiting";
69
+ break;
70
+ default:
71
+ if($item->status < 0) {
72
+ $msg = $item->message . "(code: " . $item->status . ")";
73
+ } else {
74
+ $msg = "";
75
+ }
76
+ }
77
+ return "<div id='sp-cust-msg-C-" . $item->id . "'>" . $msg . "</div>";
78
+ break;
79
+ case 'options':
80
+ return ($item->compression_type == 1 ? "Lossy" : "Lossless")
81
+ . ($item->keep_exif == 1 ? "": ", Keep EXIF")
82
+ . ($item->cmyk2rgb ? "": ", Preserve CMYK");
83
+ case 'media_type':
84
+ return $item->$column_name;
85
+ default:
86
+ return print_r( $item, true ) ; //Show the whole array for troubleshooting purposes
87
+ }
88
+ }
89
+
90
+ public function no_items() {
91
+ echo('No images avaliable. Go to <a href="options-general.php?page=wp-shortpixel#adv-settings">Advanced Settings</a> to configure additional folders to be optimized.');
92
+ }
93
+
94
+ /**
95
+ * Columns to make sortable.
96
+ *
97
+ * @return array
98
+ */
99
+ public function get_sortable_columns() {
100
+ $sortable_columns = array(
101
+ 'name' => array( 'name', true ),
102
+ 'folder' => array( 'folder', true ),
103
+ 'status' => array( 'status', false )
104
+ );
105
+
106
+ return $sortable_columns;
107
+ }
108
+
109
+ /**
110
+ * Handles data query and filter, sorting, and pagination.
111
+ */
112
+ public function prepare_items() {
113
+
114
+ $this->_column_headers = $this->get_column_info();
115
+
116
+ $this->_column_headers[0] = $this->get_columns();
117
+
118
+ /** Process actions */
119
+ $this->process_actions();
120
+
121
+ $perPage = $this->get_items_per_page( 'images_per_page', 20 );
122
+ $currentPage = $this->get_pagenum();
123
+ $total_items = $this->record_count();
124
+
125
+ $this->set_pagination_args( [
126
+ 'total_items' => $total_items, //WE have to calculate the total number of items
127
+ 'per_page' => $perPage //WE have to determine how many items to show on a page
128
+ ] );
129
+
130
+ $orderby = ( ! empty( $_GET['orderby'] ) ) ? $_GET['orderby'] : 'ts_added';
131
+ // If no order, default to asc
132
+ $order = ( ! empty($_GET['order'] ) ) ? $_GET['order'] : 'desc';
133
+
134
+ $this->items = $this->spMetaDao->getPaginatedMetas($this->hasNextGen, $perPage, $currentPage, $orderby, $order);
135
+ return $this->items;
136
+ }
137
+
138
+ public function record_count() {
139
+ return $this->spMetaDao->getCustomMetaCount();
140
+ }
141
+
142
+ public function action_optimize_image( $id ) {
143
+ $this->ctrl->optimizeCustomImage($id);
144
+ }
145
+
146
+ public function process_actions() {
147
+
148
+ //Detect when a bulk action is being triggered...
149
+ if ('optimize' === $this->current_action()) {
150
+
151
+ // In our file that handles the request, verify the nonce.
152
+ $nonce = esc_attr($_REQUEST['_wpnonce']);
153
+
154
+ if (!wp_verify_nonce($nonce, 'sp_optimize_image')) {
155
+ die('Error.');
156
+ } else {
157
+ $this->action_optimize_image(absint($_GET['image']));
158
+
159
+ wp_redirect(esc_url(add_query_arg()));
160
+ exit;
161
+ }
162
+ }
163
+
164
+ // If the delete bulk action is triggered
165
+ if (( isset($_POST['action']) && $_POST['action'] == 'bulk-optimize' ) || ( isset($_POST['action2']) && $_POST['action2'] == 'bulk-optimize' )
166
+ ) {
167
+
168
+ $optimize_ids = esc_sql($_POST['bulk-optimize']);
169
+
170
+ // loop over the array of record IDs and delete them
171
+ foreach ($optimize_ids as $id) {
172
+ $this->action_optimize_image($id);
173
+ }
174
+
175
+ wp_redirect(esc_url(add_query_arg()));
176
+ exit;
177
+ }
178
+ }
179
+
180
+ }
class/view/shortpixel_view.php ADDED
@@ -0,0 +1,971 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ShortPixelView {
4
+
5
+ private $ctrl;
6
+
7
+ public function __construct($controller) {
8
+ $this->ctrl = $controller;
9
+ }
10
+
11
+ //handling older
12
+ public function ShortPixelView($controller) {
13
+ $this->__construct($controller);
14
+ }
15
+
16
+ public function displayQuotaExceededAlert($quotaData)
17
+ { ?>
18
+ <br/>
19
+ <div class="wrap sp-quota-exceeded-alert">
20
+ <h3>Quota Exceeded</h3>
21
+ <p>The plugin has optimized <strong><?php echo(number_format($quotaData['APICallsMadeNumeric'] + $quotaData['APICallsMadeOneTimeNumeric']));?> images</strong> and stopped because it reached the available quota limit.
22
+ <?php if($quotaData['totalProcessedFiles'] < $quotaData['totalFiles']) { ?>
23
+ <strong><?php echo(number_format($quotaData['mainFiles'] - $quotaData['mainProcessedFiles']));?> images and
24
+ <?php echo(number_format(($quotaData['totalFiles'] - $quotaData['mainFiles']) - ($quotaData['totalProcessedFiles'] - $quotaData['mainProcessedFiles'])));?> thumbnails</strong> are not yet optimized by ShortPixel.
25
+ <?php } ?></p>
26
+ <div> <!-- style='float:right;margin-top:20px;'> -->
27
+ <a class='button button-primary' href='https://shortpixel.com/login/<?php echo($this->ctrl->getApiKey());?>' target='_blank'>Upgrade</a>
28
+ <input type='button' name='checkQuota' class='button' value='Confirm New Quota' onclick="javascript:window.location.reload();">
29
+ </div>
30
+ <!-- <p>It’s simple to upgrade, just <a href='https://shortpixel.com/login/<?php echo($this->ctrl->getApiKey());?>' target='_blank'>log into your account</a> and see the available options.
31
+ You can immediately start processing 5,000 images/month for &#36;4,99, choose another plan that suits you or <a href='https://shortpixel.com/contact' target='_blank'>contact us</a> for larger compression needs.</p> -->
32
+ <p>Get more image credits by referring ShortPixel to your friends! <a href="https://shortpixel.com/login/<?php echo($this->ctrl->getApiKey());?>/tell-a-friend" target="_blank">Check your account</a> for your unique referral link. For each user that joins, you will receive +100 additional image credits/month.</p>
33
+
34
+ </div> <?php
35
+ }
36
+
37
+ public static function displayApiKeyAlert()
38
+ { ?>
39
+ <p>In order to start the optimization process, you need to validate your API Key in the <a href="options-general.php?page=wp-shortpixel">ShortPixel Settings</a> page in your WordPress Admin.</p>
40
+ <p>If you don’t have an API Key, you can get one delivered to your inbox, for free.</p>
41
+ <p>Please <a href="https://shortpixel.com/wp-apikey" target="_blank">sign up</a> to get your API key.</p>
42
+ <?php
43
+ }
44
+
45
+ public static function displayActivationNotice($when = 'activate') { ?>
46
+ <div class='notice notice-warning' id='short-pixel-notice-<?php echo($when);?>'>
47
+ <?php if($when != 'activate') { ?>
48
+ <div style="float:right;"><a href="javascript:dismissShortPixelNotice('<?php echo($when);?>')" class="button" style="margin-top:10px;">Dismiss</a></div>
49
+ <?php } ?>
50
+ <h3>ShortPixel Optimization</h3> <?php
51
+ switch($when) {
52
+ case '2h' :
53
+ echo "Action needed. Please <a href='https://shortpixel.com/wp-apikey' target='_blank'>get your API key</a> to activate your ShortPixel plugin.<BR><BR>";
54
+ break;
55
+ case '3d':
56
+ echo "Your image gallery is not optimized. It takes 2 minutes to <a href='https://shortpixel.com/wp-apikey' target='_blank'>get your API key</a> and activate your ShortPixel plugin.<BR><BR>";
57
+ break;
58
+ case 'activate':
59
+ self::displayApiKeyAlert();
60
+ break;
61
+ }
62
+ ?>
63
+ </div>
64
+ <?php
65
+ }
66
+
67
+ public function displayBulkProcessingForm($quotaData, $thumbsProcessedCount, $under5PercentCount, $bulkRan,
68
+ $averageCompression, $filesOptimized, $savedSpace, $percent, $customCount) {
69
+ ?>
70
+ <div class="wrap short-pixel-bulk-page">
71
+ <h1>Bulk Image Optimization by ShortPixel</h1>
72
+ <?php
73
+ if ( !$bulkRan ) {
74
+ ?>
75
+ <div class="notice notice-info sp-floating-block sp-full-width">
76
+ <form class='start' action='' method='POST' id='startBulk'>
77
+ <input type='hidden' id='mainToProcess' value='<?php echo($quotaData['mainFiles'] - $quotaData['mainProcessedFiles']);?>'/>
78
+ <input type='hidden' id='totalToProcess' value='<?php echo($quotaData['totalFiles'] - $quotaData['totalProcessedFiles']);?>'/>
79
+ <div class="bulk-stats-container">
80
+ <h3 style='margin-top:0;'>Your media library</h3>
81
+ <div class="bulk-label">Original images</div>
82
+ <div class="bulk-val"><?php echo(number_format($quotaData['mainMlFiles']));?></div><br>
83
+ <div class="bulk-label">Smaller thumbnails</div>
84
+ <div class="bulk-val"><?php echo(number_format($quotaData['totalMlFiles'] - $quotaData['mainMlFiles']));?></div>
85
+ <div style='width:165px; display:inline-block; padding-left: 5px'>
86
+ <input type='checkbox' id='thumbnails' name='thumbnails' onclick='ShortPixel.checkThumbsUpdTotal(this)' <?php echo($this->ctrl->processThumbnails() ? "checked":"");?>> Include thumbnails
87
+ </div><br>
88
+ <?php if($quotaData["totalProcessedMlFiles"] > 0) { ?>
89
+ <div class="bulk-label bulk-total">Total images</div>
90
+ <div class="bulk-val bulk-total"><?php echo(number_format($quotaData['totalMlFiles']));?></div>
91
+ <br><div class="bulk-label">Already optimized originals</div>
92
+ <div class="bulk-val"><?php echo(number_format($quotaData['mainProcessedMlFiles']));?></div><br>
93
+ <div class="bulk-label">Already optimized thumbnails</div>
94
+ <div class="bulk-val"><?php echo(number_format($quotaData['totalProcessedMlFiles'] - $quotaData['mainProcessedMlFiles']));?></div><br>
95
+ <?php } ?>
96
+ <div class="bulk-label bulk-total">Total to be optimized</div>
97
+ <div class="bulk-val bulk-total" id='displayTotal'><?php echo(number_format($quotaData['totalMlFiles'] - $quotaData['totalProcessedMlFiles']));?></div>
98
+
99
+ <?php if($customCount > 0) { ?>
100
+ <h3 style='margin-bottom:10px;'>Your custom folders</h3>
101
+ <div class="bulk-label bulk-total">Total to be optimized</div>
102
+ <div class="bulk-val bulk-total" id='displayTotal'><?php echo(number_format($customCount));?></div>
103
+ <?php } ?>
104
+ </div>
105
+ <?php if($quotaData['totalFiles'] - $quotaData['totalProcessedFiles'] + $customCount > 0) { ?>
106
+ <div class="bulk-play">
107
+ <input type='hidden' name='bulkProcess' id='bulkProcess' value='Start Optimizing'/>
108
+ <a href='javascript:void(0);' onclick="document.getElementById('startBulk').submit();" class='button'>
109
+ <div style="width: 320px">
110
+ <div class="bulk-btn-img" class="bulk-btn-img">
111
+ <img src='<?php echo(plugins_url( 'shortpixel-image-optimiser/res/img/robo-slider.png' ));?>'/>
112
+ </div>
113
+ <div class="bulk-btn-txt">
114
+ <span class="label">Start Optimizing</span><br>
115
+ <span class='total'><?php echo(number_format($quotaData['totalFiles'] - $quotaData['totalProcessedFiles']));?></span> images
116
+ </div>
117
+ <div class="bulk-btn-img" class="bulk-btn-img">
118
+ <img src='<?php echo(plugins_url( 'shortpixel-image-optimiser/res/img/arrow.png' ));?>'/>
119
+ </div>
120
+ </div>
121
+ </a>
122
+ </div>
123
+ <?php } else {?>
124
+ <div class="bulk-play bulk-nothing-optimize">
125
+ Nothing to optimize! The images that you add to Media Gallery will be automatically optimized after upload.
126
+ </div>
127
+ <?php } ?>
128
+ </form>
129
+ </div>
130
+ <?php if($quotaData['totalFiles'] - $quotaData['totalProcessedFiles'] > 0) { ?>
131
+ <div class='shortpixel-clearfix'></div>
132
+ <div class="bulk-wide">
133
+ <h3 style='font-size: 1.1em; font-weight: bold;'>After you start the bulk process, in order for the optimization to run, you must keep this page open and your computer running. If you close the page for whatever reason, just turn back to it and the bulk process will resume.</h3>
134
+ </div>
135
+ <?php } ?>
136
+ <div class='shortpixel-clearfix'></div>
137
+ <div class="bulk-text-container">
138
+ <h3>What are Thumbnails?</h3>
139
+ <p>Thumbnails are smaller images usually generated by your WP theme. Most themes generate between 3 and 6 thumbnails for each Media Library image.</p>
140
+ <p>The thumbnails also generate traffic on your website pages and they influence your website's speed.</p>
141
+ <p>It's highly recommended that you include thumbnails in the optimization as well.</p>
142
+ </div>
143
+ <div class="bulk-text-container" style="padding-right:0">
144
+ <h3>How does it work?</h3>
145
+ <p>The plugin processes images starting with the newest ones you uploaded in your Media Library.</p>
146
+ <p>You will be able to pause the process anytime.</p>
147
+ <p><?php echo($this->ctrl->backupImages() ? "<p>Your original images will be stored in a separate back-up folder.</p>" : "");?></p>
148
+ <p>You can watch the images being processed live, right here, after you start optimizing.</p>
149
+ </div>
150
+ <?php
151
+ } elseif($percent) // bulk is paused
152
+ { ?>
153
+ <?php echo($this->displayBulkProgressBar(false, $percent, "", $quotaData['APICallsRemaining'], $this->ctrl->getAverageCompression(), 1, $customCount));?>
154
+ <p>Please see below the optimization status so far:</p>
155
+ <?php $this->displayBulkStats($quotaData['totalProcessedFiles'], $quotaData['mainProcessedFiles'], $under5PercentCount, $averageCompression, $savedSpace);?>
156
+ <?php if($quotaData['totalProcessedFiles'] < $quotaData['totalFiles']) { ?>
157
+ <p><?php echo(number_format($quotaData['mainFiles'] - $quotaData['mainProcessedFiles']));?> images and
158
+ <?php echo(number_format(($quotaData['totalFiles'] - $quotaData['mainFiles']) - ($quotaData['totalProcessedFiles'] - $quotaData['mainProcessedFiles'])));
159
+ ?> thumbnails are not yet optimized by ShortPixel.</p>
160
+ <?php } ?>
161
+ <p>You can continue optimizing your Media Gallery from where you left, by clicking the Resume processing button. Already optimized images will not be reprocessed.</p>
162
+ <?php
163
+ } else { ?>
164
+ <div class="sp-container">
165
+ <div class='notice notice-success sp-floating-block sp-single-width' style="height: 80px;overflow:hidden;">
166
+ <div style='float:left;margin:5px 20px 5px 0'><img src="<?php echo(plugins_url( 'shortpixel-image-optimiser/res/img/slider.png' ));?>"></div>
167
+ <div class="sp-bulk-summary">
168
+ <input type="text" value="<?php echo("" . round($averageCompression))?>" id="sp-total-optimization-dial" class="dial">
169
+ </div>
170
+ <p style="margin-top:4px;">
171
+ <span style="font-size:1.2em;font-weight:bold">Congratulations!</span><br>Your media library has been successfully optimized!
172
+ <span class="sp-bulk-summary"><a href='javascript:void(0);'>Summary</a></span>
173
+ </p>
174
+ </div>
175
+ <div class='notice notice-success sp-floating-block sp-single-width' style="height: 80px;overflow:hidden;padding-right: 0;">
176
+ <div style="float:left; margin-top:-5px">
177
+ <p style='margin-bottom: -2px; font-weight: bold;'>
178
+ Share your optimization results:
179
+ </p>
180
+ <div style='display:inline-block; margin: 16px 16px 6px 0;float:left'>
181
+ <div id="fb-root"></div>
182
+ <script>
183
+ (function(d, s, id) {
184
+ var js, fjs = d.getElementsByTagName(s)[0];
185
+ if (d.getElementById(id)) return;
186
+ js = d.createElement(s); js.id = id;
187
+ js.src = "//connect.facebook.net/ro_RO/sdk.js#xfbml=1&version=v2.6";
188
+ fjs.parentNode.insertBefore(js, fjs);
189
+ }(document, 'script', 'facebook-jssdk'));
190
+ </script>
191
+ <div style="float:left;width:240px;">
192
+ <div class="fb-like" data-href="https://www.facebook.com/ShortPixel" data-width="260" data-layout="button_count" data-action="like" data-show-faces="true" data-share="true"></div>
193
+ </div>
194
+ <div style="float:left;margin:-7px 0 0 10px">
195
+ <a href="https://twitter.com/share" class="twitter-share-button" data-url="https://shortpixel.com"
196
+ data-text="I just optimized my images<?php echo(0+$averageCompression>20 ? " by ".round($averageCompression) ."%" : "");?><?php echo(false && (0+$savedSpace>0) ? " saving $savedSpace" : "");?> with @ShortPixel, a great plugin for increasing #WordPress page speed:" data-size='large'>Tweet</a>
197
+ </div>
198
+ <script>
199
+ jQuery(function() {
200
+ jQuery("#sp-total-optimization-dial").val("<?php echo("" . round($averageCompression))?>");
201
+ ShortPixel.percentDial("#sp-total-optimization-dial", 60);
202
+
203
+ jQuery(".sp-bulk-summary").tooltip({
204
+ tooltipSource: "inline",
205
+ tooltipSourceID: "#sp-bulk-stats"
206
+ });
207
+ });
208
+ !function(d,s,id){//Just optimized my site with ShortPixel image optimization plugin
209
+ var js,
210
+ fjs=d.getElementsByTagName(s)[0],
211
+ p=/^http:/.test(d.location)?'http':'https';
212
+ if(!d.getElementById(id)){js=d.createElement(s);
213
+ js.id=id;js.src=p+'://platform.twitter.com/widgets.js';
214
+ fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');
215
+ </script>
216
+ </div>
217
+ </div>
218
+ <?php if(0+$averageCompression>30) {?>
219
+ <div class='shortpixel-rate-us' style='float:left;padding-top:0'>
220
+ <a href="https://wordpress.org/support/view/plugin-reviews/shortpixel-image-optimiser?rate=5#postform" target="_blank">
221
+ <span>
222
+ Please rate us!&nbsp;
223
+ </span><br><img src="<?php echo(plugins_url( 'shortpixel-image-optimiser/res/img/stars.png' ));?>">
224
+ </a>
225
+ </div>
226
+ <?php } ?>
227
+ </div>
228
+ <div id="sp-bulk-stats" style="display:none">
229
+ <?php $this->displayBulkStats($quotaData['totalProcessedFiles'], $quotaData['mainProcessedFiles'], $under5PercentCount, $averageCompression, $savedSpace);?>
230
+ </div>
231
+ </div>
232
+ <p>Go to the ShortPixel <a href='<?php echo(get_admin_url());?>options-general.php?page=wp-shortpixel#stats'>Stats</a> and see all your websites' optimized stats. Download your detailed <a href="https://api.shortpixel.com/v2/report.php?key=<?php echo($this->ctrl->getApiKey());?>">Optimization Report</a> to check your image optimization statistics for the last 40 days.</p>
233
+ <?php
234
+ $failed = $this->ctrl->getPrioQ()->getFailed();
235
+ if(count($failed)) { ?>
236
+ <div class="bulk-progress" style="margin-bottom: 15px">
237
+ <p>
238
+ The following images could not be processed because of their limited write rights. This usually happens if you have changed your hosting provider. Please restart the optimization process after you granted write rights to all the files below.
239
+ </p>
240
+ <?php $this->displayFailed($failed); ?>
241
+ </div>
242
+ <?php } ?>
243
+ <div class="bulk-progress notice notice-info sp-floating-block sp-double-width">
244
+ <?php
245
+ $todo = $reopt = false;
246
+ if($quotaData['totalProcessedFiles'] < $quotaData['totalFiles']) {
247
+ $todo = true;
248
+ $mainNotProcessed = $quotaData['mainFiles'] - $quotaData['mainProcessedFiles'];
249
+ $thumbsNotProcessed = ($quotaData['totalFiles'] - $quotaData['mainFiles']) - ($quotaData['totalProcessedFiles'] - $quotaData['mainProcessedFiles']);
250
+ ?>
251
+ <p>
252
+ <?php echo($mainNotProcessed ? number_format($mainNotProcessed) . " images" : "");?>
253
+ <?php echo($mainNotProcessed && $thumbsNotProcessed ? " and" : "");?>
254
+ <?php echo($thumbsNotProcessed ? number_format($thumbsNotProcessed) . " thumbnails" : "");?> are not yet optimized by ShortPixel.
255
+ <?php if (count($quotaData['filesWithErrors'])) { ?>
256
+ Some have errors:
257
+ <?php foreach($quotaData['filesWithErrors'] as $id => $data) {
258
+ if(ShortPixelMetaFacade::isCustomQueuedId($id)) {
259
+ echo('<a href="'.trailingslashit(network_site_url("/")) . 'wp-content/' . ShortPixelMetaFacade::filenameToContentRelative($data['Path']).'" title="'.$data['Message'].'" target="_blank">'.$data['Name'].'</a>,&nbsp;');
260
+ } else {
261
+ echo('<a href="post.php?post='.$id.'&action=edit" title="'.$data['Message'].'">'.$data['Name'].'</a>,&nbsp;');
262
+ }
263
+ } ?>
264
+ <?php } ?>
265
+ </p>
266
+ <?php }
267
+ $settings = $this->ctrl->getSettings();
268
+ $optType = $settings->compressionType == '1' ? 'lossy' : 'lossless';
269
+ $otherType = $settings->compressionType == '1' ? 'lossless' : 'lossy';
270
+ if( !$this->ctrl->backupFolderIsEmpty()
271
+ && ( ($quotaData['totalProcLossyFiles'] > 0 && $settings->compressionType == 0)
272
+ || ($quotaData['totalProcLosslessFiles'] > 0 && $settings->compressionType == 1)))
273
+ {
274
+ $todo = $reopt = true;
275
+ $statType = $settings->compressionType == '1' ? 'Lossless' : 'Lossy';
276
+ $thumbsCount = $quotaData['totalProc'.$statType.'Files'] - $quotaData['mainProc'.$statType.'Files'];
277
+ ?>
278
+ <p id="with-thumbs" <?php echo(!$settings->processThumbnails ? 'style="display:none;"' : "");?>>
279
+ <?php echo(number_format($quotaData['mainProc'.$statType.'Files']));?> images and
280
+ <?php echo(number_format($quotaData['totalProc'.$statType.'Files'] - $quotaData['mainProc'.$statType.'Files']));?> thumbnails were optimized
281
+ <strong>
282
+ <?php echo($otherType);?>
283
+ </strong>. You can re-optimize
284
+ <strong>
285
+ <?php echo($optType);?>
286
+ </strong> the ones that have backup.
287
+ </p>
288
+ <p id="without-thumbs" <?php echo($settings->processThumbnails ? 'style="display:none;"' : "");?>>
289
+ <?php echo(number_format($quotaData['mainProc'.$statType.'Files']));?> images are optimized
290
+ <strong>
291
+ <?php echo($otherType);?>
292
+ </strong>. You can re-optimize
293
+ <strong>
294
+ <?php echo($optType);?>
295
+ </strong> the ones that have backup.
296
+ <?php echo($thumbsCount ? number_format($thumbsCount) . ' thumbnails will be restored to originals.' : '');?>
297
+ </p>
298
+ <?php
299
+ } ?>
300
+ <p>Restart the optimization process for <?php echo($todo ? 'these images' : 'new images added to your library');?> by clicking the button below.
301
+ Already <strong><?php echo($todo ? ($optType) : '');?></strong> optimized images will not be reprocessed.
302
+ <?php if($reopt) { ?>
303
+ <br>Please note that reoptimizing images as <strong>lossy/lossless</strong> may use additional credits. <a href="http://blog.shortpixel.com/the-all-new-re-optimization-functions-in-shortpixel/" target="_blank">More info</a>
304
+ <?php } ?>
305
+ </p>
306
+ <form action='' method='POST' >
307
+ <input type='checkbox' id='bulk-thumbnails' name='thumbnails' <?php echo($this->ctrl->processThumbnails() ? "checked":"");?> onchange="ShortPixel.onBulkThumbsCheck(this)"> Include thumbnails<br><br>
308
+ <input type='submit' name='bulkProcess' id='bulkProcess' class='button button-primary' value='Restart Optimizing'>
309
+ </form>
310
+ </div>
311
+ <?php } ?>
312
+ </div>
313
+ <?php
314
+ }
315
+
316
+ public function displayBulkProcessingRunning($percent, $message, $remainingQuota, $averageCompression, $type) {
317
+ ?>
318
+ <div class="wrap short-pixel-bulk-page">
319
+ <h1>Bulk Image Optimization by ShortPixel</h1>
320
+ <?php $this->displayBulkProgressBar(true, $percent, $message, $remainingQuota, $averageCompression, $type);?>
321
+ <div class="sp-floating-block notice bulk-notices-parent">
322
+ <div class="bulk-notice-container">
323
+ <div class="bulk-notice-msg bulk-lengthy">
324
+ <img src="<?php echo(plugins_url( 'shortpixel-image-optimiser/res/img/loading-dark-big.gif' ));?>">
325
+ Lengthy operation in progress:<br>
326
+ Optimizing image <a href="#" data-href="<?php echo(get_admin_url());?>/post.php?post=__ID__&action=edit" target="_blank">lala.png</a>
327
+ </div>
328
+ <div class="bulk-notice-msg bulk-error" id="bulk-error-template">
329
+ <div style="float: right; margin-top: -4px; margin-right: -8px;">
330
+ <a href="javascript:void(0);" onclick="ShortPixel.removeBulkMsg(this)" style='color: #c32525;'>&#10006;</a>
331
+ </div>
332
+ <img src="<?php echo(plugins_url( 'shortpixel-image-optimiser/res/img/exclamation-big.png' ));?>">
333
+ <span class="sp-err-title">Error processing file:<br></span>
334
+ <span class="sp-err-content"><?php echo $message; ?></span> <a class="sp-post-link" href="<?php echo(get_admin_url());?>/post.php?post=__ID__&action=edit" target="_blank">lala.png</a>
335
+ </div>
336
+ </div>
337
+ </div>
338
+ <div class="bulk-progress bulk-slider-container notice notice-info sp-floating-block sp-full-width">
339
+ <div class="short-pixel-block-title"><span>Just optimized:</span><span class="filename"></span></div>
340
+ <div class="bulk-slider">
341
+ <div class="bulk-slide" id="empty-slide">
342
+ <div class="bulk-slide-images">
343
+ <div class="img-original">
344
+ <div><img class="bulk-img-orig" src=""></div>
345
+ <div>Original image</div>
346
+ </div>
347
+ <div class="img-optimized">
348
+ <div><img class="bulk-img-opt" src=""></div>
349
+ <div>Optimized image</div>
350
+ </div>
351
+ </div>
352
+ <div class="img-info">
353
+ <div style="font-size: 14px; line-height: 10px; margin-bottom:16px;">Optimized by:</div>
354
+ <span class="bulk-opt-percent"></span>
355
+ </div>
356
+ </div>
357
+ </div>
358
+ </div>
359
+ </div>
360
+ <?php
361
+ }
362
+
363
+ public function displayBulkProgressBar($running, $percent, $message, $remainingQuota, $averageCompression, $type = 1, $customPending = false) {
364
+ $percentBefore = $percentAfter = '';
365
+ if($percent > 24) {
366
+ $percentBefore = $percent . "%";
367
+ } else {
368
+ $percentAfter = $percent . "%";
369
+ }
370
+ ?>
371
+ <div class="notice notice-info bulk-progress sp-floating-block sp-full-width">
372
+ <div style="float:right">
373
+ <?php if(false) { ?>
374
+ <div class="bulk-progress-indicator">
375
+ <div style="margin-bottom:5px">Remaining credits</div>
376
+ <div style="margin-top:22px;margin-bottom: 5px;font-size:2em;font-weight: bold;"><?php echo(number_format($remainingQuota))?></div>
377
+ <div>images</div>
378
+ </div>
379
+ <?php } ?>
380
+ <div class="bulk-progress-indicator">
381
+ <div style="margin-bottom:5px">Average reduction</div>
382
+ <div id="sp-avg-optimization"><input type="text" id="sp-avg-optimization-dial" value="<?php echo("" . round($averageCompression))?>" class="dial"></div>
383
+ <script>
384
+ jQuery(function() {
385
+ ShortPixel.percentDial("#sp-avg-optimization-dial", 60);
386
+ });
387
+ </script>
388
+ </div>
389
+ </div>
390
+ <?php if($running) { ?>
391
+ <h2><?php echo($type & 1 ? "Media Library " : "") ?><?php echo($type & 3 == 3 ? "and " : "") ?><?php echo($type & 2 ? "Custom folders " : "") ?>optimization in progress ...</h2>
392
+ <p style="margin: 0 0 18px;">Bulk optimization has started.<br>
393
+ This process will take some time, depending on the number of images in your library. In the meantime, you can continue using
394
+ the admin as usual, <a href='<?php echo(get_admin_url());?>' target='_blank'>in a different browser window or tab</a>.<br>
395
+ However, <strong>if you close this window, the bulk processing will pause</strong> until you open the media gallery or the ShortPixel bulk page again.
396
+ </p>
397
+ <?php } else { ?>
398
+ <h2>Media Library <?php ($type & 2 ? "and Custom folders " : "") ?>optimization paused</h2>
399
+ <p style="margin: 0 0 50px;">Bulk processing is paused until you resume the optimization process.</p>
400
+ <?php }?>
401
+ <div id="bulk-progress" class="progress" >
402
+ <div class="progress-img" style="left: <?php echo($percent);?>%;">
403
+ <img src="<?php echo(plugins_url( 'shortpixel-image-optimiser/res/img/slider.png' ));?>">
404
+ <span><?php echo($percentAfter);?></span>
405
+ </div>
406
+ <div class="progress-left" style="width: <?php echo($percent);?>%"><?php echo($percentBefore);?></div>
407
+ </div>
408
+ <div class="bulk-estimate">
409
+ &nbsp;<?php echo($message);?>
410
+ </div>
411
+ <?php if (true || ($type & 1)) { //now we display the action buttons always when a type of bulk is running ?>
412
+ <form action='' method='POST' style="display:inline;">
413
+ <input type="submit" class="button button-primary bulk-cancel" onclick="clearBulkProcessor();"
414
+ name="bulkProcessStop" value="Stop" style="margin-left:10px"/>
415
+ <input type="submit" class="button button-primary bulk-cancel" onclick="clearBulkProcessor();"
416
+ name="<?php echo($running ? "bulkProcessPause" : "bulkProcessResume");?>" value="<?php echo($running ? "Pause" : "Resume processing");?>"/>
417
+ <?php if(!$running && $customPending) {?>
418
+ <input type="submit" class="button button-primary bulk-cancel" onclick="clearBulkProcessor();"
419
+ name="skipToCustom" value="Only custom folders" title="Process only the custom folders, skipping the Media Library" style="margin-right:10px"/>
420
+ <?php }?>
421
+ </form>
422
+ <?php } else { ?>
423
+ <a href="options-general.php?page=wp-shortpixel" class="button button-primary bulk-cancel" style="margin-left:10px">Manage custom folders</a>
424
+ <?php }?>
425
+ </div>
426
+ <?php
427
+ }
428
+
429
+ public function displayBulkStats($totalOptimized, $mainOptimized, $under5PercentCount, $averageCompression, $savedSpace) {?>
430
+ <div class="bulk-progress bulk-stats">
431
+ <div class="label">Processed Images and PDFs:</div><div class="stat-value"><?php echo(number_format($mainOptimized));?></div><br>
432
+ <div class="label">Processed Thumbnails:</div><div class="stat-value"><?php echo(number_format($totalOptimized - $mainOptimized));?></div><br>
433
+ <div class="label totals">Total files processed:</div><div class="stat-value"><?php echo(number_format($totalOptimized));?></div><br>
434
+ <div class="label totals">Minus files with <5% optimization (free):</div><div class="stat-value"><?php echo(number_format($under5PercentCount));?></div><br><br>
435
+ <div class="label totals">Used quota:</div><div class="stat-value"><?php echo(number_format($totalOptimized - $under5PercentCount));?></div><br>
436
+ <br>
437
+ <div class="label">Average optimization:</div><div class="stat-value"><?php echo($averageCompression);?>%</div><br>
438
+ <div class="label">Saved space:</div><div class="stat-value"><?php echo($savedSpace);?></div>
439
+ </div>
440
+ <?php
441
+ }
442
+
443
+ public function displayFailed($failed) {
444
+ ?>
445
+ <div class="bulk-progress bulk-stats">
446
+ <?php foreach($failed as $fail) {
447
+ if($fail->type == ShortPixelMetaFacade::CUSTOM_TYPE) {
448
+ $meta = $fail->meta;
449
+ ?> <div class="label"><a href="<?php echo(trailingslashit(network_site_url("/")) . "wp-content/" . $fail->meta->getWebPath());?>"><?php echo(substr($fail->meta->getName(), 0, 80));?> - ID: C-<?php echo($fail->id);?></a></div><br/>
450
+ <?php } else {
451
+ $meta = wp_get_attachment_metadata($fail);
452
+ ?> <div class="label"><a href="/wp-admin/post.php?post=<?php echo($fail->id);?>&action=edit"><?php echo(substr($fail->meta["file"], 0, 80));?> - ID: <?php echo($fail->id);?></a></div><br/>
453
+ <?php }
454
+ }?>
455
+ </div>
456
+ <?php
457
+ }
458
+
459
+ function displaySettings($showApiKey, $editApiKey, $quotaData, $notice, $resources = null, $averageCompression = null, $savedSpace = null, $savedBandwidth = null,
460
+ $remainingImages = null, $totalCallsMade = null, $fileCount = null, $backupFolderSize = null,
461
+ $customFolders = null, $folderMsg = false, $addedFolder = false, $showAdvanced = false) {
462
+ //wp_enqueue_script('jquery.idTabs.js', plugins_url('/js/jquery.idTabs.js',__FILE__) );
463
+ ?>
464
+ <h1>ShortPixel Plugin Settings</h1>
465
+ <p style="font-size:18px">
466
+ <a href="https://shortpixel.com/<?php echo($this->ctrl->getVerifiedKey() ? "login/".$this->ctrl->getApiKey() : "pricing");?>" target="_blank" style="font-size:18px">
467
+ Upgrade now
468
+ </a> |
469
+ <a href="https://shortpixel.com/contact/<?php echo($this->ctrl->getEncryptedData());?>" target="_blank" style="font-size:18px">Support </a>
470
+ </p>
471
+ <?php if($notice !== null) { ?>
472
+ <br/>
473
+ <div style="background-color: #fff; border-left: 4px solid <?php echo($notice['status'] == 'error' ? '#ff0000' : ($notice['status'] == 'warn' ? '#FFC800' : '#7ad03a'));?>; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); padding: 1px 12px;;width: 95%">
474
+ <p><?php echo($notice['msg']);?></p>
475
+ </div>
476
+ <?php } ?>
477
+ <?php if($folderMsg) { ?>
478
+ <br/>
479
+ <div style="background-color: #fff; border-left: 4px solid #ff0000; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); padding: 1px 12px;;width: 95%">
480
+ <p><?php echo($folderMsg);?></p>
481
+ </div>
482
+ <?php } ?>
483
+
484
+ <article id="shortpixel-settings-tabs" class="sp-tabs">
485
+ <form name='wp_shortpixel_options' action='options-general.php?page=wp-shortpixel&noheader=true' method='post' id='wp_shortpixel_options'>
486
+ <section <?php echo($showAdvanced ? "" : "class='sel-tab'");?> id="tab-settings">
487
+ <h2><a class='tab-link' href='javascript:void(0);' data-id="tab-settings">General</a></h2>
488
+ <?php $this->displaySettingsForm($showApiKey, $editApiKey, $quotaData);?>
489
+ </section>
490
+ <section <?php echo($showAdvanced ? "class='sel-tab'" : "");?> id="tab-adv-settings">
491
+ <h2><a class='tab-link' href='javascript:void(0);' data-id="tab-adv-settings">Advanced</a></h2>
492
+ <?php $this->displayAdvancedSettingsForm($customFolders, $addedFolder);?>
493
+ </section>
494
+ </form><span style="display:none">&nbsp;</span><?php //the span is a trick to keep the sections ordered as nth-child in styles: 1,2,3,4 (otherwise the third section would be nth-child(2) too, because of the form)
495
+ if($averageCompression !== null) {?>
496
+ <section id="tab-stats">
497
+ <h2><a class='tab-link' href='javascript:void(0);' data-id="tab-stats">Statistics</a></h2>
498
+ <?php
499
+ $this->displaySettingsStats($quotaData, $averageCompression, $savedSpace, $savedBandwidth,
500
+ $remainingImages, $totalCallsMade, $fileCount, $backupFolderSize);?>
501
+ </section>
502
+ <?php }
503
+ if($resources !== null) {?>
504
+ <section id="tab-resources">
505
+ <h2><a class='tab-link' href='javascript:void(0);' data-id="tab-resources">WP Resources</a></h2>
506
+ <?php echo((isset($resources['body']) ? $resources['body'] : "Please reload"));?>
507
+ </section>
508
+ <?php } ?>
509
+ </article>
510
+ <script>
511
+ jQuery(document).ready(function () {
512
+ ShortPixel.adjustSettingsTabs();
513
+
514
+ if(window.location.hash) {
515
+ var target = 'tab-' + window.location.hash.substring(window.location.hash.indexOf("#")+1)
516
+ ShortPixel.switchSettingsTab(target);
517
+ }
518
+ jQuery("article.sp-tabs a.tab-link").click(function(){ShortPixel.switchSettingsTab(jQuery(this).data("id"))});
519
+ });
520
+ </script>
521
+ <?php
522
+ }
523
+
524
+ public function displaySettingsForm($showApiKey, $editApiKey, $quotaData) {
525
+ $settings = $this->ctrl->getSettings();
526
+ $checked = ($this->ctrl->processThumbnails() ? 'checked' : '');
527
+ $checkedBackupImages = ($this->ctrl->backupImages() ? 'checked' : '');
528
+ $cmyk2rgb = ($this->ctrl->getCMYKtoRGBconversion() ? 'checked' : '');
529
+ $removeExif = ($settings->keepExif ? '' : 'checked');
530
+ $resize = ($this->ctrl->getResizeImages() ? 'checked' : '');
531
+ $resizeDisabled = ($this->ctrl->getResizeImages() ? '' : 'disabled');
532
+ $minSizes = $this->ctrl->getMaxIntermediateImageSize();
533
+ $thumbnailsToProcess = isset($quotaData['totalFiles']) ? ($quotaData['totalFiles'] - $quotaData['mainFiles']) - ($quotaData['totalProcessedFiles'] - $quotaData['mainProcessedFiles']) : 0;
534
+ ?>
535
+ <div class="wp-shortpixel-options">
536
+ <?php if($this->ctrl->getVerifiedKey()) { ?>
537
+ <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="<?php echo(get_admin_url());?>upload.php?page=wp-short-pixel-bulk">Bulk Optimization Tool</a>.</p>
538
+ <?php } else {
539
+ if($showApiKey) {?>
540
+ <h3>Step 1:</h3>
541
+ <p style='font-size: 14px'>If you don't have an API Key, <a href="https://shortpixel.com/wp-apikey<?php echo( $this->ctrl->getAffiliateSufix() );?>" target="_blank">sign up here.</a> It's free and it only takes one minute, we promise!</p>
542
+ <h3>Step 2:</h3>
543
+ <p style='font-size: 14px'>Please enter here the API Key you received by email and press Validate.</p>
544
+ <?php }
545
+ }?>
546
+ <table class="form-table">
547
+ <tbody>
548
+ <tr>
549
+ <th scope="row"><label for="key">API Key:</label></th>
550
+ <td>
551
+ <?php
552
+ $canValidate = false;
553
+ if($showApiKey) {
554
+ $canValidate = true;?>
555
+ <input name="key" type="text" id="key" value="<?php echo( $this->ctrl->getApiKey() );?>"
556
+ class="regular-text" <?php echo($editApiKey ? "" : 'disabled') ?>>
557
+ <?php } elseif(defined("SHORTPIXEL_API_KEY")) {
558
+ $canValidate = true;?>
559
+ <input name="key" type="text" id="key" disabled="true" placeholder="Multisite API Key" class="regular-text">
560
+ <?php } ?>
561
+ <input type="hidden" name="validate" id="valid" value=""/>
562
+ <button type="button" id="validate" class="button button-primary" title="Validate the provided API key"
563
+ onclick="validateKey()" <?php echo $canValidate ? "" : "disabled"?>>Validate</button>
564
+ <?php if($showApiKey && !$editApiKey) { ?>
565
+ <p class="settings-info">Key defined in wp-config.php.</p>
566
+ <?php } ?>
567
+
568
+ </td>
569
+ </tr>
570
+ <?php if (!$this->ctrl->getVerifiedKey()) { //if invalid key we display the link to the API Key ?>
571
+ </tbody>
572
+ </table>
573
+ <?php } else { //if valid key we display the rest of the options ?>
574
+ <tr>
575
+ <th scope="row">
576
+ <label for="compressionType">Compression type:</label>
577
+ </th>
578
+ <td>
579
+ <input type="radio" name="compressionType" value="1" <?php echo( $this->ctrl->getCompressionType() == 1 ? "checked" : "" );?>>Lossy (recommended)</br>
580
+ <p class="settings-info"> <b>Lossy compression: </b>lossy has a better compression rate than lossless compression.</br>The resulting image is identical with the original to the human eye. You can run a test for free
581
+ <a href="https://shortpixel.com/online-image-compression" target="_blank">here</a>.</p></br>
582
+ <input type="radio" name="compressionType" value="0" <?php echo( $this->ctrl->getCompressionType() != 1 ? "checked" : "" );?>>Lossless
583
+ <p class="settings-info"><b>Lossless compression: </b> the shrunk image will be identical with the original and smaller in size.</br>In some rare cases you will need to use
584
+ this type of compression. Some technical drawings or images from vector graphics are possible situations.</p>
585
+ </td>
586
+ </tr>
587
+ </tbody>
588
+ </table>
589
+ <table class="form-table">
590
+ <tbody>
591
+ <tr>
592
+ <th scope="row"><label for="thumbnails">Also include thumbnails:</label></th>
593
+ <td><input name="thumbnails" type="checkbox" id="thumbnails" <?php echo( $checked );?>> Apply compression also to
594
+ <strong>image thumbnails.</strong> <?php echo($thumbnailsToProcess ? "(" . number_format($thumbnailsToProcess) . " thumbnails to optimize)" : "");?>
595
+ <p class="settings-info">It is highly recommended that you optimize the thumbnails as they are usually the images most viewed by end users and can generate most traffic.<br>Please note that thumbnails count up to your total quota.</p>
596
+ </td>
597
+ </tr>
598
+ <tr>
599
+ <th scope="row"><label for="backupImages">Image backup</label></th>
600
+ <td>
601
+ <input name="backupImages" type="checkbox" id="backupImages" <?php echo( $checkedBackupImages );?>> Save and keep a backup of your original images in a separate folder.
602
+ <p class="settings-info">You <strong>need to have backup active</strong> in order to be able to restore images to originals or to convert from Lossy to Lossless and back.</p>
603
+ </td>
604
+ </tr>
605
+ <tr>
606
+ <th scope="row"><label for="cmyk2rgb">CMYK to RGB conversion</label></th>
607
+ <td>
608
+ <input name="cmyk2rgb" type="checkbox" id="cmyk2rgb" <?php echo( $cmyk2rgb );?>>Adjust your images for computer and mobile screen display.
609
+ <p class="settings-info">Images for the web only need RGB format and converting them from CMYK to RGB makes them smaller.</p>
610
+ </td>
611
+ </tr>
612
+ <tr>
613
+ <th scope="row"><label for="removeExif">Remove EXIF</label></th>
614
+ <td>
615
+ <input name="removeExif" type="checkbox" id="removeExif" <?php echo( $removeExif );?>>Remove the EXIF tag of the image (recommended).
616
+ <p class="settings-info"> EXIF is a set of various pieces of information that are automatically embedded into the image upon creation. This can include GPS position, camera manufacturer, date and time, etc.
617
+ Unless you really need that data to be preserved, we recommend removing it as it can lead to <a href="http://blog.shortpixel.com/how-much-smaller-can-be-images-without-exif-icc" target="_blank">better compression rates</a>.</p>
618
+ </td>
619
+ </tr>
620
+ <tr>
621
+ <th scope="row"><label for="resize">Resize large images</label></th>
622
+ <td>
623
+ <input name="resize" type="checkbox" id="resize" <?php echo( $resize );?>> to maximum
624
+ <input type="text" name="width" id="width" style="width:70px" value="<?php echo( max($this->ctrl->getResizeWidth(), min(1024, $minSizes['width'])) );?>" <?php echo( $resizeDisabled );?>/> pixels wide &times;
625
+ <input type="text" name="height" id="height" style="width:70px" value="<?php echo( max($this->ctrl->getResizeHeight(), min(1024, $minSizes['height'])) );?>" <?php echo( $resizeDisabled );?>/> pixels high (original aspect ratio is preserved)
626
+ <p class="settings-info"> Recommended for large photos, like the ones taken with your phone. Saved space can go up to 80% or more after resizing.<br/>
627
+ The new resolution should not be less than your largest thumbnail size, which is <?php echo($minSizes['width']);?> &times; <?php echo($minSizes['height']);?> pixels,
628
+ or, if you have a Retina images plugin, <?php echo(2 * $minSizes['width']);?> &times; <?php echo(2 * $minSizes['height']);?> pixels.<br>
629
+ The resize keeps the aspect ratio and is done so that the resulting image will be resized to no less than the configured sizes. For example, if you set the resize dimensions at 1000x1200, an image of 2000x3000px will be resized to 1000x1500px while an image of 3000x2000px will be resized to 1800x1200px</p>
630
+ </td>
631
+ </tr>
632
+ </tbody>
633
+ </table>
634
+ <p class="submit">
635
+ <input type="submit" name="save" id="save" class="button button-primary" title="Save Changes" value="Save Changes"> &nbsp;
636
+ <input type="submit" name="save" id="bulk" class="button button-primary" title="Save and go to the Bulk Processing page" value="Save and Go to Bulk Process"> &nbsp;
637
+ </p>
638
+ </div>
639
+ <?php }
640
+ }
641
+
642
+ public function displayAdvancedSettingsForm($customFolders = false, $addedFolder = false) {
643
+ $settings = $this->ctrl->getSettings();
644
+ $minSizes = $this->ctrl->getMaxIntermediateImageSize();
645
+ $hasNextGen = $this->ctrl->hasNextGen();
646
+ $frontBootstrap = ($settings->frontBootstrap ? 'checked' : '');
647
+ $includeNextGen = ($settings->includeNextGen ? 'checked' : '');
648
+ ?>
649
+ <div class="wp-shortpixel-options">
650
+ <?php if(!$this->ctrl->getVerifiedKey()) { ?>
651
+ <p>Please enter your API key in the General tab first.</p>
652
+ <?php } else { //if valid key we display the rest of the options ?>
653
+ <table class="form-table">
654
+ <tbody>
655
+ <tr>
656
+ <th scope="row"><label for="resize">Additional media folders</label></th>
657
+ <td>
658
+ <?php if($customFolders) { ?>
659
+ <table class="shortpixel-folders-list">
660
+ <tr style="font-weight: bold;">
661
+ <td>Folder name</td>
662
+ <td>Type &amp;<br>Status</td>
663
+ <td>Files</td>
664
+ <td>Last change</td>
665
+ <td></td>
666
+ </tr>
667
+ <?php foreach($customFolders as $folder) {
668
+ $typ = $folder->getType();
669
+ $typ = $typ ? $typ . "<br>" : "";
670
+ $stat = $this->ctrl->getSpMetaDao()->getFolderOptimizationStatus($folder->getId());
671
+ $cnt = $folder->getFileCount();
672
+ $st = ($cnt == 0
673
+ ? "Empty"
674
+ : ($stat->Total == $stat->Optimized
675
+ ? "Optimized"
676
+ : ($stat->Optimized + $stat->Pending > 0 ? "Pending" : "Waiting")));
677
+ $err = $stat->Failed > 0 && !$st == "Empty" ? " ({$stat->Failed} failed)" : "";
678
+ $action = ($st == "Optimized" || $st == "Empty" ? "monitoring" : "optimizing");
679
+ $fullStat = $st == "Empty" ? "" : "Optimized: " . $stat->Optimized . ", Pending: " . $stat->Pending . ", Waiting: " . $stat->Waiting . ", Failed: " . $stat->Failed;
680
+ ?>
681
+ <tr>
682
+ <td>
683
+ <?php echo($folder->getPath()); ?>
684
+ </td>
685
+ <td>
686
+ <?php if(!($st == "Empty")) { ?>
687
+ <a href="javascript:none();" title="<?php echo $fullStat; ?>" style="text-decoration: none;">
688
+ <img src='<?php echo(plugins_url( 'shortpixel-image-optimiser/res/img/info-icon.png' ));?>' style="margin-bottom: -2px;"/>
689
+ </a>&nbsp;<?php } echo($typ.$st.$err); ?>
690
+
691
+ </td>
692
+ <td>
693
+ <?php echo($cnt); ?> files
694
+ </td>
695
+ <td>
696
+ <?php echo($folder->getTsUpdated()); ?>
697
+ </td>
698
+ <td>
699
+ <input type="button" class="button remove-folder-button" data-value="<?php echo($folder->getPath()); ?>" title="Stop <?php echo($action . " " . $folder->getPath()); ?>" value="Stop <?php echo $action;?>">
700
+ </td>
701
+ </tr>
702
+ <?php }?>
703
+ </table>
704
+ <?php } ?>
705
+ <input type="hidden" name="removeFolder" id="removeFolder"/>
706
+ <input type="text" name="addCustomFolderView" id="addCustomFolderView" class="regular-text" value="<?php echo($addedFolder);?>" disabled style="width: 50em;max-width: 70%;">&nbsp;
707
+ <input type="hidden" name="addCustomFolder" id="addCustomFolder" value="<?php echo($addedFolder);?>"/>
708
+ <input type="hidden" id="customFolderBase" value="<?php echo $this->ctrl->getCustomFolderBase(); ?>">
709
+ <a class="button button-primary select-folder-button" title="Select the images folder on your server" href="javascript:void(0);">Select ... </a>
710
+ <input type="submit" name="saveAdv" id="saveAdvAddFolder" class="button button-primary" title="Add Folder" value="Add Folder">
711
+ <p class="settings-info"> Use the Select... button to select folders from <i>wp-content</i>. ShortPixel will optimize images and PDFs from the specified folders and their subfolders. The optimization status for each image or PDF in these folders can be seen in the <a href="upload.php?page=wp-short-pixel-custom">Other Media list</a>, under the Media menu.</p>
712
+ <div class="sp-folder-picker-shade">
713
+ <div class="sp-folder-picker-popup">
714
+ <div class="sp-folder-picker-title">Select the images folder</div>
715
+ <div class="sp-folder-picker"></div>
716
+ <input type="button" class="button button-info select-folder-cancel" value="Cancel" style="margin-right: 30px;">
717
+ <input type="button" class="button button-primary select-folder" value="Select">
718
+ </div>
719
+ </div>
720
+ <script>
721
+ jQuery(document).ready(function () {
722
+ ShortPixel.initFolderSelector();
723
+ });
724
+ </script>
725
+ </td>
726
+ </tr>
727
+ <?php if($hasNextGen) { ?>
728
+ <tr>
729
+ <th scope="row"><label for="resize">Optimize NextGen galleries</label></th>
730
+ <td>
731
+ <input name="nextGen" type="checkbox" id="resize" <?php echo( $includeNextGen );?>> Optimize NextGen galleries.
732
+ <p class="settings-info">Check this to add all your current NextGen galleries to the custom folders list and to also have all the future NextGen galleries and images optimized automatically by ShortPixel.</p>
733
+ </td>
734
+ </tr>
735
+ <?php } ?>
736
+ <tr>
737
+ <th scope="row"><label for="authentication">HTTP AUTH credentials</label></th>
738
+ <td>
739
+ <input name="siteAuthUser" type="text" id="siteAuthUser" value="<?php echo( $settings->siteAuthUser );?>" class="regular-text" placeholder="User"><br>
740
+ <input name="siteAuthPass" type="text" id="siteAuthPass" value="<?php echo( $settings->siteAuthPass );?>" class="regular-text" placeholder="Password">
741
+ <p class="settings-info"> Only fill in these fields if your site (front-end) is not publicly accessible and visitors need a user/pass to connect to it. If you don't know what is this then just <strong>leave the fields empty</strong>.</p>
742
+ </td>
743
+ </tr>
744
+ <tr>
745
+ <th scope="row"><label for="resize">Process in front-end</label></th>
746
+ <td>
747
+ <input name="frontBootstrap" type="checkbox" id="resize" <?php echo( $frontBootstrap );?>> Automatically optimize images added by users in front end.
748
+ <p class="settings-info">Check this if you have users that add images or PDF documents from custom forms in the front-end. This could increase the load on your server if you have a lot of users simultaneously connected.</p>
749
+ </td>
750
+ </tr>
751
+ </tbody>
752
+ </table>
753
+ <p class="submit">
754
+ <input type="submit" name="saveAdv" id="saveAdv" class="button button-primary" title="Save Changes" value="Save Changes"> &nbsp;
755
+ <input type="submit" name="saveAdv" id="bulkAdvGo" class="button button-primary" title="Save and go to the Bulk Processing page" value="Save and Go to Bulk Process"> &nbsp;
756
+ </p>
757
+ </div>
758
+ <script>
759
+ var rad = document.wp_shortpixel_options.compressionType;
760
+ var prev = null;
761
+ var minWidth = Math.min(1024, <?php echo($minSizes['width']);?>),
762
+ minHeight = Math.min(1024, <?php echo($minSizes['height']);?>);
763
+ for(var i = 0; i < rad.length; i++) {
764
+ rad[i].onclick = function() {
765
+
766
+ if(this !== prev) {
767
+ prev = this;
768
+ }
769
+ alert('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.');
770
+ };
771
+ }
772
+ function enableResize(elm) {
773
+ if(jQuery(elm).is(':checked')) {
774
+ jQuery("#width,#height").removeAttr("disabled");
775
+ } else {
776
+ jQuery("#width,#height").attr("disabled", "disabled");
777
+ }
778
+ }
779
+ enableResize("#resize");
780
+ jQuery("#resize").change(function(){ enableResize(this) });
781
+ jQuery("#width").blur(function(){
782
+ jQuery(this).val(Math.max(minWidth, parseInt(jQuery(this).val())));
783
+ });
784
+ jQuery("#height").blur(function(){
785
+ jQuery(this).val(Math.max(minHeight, parseInt(jQuery(this).val())));
786
+ });
787
+ jQuery("input.remove-folder-button").click(function(){
788
+ var path = jQuery(this).data("value");
789
+ var r = confirm("Are you sure you want to stop optimizing the folder " + path + "?");
790
+ if (r == true) {
791
+ jQuery("#removeFolder").val(path);
792
+ jQuery('#wp_shortpixel_options').submit();
793
+ }
794
+ });
795
+ </script>
796
+ <?php } ?>
797
+ <script>
798
+ function validateKey(){
799
+ jQuery('#valid').val('validate');
800
+ jQuery('#wp_shortpixel_options').submit();
801
+ }
802
+ jQuery("#key").keypress(function(e) {
803
+ if(e.which == 13) {
804
+ jQuery('#valid').val('validate');
805
+ }
806
+ });
807
+ </script>
808
+ <?php
809
+ }
810
+
811
+ function displaySettingsStats($quotaData, $averageCompression, $savedSpace, $savedBandwidth,
812
+ $remainingImages, $totalCallsMade, $fileCount, $backupFolderSize) { ?>
813
+ <a id="facts"></a>
814
+ <h3>Your ShortPixel Stats</h3>
815
+ <table class="form-table">
816
+ <tbody>
817
+ <tr>
818
+ <th scope="row"><label for="averagCompression">Average compression of your files:</label></th>
819
+ <td><?php echo($averageCompression);?>%</td>
820
+ </tr>
821
+ <tr>
822
+ <th scope="row"><label for="savedSpace">Saved disk space by ShortPixel</label></th>
823
+ <td><?php echo($savedSpace);?></td>
824
+ </tr>
825
+ <tr>
826
+ <th scope="row"><label for="savedBandwidth">Bandwith* saved with ShortPixel:</label></th>
827
+ <td><?php echo($savedBandwidth);?></td>
828
+ </tr>
829
+ </tbody>
830
+ </table>
831
+
832
+ <p style="padding-top: 0px; color: #818181;" >* Saved bandwidth is calculated at 10,000 impressions/image</p>
833
+
834
+ <h3>Your ShortPixel Plan</h3>
835
+ <table class="form-table">
836
+ <tbody>
837
+ <tr>
838
+ <th scope="row" bgcolor="#ffffff"><label for="apiQuota">Your ShortPixel plan</label></th>
839
+ <td bgcolor="#ffffff">
840
+ <?php echo($quotaData['APICallsQuota']);?>/month, renews in <?php echo(floor(30 + (strtotime($quotaData['APILastRenewalDate']) - time()) / 86400));?> days, on <?php echo(date('M d, Y', strtotime($quotaData['APILastRenewalDate']. ' + 30 days')));?> ( <a href="https://shortpixel.com/login/<?php echo($this->ctrl->getApiKey());?>" target="_blank">Need More? See the options available</a> )<br/>
841
+ <a href="https://shortpixel.com/login/<?php echo($this->ctrl->getApiKey());?>/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.
842
+ </td>
843
+ </tr>
844
+ <tr>
845
+ <th scope="row"><label for="usedQUota">One time credits:</label></th>
846
+ <td><?php echo( number_format($quotaData['APICallsQuotaOneTimeNumeric']));?></td>
847
+ </tr>
848
+ <tr>
849
+ <th scope="row"><label for="usedQUota">Number of images processed this month:</label></th>
850
+ <td><?php echo($totalCallsMade);?> (<a href="https://api.shortpixel.com/v2/report.php?key=<?php echo($this->ctrl->getApiKey());?>" target="_blank">see report</a>)</td>
851
+ </tr>
852
+ <tr>
853
+ <th scope="row"><label for="remainingImages">Remaining** images in your plan: </label></th>
854
+ <td><?php echo($remainingImages);?> images</td>
855
+ </tr>
856
+ </tbody>
857
+ </table>
858
+
859
+ <p style="padding-top: 0px; color: #818181;" >** Increase your image quota by <a href="https://shortpixel.com/login/<?php echo($this->ctrl->getApiKey());?>" target="_blank">upgrading your ShortPixel plan.</a></p>
860
+
861
+ <table class="form-table">
862
+ <tbody>
863
+ <tr>
864
+ <th scope="row"><label for="totalFiles">Total number of processed files:</label></th>
865
+ <td><?php echo($fileCount);?></td>
866
+ </tr>
867
+ <?php if($this->ctrl->backupImages()) { ?>
868
+ <tr>
869
+ <th scope="row"><label for="sizeBackup">Original images are stored in a backup folder. Your backup folder size is now:</label></th>
870
+ <td>
871
+ <form action="" method="POST">
872
+ <?php echo($backupFolderSize);?>
873
+ <input type="submit" style="margin-left: 15px; vertical-align: middle;" class="button button-secondary" name="emptyBackup" value="Empty backups"/>
874
+ </form>
875
+ </td>
876
+ </tr>
877
+ <?php } ?>
878
+ </tbody>
879
+ </table>
880
+ <div style="display:none">
881
+
882
+ </div>
883
+ <?php
884
+ }
885
+
886
+ public function renderCustomColumn($id, $data){ ?>
887
+ <div id='sp-msg-<?php echo($id);?>' class='column-wp-shortPixel'>
888
+ <?php switch($data['status']) {
889
+ case 'n/a': ?>
890
+ Optimization N/A <?php
891
+ break;
892
+ case 'notFound': ?>
893
+ Image does not exist. <?php
894
+ break;
895
+ case 'invalidKey':
896
+ if(defined("SHORTPIXEL_API_KEY")) { // multisite key - need to be validated on each site but it's not invalid
897
+ ?> Please <a href="options-general.php?page=wp-shortpixel">go to Settings</a> to validate the API Key. <?php
898
+ } else {
899
+ ?> Invalid API Key. <a href="options-general.php?page=wp-shortpixel">Check your Settings</a> <?php
900
+ }
901
+ break;
902
+ case 'quotaExceeded':
903
+ echo($this->getQuotaExceededHTML(isset($data['message']) ? $data['message'] : ''));
904
+ break;
905
+ case 'optimizeNow':
906
+ echo($data['message']);
907
+ if($data['showActions']) { ?>
908
+ <a class='button button-smaller button-primary' href="javascript:manualOptimization('<?php echo($id)?>')">Optimize now</a>
909
+ <?php }
910
+ if(isset($data['thumbsTotal']) && $data['thumbsTotal'] > 0) {
911
+ echo("<br>+" . $data['thumbsTotal'] . " thumbnails");
912
+ }
913
+ break;
914
+ case 'retry': ?>
915
+ <?php echo($data['message'])?> <a class='button button-smaller button-primary' href="javascript:manualOptimization('<?php echo($id)?>')">Retry</a> <?php
916
+ break;
917
+ case 'pdfOptimized':
918
+ case 'imgOptimized':
919
+ $this->renderListCell($id, $data['showActions'],
920
+ !$data['thumbsOpt'] && $data['thumbsTotal'], $data['thumbsTotal'], $data['backup'], $data['type'],
921
+ $this->getSuccessText($data['percent'],$data['bonus'],$data['type'],$data['thumbsOpt'],$data['thumbsTotal']));
922
+ break;
923
+ }
924
+ //die(var_dump($data));
925
+ ?>
926
+ </div>
927
+ <?php
928
+ }
929
+
930
+ public function getSuccessText($percent, $bonus, $type, $thumbsOpt = 0, $thumbsTotal = 0) {
931
+ return ($percent ? 'Reduced by ' . $percent . '% ' : '')
932
+ .(!$bonus ? ' ('.$type.')':'')
933
+ .($bonus && $percent ? '<br>' : '')
934
+ .($bonus ? 'Bonus processing' : '')
935
+ .($bonus ? ' ('.$type.')':'') . '<br>'
936
+ .($thumbsOpt ? "+" . $thumbsOpt . ($thumbsTotal > $thumbsOpt ? " of ".$thumbsTotal : '') . " thumbnails optimized" : '');
937
+ }
938
+
939
+ public function renderListCell($id, $showActions, $optimizeThumbs, $thumbsTotal, $backup, $type, $message) {
940
+ if($showActions) { ?>
941
+ <div class='sp-column-actions'>
942
+ <?php if($optimizeThumbs) { ?>
943
+ <a class='button button-smaller button-primary' href="javascript:optimizeThumbs(<?php echo($id)?>);">
944
+ Optimize <?php echo($thumbsTotal);?> thumbnails
945
+ </a>
946
+ <?php }
947
+ if($backup) {
948
+ if($type) {
949
+ $invType = $type == 'lossy' ? 'lossless' : 'lossy'; ?>
950
+ <a class='button button-smaller' href="javascript:reoptimize('<?php echo($id)?>', '<?php echo($invType)?>');" title="Reoptimize from the backed-up image">
951
+ Re-optimize <?php echo($invType)?>
952
+ </a><?php
953
+ } ?>
954
+ <a class='button button-smaller' href="admin.php?action=shortpixel_restore_backup&attachment_ID=<?php echo($id)?>">
955
+ Restore backup
956
+ </a>
957
+ <?php } ?>
958
+ </div>
959
+ <?php } ?>
960
+ <div class='sp-column-info'>
961
+ <?php echo($message);?>
962
+ </div> <?php
963
+ }
964
+
965
+ public function getQuotaExceededHTML($message = '') {
966
+ return "<div class='sp-column-actions' style='width:110px;'>
967
+ <a class='button button-smaller button-primary' href='https://shortpixel.com/login/". $this->ctrl->getApiKey() . "' target='_blank'>Extend Quota</a>
968
+ <a class='button button-smaller' href='admin.php?action=shortpixel_check_quota'>Check&nbsp;&nbsp;Quota</a></div>
969
+ <div class='sp-column-info'>" . $message . " Quota Exceeded.</div>";
970
+ }
971
+ }
readme.txt CHANGED
@@ -5,24 +5,28 @@ Tags: image optimizer, image optimization, compress pdf, compress jpeg, compress
5
 
6
  Requires at least: 3.2.0
7
  Tested up to: 4.6
8
- Stable tag: 3.3.8
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
12
- Easy-to-use, lightweight plugin that optimizes images & PDFs. Make your website faster, keeping the images’ high quality. WooCommerce compatible.
13
 
14
  == Description ==
15
 
16
  ShortPixel makes your website load faster by resizing/rescaling and then compressing the images. Optimized images mean better user experience, better PageSpeed Insights results, better Google PageRank (SEO) and more visitors. Both lossy and lossless <a rel="friend" href="https://shortpixel.com" target="_blank">image compression</a> available for all common image types (JPG, PNG and GIF), plus PDF files.
17
 
18
- ShortPixel optimized so far over 60M images from 32,000 different websites saving them more than 10 Petabytes of traffic.
 
 
 
 
19
 
20
  **How does it work?**
21
 
22
  * choose your favorite settings like lossy/lossless, keep/remove EXIF, backup, optimize thumbs, etc.
23
  * all your current pics can be easily scaled and optimized with a single click via our **bulk optimization** page.
24
  * new images are automatically resized and compressed in the cloud right after they are uploaded.
25
- * smaller images will start being served from your website once they were optimized
26
 
27
  It's that easy indeed.
28
 
@@ -32,9 +36,10 @@ You can try a **live demo** <a href="https://addendio.com/try-plugin/?slug=short
32
 
33
  **Why use ShortPixel to optimize your images? Here are some of the features:**
34
 
35
- * **24h support** directly from the developers
36
  * compress JPG, PNG, GIF (still and animated) images and PDF documents
37
  * optimize thumbnails as well as featured images
 
38
  * featured images can be rescaled before being optimized. **No need for additional plugins** like Imsanity
39
  * CMYK to RGB conversion
40
  * skip already optimized images
@@ -78,9 +83,10 @@ We will continue to purchase this software for any site we develop with a lot of
78
 
79
  **New features coming soon:**
80
 
81
- * support for NextGen Galley
82
- * support for custom image locations.
83
- * mass restore for backed-up images
 
84
 
85
 
86
  **Get in touch!**
@@ -91,7 +97,7 @@ We will continue to purchase this software for any site we develop with a lot of
91
  * Facebook <a href="https://www.facebook.com/ShortPixel" target="_blank">https://www.facebook.com/ShortPixel</a>
92
  * LinkedIn <a href="https://www.linkedin.com/company/shortpixel" target="_blank">https://www.linkedin.com/company/shortpixel</a>
93
 
94
- **Keywords:** picture, optimization, image editor, pngout, upload speed, shortpixel, compression, jpegmini, webp, lossless, cwebp, media, jpegtran, image, image optimisation, image optimization, 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, pdf, pdf optimisation, pdf optimization, optimize pdf, optimise pdf, shrink pdf, jpg, jpeg, jpg optimisation, jpg optimization, optimize jpg, optimise jpg, shrink jpg, gif, animated gif, optimize gif, optimise gif, optimizer, optimiser, compresion, optimization, cruncher, image cruncher, compress png, compress jpg, compress jpeg, compress pdf, faster loading times, image optimiser, improve pagerank, optimise, optimize animated gif, optimise jpeg, optimize jpeg, optimize png, optimise png, optimise pdf, optimize pdf, tinyjpg, short pixel, shortpixel, woocommerce compatible, wpml compatible, smush, imsanity, scale, wp smush, compress images, pdf compression, optimize images, shrink jpeg, compressor, faster website, google pagerank, imagify, prizm, optimus, zara, improve page speed, PageSpeed Insights, sitespeed, smaller images, tinyjpeg, wordpress compression, wordPress image tool, reduce image size, bandwidth, pics, keep exif, remove exif, speed up site, speed up website, compress thumbnails, optimize thumbnails
95
 
96
 
97
  == Installation ==
@@ -193,23 +199,10 @@ The ShortPixel team is here to help. <a href="https://shortpixel.com/contact">Co
193
 
194
  == Changelog ==
195
 
196
- = 3.3.8 =
197
 
198
- * Handle malformed image metadata.
199
- * Go directly to settings after activating if the API Key is not set up.
200
-
201
- = 3.3.7 =
202
-
203
- * Solve CSS compatibility issue with WPEstate
204
- * PHP 7 compatibility
205
-
206
- = 3.3.6 =
207
-
208
- * When quota exceeded, download the images that are already processed on the server
209
-
210
- = 3.3.5 =
211
-
212
- * fix the size problem of the settings tabs when not yet activated
213
 
214
  = 3.3.4 =
215
 
5
 
6
  Requires at least: 3.2.0
7
  Tested up to: 4.6
8
+ Stable tag: 4.0.0
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
12
+ Easy-to-use, lightweight plugin that optimizes images & PDFs. Make your website faster, keeping the images’ high quality. WooCommerce compatible
13
 
14
  == Description ==
15
 
16
  ShortPixel makes your website load faster by resizing/rescaling and then compressing the images. Optimized images mean better user experience, better PageSpeed Insights results, better Google PageRank (SEO) and more visitors. Both lossy and lossless <a rel="friend" href="https://shortpixel.com" target="_blank">image compression</a> available for all common image types (JPG, PNG and GIF), plus PDF files.
17
 
18
+ **Compatible with**
19
+ * any image gallery (including NextGEN)
20
+ * any slider
21
+ * Woo Commerce plugin
22
+ ShortPixel is also able to optimize any images regardless of where they are located on disk (e.g. theme specific icons, logos, images).
23
 
24
  **How does it work?**
25
 
26
  * choose your favorite settings like lossy/lossless, keep/remove EXIF, backup, optimize thumbs, etc.
27
  * all your current pics can be easily scaled and optimized with a single click via our **bulk optimization** page.
28
  * new images are automatically resized and compressed in the cloud right after they are uploaded.
29
+ * smaller images will start being served from your website once they are optimized
30
 
31
  It's that easy indeed.
32
 
36
 
37
  **Why use ShortPixel to optimize your images? Here are some of the features:**
38
 
39
+ * **24h support** (24/7) directly from the developers
40
  * compress JPG, PNG, GIF (still and animated) images and PDF documents
41
  * optimize thumbnails as well as featured images
42
+ * ability to optimize any image on your site including images in NextGEN Gallery and any other image gallery or slider
43
  * featured images can be rescaled before being optimized. **No need for additional plugins** like Imsanity
44
  * CMYK to RGB conversion
45
  * skip already optimized images
83
 
84
  **New features coming soon:**
85
 
86
+ * new resize options
87
+ * WebP support
88
+ * Retina optimiziation and support
89
+ * mass restore for backed-up images.
90
 
91
 
92
  **Get in touch!**
97
  * Facebook <a href="https://www.facebook.com/ShortPixel" target="_blank">https://www.facebook.com/ShortPixel</a>
98
  * LinkedIn <a href="https://www.linkedin.com/company/shortpixel" target="_blank">https://www.linkedin.com/company/shortpixel</a>
99
 
100
+ **Keywords:** picture, optimization, image editor, pngout, upload speed, shortpixel, compression, nextgen jpegmini, webp, lossless, cwebp, media, jpegtran, image, image optimisation, image optimization, 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, pdf, pdf optimisation, pdf optimization, optimize pdf, optimise pdf, shrink pdf, jpg, jpeg, jpg optimisation, jpg optimization, optimize jpg, optimise jpg, shrink jpg, gif, animated gif, optimize gif, optimise gif, optimizer, optimiser, compresion, optimization, cruncher, image cruncher, compress png, compress jpg, compress jpeg, compress pdf, faster loading times, image optimiser, improve pagerank, optimise, optimize animated gif, optimise jpeg, optimize jpeg, optimize png, optimise png, optimise pdf, optimize pdf, tinyjpg, short pixel, shortpixel, woocommerce compatible, wpml compatible, smush, imsanity, scale, wp smush, compress images, pdf compression, optimize images, shrink jpeg, compressor, faster website, google pagerank, imagify, prizm, optimus, zara, improve page speed, PageSpeed Insights, sitespeed, smaller images, tinyjpeg, wordpress compression, wordPress image tool, reduce image size, bandwidth, pics, keep exif, remove exif, speed up site, speed up website, compress thumbnails, optimize thumbnails
101
 
102
 
103
  == Installation ==
199
 
200
  == Changelog ==
201
 
202
+ = 4.0.0 =
203
 
204
+ * Custom folders optimization
205
+ * NextGen galleries optimization
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
  = 3.3.4 =
208
 
{css → res/css}/short-pixel.css RENAMED
@@ -1,18 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  div.shortpixel-rate-us {
2
  display:inline-block;
3
- margin-left: 20px;
4
  vertical-align: top;
5
  font-weight: bold;
6
- padding-top: 5px;
7
  }
8
  div.shortpixel-rate-us > a {
9
  vertical-align: middle;
10
- padding: 16px 5px 0;
 
 
11
  }
12
- div.shortpixel-rate-us > a > div {
13
  display: inline-block;
14
  vertical-align: top;
15
- margin-top: 14px;
16
  }
17
  div.shortpixel-rate-us > a > img {
18
  padding-top: 7px;
@@ -82,7 +173,7 @@ li.shortpixel-hide {
82
  width:98%;
83
  background-color:white;
84
  padding:10px 10px 0;
85
- margin-top: 2em;
86
  }
87
 
88
  .bulk-stats-container{
@@ -132,6 +223,14 @@ li.shortpixel-hide {
132
  float:left;
133
  margin-bottom:20px;
134
  }
 
 
 
 
 
 
 
 
135
  .wp-core-ui .bulk-play a.button{
136
  height:60px;
137
  margin-top: 27px;
@@ -172,7 +271,7 @@ li.shortpixel-hide {
172
  .bulk-progress {
173
  padding: 20px 32px 17px;
174
  background-color: #ffffff;
175
- border: 1px dotted #c4c2c2;
176
  }
177
  .bulk-progress.bulk-stats > div{
178
  display:inline-block;
@@ -230,19 +329,39 @@ li.shortpixel-hide {
230
  }
231
  .short-pixel-block-title {
232
  font-size: 22px;
 
233
  margin-bottom: 15px;
 
 
 
 
 
 
 
 
 
234
  }
235
  .bulk-slider-container {
236
  margin-top: 20px;
237
- display: none;
238
- min-height: 500px;
239
  }
240
  .bulk-slider-container h2{
241
  margin-bottom: 15px;
242
  }
 
 
 
 
 
 
 
243
  .bulk-slider .bulk-slide {
244
- position: absolute;
245
- width: 100%;
 
 
 
246
  }
247
  .bulk-slider .img-original,
248
  .bulk-slider .img-optimized{
@@ -264,6 +383,12 @@ li.shortpixel-hide {
264
  vertical-align: top;
265
  font-size: 48px;
266
  max-width: 150px;
 
 
 
 
 
 
267
  }
268
  p.settings-info {
269
  padding-top: 0px;
@@ -276,7 +401,7 @@ article.sp-tabs {
276
  position: relative;
277
  display: block;
278
  width: 100%;
279
- height: 900px;
280
  margin: 2em auto;
281
  }
282
  article.sp-tabs section {
@@ -284,7 +409,7 @@ article.sp-tabs section {
284
  display: block;
285
  top: 1.8em;
286
  left: 0;
287
- height: 900px;
288
  width:94%;
289
  padding: 10px 20px;
290
  background-color: #ddd;
@@ -315,6 +440,9 @@ article.sp-tabs section:nth-child(2) h2 {
315
  article.sp-tabs section:nth-child(3) h2 {
316
  left: 374px;
317
  }
 
 
 
318
  article.sp-tabs section h2 a {
319
  display: block;
320
  width: 100%;
@@ -355,3 +483,50 @@ section#tab-resources .text-center {
355
  section#tab-resources p {
356
  font-size: 16px;
357
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ div.fb-like {
2
+ transform: scale(1.3);
3
+ -ms-transform: scale(1.3);
4
+ -webkit-transform: scale(1.3);
5
+ -o-transform: scale(1.3);
6
+ -moz-transform: scale(1.3);
7
+ transform-origin: bottom left;
8
+ -ms-transform-origin: bottom left;
9
+ -webkit-transform-origin: bottom left;
10
+ -moz-transform-origin: bottom left;
11
+ -webkit-transform-origin: bottom left;
12
+ }
13
+ div.short-pixel-bulk-page input.dial {
14
+ font-size: 16px !important;
15
+ }
16
+ div.short-pixel-bulk-page h1 {
17
+ margin-bottom:20px;
18
+ }
19
+ div.bulk-progress h2 {
20
+ margin-top:0;
21
+ margin-bottom: 10px;
22
+ }
23
+ div.bulk-progress .bulk-progress-indicator {
24
+ display: inline-block;
25
+ text-align: center;
26
+ padding: 0 10px;
27
+ margin-left: 10px;
28
+ float: left;
29
+ height: 90px;
30
+ overflow: hidden;
31
+ border: 1px solid #1CAECB;
32
+ }
33
+ div.wrap.short-pixel-bulk-page .bulk-notice-container {
34
+ margin-top: 15px;
35
+ position: absolute;
36
+ }
37
+ div.wrap.short-pixel-bulk-page .bulk-notice-container .bulk-notice-msg {
38
+ text-align: center;
39
+ margin: 10px 0 0 32px;
40
+ overflow: hidden;
41
+ border: 1px solid #1CAECB;
42
+ background-color: #9ddbe0;
43
+ border-radius: 5px;
44
+ padding: 7px 10px 10px;
45
+ display: none;
46
+ max-width: 600px;
47
+ }
48
+ div.wrap.short-pixel-bulk-page .bulk-notice-container .bulk-notice-msg.bulk-error {
49
+ border: 1px solid #c32525;
50
+ background-color: #ff969d;
51
+ }
52
+ div.wrap.short-pixel-bulk-page .bulk-notice-msg img {
53
+ float:left;
54
+ margin-top:3px;
55
+ margin-right: 5px;
56
+ }
57
+ div.sp-bulk-summary {
58
+ float:right;
59
+ margin:8px 5px 3px 20px;
60
+ }
61
+ input.dial {
62
+ box-shadow: none;
63
+ }
64
+ .shortpixel-table .column-filename {
65
+ max-width: 32em;
66
+ width: 40%;
67
+ }
68
+ .shortpixel-table .column-folder {
69
+ max-width: 20em;
70
+ width: 20%;
71
+ }
72
+ .shortpixel-table .column-media_type {
73
+ max-width: 8em;
74
+ width: 10%;
75
+ }
76
+ .shortpixel-table .column-status {
77
+ max-width: 16em;
78
+ width: 15%;
79
+ }
80
+ .shortpixel-table .column-options {
81
+ max-width: 16em;
82
+ width: 15%;
83
+ }
84
+
85
+ .form-table table.shortpixel-folders-list tr {
86
+ background-color: #eee;
87
+ }
88
+ .form-table table.shortpixel-folders-list td {
89
+ padding: 5px 10px;
90
+ }
91
  div.shortpixel-rate-us {
92
  display:inline-block;
93
+ margin-left: 10px;
94
  vertical-align: top;
95
  font-weight: bold;
 
96
  }
97
  div.shortpixel-rate-us > a {
98
  vertical-align: middle;
99
+ padding: 1px 5px 0;
100
+ text-align:center;
101
+ display: inline-block;
102
  }
103
+ div.shortpixel-rate-us > a > span {
104
  display: inline-block;
105
  vertical-align: top;
106
+ margin-top: 5px;
107
  }
108
  div.shortpixel-rate-us > a > img {
109
  padding-top: 7px;
173
  width:98%;
174
  background-color:white;
175
  padding:10px 10px 0;
176
+ /*margin-top: 2em;*/
177
  }
178
 
179
  .bulk-stats-container{
223
  float:left;
224
  margin-bottom:20px;
225
  }
226
+ .wp-core-ui .bulk-play.bulk-nothing-optimize {
227
+ font-weight: bold;
228
+ color: #0080b2;
229
+ border: 1px solid;
230
+ border-radius: 5px;
231
+ margin-top: 60px;
232
+ padding: 5px 12px;
233
+ }
234
  .wp-core-ui .bulk-play a.button{
235
  height:60px;
236
  margin-top: 27px;
271
  .bulk-progress {
272
  padding: 20px 32px 17px;
273
  background-color: #ffffff;
274
+ /*border: 1px dotted #c4c2c2;*/
275
  }
276
  .bulk-progress.bulk-stats > div{
277
  display:inline-block;
329
  }
330
  .short-pixel-block-title {
331
  font-size: 22px;
332
+ font-weight: bold;
333
  margin-bottom: 15px;
334
+ text-align: center;
335
+ margin-bottom: 30px;
336
+ }
337
+ .sp-floating-block.bulk-slider-container {
338
+ display: none;
339
+ }
340
+ .sp-floating-block.notice.bulk-notices-parent {
341
+ padding: 0;
342
+ margin: 0;
343
  }
344
  .bulk-slider-container {
345
  margin-top: 20px;
346
+ min-height: 300px;
347
+ overflow: hidden;
348
  }
349
  .bulk-slider-container h2{
350
  margin-bottom: 15px;
351
  }
352
+ .bulk-slider-container span.filename {
353
+ font-weight: normal;
354
+ }
355
+ .bulk-slider {
356
+ display: table;
357
+ margin: 0 auto;
358
+ }
359
  .bulk-slider .bulk-slide {
360
+ /*position: absolute;*/
361
+ margin: 0 auto;
362
+ padding-left: 120px;
363
+ display: inline-block;
364
+ font-weight: bold;
365
  }
366
  .bulk-slider .img-original,
367
  .bulk-slider .img-optimized{
383
  vertical-align: top;
384
  font-size: 48px;
385
  max-width: 150px;
386
+ padding: 10px 0 0 20px;
387
+ }
388
+ .bulk-slide-images {
389
+ display: inline-block;
390
+ border: 1px solid #1CAECB;
391
+ padding: 15px 0 0 20px;
392
  }
393
  p.settings-info {
394
  padding-top: 0px;
401
  position: relative;
402
  display: block;
403
  width: 100%;
404
+ height: 1100px;
405
  margin: 2em auto;
406
  }
407
  article.sp-tabs section {
409
  display: block;
410
  top: 1.8em;
411
  left: 0;
412
+ height: 1100px;
413
  width:94%;
414
  padding: 10px 20px;
415
  background-color: #ddd;
440
  article.sp-tabs section:nth-child(3) h2 {
441
  left: 374px;
442
  }
443
+ article.sp-tabs section:nth-child(4) h2 {
444
+ left: 556px;
445
+ }
446
  article.sp-tabs section h2 a {
447
  display: block;
448
  width: 100%;
483
  section#tab-resources p {
484
  font-size: 16px;
485
  }
486
+
487
+ /* ShortPixel columns layout */
488
+ .wrap.short-pixel-bulk-page {
489
+ margin-right: 0;
490
+ }
491
+ .sp-container {
492
+ overflow: hidden;
493
+ display:block;
494
+ width:100%;
495
+ }
496
+ .sp-floating-block {
497
+ overflow: hidden;
498
+ display:inline-block;
499
+ float: left;
500
+ margin-right: 1.1% !important;
501
+ }
502
+ .sp-full-width {
503
+ width: 98.8%;
504
+ box-sizing: border-box;
505
+ }
506
+ .sp-double-width {
507
+ width: 65.52%;
508
+ box-sizing: border-box;
509
+ }
510
+ .sp-single-width {
511
+ width: 32.23%;
512
+ box-sizing: border-box;
513
+ }
514
+ @media(max-width: 1759px) {
515
+ .sp-floating-block {
516
+ margin-right: 1.3% !important;
517
+ }
518
+ .sp-double-width, .sp-full-width {
519
+ width: 98.65%;
520
+ }
521
+ .sp-single-width {
522
+ width: 48.7%;
523
+ }
524
+ }
525
+ @media(max-width: 1249px) {
526
+ .sp-floating-block {
527
+ margin-right: 2% !important;
528
+ }
529
+ .sp-double-width, .sp-full-width, .sp-single-width {
530
+ width: 97%;
531
+ }
532
+ }
res/css/sp-file-tree.css ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ div.sp-folder-picker-shade {
2
+ display: none; /* Hidden by default */
3
+ position: fixed; /* Stay in place */
4
+ z-index: 10; /* Sit on top */
5
+ left: 0;
6
+ top: 0;
7
+ width: 100%; /* Full width */
8
+ height: 100%; /* Full height */
9
+ overflow: auto; /* Enable scroll if needed */
10
+ background-color: rgb(0,0,0); /* Fallback color */
11
+ background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
12
+ }
13
+ div.sp-folder-picker-popup {
14
+ background-color: #fefefe;
15
+ margin: 15% auto; /* 15% from the top and centered */
16
+ padding: 20px;
17
+ border: 1px solid #888;
18
+ width: 30%; /* Could be more or less, depending on screen size */
19
+ min-width: 300px; /* Could be more or less, depending on screen size */
20
+ }
21
+ div.sp-folder-picker-title {
22
+ font-size: 1.4em;
23
+ }
24
+ div.sp-folder-picker {
25
+ margin: 20px 0; /* 15% from the top and centered */
26
+ border: 1px solid #888;
27
+ max-height: 400px;
28
+ overflow: auto;}
29
+
30
+ UL.jqueryFileTree LI.directory.selected {
31
+ background-color: #209fd2;
32
+ }
33
+
34
+ UL.jqueryFileTree {
35
+ font-family: Verdana, sans-serif;
36
+ font-size: 11px;
37
+ line-height: 18px;
38
+ padding: 0;
39
+ margin: 0;
40
+ display: none;
41
+ }
42
+ UL.jqueryFileTree LI {
43
+ list-style: none;
44
+ padding: 0;
45
+ padding-left: 20px;
46
+ margin: 0;
47
+ white-space: nowrap;
48
+ }
49
+ UL.jqueryFileTree LI.directory {
50
+ background: url(../img/file-tree/directory.png) left top no-repeat;
51
+ }
52
+ UL.jqueryFileTree LI.directory-locked {
53
+ background: url(../img/file-tree/directory-lock.png) left top no-repeat;
54
+ }
55
+ UL.jqueryFileTree LI.expanded {
56
+ background: url(../img/file-tree/folder_open.png) left top no-repeat;
57
+ }
58
+ UL.jqueryFileTree LI.file {
59
+ background: url(../img/file-tree/file.png) left top no-repeat;
60
+ }
61
+ UL.jqueryFileTree LI.file-locked {
62
+ background: url(../img/file-tree/file-lock.png) left top no-repeat !important;
63
+ }
64
+ UL.jqueryFileTree LI.wait {
65
+ background: url(../img/file-tree/spinner.gif) left top no-repeat;
66
+ }
67
+ UL.jqueryFileTree LI.selected > a {
68
+ font-weight: bold;
69
+ }
70
+ UL.jqueryFileTree LI.ext_3gp {
71
+ background: url(../img/file-tree/film.png) left top no-repeat;
72
+ }
73
+ UL.jqueryFileTree LI.ext_afp {
74
+ background: url(../img/file-tree/code.png) left top no-repeat;
75
+ }
76
+ UL.jqueryFileTree LI.ext_afpa {
77
+ background: url(../img/file-tree/code.png) left top no-repeat;
78
+ }
79
+ UL.jqueryFileTree LI.ext_asp {
80
+ background: url(../img/file-tree/code.png) left top no-repeat;
81
+ }
82
+ UL.jqueryFileTree LI.ext_aspx {
83
+ background: url(../img/file-tree/code.png) left top no-repeat;
84
+ }
85
+ UL.jqueryFileTree LI.ext_avi {
86
+ background: url(../img/file-tree/film.png) left top no-repeat;
87
+ }
88
+ UL.jqueryFileTree LI.ext_bat {
89
+ background: url(../img/file-tree/application.png) left top no-repeat;
90
+ }
91
+ UL.jqueryFileTree LI.ext_bmp {
92
+ background: url(../img/file-tree/picture.png) left top no-repeat;
93
+ }
94
+ UL.jqueryFileTree LI.ext_c {
95
+ background: url(../img/file-tree/code.png) left top no-repeat;
96
+ }
97
+ UL.jqueryFileTree LI.ext_cfm {
98
+ background: url(../img/file-tree/code.png) left top no-repeat;
99
+ }
100
+ UL.jqueryFileTree LI.ext_cgi {
101
+ background: url(../img/file-tree/code.png) left top no-repeat;
102
+ }
103
+ UL.jqueryFileTree LI.ext_com {
104
+ background: url(../img/file-tree/application.png) left top no-repeat;
105
+ }
106
+ UL.jqueryFileTree LI.ext_cpp {
107
+ background: url(../img/file-tree/code.png) left top no-repeat;
108
+ }
109
+ UL.jqueryFileTree LI.ext_css {
110
+ background: url(../img/file-tree/css.png) left top no-repeat;
111
+ }
112
+ UL.jqueryFileTree LI.ext_doc {
113
+ background: url(../img/file-tree/doc.png) left top no-repeat;
114
+ }
115
+ UL.jqueryFileTree LI.ext_exe {
116
+ background: url(../img/file-tree/application.png) left top no-repeat;
117
+ }
118
+ UL.jqueryFileTree LI.ext_gif {
119
+ background: url(../img/file-tree/picture.png) left top no-repeat;
120
+ }
121
+ UL.jqueryFileTree LI.ext_fla {
122
+ background: url(../img/file-tree/flash.png) left top no-repeat;
123
+ }
124
+ UL.jqueryFileTree LI.ext_h {
125
+ background: url(../img/file-tree/code.png) left top no-repeat;
126
+ }
127
+ UL.jqueryFileTree LI.ext_htm {
128
+ background: url(../img/file-tree/html.png) left top no-repeat;
129
+ }
130
+ UL.jqueryFileTree LI.ext_html {
131
+ background: url(../img/file-tree/html.png) left top no-repeat;
132
+ }
133
+ UL.jqueryFileTree LI.ext_jar {
134
+ background: url(../img/file-tree/java.png) left top no-repeat;
135
+ }
136
+ UL.jqueryFileTree LI.ext_jpg {
137
+ background: url(../img/file-tree/picture.png) left top no-repeat;
138
+ }
139
+ UL.jqueryFileTree LI.ext_jpeg {
140
+ background: url(../img/file-tree/picture.png) left top no-repeat;
141
+ }
142
+ UL.jqueryFileTree LI.ext_js {
143
+ background: url(../img/file-tree/script.png) left top no-repeat;
144
+ }
145
+ UL.jqueryFileTree LI.ext_lasso {
146
+ background: url(../img/file-tree/code.png) left top no-repeat;
147
+ }
148
+ UL.jqueryFileTree LI.ext_log {
149
+ background: url(../img/file-tree/txt.png) left top no-repeat;
150
+ }
151
+ UL.jqueryFileTree LI.ext_m4p {
152
+ background: url(../img/file-tree/music.png) left top no-repeat;
153
+ }
154
+ UL.jqueryFileTree LI.ext_mov {
155
+ background: url(../img/file-tree/film.png) left top no-repeat;
156
+ }
157
+ UL.jqueryFileTree LI.ext_mp3 {
158
+ background: url(../img/file-tree/music.png) left top no-repeat;
159
+ }
160
+ UL.jqueryFileTree LI.ext_mp4 {
161
+ background: url(../img/file-tree/film.png) left top no-repeat;
162
+ }
163
+ UL.jqueryFileTree LI.ext_mpg {
164
+ background: url(../img/file-tree/film.png) left top no-repeat;
165
+ }
166
+ UL.jqueryFileTree LI.ext_mpeg {
167
+ background: url(../img/file-tree/film.png) left top no-repeat;
168
+ }
169
+ UL.jqueryFileTree LI.ext_ogg {
170
+ background: url(../img/file-tree/music.png) left top no-repeat;
171
+ }
172
+ UL.jqueryFileTree LI.ext_ogv {
173
+ background: url(../img/file-tree/film.png) left top no-repeat;
174
+ }
175
+ UL.jqueryFileTree LI.ext_pcx {
176
+ background: url(../img/file-tree/picture.png) left top no-repeat;
177
+ }
178
+ UL.jqueryFileTree LI.ext_pdf {
179
+ background: url(../img/file-tree/pdf.png) left top no-repeat;
180
+ }
181
+ UL.jqueryFileTree LI.ext_php {
182
+ background: url(../img/file-tree/php.png) left top no-repeat;
183
+ }
184
+ UL.jqueryFileTree LI.ext_png {
185
+ background: url(../img/file-tree/picture.png) left top no-repeat;
186
+ }
187
+ UL.jqueryFileTree LI.ext_ppt {
188
+ background: url(../img/file-tree/ppt.png) left top no-repeat;
189
+ }
190
+ UL.jqueryFileTree LI.ext_psd {
191
+ background: url(../img/file-tree/psd.png) left top no-repeat;
192
+ }
193
+ UL.jqueryFileTree LI.ext_pl {
194
+ background: url(../img/file-tree/script.png) left top no-repeat;
195
+ }
196
+ UL.jqueryFileTree LI.ext_py {
197
+ background: url(../img/file-tree/script.png) left top no-repeat;
198
+ }
199
+ UL.jqueryFileTree LI.ext_rb {
200
+ background: url(../img/file-tree/ruby.png) left top no-repeat;
201
+ }
202
+ UL.jqueryFileTree LI.ext_rbx {
203
+ background: url(../img/file-tree/ruby.png) left top no-repeat;
204
+ }
205
+ UL.jqueryFileTree LI.ext_rhtml {
206
+ background: url(../img/file-tree/ruby.png) left top no-repeat;
207
+ }
208
+ UL.jqueryFileTree LI.ext_rpm {
209
+ background: url(../img/file-tree/linux.png) left top no-repeat;
210
+ }
211
+ UL.jqueryFileTree LI.ext_ruby {
212
+ background: url(../img/file-tree/ruby.png) left top no-repeat;
213
+ }
214
+ UL.jqueryFileTree LI.ext_sql {
215
+ background: url(../img/file-tree/db.png) left top no-repeat;
216
+ }
217
+ UL.jqueryFileTree LI.ext_swf {
218
+ background: url(../img/file-tree/flash.png) left top no-repeat;
219
+ }
220
+ UL.jqueryFileTree LI.ext_tif {
221
+ background: url(../img/file-tree/picture.png) left top no-repeat;
222
+ }
223
+ UL.jqueryFileTree LI.ext_tiff {
224
+ background: url(../img/file-tree/picture.png) left top no-repeat;
225
+ }
226
+ UL.jqueryFileTree LI.ext_txt {
227
+ background: url(../img/file-tree/txt.png) left top no-repeat;
228
+ }
229
+ UL.jqueryFileTree LI.ext_vb {
230
+ background: url(../img/file-tree/code.png) left top no-repeat;
231
+ }
232
+ UL.jqueryFileTree LI.ext_wav {
233
+ background: url(../img/file-tree/music.png) left top no-repeat;
234
+ }
235
+ UL.jqueryFileTree LI.ext_webm {
236
+ background: url(../img/file-tree/film.png) left top no-repeat;
237
+ }
238
+ UL.jqueryFileTree LI.ext_wmv {
239
+ background: url(../img/file-tree/film.png) left top no-repeat;
240
+ }
241
+ UL.jqueryFileTree LI.ext_xls {
242
+ background: url(../img/file-tree/xls.png) left top no-repeat;
243
+ }
244
+ UL.jqueryFileTree LI.ext_xml {
245
+ background: url(../img/file-tree/code.png) left top no-repeat;
246
+ }
247
+ UL.jqueryFileTree LI.ext_zip {
248
+ background: url(../img/file-tree/zip.png) left top no-repeat;
249
+ }
250
+ UL.jqueryFileTree A {
251
+ color: #333;
252
+ text-decoration: none;
253
+ display: inline-block;
254
+ padding: 0 2px;
255
+ cursor: pointer;
256
+ }
257
+ UL.jqueryFileTree A:hover {
258
+ background: #BDF;
259
+ }
{img → res/img}/arrow.png RENAMED
File without changes
res/img/exclamation-big.png ADDED
Binary file
res/img/file-tree/application.png ADDED
Binary file
res/img/file-tree/code.png ADDED
Binary file
res/img/file-tree/css.png ADDED
Binary file
res/img/file-tree/db.png ADDED
Binary file
res/img/file-tree/directory-lock.png ADDED
Binary file
res/img/file-tree/directory.png ADDED
Binary file
res/img/file-tree/doc.png ADDED
Binary file
res/img/file-tree/file-lock.png ADDED
Binary file
res/img/file-tree/file.png ADDED
Binary file
res/img/file-tree/film.png ADDED
Binary file
res/img/file-tree/flash.png ADDED
Binary file
res/img/file-tree/folder_open.png ADDED
Binary file
res/img/file-tree/html.png ADDED
Binary file
res/img/file-tree/java.png ADDED
Binary file
res/img/file-tree/linux.png ADDED
Binary file
res/img/file-tree/music.png ADDED
Binary file
res/img/file-tree/pdf.png ADDED
Binary file
res/img/file-tree/php.png ADDED
Binary file
res/img/file-tree/picture.png ADDED
Binary file
res/img/file-tree/ppt.png ADDED
Binary file
res/img/file-tree/psd.png ADDED
Binary file
res/img/file-tree/ruby.png ADDED
Binary file
res/img/file-tree/script.png ADDED
Binary file
res/img/file-tree/spinner.gif ADDED
Binary file
res/img/file-tree/txt.png ADDED
Binary file
res/img/file-tree/xls.png ADDED
Binary file
res/img/file-tree/zip.png ADDED
Binary file
res/img/info-icon.png ADDED
Binary file
{img → res/img}/loading-dark-big.gif RENAMED
File without changes
{img → res/img}/loading-dark.gif RENAMED
File without changes
{img → res/img}/loading.gif RENAMED
File without changes
res/img/logo-pdf.png ADDED
Binary file
res/img/robo-slider.png ADDED
Binary file
{img → res/img}/shortpixel-alert.png RENAMED
File without changes
{img → res/img}/shortpixel.png RENAMED
File without changes
{img → res/img}/slider.png RENAMED
File without changes
{img → res/img}/stars.png RENAMED
File without changes
res/js/jquery.knob.js ADDED
@@ -0,0 +1,805 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!jQuery Knob*/
2
+ /**
3
+ * Downward compatible, touchable dial
4
+ *
5
+ * Version: 1.2.12
6
+ * Requires: jQuery v1.7+
7
+ *
8
+ * Copyright (c) 2012 Anthony Terrien
9
+ * Under MIT License (http://www.opensource.org/licenses/mit-license.php)
10
+ *
11
+ * Thanks to vor, eskimoblood, spiffistan, FabrizioC
12
+ */
13
+ (function (factory) {
14
+ if (typeof exports === 'object') {
15
+ // CommonJS
16
+ module.exports = factory(require('jquery'));
17
+ } else if (typeof define === 'function' && define.amd) {
18
+ // AMD. Register as an anonymous module.
19
+ define(['jquery'], factory);
20
+ } else {
21
+ // Browser globals
22
+ factory(jQuery);
23
+ }
24
+ }(function ($) {
25
+
26
+ /**
27
+ * Kontrol library
28
+ */
29
+ "use strict";
30
+
31
+ /**
32
+ * Definition of globals and core
33
+ */
34
+ var k = {}, // kontrol
35
+ max = Math.max,
36
+ min = Math.min;
37
+
38
+ k.c = {};
39
+ k.c.d = $(document);
40
+ k.c.t = function (e) {
41
+ return e.originalEvent.touches.length - 1;
42
+ };
43
+
44
+ /**
45
+ * Kontrol Object
46
+ *
47
+ * Definition of an abstract UI control
48
+ *
49
+ * Each concrete component must call this one.
50
+ * <code>
51
+ * k.o.call(this);
52
+ * </code>
53
+ */
54
+ k.o = function () {
55
+ var s = this;
56
+
57
+ this.o = null; // array of options
58
+ this.$ = null; // jQuery wrapped element
59
+ this.i = null; // mixed HTMLInputElement or array of HTMLInputElement
60
+ this.g = null; // deprecated 2D graphics context for 'pre-rendering'
61
+ this.v = null; // value ; mixed array or integer
62
+ this.cv = null; // change value ; not commited value
63
+ this.x = 0; // canvas x position
64
+ this.y = 0; // canvas y position
65
+ this.w = 0; // canvas width
66
+ this.h = 0; // canvas height
67
+ this.$c = null; // jQuery canvas element
68
+ this.c = null; // rendered canvas context
69
+ this.t = 0; // touches index
70
+ this.isInit = false;
71
+ this.fgColor = null; // main color
72
+ this.pColor = null; // previous color
73
+ this.dH = null; // draw hook
74
+ this.cH = null; // change hook
75
+ this.eH = null; // cancel hook
76
+ this.rH = null; // release hook
77
+ this.scale = 1; // scale factor
78
+ this.relative = false;
79
+ this.relativeWidth = false;
80
+ this.relativeHeight = false;
81
+ this.$div = null; // component div
82
+
83
+ this.run = function () {
84
+ var cf = function (e, conf) {
85
+ var k;
86
+ for (k in conf) {
87
+ s.o[k] = conf[k];
88
+ }
89
+ s._carve().init();
90
+ s._configure()
91
+ ._draw();
92
+ };
93
+
94
+ if (this.$.data('kontroled')) return;
95
+ this.$.data('kontroled', true);
96
+
97
+ this.extend();
98
+ this.o = $.extend({
99
+ // Config
100
+ min: this.$.data('min') !== undefined ? this.$.data('min') : 0,
101
+ max: this.$.data('max') !== undefined ? this.$.data('max') : 100,
102
+ stopper: true,
103
+ readOnly: this.$.data('readonly') || (this.$.attr('readonly') === 'readonly'),
104
+
105
+ // UI
106
+ cursor: this.$.data('cursor') === true && 30
107
+ || this.$.data('cursor') || 0,
108
+ thickness: this.$.data('thickness')
109
+ && Math.max(Math.min(this.$.data('thickness'), 1), 0.01)
110
+ || 0.35,
111
+ lineCap: this.$.data('linecap') || 'butt',
112
+ width: this.$.data('width') || 200,
113
+ height: this.$.data('height') || 200,
114
+ displayInput: this.$.data('displayinput') == null || this.$.data('displayinput'),
115
+ displayPrevious: this.$.data('displayprevious'),
116
+ fgColor: this.$.data('fgcolor') || '#87CEEB',
117
+ inputColor: this.$.data('inputcolor'),
118
+ font: this.$.data('font') || 'Arial',
119
+ fontWeight: this.$.data('font-weight') || 'bold',
120
+ inline: false,
121
+ step: this.$.data('step') || 1,
122
+ rotation: this.$.data('rotation'),
123
+
124
+ // Hooks
125
+ draw: null, // function () {}
126
+ change: null, // function (value) {}
127
+ cancel: null, // function () {}
128
+ release: null, // function (value) {}
129
+
130
+ // Output formatting, allows to add unit: %, ms ...
131
+ format: function(v) {
132
+ return v;
133
+ },
134
+ parse: function (v) {
135
+ return parseFloat(v);
136
+ }
137
+ }, this.o
138
+ );
139
+
140
+ // finalize options
141
+ this.o.flip = this.o.rotation === 'anticlockwise' || this.o.rotation === 'acw';
142
+ if (!this.o.inputColor) {
143
+ this.o.inputColor = this.o.fgColor;
144
+ }
145
+
146
+ // routing value
147
+ if (this.$.is('fieldset')) {
148
+
149
+ // fieldset = array of integer
150
+ this.v = {};
151
+ this.i = this.$.find('input');
152
+ this.i.each(function(k) {
153
+ var $this = $(this);
154
+ s.i[k] = $this;
155
+ s.v[k] = s.o.parse($this.val());
156
+
157
+ $this.bind(
158
+ 'change blur',
159
+ function () {
160
+ var val = {};
161
+ val[k] = $this.val();
162
+ s.val(s._validate(val));
163
+ }
164
+ );
165
+ });
166
+ this.$.find('legend').remove();
167
+ } else {
168
+
169
+ // input = integer
170
+ this.i = this.$;
171
+ this.v = this.o.parse(this.$.val());
172
+ this.v === '' && (this.v = this.o.min);
173
+ this.$.bind(
174
+ 'change blur',
175
+ function () {
176
+ s.val(s._validate(s.o.parse(s.$.val())));
177
+ }
178
+ );
179
+
180
+ }
181
+
182
+ !this.o.displayInput && this.$.hide();
183
+
184
+ // adds needed DOM elements (canvas, div)
185
+ this.$c = $(document.createElement('canvas')).attr({
186
+ width: this.o.width,
187
+ height: this.o.height
188
+ });
189
+
190
+ // wraps all elements in a div
191
+ // add to DOM before Canvas init is triggered
192
+ this.$div = $('<div style="'
193
+ + (this.o.inline ? 'display:inline;' : '')
194
+ + 'width:' + this.o.width + 'px;height:' + this.o.height + 'px;'
195
+ + '"></div>');
196
+
197
+ this.$.wrap(this.$div).before(this.$c);
198
+ this.$div = this.$.parent();
199
+
200
+ if (typeof G_vmlCanvasManager !== 'undefined') {
201
+ G_vmlCanvasManager.initElement(this.$c[0]);
202
+ }
203
+
204
+ this.c = this.$c[0].getContext ? this.$c[0].getContext('2d') : null;
205
+
206
+ if (!this.c) {
207
+ throw {
208
+ name: "CanvasNotSupportedException",
209
+ message: "Canvas not supported. Please use excanvas on IE8.0.",
210
+ toString: function(){return this.name + ": " + this.message}
211
+ }
212
+ }
213
+
214
+ // hdpi support
215
+ this.scale = (window.devicePixelRatio || 1) / (
216
+ this.c.webkitBackingStorePixelRatio ||
217
+ this.c.mozBackingStorePixelRatio ||
218
+ this.c.msBackingStorePixelRatio ||
219
+ this.c.oBackingStorePixelRatio ||
220
+ this.c.backingStorePixelRatio || 1
221
+ );
222
+
223
+ // detects relative width / height
224
+ this.relativeWidth = this.o.width % 1 !== 0
225
+ && this.o.width.indexOf('%');
226
+ this.relativeHeight = this.o.height % 1 !== 0
227
+ && this.o.height.indexOf('%');
228
+ this.relative = this.relativeWidth || this.relativeHeight;
229
+
230
+ // computes size and carves the component
231
+ this._carve();
232
+
233
+ // prepares props for transaction
234
+ if (this.v instanceof Object) {
235
+ this.cv = {};
236
+ this.copy(this.v, this.cv);
237
+ } else {
238
+ this.cv = this.v;
239
+ }
240
+
241
+ // binds configure event
242
+ this.$
243
+ .bind("configure", cf)
244
+ .parent()
245
+ .bind("configure", cf);
246
+
247
+ // finalize init
248
+ this._listen()
249
+ ._configure()
250
+ ._xy()
251
+ .init();
252
+
253
+ this.isInit = true;
254
+
255
+ this.$.val(this.o.format(this.v));
256
+ this._draw();
257
+
258
+ return this;
259
+ };
260
+
261
+ this._carve = function() {
262
+ if (this.relative) {
263
+ var w = this.relativeWidth ?
264
+ this.$div.parent().width() *
265
+ parseInt(this.o.width) / 100
266
+ : this.$div.parent().width(),
267
+ h = this.relativeHeight ?
268
+ this.$div.parent().height() *
269
+ parseInt(this.o.height) / 100
270
+ : this.$div.parent().height();
271
+
272
+ // apply relative
273
+ this.w = this.h = Math.min(w, h);
274
+ } else {
275
+ this.w = this.o.width;
276
+ this.h = this.o.height;
277
+ }
278
+
279
+ // finalize div
280
+ this.$div.css({
281
+ 'width': this.w + 'px',
282
+ 'height': this.h + 'px'
283
+ });
284
+
285
+ // finalize canvas with computed width
286
+ this.$c.attr({
287
+ width: this.w,
288
+ height: this.h
289
+ });
290
+
291
+ // scaling
292
+ if (this.scale !== 1) {
293
+ this.$c[0].width = this.$c[0].width * this.scale;
294
+ this.$c[0].height = this.$c[0].height * this.scale;
295
+ this.$c.width(this.w);
296
+ this.$c.height(this.h);
297
+ }
298
+
299
+ return this;
300
+ };
301
+
302
+ this._draw = function () {
303
+
304
+ // canvas pre-rendering
305
+ var d = true;
306
+
307
+ s.g = s.c;
308
+
309
+ s.clear();
310
+
311
+ s.dH && (d = s.dH());
312
+
313
+ d !== false && s.draw();
314
+ };
315
+
316
+ this._touch = function (e) {
317
+ var touchMove = function (e) {
318
+ var v = s.xy2val(
319
+ e.originalEvent.touches[s.t].pageX,
320
+ e.originalEvent.touches[s.t].pageY
321
+ );
322
+
323
+ if (v == s.cv) return;
324
+
325
+ if (s.cH && s.cH(v) === false) return;
326
+
327
+ s.change(s._validate(v));
328
+ s._draw();
329
+ };
330
+
331
+ // get touches index
332
+ this.t = k.c.t(e);
333
+
334
+ // First touch
335
+ touchMove(e);
336
+
337
+ // Touch events listeners
338
+ k.c.d
339
+ .bind("touchmove.k", touchMove)
340
+ .bind(
341
+ "touchend.k",
342
+ function () {
343
+ k.c.d.unbind('touchmove.k touchend.k');
344
+ s.val(s.cv);
345
+ }
346
+ );
347
+
348
+ return this;
349
+ };
350
+
351
+ this._mouse = function (e) {
352
+ var mouseMove = function (e) {
353
+ var v = s.xy2val(e.pageX, e.pageY);
354
+
355
+ if (v == s.cv) return;
356
+
357
+ if (s.cH && (s.cH(v) === false)) return;
358
+
359
+ s.change(s._validate(v));
360
+ s._draw();
361
+ };
362
+
363
+ // First click
364
+ mouseMove(e);
365
+
366
+ // Mouse events listeners
367
+ k.c.d
368
+ .bind("mousemove.k", mouseMove)
369
+ .bind(
370
+ // Escape key cancel current change
371
+ "keyup.k",
372
+ function (e) {
373
+ if (e.keyCode === 27) {
374
+ k.c.d.unbind("mouseup.k mousemove.k keyup.k");
375
+
376
+ if (s.eH && s.eH() === false)
377
+ return;
378
+
379
+ s.cancel();
380
+ }
381
+ }
382
+ )
383
+ .bind(
384
+ "mouseup.k",
385
+ function (e) {
386
+ k.c.d.unbind('mousemove.k mouseup.k keyup.k');
387
+ s.val(s.cv);
388
+ }
389
+ );
390
+
391
+ return this;
392
+ };
393
+
394
+ this._xy = function () {
395
+ var o = this.$c.offset();
396
+ this.x = o.left;
397
+ this.y = o.top;
398
+
399
+ return this;
400
+ };
401
+
402
+ this._listen = function () {
403
+ if (!this.o.readOnly) {
404
+ this.$c
405
+ .bind(
406
+ "mousedown",
407
+ function (e) {
408
+ e.preventDefault();
409
+ s._xy()._mouse(e);
410
+ }
411
+ )
412
+ .bind(
413
+ "touchstart",
414
+ function (e) {
415
+ e.preventDefault();
416
+ s._xy()._touch(e);
417
+ }
418
+ );
419
+
420
+ this.listen();
421
+ } else {
422
+ this.$.attr('readonly', 'readonly');
423
+ }
424
+
425
+ if (this.relative) {
426
+ $(window).resize(function() {
427
+ s._carve().init();
428
+ s._draw();
429
+ });
430
+ }
431
+
432
+ return this;
433
+ };
434
+
435
+ this._configure = function () {
436
+
437
+ // Hooks
438
+ if (this.o.draw) this.dH = this.o.draw;
439
+ if (this.o.change) this.cH = this.o.change;
440
+ if (this.o.cancel) this.eH = this.o.cancel;
441
+ if (this.o.release) this.rH = this.o.release;
442
+
443
+ if (this.o.displayPrevious) {
444
+ this.pColor = this.h2rgba(this.o.fgColor, "0.4");
445
+ this.fgColor = this.h2rgba(this.o.fgColor, "0.6");
446
+ } else {
447
+ this.fgColor = this.o.fgColor;
448
+ }
449
+
450
+ return this;
451
+ };
452
+
453
+ this._clear = function () {
454
+ this.$c[0].width = this.$c[0].width;
455
+ };
456
+
457
+ this._validate = function (v) {
458
+ var val = (~~ (((v < 0) ? -0.5 : 0.5) + (v/this.o.step))) * this.o.step;
459
+ return Math.round(val * 100) / 100;
460
+ };
461
+
462
+ // Abstract methods
463
+ this.listen = function () {}; // on start, one time
464
+ this.extend = function () {}; // each time configure triggered
465
+ this.init = function () {}; // each time configure triggered
466
+ this.change = function (v) {}; // on change
467
+ this.val = function (v) {}; // on release
468
+ this.xy2val = function (x, y) {}; //
469
+ this.draw = function () {}; // on change / on release
470
+ this.clear = function () { this._clear(); };
471
+
472
+ // Utils
473
+ this.h2rgba = function (h, a) {
474
+ var rgb;
475
+ h = h.substring(1,7);
476
+ rgb = [
477
+ parseInt(h.substring(0,2), 16),
478
+ parseInt(h.substring(2,4), 16),
479
+ parseInt(h.substring(4,6), 16)
480
+ ];
481
+
482
+ return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + a + ")";
483
+ };
484
+
485
+ this.copy = function (f, t) {
486
+ for (var i in f) {
487
+ t[i] = f[i];
488
+ }
489
+ };
490
+ };
491
+
492
+
493
+ /**
494
+ * k.Dial
495
+ */
496
+ k.Dial = function () {
497
+ k.o.call(this);
498
+
499
+ this.startAngle = null;
500
+ this.xy = null;
501
+ this.radius = null;
502
+ this.lineWidth = null;
503
+ this.cursorExt = null;
504
+ this.w2 = null;
505
+ this.PI2 = 2*Math.PI;
506
+
507
+ this.extend = function () {
508
+ this.o = $.extend({
509
+ bgColor: this.$.data('bgcolor') || '#EEEEEE',
510
+ angleOffset: this.$.data('angleoffset') || 0,
511
+ angleArc: this.$.data('anglearc') || 360,
512
+ inline: true
513
+ }, this.o);
514
+ };
515
+
516
+ this.val = function (v, triggerRelease) {
517
+ if (null != v) {
518
+
519
+ // reverse format
520
+ v = this.o.parse(v);
521
+
522
+ if (triggerRelease !== false
523
+ && v != this.v
524
+ && this.rH
525
+ && this.rH(v) === false) { return; }
526
+
527
+ this.cv = this.o.stopper ? max(min(v, this.o.max), this.o.min) : v;
528
+ this.v = this.cv;
529
+ this.$.val(this.o.format(this.v));
530
+ this._draw();
531
+ } else {
532
+ return this.v;
533
+ }
534
+ };
535
+
536
+ this.xy2val = function (x, y) {
537
+ var a, ret;
538
+
539
+ a = Math.atan2(
540
+ x - (this.x + this.w2),
541
+ - (y - this.y - this.w2)
542
+ ) - this.angleOffset;
543
+
544
+ if (this.o.flip) {
545
+ a = this.angleArc - a - this.PI2;
546
+ }
547
+
548
+ if (this.angleArc != this.PI2 && (a < 0) && (a > -0.5)) {
549
+
550
+ // if isset angleArc option, set to min if .5 under min
551
+ a = 0;
552
+ } else if (a < 0) {
553
+ a += this.PI2;
554
+ }
555
+
556
+ ret = (a * (this.o.max - this.o.min) / this.angleArc) + this.o.min;
557
+
558
+ this.o.stopper && (ret = max(min(ret, this.o.max), this.o.min));
559
+
560
+ return ret;
561
+ };
562
+
563
+ this.listen = function () {
564
+
565
+ // bind MouseWheel
566
+ var s = this, mwTimerStop,
567
+ mwTimerRelease,
568
+ mw = function (e) {
569
+ e.preventDefault();
570
+
571
+ var ori = e.originalEvent,
572
+ deltaX = ori.detail || ori.wheelDeltaX,
573
+ deltaY = ori.detail || ori.wheelDeltaY,
574
+ v = s._validate(s.o.parse(s.$.val()))
575
+ + (
576
+ deltaX > 0 || deltaY > 0
577
+ ? s.o.step
578
+ : deltaX < 0 || deltaY < 0 ? -s.o.step : 0
579
+ );
580
+
581
+ v = max(min(v, s.o.max), s.o.min);
582
+
583
+ s.val(v, false);
584
+
585
+ if (s.rH) {
586
+ // Handle mousewheel stop
587
+ clearTimeout(mwTimerStop);
588
+ mwTimerStop = setTimeout(function () {
589
+ s.rH(v);
590
+ mwTimerStop = null;
591
+ }, 100);
592
+
593
+ // Handle mousewheel releases
594
+ if (!mwTimerRelease) {
595
+ mwTimerRelease = setTimeout(function () {
596
+ if (mwTimerStop)
597
+ s.rH(v);
598
+ mwTimerRelease = null;
599
+ }, 200);
600
+ }
601
+ }
602
+ },
603
+ kval,
604
+ to,
605
+ m = 1,
606
+ kv = {
607
+ 37: -s.o.step,
608
+ 38: s.o.step,
609
+ 39: s.o.step,
610
+ 40: -s.o.step
611
+ };
612
+
613
+ this.$
614
+ .bind(
615
+ "keydown",
616
+ function (e) {
617
+ var kc = e.keyCode;
618
+
619
+ // numpad support
620
+ if (kc >= 96 && kc <= 105) {
621
+ kc = e.keyCode = kc - 48;
622
+ }
623
+
624
+ kval = parseInt(String.fromCharCode(kc));
625
+
626
+ if (isNaN(kval)) {
627
+ (kc !== 13) // enter
628
+ && kc !== 8 // bs
629
+ && kc !== 9 // tab
630
+ && kc !== 189 // -
631
+ && (kc !== 190
632
+ || s.$.val().match(/\./)) // . allowed once
633
+ && e.preventDefault();
634
+
635
+ // arrows
636
+ if ($.inArray(kc,[37,38,39,40]) > -1) {
637
+ e.preventDefault();
638
+
639
+ var v = s.o.parse(s.$.val()) + kv[kc] * m;
640
+ s.o.stopper && (v = max(min(v, s.o.max), s.o.min));
641
+
642
+ s.change(s._validate(v));
643
+ s._draw();
644
+
645
+ // long time keydown speed-up
646
+ to = window.setTimeout(function () {
647
+ m *= 2;
648
+ }, 30);
649
+ }
650
+ }
651
+ }
652
+ )
653
+ .bind(
654
+ "keyup",
655
+ function (e) {
656
+ if (isNaN(kval)) {
657
+ if (to) {
658
+ window.clearTimeout(to);
659
+ to = null;
660
+ m = 1;
661
+ s.val(s.$.val());
662
+ }
663
+ } else {
664
+ // kval postcond
665
+ (s.$.val() > s.o.max && s.$.val(s.o.max))
666
+ || (s.$.val() < s.o.min && s.$.val(s.o.min));
667
+ }
668
+ }
669
+ );
670
+
671
+ this.$c.bind("mousewheel DOMMouseScroll", mw);
672
+ this.$.bind("mousewheel DOMMouseScroll", mw);
673
+ };
674
+
675
+ this.init = function () {
676
+ if (this.v < this.o.min
677
+ || this.v > this.o.max) { this.v = this.o.min; }
678
+
679
+ this.$.val(this.v);
680
+ this.w2 = this.w / 2;
681
+ this.cursorExt = this.o.cursor / 100;
682
+ this.xy = this.w2 * this.scale;
683
+ this.lineWidth = this.xy * this.o.thickness;
684
+ this.lineCap = this.o.lineCap;
685
+ this.radius = this.xy - this.lineWidth / 2;
686
+
687
+ this.o.angleOffset
688
+ && (this.o.angleOffset = isNaN(this.o.angleOffset) ? 0 : this.o.angleOffset);
689
+
690
+ this.o.angleArc
691
+ && (this.o.angleArc = isNaN(this.o.angleArc) ? this.PI2 : this.o.angleArc);
692
+
693
+ // deg to rad
694
+ this.angleOffset = this.o.angleOffset * Math.PI / 180;
695
+ this.angleArc = this.o.angleArc * Math.PI / 180;
696
+
697
+ // compute start and end angles
698
+ this.startAngle = 1.5 * Math.PI + this.angleOffset;
699
+ this.endAngle = 1.5 * Math.PI + this.angleOffset + this.angleArc;
700
+
701
+ var s = max(
702
+ String(Math.abs(this.o.max)).length,
703
+ String(Math.abs(this.o.min)).length,
704
+ 2
705
+ ) + 2;
706
+
707
+ this.o.displayInput
708
+ && this.i.css({
709
+ 'width' : ((this.w / 2 + 4) >> 0) + 'px',
710
+ 'height' : ((this.w / 3) >> 0) + 'px',
711
+ 'position' : 'absolute',
712
+ 'vertical-align' : 'middle',
713
+ 'margin-top' : ((this.w / 3) >> 0) + 'px',
714
+ 'margin-left' : '-' + ((this.w * 3 / 4 + 2) >> 0) + 'px',
715
+ 'border' : 0,
716
+ 'background' : 'none',
717
+ 'font' : this.o.fontWeight + ' ' + ((this.w / s) >> 0) + 'px ' + this.o.font,
718
+ 'text-align' : 'center',
719
+ 'color' : this.o.inputColor || this.o.fgColor,
720
+ 'padding' : '0px',
721
+ '-webkit-appearance': 'none'
722
+ }) || this.i.css({
723
+ 'width': '0px',
724
+ 'visibility': 'hidden'
725
+ });
726
+ };
727
+
728
+ this.change = function (v) {
729
+ this.cv = v;
730
+ this.$.val(this.o.format(v));
731
+ };
732
+
733
+ this.angle = function (v) {
734
+ return (v - this.o.min) * this.angleArc / (this.o.max - this.o.min);
735
+ };
736
+
737
+ this.arc = function (v) {
738
+ var sa, ea;
739
+ v = this.angle(v);
740
+ if (this.o.flip) {
741
+ sa = this.endAngle + 0.00001;
742
+ ea = sa - v - 0.00001;
743
+ } else {
744
+ sa = this.startAngle - 0.00001;
745
+ ea = sa + v + 0.00001;
746
+ }
747
+ this.o.cursor
748
+ && (sa = ea - this.cursorExt)
749
+ && (ea = ea + this.cursorExt);
750
+
751
+ return {
752
+ s: sa,
753
+ e: ea,
754
+ d: this.o.flip && !this.o.cursor
755
+ };
756
+ };
757
+
758
+ this.draw = function () {
759
+ var c = this.g, // context
760
+ a = this.arc(this.cv), // Arc
761
+ pa, // Previous arc
762
+ r = 1;
763
+
764
+ c.lineWidth = this.lineWidth;
765
+ c.lineCap = this.lineCap;
766
+
767
+ if (this.o.bgColor !== "none") {
768
+ c.beginPath();
769
+ c.strokeStyle = this.o.bgColor;
770
+ c.arc(this.xy, this.xy, this.radius, this.endAngle - 0.00001, this.startAngle + 0.00001, true);
771
+ c.stroke();
772
+ }
773
+
774
+ if (this.o.displayPrevious) {
775
+ pa = this.arc(this.v);
776
+ c.beginPath();
777
+ c.strokeStyle = this.pColor;
778
+ c.arc(this.xy, this.xy, this.radius, pa.s, pa.e, pa.d);
779
+ c.stroke();
780
+ r = this.cv == this.v;
781
+ }
782
+
783
+ c.beginPath();
784
+ c.strokeStyle = r ? this.o.fgColor : this.fgColor ;
785
+ c.arc(this.xy, this.xy, this.radius, a.s, a.e, a.d);
786
+ c.stroke();
787
+ };
788
+
789
+ this.cancel = function () {
790
+ this.val(this.v);
791
+ };
792
+ };
793
+
794
+ $.fn.dial = $.fn.knob = function (o) {
795
+ return this.each(
796
+ function () {
797
+ var d = new k.Dial();
798
+ d.o = o;
799
+ d.$ = $(this);
800
+ d.run();
801
+ }
802
+ ).parent();
803
+ };
804
+
805
+ }));
res/js/jquery.tooltip.js ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function($){
2
+
3
+ $.fn.tooltip = function(instanceSettings){
4
+
5
+ $.fn.tooltip.defaultsSettings = {
6
+ attributeName:'title',
7
+ borderColor:'#ccc',
8
+ borderSize:'1',
9
+ cancelClick:0,
10
+ followMouse:1,
11
+ height:'auto',
12
+ hoverIntent:{sensitivity:7,interval:100,timeout:0},
13
+ loader:0,
14
+ loaderHeight:0,
15
+ loaderImagePath:'',
16
+ loaderWidth:0,
17
+ positionTop: 12,
18
+ positionLeft: 12,
19
+ width:'auto',
20
+ titleAttributeContent:'',
21
+ tooltipBGColor:'#fff',
22
+ tooltipBGImage:'none', // http path
23
+ tooltipHTTPType:'get',
24
+ tooltipPadding:10,
25
+ tooltipSource:'attribute', //inline, ajax, iframe, attribute
26
+ tooltipSourceID:'',
27
+ tooltipSourceURL:'',
28
+ tooltipID:'tooltip'
29
+ };
30
+
31
+ //s = settings
32
+ var s = $.extend({}, $.fn.tooltip.defaultsSettings , instanceSettings || {});
33
+
34
+ var positionTooltip = function(e){
35
+
36
+ var posx = 0;
37
+ var posy = 0;
38
+ if (!e) var e = window.event;
39
+ if (e.pageX || e.pageY) {
40
+ posx = e.pageX;
41
+ posy = e.pageY;
42
+ }
43
+ else if (e.clientX || e.clientY) {
44
+ posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
45
+ posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
46
+ }
47
+
48
+ var p = {
49
+ x: posx + s.positionLeft,
50
+ y: posy + s.positionTop,
51
+ w: $('#'+s.tooltipID).width(),
52
+ h: $('#'+s.tooltipID).height()
53
+ }
54
+
55
+ var v = {
56
+ x: $(window).scrollLeft(),
57
+ y: $(window).scrollTop(),
58
+ w: $(window).width() - 20,
59
+ h: $(window).height() - 20
60
+ };
61
+
62
+ //don't go off screen
63
+ if(p.y + p.h > v.y + v.h && p.x + p.w > v.x + v.w){
64
+ p.x = (p.x - p.w) - 45;
65
+ p.y = (p.y - p.h) - 45;
66
+ }else if(p.x + p.w > v.x + v.w){
67
+ p.x = p.x - (((p.x+p.w)-(v.x+v.w)) + 20);
68
+ }else if(p.y + p.h > v.y + v.h){
69
+ p.y = p.y - (((p.y+p.h)-(v.y+v.h)) + 20);
70
+ }
71
+
72
+ $('#'+s.tooltipID).css({'left':p.x + 'px','top':p.y + 'px'});
73
+ }
74
+
75
+ var showTooltip = function(){
76
+ $('#tooltipLoader').remove();
77
+ $('#'+s.tooltipID+' #tooltipContent').show();
78
+
79
+ if($.browser.version == '6.0'){//IE6 only
80
+ $('#'+s.tooltipID).append('<iframe id="tooltipIE6FixIframe" style="width:'+($('#'+s.tooltipID).width()+parseFloat(s.borderSize)+parseFloat(s.borderSize)+20)+'px;height:'+($('#'+s.tooltipID).height()+parseFloat(s.borderSize)+parseFloat(s.borderSize)+20)+'px;position:absolute;top:-'+s.borderSize+'px;left:-'+s.borderSize+'px;filter:alpha(opacity=0);"src="blank.html"></iframe>');
81
+ };
82
+ }
83
+
84
+ var hideTooltip = function(valueOfThis){
85
+ $('#'+s.tooltipID).fadeOut('fast').trigger("unload").remove();
86
+ if($(valueOfThis).filter('[title]')){
87
+ $(valueOfThis).attr('title',s.titleAttributeContent);
88
+ }
89
+ }
90
+
91
+ var urlQueryToObject = function(s){
92
+ var query = {};
93
+ s.replace(/b([^&=]*)=([^&=]*)b/g, function (m, a, d) {
94
+ if (typeof query[a] != 'undefined') {
95
+ query[a] += ',' + d;
96
+ } else {
97
+ query[a] = d;
98
+ }
99
+ });
100
+ return query;
101
+ };
102
+
103
+ return this.each(function(index){
104
+
105
+ if(s.cancelClick){
106
+ $(this).bind("click", function(){return false});
107
+ }
108
+
109
+ if($.fn.hoverIntent){
110
+ $(this).hoverIntent({
111
+ sensitivity:s.hoverIntent.sensitivity,
112
+ interval:s.hoverIntent.interval,
113
+ over:on,
114
+ timeout:s.hoverIntent.timeout,
115
+ out:off
116
+ });
117
+ }else{
118
+ $(this).hover(on,off);
119
+ }
120
+
121
+ function on(e){
122
+
123
+ $('body').append('<div id="'+s.tooltipID+'" style="background-repeat:no-repeat;background-image:url('+s.tooltipBGImage+');padding:'+s.tooltipPadding+'px;display:none;height:'+s.height+';width:'+s.width+';background-color:'+s.tooltipBGColor+';border:'+s.borderSize+'px solid '+s.borderColor+'; position:absolute;z-index:100000000000;"><div id="tooltipContent" style="display:none;"></div></div>');
124
+
125
+ var $tt = $('#'+s.tooltipID);
126
+ var $ttContent = $('#'+s.tooltipID+' #tooltipContent');
127
+
128
+ if(s.loader && s.loaderImagePath != ''){
129
+ $tt.append('<div id="tooltipLoader" style="width:'+s.loaderWidth+'px;height:'+s.loaderHeight+'px;"><img src="'+s.loaderImagePath+'" /></div>');
130
+ }
131
+
132
+ if($(this).attr('title')){
133
+ s.titleAttributeContent = $(this).attr('title');
134
+ $(this).attr('title','');
135
+ }
136
+
137
+ if($(this).is('input')){
138
+ $(this).focus(function(){ hideTooltip(this); });
139
+ }
140
+
141
+ e.preventDefault();//stop
142
+ positionTooltip(e);
143
+
144
+ $tt.show();
145
+
146
+ //get values from element clicked, or assume its passed as an option
147
+ s.tooltipSourceID = $(this).attr('href') || s.tooltipSourceID;
148
+ s.tooltipSourceURL = $(this).attr('href') || s.tooltipSourceURL;
149
+
150
+ switch(s.tooltipSource){
151
+ case 'attribute':/*/////////////////////////////// attribute //////////////////////////////////////////*/
152
+ $ttContent.text(s.titleAttributeContent);
153
+ showTooltip();
154
+ break;
155
+ case 'inline':/*/////////////////////////////// inline //////////////////////////////////////////*/
156
+ $ttContent.html($(s.tooltipSourceID).children());
157
+ $tt.unload(function(){// move elements back when you're finished
158
+ $(s.tooltipSourceID).html($ttContent.children());
159
+ });
160
+ showTooltip();
161
+ break;
162
+ case 'ajax':/*/////////////////////////////// ajax //////////////////////////////////////////*/
163
+ if(s.tooltipHTTPType == 'post'){
164
+ var urlOnly, urlQueryObject;
165
+ if(s.tooltipSourceURL.indexOf("?") !== -1){//has a query string
166
+ urlOnly = s.windowSourceURL.substr(0, s.windowSourceURL.indexOf("?"));
167
+ urlQueryObject = urlQueryToObject(s.tooltipSourceURL);
168
+ }else{
169
+ urlOnly = s.tooltipSourceURL;
170
+ urlQueryObject = {};
171
+ }
172
+ $ttContent.load(urlOnly,urlQueryObject,function(){
173
+ showTooltip();
174
+ });
175
+ }else{
176
+ if(s.tooltipSourceURL.indexOf("?") == -1){ //no query string, so add one
177
+ s.tooltipSourceURL += '?';
178
+ }
179
+ $ttContent.load(
180
+ s.tooltipSourceURL + '&random=' + (new Date().getTime()),function(){
181
+ showTooltip();
182
+ });
183
+ }
184
+ break;
185
+ };
186
+
187
+ return false;
188
+
189
+ };
190
+
191
+
192
+ function off(e){
193
+ hideTooltip(this);
194
+ return false;
195
+ };
196
+
197
+ if(s.followMouse){
198
+ $(this).bind("mousemove", function(e){
199
+ positionTooltip(e);
200
+ return false;
201
+ });
202
+ }
203
+
204
+ });
205
+ };
206
+
207
+ })(jQuery);
{js → res/js}/short-pixel.js RENAMED
@@ -18,6 +18,7 @@ jQuery(document).ready(function($){
18
  clearBulkProcessor();
19
  }
20
  });
 
21
  //check if bulk processing
22
  checkQuotaExceededAlert();
23
  checkBulkProgress();
@@ -42,10 +43,10 @@ var ShortPixel = function() {
42
  var section = jQuery("section#" +target);
43
  if(section.length > 0){
44
  jQuery("section").removeClass("sel-tab");
45
- jQuery("section#" +target).addClass("sel-tab");
46
  }
47
  }
48
-
49
  function adjustSettingsTabsHeight(){
50
  var sectionHeight = jQuery('.wp-shortpixel-options').height() + 60;
51
  sectionHeight = Math.max(sectionHeight, jQuery('section#tab-resources .area1').height() + 20);
@@ -55,7 +56,7 @@ var ShortPixel = function() {
55
 
56
  function dismissMediaAlert() {
57
  var data = { action : 'shortpixel_dismiss_media_alert'};
58
- jQuery.get(ajaxurl, data, function(response) {
59
  data = JSON.parse(response);
60
  if(data["Status"] == 'success') {
61
  jQuery("#short-pixel-media-alert").hide();
@@ -74,6 +75,39 @@ var ShortPixel = function() {
74
  }
75
  }
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  function retry(msg) {
78
  ShortPixel.retries++;
79
  if(isNaN(ShortPixel.retries)) ShortPixel.retries = 1;
@@ -81,10 +115,100 @@ var ShortPixel = function() {
81
  console.log("Invalid response from server (Error: " + msg + "). Retrying pass " + (ShortPixel.retries + 1) + "...");
82
  setTimeout(checkBulkProgress, 5000);
83
  } else {
 
84
  console.log("Invalid response from server 6 times. Giving up.");
85
  }
86
  }
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  return {
89
  setOptions : setOptions,
90
  checkThumbsUpdTotal : checkThumbsUpdTotal,
@@ -92,7 +216,17 @@ var ShortPixel = function() {
92
  adjustSettingsTabs : adjustSettingsTabsHeight,
93
  onBulkThumbsCheck : onBulkThumbsCheck,
94
  dismissMediaAlert : dismissMediaAlert,
95
- retry : retry
 
 
 
 
 
 
 
 
 
 
96
  }
97
  }();
98
 
@@ -102,6 +236,7 @@ function showToolBarAlert($status, $message) {
102
  case ShortPixel.STATUS_QUOTA_EXCEEDED:
103
  if( window.location.href.search("wp-short-pixel-bulk") > 0
104
  && jQuery(".sp-quota-exceeded-alert").length == 0) { //if we're in bulk and the alert is not displayed reload to see all options
 
105
  location.reload();
106
  return;
107
  }
@@ -130,11 +265,12 @@ function showToolBarAlert($status, $message) {
130
  break;
131
  case ShortPixel.STATUS_SUCCESS:
132
  case ShortPixel.STATUS_RETRY:
 
133
  robo.removeClass("shortpixel-alert");
134
  jQuery("a", robo).removeAttr("target");
135
  jQuery("a", robo).attr("href", jQuery("a img", robo).attr("success-url"));
136
  }
137
- jQuery("li.shortpixel-toolbar-processing").removeClass("shortpixel-hide");
138
  }
139
  function hideToolBarAlert () {
140
  jQuery("li.shortpixel-toolbar-processing.shortpixel-processing").addClass("shortpixel-hide");
@@ -162,7 +298,10 @@ function checkBulkProgress() {
162
  && window.location.href.search(ShortPixel.WP_ADMIN_URL + "edit.php") < 0
163
  && window.location.href.search(ShortPixel.WP_ADMIN_URL + "edit-tags.php") < 0
164
  && window.location.href.search(ShortPixel.WP_ADMIN_URL + "post-new.php") < 0
165
- && window.location.href.search(ShortPixel.WP_ADMIN_URL + "post.php") < 0) {
 
 
 
166
  hideToolBarAlert();
167
  return;
168
  }
@@ -183,11 +322,15 @@ function checkBulkProgress() {
183
  //if I'm not the bulk processor, check every 20 sec. if the bulk processor is running, otherwise take the role
184
  if(ShortPixel.bulkProcessor == true || typeof localStorage.bulkTime == 'undefined' || Math.floor(Date.now() / 1000) - localStorage.bulkTime > 90) {
185
  ShortPixel.bulkProcessor = true;
 
186
  localStorage.bulkTime = Math.floor(Date.now() / 1000);
187
  console.log(localStorage.bulkTime);
188
  checkBulkProcessingCallApi();
189
  } else {
190
- console.log("not the bulk processor");
 
 
 
191
  setTimeout(checkBulkProgress, 5000);
192
  }
193
  }
@@ -197,7 +340,7 @@ function checkBulkProcessingCallApi(){
197
  // since WP 2.8 ajaxurl is always defined in the admin header and points to admin-ajax.php
198
  jQuery.ajax({
199
  type: "POST",
200
- url: ajaxurl,
201
  data: data,
202
  success: function(response)
203
  {
@@ -210,29 +353,33 @@ function checkBulkProcessingCallApi(){
210
  return;
211
  }
212
  var id = data["ImageID"];
213
-
214
  var isBulkPage = (jQuery("div.short-pixel-bulk-page").length > 0);
215
 
216
  switch (data["Status"]) {
217
  case ShortPixel.STATUS_NO_KEY:
218
- setCellMessage(id, data["Message"] + " | <a href=\"https://shortpixel.com/wp-apikey\" target=\"_blank\">Get API Key</a>");
 
219
  showToolBarAlert(ShortPixel.STATUS_NO_KEY);
220
  break;
221
  case ShortPixel.STATUS_QUOTA_EXCEEDED:
222
- setCellMessage(id, "<div class='sp-column-actions' style='width:110px;'><a class='button button-smaller button-primary' href=\"https://shortpixel.com/login/"
223
  + ShortPixel.API_KEY + "\" target=\"_blank\">Extend Quota</a>"
224
- + "<a class='button button-smaller' href='admin.php?action=shortpixel_check_quota'>Check&nbsp;&nbsp;Quota</a></div>"
225
- + "<div class='sp-column-info'>" + data["Message"] + "</div>" );
226
  showToolBarAlert(ShortPixel.STATUS_QUOTA_EXCEEDED);
227
- if(data['Stop'] == false) { //there are other items in queue maybe already optimized
228
- setTimeout(checkBulkProgress, 5000);
229
- }
230
  break;
231
  case ShortPixel.STATUS_FAIL:
232
- setCellMessage(id, data["Message"]);
233
  if(isBulkPage) {
 
234
  showToolBarAlert(ShortPixel.STATUS_FAIL, data["Message"]);
235
- progressUpdate(data["BulkPercent"], data["BulkMsg"]);
 
 
236
  }
237
  console.log(data["Message"]);
238
  setTimeout(checkBulkProgress, 5000);
@@ -253,32 +400,30 @@ function checkBulkProcessingCallApi(){
253
  }
254
  break;
255
  case ShortPixel.STATUS_SUCCESS:
 
 
 
256
  var percent = data["PercentImprovement"];
257
- var otherType = data["Type"].length > 0 ? (data["Type"] == "lossy" ? "lossless" : "lossy") : "";
258
-
259
- var cellMsg = (percent > 0 ? "<div class='sp-column-info'>Reduced by <span class='percent'>" + percent + "%</span> " : "")
260
- + (percent > 0 && percent < 5 ? "<br>" : '')
261
- + (percent < 5 ? "Bonus processing" : '')
262
- + (data["Type"].length > 0 ? " ("+data["Type"]+")" : "")
263
- + (0 + data['ThumbsCount'] > 0 ? "<br>+" + data['ThumbsCount'] + " thumbnails optimized" :"")
264
- + "</div>";
265
-
266
- if(data["BackupEnabled"] == 1) {
267
- cellMsg = '<div class="sp-column-actions">'
268
- + (data["ThumbsTotal"] > data["ThumbsCount"] ? "<a class='button button-smaller button-primary' href=\"javascript:optimizeThumbs(" + id + ");\">Optimize " + (data["ThumbsTotal"] - data["ThumbsCount"]) + " thumbnails</a>" : "")
269
- + (otherType.length ? "<a class='button button-smaller' href=\"javascript:reoptimize(" + id + ", '" + otherType + "');\">Re-optimize " + otherType + "</a>" : "")
270
- + "<a class='button button-smaller' href=\"admin.php?action=shortpixel_restore_backup&attachment_ID=" + id + ")\">Restore backup</a>"
271
- + "</div>" + cellMsg;
272
- }
273
-
274
  showToolBarAlert(ShortPixel.STATUS_SUCCESS, "");
275
- setCellMessage(id, cellMsg);
 
 
 
 
 
276
  var animator = new PercentageAnimator("#sp-msg-" + id + " span.percent", percent);
277
  animator.animate(percent);
278
  if(isBulkPage && typeof data["Thumb"] !== 'undefined') { // && data["PercentImprovement"] > 0) {
279
- progressUpdate(data["BulkPercent"], data["BulkMsg"]);
 
 
280
  if(data["Thumb"].length > 0){
281
- sliderUpdate(id, data["Thumb"], data["BkThumb"], data["PercentImprovement"]);
 
 
 
 
282
  }
283
  }
284
  console.log('Server response: ' + response);
@@ -287,21 +432,32 @@ function checkBulkProcessingCallApi(){
287
  }
288
  setTimeout(checkBulkProgress, 5000);
289
  break;
290
-
291
- case ShortPixel.STATUS_ERROR: //for error and skip also we retry
292
  case ShortPixel.STATUS_SKIP:
 
 
 
 
 
293
  if(typeof data["Message"] !== 'undefined') {
294
  showToolBarAlert(ShortPixel.STATUS_SKIP, data["Message"] + ' Image ID: ' + id);
295
- setCellMessage(id, data["Message"]);
296
  }
 
297
  case ShortPixel.STATUS_RETRY:
298
  console.log('Server response: ' + response);
299
  showToolBarAlert(ShortPixel.STATUS_RETRY, "");
300
  if(isBulkPage && typeof data["BulkPercent"] !== 'undefined') {
301
  progressUpdate(data["BulkPercent"], data["BulkMsg"]);
302
  }
 
 
 
303
  setTimeout(checkBulkProgress, 5000);
304
  break;
 
 
 
305
  }
306
  }
307
  },
@@ -319,61 +475,67 @@ function clearBulkProcessor(){
319
  }
320
  }
321
 
322
- function setCellMessage(id, message){
323
  var msg = jQuery("#sp-msg-" + id);
324
- if(typeof msg !== "undefined") {
325
- msg.html(message);
 
 
 
 
 
 
326
  }
327
  }
328
 
329
  function manualOptimization(id) {
330
- setCellMessage(id, "<img src='" + ShortPixel.WP_PLUGIN_URL + "/img/loading.gif' class='sp-loading-small'>Image waiting to be processed");
331
  jQuery("li.shortpixel-toolbar-processing").removeClass("shortpixel-hide");
332
  jQuery("li.shortpixel-toolbar-processing").addClass("shortpixel-processing");
333
  var data = { action : 'shortpixel_manual_optimization',
334
  image_id: id};
335
- jQuery.get(ajaxurl, data, function(response) {
336
- data = JSON.parse(response);
337
- if(data["Status"] == ShortPixel.STATUS_SUCCESS) {
338
- setTimeout(checkBulkProgress, 2000);
339
- } else {
340
- setCellMessage(id, typeof data["Message"] !== "undefined" ? data["Message"] : "This content is not processable.");
341
- }
342
  //aici e aici
343
  });
344
  }
345
 
346
  function reoptimize(id, type) {
347
- setCellMessage(id, "<img src='" + ShortPixel.WP_PLUGIN_URL + "/img/loading.gif' class='sp-loading-small'>Image waiting to be reprocessed");
348
  jQuery("li.shortpixel-toolbar-processing").removeClass("shortpixel-hide");
349
  jQuery("li.shortpixel-toolbar-processing").addClass("shortpixel-processing");
350
  var data = { action : 'shortpixel_redo',
351
  attachment_ID: id,
352
  type: type};
353
- jQuery.get(ajaxurl, data, function(response) {
354
  data = JSON.parse(response);
355
  if(data["Status"] == ShortPixel.STATUS_SUCCESS) {
356
  setTimeout(checkBulkProgress, 2000);
357
  } else {
358
  $msg = typeof data["Message"] !== "undefined" ? data["Message"] : "This content is not processable.";
359
- setCellMessage(id, $msg);
360
  showToolBarAlert(ShortPixel.STATUS_FAIL, $msg);
361
  }
362
  });
363
  }
364
 
365
  function optimizeThumbs(id) {
366
- setCellMessage(id, "<img src='" + ShortPixel.WP_PLUGIN_URL + "/img/loading.gif' class='sp-loading-small'>Image waiting to optimize thumbnails");
367
  jQuery("li.shortpixel-toolbar-processing").removeClass("shortpixel-hide");
368
  jQuery("li.shortpixel-toolbar-processing").addClass("shortpixel-processing");
369
  var data = { action : 'shortpixel_optimize_thumbs',
370
  attachment_ID: id};
371
- jQuery.get(ajaxurl, data, function(response) {
372
  data = JSON.parse(response);
373
  if(data["Status"] == ShortPixel.STATUS_SUCCESS) {
374
  setTimeout(checkBulkProgress, 2000);
375
  } else {
376
- setCellMessage(id, typeof data["Message"] !== "undefined" ? data["Message"] : "This content is not processable.");
377
  }
378
  });
379
  }
@@ -382,7 +544,7 @@ function dismissShortPixelNotice(id) {
382
  jQuery("#short-pixel-notice-" + id).hide();
383
  var data = { action : 'shortpixel_dismiss_notice',
384
  notice_id: id};
385
- jQuery.get(ajaxurl, data, function(response) {
386
  data = JSON.parse(response);
387
  if(data["Status"] == ShortPixel.STATUS_SUCCESS) {
388
  console.log("dismissed");
@@ -435,14 +597,21 @@ function progressUpdate(percent, message) {
435
  }
436
  }
437
 
438
- function sliderUpdate(id, thumb, bkThumb, percent){
 
 
439
  var oldSlide = jQuery(".bulk-slider div.bulk-slide:first-child");
440
- var newSlide = oldSlide.clone();
441
- newSlide.attr("id", "slide-" + id);
442
  if(oldSlide.attr("id") != "empty-slide") {
443
- newSlide.hide();
444
  }
445
  oldSlide.css("z-index", 1000);
 
 
 
 
 
 
 
446
  jQuery(".bulk-img-opt", newSlide).attr("src", thumb);
447
  if(bkThumb.length > 0) {
448
  jQuery(".img-original", newSlide).css("display", "inline-block");
@@ -450,14 +619,18 @@ function sliderUpdate(id, thumb, bkThumb, percent){
450
  } else {
451
  jQuery(".img-original", newSlide).css("display", "none");
452
  }
453
- jQuery(".bulk-opt-percent", newSlide).text(percent + "%");
454
 
 
455
  jQuery(".bulk-slider").append(newSlide);
 
 
 
456
  if(oldSlide.attr("id") == "empty-slide") {
457
  oldSlide.remove();
458
  jQuery(".bulk-slider-container").css("display", "block");
459
  } else {
460
- oldSlide.animate({ left: oldSlide.width() }, 'slow', 'swing', function(){
461
  oldSlide.remove();
462
  newSlide.fadeIn("slow");
463
  });
18
  clearBulkProcessor();
19
  }
20
  });
21
+ ShortPixel.setOptions(ShortPixelConstants);
22
  //check if bulk processing
23
  checkQuotaExceededAlert();
24
  checkBulkProgress();
43
  var section = jQuery("section#" +target);
44
  if(section.length > 0){
45
  jQuery("section").removeClass("sel-tab");
46
+ jQuery("section#" +target).addClass("sel-tab");
47
  }
48
  }
49
+
50
  function adjustSettingsTabsHeight(){
51
  var sectionHeight = jQuery('.wp-shortpixel-options').height() + 60;
52
  sectionHeight = Math.max(sectionHeight, jQuery('section#tab-resources .area1').height() + 20);
56
 
57
  function dismissMediaAlert() {
58
  var data = { action : 'shortpixel_dismiss_media_alert'};
59
+ jQuery.get(ShortPixel.AJAX_URL, data, function(response) {
60
  data = JSON.parse(response);
61
  if(data["Status"] == 'success') {
62
  jQuery("#short-pixel-media-alert").hide();
75
  }
76
  }
77
 
78
+ function successMsg(id, percent, type, thumbsCount) {
79
+ return (percent > 0 ? "<div class='sp-column-info'>Reduced by <span class='percent'>" + percent + "%</span> " : "")
80
+ + (percent > 0 && percent < 5 ? "<br>" : '')
81
+ + (percent < 5 ? "Bonus processing" : '')
82
+ + (type.length > 0 ? " ("+type+")" : "")
83
+ + (0 + thumbsCount > 0 ? "<br>+" + thumbsCount + " thumbnails optimized" :"")
84
+ + "</div>";
85
+ }
86
+
87
+ function percentDial(query, size) {
88
+ jQuery(query).knob({
89
+ 'readOnly': true,
90
+ 'width': size,
91
+ 'height': size,
92
+ 'fgColor': '#1CAECB',
93
+ 'format' : function (value) {
94
+ return value + '%';
95
+ }
96
+ });
97
+ }
98
+
99
+ function successActions(id, type, thumbsCount, thumbsTotal, backupEnabled) {
100
+ if(backupEnabled == 1) {
101
+ var otherType = type.length > 0 ? (type == "lossy" ? "lossless" : "lossy") : "";
102
+ return '<div class="sp-column-actions">'
103
+ + (thumbsTotal > thumbsCount ? "<a class='button button-smaller button-primary' href=\"javascript:optimizeThumbs(" + id + ");\">Optimize " + (thumbsTotal - thumbsCount) + " thumbnails</a>" : "")
104
+ + (otherType.length ? "<a class='button button-smaller' href=\"javascript:reoptimize(" + id + ", '" + otherType + "');\">Re-optimize " + otherType + "</a>" : "")
105
+ + "<a class='button button-smaller' href=\"admin.php?action=shortpixel_restore_backup&attachment_ID=" + id + ")\">Restore backup</a>"
106
+ + "</div>";
107
+ }
108
+ return "";
109
+ }
110
+
111
  function retry(msg) {
112
  ShortPixel.retries++;
113
  if(isNaN(ShortPixel.retries)) ShortPixel.retries = 1;
115
  console.log("Invalid response from server (Error: " + msg + "). Retrying pass " + (ShortPixel.retries + 1) + "...");
116
  setTimeout(checkBulkProgress, 5000);
117
  } else {
118
+ ShortPixel.bulkShowError(-1,"Invalid response from server received 6 times. Please retry later by reloading this page, or contact support. (Error: " + msg + ")", "");
119
  console.log("Invalid response from server 6 times. Giving up.");
120
  }
121
  }
122
 
123
+ function browseContent(browseData) {
124
+ browseData.action = 'shortpixel_browse_content';
125
+ var browseResponse = "";
126
+ jQuery.ajax({
127
+ type: "POST",
128
+ url: ShortPixel.AJAX_URL,
129
+ data: browseData,
130
+ success: function(response) {
131
+ browseResponse = response;
132
+ },
133
+ async: false
134
+ });
135
+ return browseResponse;
136
+ }
137
+
138
+ function initFolderSelector() {
139
+ jQuery(".select-folder-button").click(function(){
140
+ jQuery(".sp-folder-picker-shade").css("display", "block");
141
+ jQuery(".sp-folder-picker").fileTree({
142
+ script: ShortPixel.browseContent,
143
+ //folderEvent: 'dblclick',
144
+ multiFolder: false
145
+ //onlyFolders: true
146
+ });
147
+ });
148
+ jQuery(".sp-folder-picker-popup input.select-folder-cancel").click(function(){
149
+ jQuery(".sp-folder-picker-shade").css("display", "none");
150
+ });
151
+ jQuery(".sp-folder-picker-popup input.select-folder").click(function(){
152
+ var subPath = jQuery("UL.jqueryFileTree LI.directory.selected A").attr("rel");
153
+ if(subPath) {
154
+ var fullPath = jQuery("#customFolderBase").val() + subPath;
155
+ if(fullPath.slice(-1) == '/') fullPath = fullPath.slice(0, -1);
156
+ jQuery("#addCustomFolder").val(fullPath);
157
+ jQuery("#addCustomFolderView").val(fullPath);
158
+ jQuery(".sp-folder-picker-shade").css("display", "none");
159
+ } else {
160
+ alert("Please select a folder from the list.");
161
+ }
162
+ });
163
+ }
164
+
165
+ function bulkShowLengthyMsg(id, fileName, customLink){
166
+ var notice = jQuery(".bulk-notice-msg.bulk-lengthy");
167
+ if(notice.length == 0) return;
168
+ var link = jQuery("a", notice);
169
+ link.text(fileName);
170
+ if(customLink) {
171
+ link.attr("href", customLink);
172
+ } else {
173
+ link.attr("href", link.data("href").replace("__ID__", id));
174
+ }
175
+
176
+ notice.css("display", "block");
177
+
178
+ }
179
+
180
+ function bulkHideLengthyMsg(){
181
+ jQuery(".bulk-notice-msg.bulk-lengthy").css("display", "none");
182
+ }
183
+
184
+ function bulkShowError(id, msg, fileName, customLink) {
185
+ var noticeTpl = jQuery("#bulk-error-template");
186
+ if(noticeTpl.length == 0) return;
187
+ var notice = noticeTpl.clone();
188
+ notice.attr("id", "bulk-error-" + id);
189
+ if(id == -1) {
190
+ jQuery("span.sp-err-title", notice).remove();
191
+ }
192
+ jQuery("span.sp-err-content", notice).text(msg);
193
+ var link = jQuery("a.sp-post-link", notice);
194
+ if(customLink) {
195
+ link.attr("href", customLink);
196
+ } else {
197
+ link.attr("href", link.attr("href").replace("__ID__", id));
198
+ }
199
+ link.text(fileName);
200
+ noticeTpl.after(notice);
201
+ notice.css("display", "block");
202
+ }
203
+
204
+ function removeBulkMsg(me) {
205
+ jQuery(me).parent().parent().remove();
206
+ }
207
+
208
+ function isCustomImageId(id) {
209
+ return id.substring(0,2) == "C-";
210
+ }
211
+
212
  return {
213
  setOptions : setOptions,
214
  checkThumbsUpdTotal : checkThumbsUpdTotal,
216
  adjustSettingsTabs : adjustSettingsTabsHeight,
217
  onBulkThumbsCheck : onBulkThumbsCheck,
218
  dismissMediaAlert : dismissMediaAlert,
219
+ percentDial : percentDial,
220
+ successMsg : successMsg,
221
+ successActions : successActions,
222
+ retry : retry,
223
+ initFolderSelector : initFolderSelector,
224
+ browseContent : browseContent,
225
+ bulkShowLengthyMsg : bulkShowLengthyMsg,
226
+ bulkHideLengthyMsg : bulkHideLengthyMsg,
227
+ bulkShowError : bulkShowError,
228
+ removeBulkMsg : removeBulkMsg,
229
+ isCustomImageId : isCustomImageId
230
  }
231
  }();
232
 
236
  case ShortPixel.STATUS_QUOTA_EXCEEDED:
237
  if( window.location.href.search("wp-short-pixel-bulk") > 0
238
  && jQuery(".sp-quota-exceeded-alert").length == 0) { //if we're in bulk and the alert is not displayed reload to see all options
239
+ debugger;
240
  location.reload();
241
  return;
242
  }
265
  break;
266
  case ShortPixel.STATUS_SUCCESS:
267
  case ShortPixel.STATUS_RETRY:
268
+ robo.addClass("shortpixel-processing");
269
  robo.removeClass("shortpixel-alert");
270
  jQuery("a", robo).removeAttr("target");
271
  jQuery("a", robo).attr("href", jQuery("a img", robo).attr("success-url"));
272
  }
273
+ robo.removeClass("shortpixel-hide");
274
  }
275
  function hideToolBarAlert () {
276
  jQuery("li.shortpixel-toolbar-processing.shortpixel-processing").addClass("shortpixel-hide");
298
  && window.location.href.search(ShortPixel.WP_ADMIN_URL + "edit.php") < 0
299
  && window.location.href.search(ShortPixel.WP_ADMIN_URL + "edit-tags.php") < 0
300
  && window.location.href.search(ShortPixel.WP_ADMIN_URL + "post-new.php") < 0
301
+ && window.location.href.search(ShortPixel.WP_ADMIN_URL + "post.php") < 0
302
+ && window.location.href.search("page=nggallery-manage-gallery") < 0
303
+ && (ShortPixel.FRONT_BOOTSTRAP == 0 || window.location.href.search(ShortPixel.WP_ADMIN_URL) == 0)
304
+ ) {
305
  hideToolBarAlert();
306
  return;
307
  }
322
  //if I'm not the bulk processor, check every 20 sec. if the bulk processor is running, otherwise take the role
323
  if(ShortPixel.bulkProcessor == true || typeof localStorage.bulkTime == 'undefined' || Math.floor(Date.now() / 1000) - localStorage.bulkTime > 90) {
324
  ShortPixel.bulkProcessor = true;
325
+ localStorage.bulkPage = (window.location.href.search("wp-short-pixel-bulk") >= 0 ? 1 : 0);
326
  localStorage.bulkTime = Math.floor(Date.now() / 1000);
327
  console.log(localStorage.bulkTime);
328
  checkBulkProcessingCallApi();
329
  } else {
330
+ /*console.log("not the bulk processor (bulkProcessor: " + ShortPixel.bulkProcessor.toString()
331
+ + ", bulkTime: " + (localStorage.bulkTime == 'undefined' ? 'undefined': localStorage.bulkTime.toString())
332
+ + " bulkPage: " + localStorage.bulkPage.toString()
333
+ + " passed: " + (Math.floor(Date.now() / 1000) - localStorage.bulkTime).toString());*/
334
  setTimeout(checkBulkProgress, 5000);
335
  }
336
  }
340
  // since WP 2.8 ajaxurl is always defined in the admin header and points to admin-ajax.php
341
  jQuery.ajax({
342
  type: "POST",
343
+ url: ShortPixel.AJAX_URL, //formerly ajaxurl , but changed it because it's not available in the front-end and now we have an option to run in the front-end
344
  data: data,
345
  success: function(response)
346
  {
353
  return;
354
  }
355
  var id = data["ImageID"];
356
+
357
  var isBulkPage = (jQuery("div.short-pixel-bulk-page").length > 0);
358
 
359
  switch (data["Status"]) {
360
  case ShortPixel.STATUS_NO_KEY:
361
+ setCellMessage(id, "<a class='button button-smaller button-primary' href=\"https://shortpixel.com/wp-apikey\" target=\"_blank\">Get API Key</a>",
362
+ data["Message"]);
363
  showToolBarAlert(ShortPixel.STATUS_NO_KEY);
364
  break;
365
  case ShortPixel.STATUS_QUOTA_EXCEEDED:
366
+ setCellMessage(id, "<a class='button button-smaller button-primary' href=\"https://shortpixel.com/login/"
367
  + ShortPixel.API_KEY + "\" target=\"_blank\">Extend Quota</a>"
368
+ + "<a class='button button-smaller' href='admin.php?action=shortpixel_check_quota'>Check&nbsp;&nbsp;Quota</a>",
369
+ data["Message"]);
370
  showToolBarAlert(ShortPixel.STATUS_QUOTA_EXCEEDED);
371
+ if(data['Stop'] == false) { //there are other items in the priority list, maybe processed, try those
372
+ setTimeout(checkBulkProgress, 5000);
373
+ }
374
  break;
375
  case ShortPixel.STATUS_FAIL:
376
+ setCellMessage(id, data["Message"], "<a class='button button-smaller button-primary' href=\"javascript:manualOptimization('<?php echo($id)?>')\">Retry</a>");
377
  if(isBulkPage) {
378
+ ShortPixel.bulkShowError(id, data["Message"], data["Filename"], data["CustomImageLink"]);
379
  showToolBarAlert(ShortPixel.STATUS_FAIL, data["Message"]);
380
+ if(data["BulkPercent"]) {
381
+ progressUpdate(data["BulkPercent"], data["BulkMsg"]);
382
+ }
383
  }
384
  console.log(data["Message"]);
385
  setTimeout(checkBulkProgress, 5000);
400
  }
401
  break;
402
  case ShortPixel.STATUS_SUCCESS:
403
+ if(isBulkPage) {
404
+ ShortPixel.bulkHideLengthyMsg();
405
+ }
406
  var percent = data["PercentImprovement"];
407
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  showToolBarAlert(ShortPixel.STATUS_SUCCESS, "");
409
+ //for now, until 4.1
410
+ var successActions = ShortPixel.isCustomImageId(id)
411
+ ? ""
412
+ : ShortPixel.successActions(id, data["Type"], data['ThumbsCount'], data['ThumbsTotal'], data["BackupEnabled"]);
413
+
414
+ setCellMessage(id, ShortPixel.successMsg(id, percent, data["Type"], data['ThumbsCount']), successActions);
415
  var animator = new PercentageAnimator("#sp-msg-" + id + " span.percent", percent);
416
  animator.animate(percent);
417
  if(isBulkPage && typeof data["Thumb"] !== 'undefined') { // && data["PercentImprovement"] > 0) {
418
+ if(data["BulkPercent"]) {
419
+ progressUpdate(data["BulkPercent"], data["BulkMsg"]);
420
+ }
421
  if(data["Thumb"].length > 0){
422
+ sliderUpdate(id, data["Thumb"], data["BkThumb"], data["PercentImprovement"], data["Filename"]);
423
+ if(typeof data["AverageCompression"] !== 'undefined' && 0 + data["AverageCompression"] > 0){
424
+ jQuery("#sp-avg-optimization").html('<input type="text" class="dial" value="' + Math.round(data["AverageCompression"]) + '"/>');
425
+ ShortPixel.percentDial("#sp-avg-optimization .dial", 60);
426
+ }
427
  }
428
  }
429
  console.log('Server response: ' + response);
432
  }
433
  setTimeout(checkBulkProgress, 5000);
434
  break;
435
+
 
436
  case ShortPixel.STATUS_SKIP:
437
+ if(data["Silent"] !== 1) {
438
+ ShortPixel.bulkShowError(id, data["Message"], data["Filename"], data["CustomImageLink"]);
439
+ }
440
+ //fall through
441
+ case ShortPixel.STATUS_ERROR: //for error and skip also we retry
442
  if(typeof data["Message"] !== 'undefined') {
443
  showToolBarAlert(ShortPixel.STATUS_SKIP, data["Message"] + ' Image ID: ' + id);
444
+ setCellMessage(id, data["Message"], "");
445
  }
446
+ //fall through
447
  case ShortPixel.STATUS_RETRY:
448
  console.log('Server response: ' + response);
449
  showToolBarAlert(ShortPixel.STATUS_RETRY, "");
450
  if(isBulkPage && typeof data["BulkPercent"] !== 'undefined') {
451
  progressUpdate(data["BulkPercent"], data["BulkMsg"]);
452
  }
453
+ if(isBulkPage && data["Count"] > 3) {
454
+ ShortPixel.bulkShowLengthyMsg(id, data["Filename"], data["CustomImageLink"]);
455
+ }
456
  setTimeout(checkBulkProgress, 5000);
457
  break;
458
+ default:
459
+ ShortPixel.retry(e.message);
460
+ break;
461
  }
462
  }
463
  },
475
  }
476
  }
477
 
478
+ function setCellMessage(id, message, actions){
479
  var msg = jQuery("#sp-msg-" + id);
480
+ if(msg.length > 0) {
481
+ msg.html("<div class='sp-column-actions' style='width:110px;'>" + actions + "</div>"
482
+ + "<div class='sp-column-info'>" + message + "</div>");
483
+ msg.css("color", "");
484
+ }
485
+ msg = jQuery("#sp-cust-msg-" + id);
486
+ if(msg.length > 0) {
487
+ msg.html("<div class='sp-column-info'>" + message + "</div>");
488
  }
489
  }
490
 
491
  function manualOptimization(id) {
492
+ setCellMessage(id, "<img src='" + ShortPixel.WP_PLUGIN_URL + "/res/img/loading.gif' class='sp-loading-small'>Image waiting to be processed", "");
493
  jQuery("li.shortpixel-toolbar-processing").removeClass("shortpixel-hide");
494
  jQuery("li.shortpixel-toolbar-processing").addClass("shortpixel-processing");
495
  var data = { action : 'shortpixel_manual_optimization',
496
  image_id: id};
497
+ jQuery.get(ShortPixel.AJAX_URL, data, function(response) {
498
+ data = JSON.parse(response);
499
+ if(data["Status"] == ShortPixel.STATUS_SUCCESS) {
500
+ setTimeout(checkBulkProgress, 2000);
501
+ } else {
502
+ setCellMessage(id, typeof data["Message"] !== "undefined" ? data["Message"] : "This content is not processable.", "");
503
+ }
504
  //aici e aici
505
  });
506
  }
507
 
508
  function reoptimize(id, type) {
509
+ setCellMessage(id, "<img src='" + ShortPixel.WP_PLUGIN_URL + "/res/img/loading.gif' class='sp-loading-small'>Image waiting to be reprocessed", "");
510
  jQuery("li.shortpixel-toolbar-processing").removeClass("shortpixel-hide");
511
  jQuery("li.shortpixel-toolbar-processing").addClass("shortpixel-processing");
512
  var data = { action : 'shortpixel_redo',
513
  attachment_ID: id,
514
  type: type};
515
+ jQuery.get(ShortPixel.AJAX_URL, data, function(response) {
516
  data = JSON.parse(response);
517
  if(data["Status"] == ShortPixel.STATUS_SUCCESS) {
518
  setTimeout(checkBulkProgress, 2000);
519
  } else {
520
  $msg = typeof data["Message"] !== "undefined" ? data["Message"] : "This content is not processable.";
521
+ setCellMessage(id, $msg, "");
522
  showToolBarAlert(ShortPixel.STATUS_FAIL, $msg);
523
  }
524
  });
525
  }
526
 
527
  function optimizeThumbs(id) {
528
+ setCellMessage(id, "<img src='" + ShortPixel.WP_PLUGIN_URL + "/res/img/loading.gif' class='sp-loading-small'>Image waiting to optimize thumbnails", "");
529
  jQuery("li.shortpixel-toolbar-processing").removeClass("shortpixel-hide");
530
  jQuery("li.shortpixel-toolbar-processing").addClass("shortpixel-processing");
531
  var data = { action : 'shortpixel_optimize_thumbs',
532
  attachment_ID: id};
533
+ jQuery.get(ShortPixel.AJAX_URL, data, function(response) {
534
  data = JSON.parse(response);
535
  if(data["Status"] == ShortPixel.STATUS_SUCCESS) {
536
  setTimeout(checkBulkProgress, 2000);
537
  } else {
538
+ setCellMessage(id, typeof data["Message"] !== "undefined" ? data["Message"] : "This content is not processable.", "");
539
  }
540
  });
541
  }
544
  jQuery("#short-pixel-notice-" + id).hide();
545
  var data = { action : 'shortpixel_dismiss_notice',
546
  notice_id: id};
547
+ jQuery.get(ShortPixel.AJAX_URL, data, function(response) {
548
  data = JSON.parse(response);
549
  if(data["Status"] == ShortPixel.STATUS_SUCCESS) {
550
  console.log("dismissed");
597
  }
598
  }
599
 
600
+
601
+
602
+ function sliderUpdate(id, thumb, bkThumb, percent, filename){
603
  var oldSlide = jQuery(".bulk-slider div.bulk-slide:first-child");
 
 
604
  if(oldSlide.attr("id") != "empty-slide") {
605
+ oldSlide.hide();
606
  }
607
  oldSlide.css("z-index", 1000);
608
+ jQuery(".bulk-img-opt", oldSlide).attr("src", "");
609
+ if(bkThumb.length > 0) {
610
+ jQuery(".bulk-img-orig", oldSlide).attr("src", "");
611
+ }
612
+
613
+ var newSlide = oldSlide.clone();
614
+ newSlide.attr("id", "slide-" + id);
615
  jQuery(".bulk-img-opt", newSlide).attr("src", thumb);
616
  if(bkThumb.length > 0) {
617
  jQuery(".img-original", newSlide).css("display", "inline-block");
619
  } else {
620
  jQuery(".img-original", newSlide).css("display", "none");
621
  }
622
+ jQuery(".bulk-opt-percent", newSlide).html('<input type="text" class="dial" value="' + percent + '"/>');
623
 
624
+ //debugger;
625
  jQuery(".bulk-slider").append(newSlide);
626
+ ShortPixel.percentDial("#" + newSlide.attr("id") + " .dial", 100);
627
+
628
+ jQuery(".bulk-slider-container span.filename").html("&nbsp;&nbsp;" + filename);
629
  if(oldSlide.attr("id") == "empty-slide") {
630
  oldSlide.remove();
631
  jQuery(".bulk-slider-container").css("display", "block");
632
  } else {
633
+ oldSlide.animate({ left: oldSlide.width() + oldSlide.position().left }, 'slow', 'swing', function(){
634
  oldSlide.remove();
635
  newSlide.fadeIn("slow");
636
  });
res/js/sp-file-tree.js ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ /*
3
+ * jQueryFileTree Plugin
4
+ *
5
+ * @author - Cory S.N. LaViska - A Beautiful Site (http://abeautifulsite.net/) - 24 March 2008
6
+ * @author - Dave Rogers - (https://github.com/daverogers/)
7
+ *
8
+ * Usage: $('.fileTreeDemo').fileTree({ options }, callback )
9
+ *
10
+ * TERMS OF USE
11
+ *
12
+ * This plugin is dual-licensed under the GNU General Public License and the MIT License and
13
+ * is copyright 2008 A Beautiful Site, LLC.
14
+ */
15
+ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
16
+
17
+ (function($, window) {
18
+ var FileTree;
19
+ FileTree = (function() {
20
+ function FileTree(el, args, callback) {
21
+ this.onEvent = bind(this.onEvent, this);
22
+ var $el, _this, defaults;
23
+ $el = $(el);
24
+ _this = this;
25
+ defaults = {
26
+ root: '/',
27
+ script: '/files/filetree',
28
+ folderEvent: 'click',
29
+ expandSpeed: 500,
30
+ collapseSpeed: 500,
31
+ expandEasing: 'swing',
32
+ collapseEasing: 'swing',
33
+ multiFolder: true,
34
+ loadMessage: 'Loading...',
35
+ errorMessage: 'Unable to get file tree information',
36
+ multiSelect: false,
37
+ onlyFolders: false,
38
+ onlyFiles: false,
39
+ preventLinkAction: false
40
+ };
41
+ this.jqft = {
42
+ container: $el
43
+ };
44
+ this.options = $.extend(defaults, args);
45
+ this.callback = callback;
46
+ this.data = {};
47
+ $el.html('<ul class="jqueryFileTree start"><li class="wait">' + this.options.loadMessage + '<li></ul>');
48
+ _this.showTree($el, escape(this.options.root), function() {
49
+ return _this._trigger('filetreeinitiated', {});
50
+ });
51
+ $el.delegate("li a", this.options.folderEvent, _this.onEvent);
52
+ }
53
+
54
+ FileTree.prototype.onEvent = function(event) {
55
+ var $ev, _this, callback, jqft, options, ref;
56
+ $ev = $(event.target);
57
+ options = this.options;
58
+ jqft = this.jqft;
59
+ _this = this;
60
+ callback = this.callback;
61
+ _this.data = {};
62
+ _this.data.li = $ev.closest('li');
63
+ _this.data.type = (ref = _this.data.li.hasClass('directory')) != null ? ref : {
64
+ 'directory': 'file'
65
+ };
66
+ _this.data.value = $ev.text();
67
+ _this.data.rel = $ev.prop('rel');
68
+ _this.data.container = jqft.container;
69
+ if (options.preventLinkAction) {
70
+ event.preventDefault();
71
+ }
72
+ if ($ev.parent().hasClass('directory')) {
73
+ _this.jqft.container.find('LI.directory').removeClass('selected');
74
+ $ev.parent().addClass('selected');
75
+ if ($ev.parent().hasClass('collapsed')) {
76
+ if (!options.multiFolder) {
77
+ $ev.parent().parent().find('UL').slideUp({
78
+ duration: options.collapseSpeed,
79
+ easing: options.collapseEasing
80
+ });
81
+ $ev.parent().parent().find('LI.directory').removeClass('expanded').addClass('collapsed');
82
+ }
83
+ $ev.parent().removeClass('collapsed').addClass('expanded');
84
+ $ev.parent().find('UL').remove();
85
+ return _this.showTree($ev.parent(), $ev.attr('rel'), function() {
86
+ _this._trigger('filetreeexpanded', _this.data);
87
+ return callback != null;
88
+ });
89
+ } else {
90
+ return $ev.parent().find('UL').slideUp({
91
+ duration: options.collapseSpeed,
92
+ easing: options.collapseEasing,
93
+ start: function() {
94
+ return _this._trigger('filetreecollapse', _this.data);
95
+ },
96
+ complete: function() {
97
+ $ev.parent().removeClass('expanded').addClass('collapsed');
98
+ _this._trigger('filetreecollapsed', _this.data);
99
+ return callback != null;
100
+ }
101
+ });
102
+ }
103
+ } else {
104
+ if (!options.multiSelect) {
105
+ jqft.container.find('li').removeClass('selected');
106
+ $ev.parent().addClass('selected');
107
+ } else {
108
+ if ($ev.parent().find('input').is(':checked')) {
109
+ $ev.parent().find('input').prop('checked', false);
110
+ $ev.parent().removeClass('selected');
111
+ } else {
112
+ $ev.parent().find('input').prop('checked', true);
113
+ $ev.parent().addClass('selected');
114
+ }
115
+ }
116
+ _this._trigger('filetreeclicked', _this.data);
117
+ return typeof callback === "function" ? callback($ev.attr('rel')) : void 0;
118
+ }
119
+ };
120
+
121
+ FileTree.prototype.showTree = function(el, dir, finishCallback) {
122
+ var $el, _this, data, handleFail, handleResult, options, result;
123
+ $el = $(el);
124
+ options = this.options;
125
+ _this = this;
126
+ $el.addClass('wait');
127
+ $(".jqueryFileTree.start").remove();
128
+ data = {
129
+ dir: dir,
130
+ onlyFolders: options.onlyFolders,
131
+ onlyFiles: options.onlyFiles,
132
+ multiSelect: options.multiSelect
133
+ };
134
+ handleResult = function(result) {
135
+ var li;
136
+ $el.find('.start').html('');
137
+ $el.removeClass('wait').append(result);
138
+ if (options.root === dir) {
139
+ $el.find('UL:hidden').show(typeof callback !== "undefined" && callback !== null);
140
+ } else {
141
+ if (jQuery.easing[options.expandEasing] === void 0) {
142
+ console.log('Easing library not loaded. Include jQueryUI or 3rd party lib.');
143
+ options.expandEasing = 'swing';
144
+ }
145
+ $el.find('UL:hidden').slideDown({
146
+ duration: options.expandSpeed,
147
+ easing: options.expandEasing,
148
+ start: function() {
149
+ return _this._trigger('filetreeexpand', _this.data);
150
+ },
151
+ complete: finishCallback
152
+ });
153
+ }
154
+ li = $('[rel="' + decodeURIComponent(dir) + '"]').parent();
155
+ if (options.multiSelect && li.children('input').is(':checked')) {
156
+ li.find('ul li input').each(function() {
157
+ $(this).prop('checked', true);
158
+ return $(this).parent().addClass('selected');
159
+ });
160
+ }
161
+ return false;
162
+ };
163
+ handleFail = function() {
164
+ $el.find('.start').html('');
165
+ $el.removeClass('wait').append("<p>" + options.errorMessage + "</p>");
166
+ return false;
167
+ };
168
+ if (typeof options.script === 'function') {
169
+ result = options.script(data);
170
+ if (typeof result === 'string' || result instanceof jQuery) {
171
+ return handleResult(result);
172
+ } else {
173
+ return handleFail();
174
+ }
175
+ } else {
176
+ return $.ajax({
177
+ url: options.script,
178
+ type: 'POST',
179
+ dataType: 'HTML',
180
+ data: data
181
+ }).done(function(result) {
182
+ return handleResult(result);
183
+ }).fail(function() {
184
+ return handleFail();
185
+ });
186
+ }
187
+ };
188
+
189
+ FileTree.prototype._trigger = function(eventType, data) {
190
+ var $el;
191
+ $el = this.jqft.container;
192
+ return $el.triggerHandler(eventType, data);
193
+ };
194
+
195
+ return FileTree;
196
+
197
+ })();
198
+ return $.fn.extend({
199
+ fileTree: function(args, callback) {
200
+ return this.each(function() {
201
+ var $this, data;
202
+ $this = $(this);
203
+ data = $this.data('fileTree');
204
+ if (!data) {
205
+ $this.data('fileTree', (data = new FileTree(this, args, callback)));
206
+ }
207
+ if (typeof args === 'string') {
208
+ return data[option].apply(data);
209
+ }
210
+ });
211
+ }
212
+ });
213
+ })(window.jQuery, window);
shortpixel-debug.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ function process_error_backtrace($errno, $errstr, $errfile, $errline, $errcontext) {
3
+ if(!(error_reporting() & $errno))
4
+ return;
5
+ switch($errno) {
6
+ case E_WARNING :
7
+ case E_USER_WARNING :
8
+ case E_STRICT :
9
+ case E_NOTICE :
10
+ case E_USER_NOTICE :
11
+ $type = 'warning';
12
+ $fatal = false;
13
+ break;
14
+ default :
15
+ $type = 'fatal error';
16
+ $fatal = true;
17
+ break;
18
+ }
19
+ $trace = array_reverse(debug_backtrace());
20
+ array_pop($trace);
21
+ if(php_sapi_name() == 'cli') {
22
+ echo 'Backtrace from ' . $type . ' \'' . $errstr . '\' at ' . $errfile . ' ' . $errline . ':' . "\n";
23
+ foreach($trace as $item)
24
+ echo ' ' . (isset($item['file']) ? $item['file'] : '<unknown file>') . ' ' . (isset($item['line']) ? $item['line'] : '<unknown line>') . ' calling ' . $item['function'] . '()' . "\n";
25
+ } else {
26
+ echo '<p class="error_backtrace">' . "\n";
27
+ echo ' Backtrace from ' . $type . ' \'' . $errstr . '\' at ' . $errfile . ' ' . $errline . ':' . "\n";
28
+ echo ' <ol>' . "\n";
29
+ foreach($trace as $item)
30
+ echo ' <li>' . (isset($item['file']) ? $item['file'] : '<unknown file>') . ' ' . (isset($item['line']) ? $item['line'] : '<unknown line>') . ' calling ' . $item['function'] . '()</li>' . "\n";
31
+ echo ' </ol>' . "\n";
32
+ echo '</p>' . "\n";
33
+ }
34
+ if(ini_get('log_errors')) {
35
+ $items = array();
36
+ foreach($trace as $item)
37
+ $items[] = (isset($item['file']) ? $item['file'] : '<unknown file>') . ' ' . (isset($item['line']) ? $item['line'] : '<unknown line>') . ' calling ' . $item['function'] . '()';
38
+ $message = 'Backtrace from ' . $type . ' \'' . $errstr . '\' at ' . $errfile . ' ' . $errline . ': ' . join(' | ', $items);
39
+ error_log($message);
40
+ }
41
+ if($fatal)
42
+ exit(1);
43
+ }
44
+
45
+ if(WP_DEBUG === true) {
46
+ set_error_handler('process_error_backtrace');
47
+ }
shortpixel_api.php CHANGED
@@ -14,6 +14,11 @@ class ShortPixelAPI {
14
  const STATUS_NOT_FOUND = -5;
15
  const STATUS_NO_KEY = -6;
16
  const STATUS_RETRY = -7;
 
 
 
 
 
17
 
18
  private $_settings;
19
  private $_maxAttempts = 10;
@@ -23,14 +28,23 @@ class ShortPixelAPI {
23
  public function __construct($settings) {
24
  $this->_settings = $settings;
25
  $this->_apiEndPoint = $this->_settings->httpProto . '://api.shortpixel.com/v2/reducer.php';
26
- add_action('processImageAction', array(&$this, 'processImageAction'), 10, 4);
27
  }
28
 
29
- public function processImageAction($url, $filePaths, $ID, $time) {
30
- $this->processImage($URLs, $PATHs, $ID, $time);
31
- }
 
32
 
33
- public function doRequests($URLs, $Blocking, $ID, $compressionType = false) {
 
 
 
 
 
 
 
 
34
 
35
  $requestParameters = array(
36
  'plugin_version' => PLUGIN_VERSION,
@@ -54,15 +68,20 @@ class ShortPixelAPI {
54
  'body' => json_encode($requestParameters),
55
  'cookies' => array()
56
  );
57
- //add this explicitely only for https, otherwise (for http) it slows the request
58
  if($this->_settings->httpProto !== 'https') {
59
  unset($arguments['sslverify']);
60
  }
 
 
 
61
  $response = wp_remote_post($this->_apiEndPoint, $arguments );
62
 
63
  //only if $Blocking is true analyze the response
64
  if ( $Blocking )
65
  {
 
 
66
  //die(var_dump(array('URL: ' => $this->_apiEndPoint, '<br><br>REQUEST:' => $arguments, '<br><br>RESPONSE: ' => $response )));
67
  //there was an error, save this error inside file's SP optimization field
68
  if ( is_object($response) && get_class($response) == 'WP_Error' )
@@ -78,79 +97,109 @@ class ShortPixelAPI {
78
 
79
  if ( isset($errorMessage) )
80
  {//set details inside file so user can know what happened
81
- $meta = wp_get_attachment_metadata($ID);
82
- $meta['ShortPixelImprovement'] = 'Error: <i>' . $errorMessage . '</i>';
83
- unset($meta['ShortPixel']['WaitingProcessing']);
84
- wp_update_attachment_metadata($ID, $meta);
 
 
85
  return array("response" => array("code" => $errorCode, "message" => $errorMessage ));
86
  }
87
 
88
  return $response;//this can be an error or a good response
89
  }
90
-
91
  return $response;
92
  }
93
 
 
 
 
 
 
94
  public function parseResponse($response) {
95
  $data = $response['body'];
96
- $data = $this->parseJSON($data);
97
  return (array)$data;
98
  }
99
 
100
- //handles the processing of the image using the ShortPixel API
101
- public function processImage($URLs, $PATHs, $ID = null, $startTime = 0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  {
103
-
104
- $meta = wp_get_attachment_metadata($ID);
105
 
106
  $PATHs = self::CheckAndFixImagePaths($PATHs);//check for images to make sure they exist on disk
107
  if ( $PATHs === false ) {
108
  $msg = 'The file(s) do not exist on disk.';
109
- $meta['ShortPixelImprovement'] = $msg;
110
- wp_update_attachment_metadata($ID, $meta);
111
- return array("Status" => self::STATUS_SKIP, "Message" => $msg);
 
112
  }
113
 
114
  //tries multiple times (till timeout almost reached) to fetch images.
115
  if($startTime == 0) {
116
  $startTime = time();
117
  }
118
- $apiRetries = get_option('wp-short-pixel-api-retries');
119
 
120
  if( time() - $startTime > MAX_EXECUTION_TIME)
121
  {//keeps track of time
122
  if ( $apiRetries > MAX_API_RETRIES )//we tried to process this time too many times, giving up...
123
  {
124
- $meta['ShortPixelImprovement'] = 'Timed out while processing.';
125
- unset($meta['ShortPixel']['WaitingProcessing']);
126
- update_option('wp-short-pixel-api-retries', 0);//fai added to solve a bug?
127
- wp_update_attachment_metadata($ID, $meta);
128
- return array("Status" => self::STATUS_SKIP, "Message" => 'Image ID: ' . $ID .' Skip this image, try the next one.');
 
 
 
 
129
  }
130
  else
131
  {//we'll try again next time user visits a page on admin panel
132
  $apiRetries++;
133
- update_option('wp-short-pixel-api-retries', $apiRetries);
134
- return array("Status" => self::STATUS_RETRY, "Message" => 'Timed out while processing. (pass '.$apiRetries.')');
 
135
  }
136
  }
137
 
138
- $compressionType = isset($meta['ShortPixel']['type']) ? ($meta['ShortPixel']['type'] == 'lossy' ? 1 : 0) : $this->_settings->compressionType;
139
- $response = $this->doRequests($URLs, true, $ID, $compressionType);//send requests to API
 
 
140
 
141
  if($response['response']['code'] != 200)//response <> 200 -> there was an error apparently?
142
  return array("Status" => self::STATUS_FAIL, "Message" => "There was an error and your request was not processed.");
143
 
144
  $APIresponse = $this->parseResponse($response);//get the actual response from API, its an array
145
-
146
- if ( isset($APIresponse[0]) )//API returned image details
147
  {
148
- foreach ( $APIresponse as $imageObject )//this part makes sure that all the sizes were processed and ready to be downloaded
149
- {
150
- if ( $imageObject->Status->Code == 0 || $imageObject->Status->Code == 1 )
151
- {
152
  sleep(1);
153
- return $this->processImage($URLs, $PATHs, $ID, $startTime);
154
  }
155
  }
156
 
@@ -159,43 +208,67 @@ class ShortPixelAPI {
159
  {
160
  case 2:
161
  //handle image has been processed
162
- if(!isset($firstImage->Status->quotaExceeded)) {
163
  $this->_settings->quotaExceeded = 0;//reset the quota exceeded flag
164
  }
165
- return $this->handleSuccess($APIresponse, $URLs, $PATHs, $ID, $compressionType);
166
- break;
167
  default:
168
  //handle error
169
- if ( !file_exists($PATHs[0]) )
170
- return array("Status" => self::STATUS_NOT_FOUND, "Message" => "File not found on disk. Image ID: " .$ID);
 
 
 
 
171
  elseif ( isset($APIresponse[0]->Status->Message) ) {
172
  //return array("Status" => self::STATUS_FAIL, "Message" => "There was an error and your request was not processed (" . $APIresponse[0]->Status->Message . "). REQ: " . json_encode($URLs));
173
- return array("Status" => self::STATUS_FAIL, "Message" => "There was an error and your request was not processed (" . $APIresponse[0]->Status->Message . ")");
 
 
 
174
  }
175
 
176
- return array("Status" => self::STATUS_FAIL, "Message" => "There was an error and your request was not processed");
177
- break;
 
 
 
 
 
 
178
  }
179
  }
180
 
181
- switch($APIresponse['Status']->Code)
182
- {
183
-
184
- case -403:
185
- @delete_option('bulkProcessingStatus');
186
- update_option( 'wp-short-pixel-quota-exceeded', 1);
187
- return array("Status" => self::STATUS_QUOTA_EXCEEDED, "Message" => "Quota exceeded.");
188
- break;
 
 
 
 
 
 
 
 
 
 
 
189
  }
190
-
191
- //sometimes the response array can be different
192
- if ( is_numeric($APIresponse['Status']->Code) )
193
- return array("Status" => self::STATUS_FAIL, "Message" => $APIresponse['Status']->Message);
194
- else
195
- return array("Status" => self::STATUS_FAIL, "Message" => $APIresponse[0]->Status->Message);
196
-
197
  }
198
 
 
 
 
 
 
 
 
 
199
  public function setPreferredProtocol($url, $reset = false) {
200
  //switch protocol based on the formerly detected working protocol
201
  if($this->_settings->downloadProto == '' || $reset) {
@@ -211,7 +284,13 @@ class ShortPixelAPI {
211
 
212
  }
213
 
214
- public function handleDownload($fileData,$counter, $compressionType){
 
 
 
 
 
 
215
  //var_dump($fileData);
216
  if($compressionType)
217
  {
@@ -232,83 +311,103 @@ class ShortPixelAPI {
232
  $fileURL = $this->setPreferredProtocol(urldecode($fileData->$fileType));
233
 
234
  $downloadTimeout = max(ini_get('max_execution_time') - 10, 15);
235
- $tempFiles[$counter] = download_url($fileURL, $downloadTimeout);
236
  //var_dump($tempFiles);
237
 
238
- if(is_wp_error( $tempFiles[$counter] ))
239
  { //try to switch the default protocol
240
  $fileURL = $this->setPreferredProtocol(urldecode($fileData->$fileType), true); //force recheck of the protocol
241
- $tempFiles[$counter] = download_url($fileURL, $downloadTimeout);
242
  }
243
  //on success we return this
244
- $returnMessage = array("Status" => self::STATUS_SUCCESS, "Message" => $tempFiles[$counter]);
245
 
246
- if ( is_wp_error( $tempFiles[$counter] ) ) {
247
- @unlink($tempFiles[$counter]);
248
  $returnMessage = array(
249
  "Status" => self::STATUS_ERROR,
250
- "Message" => "Error downloading file ({$fileData->$fileType}) " . $tempFiles[$counter]->get_error_message());
251
  }
252
  //check response so that download is OK
253
- elseif( filesize($tempFiles[$counter]) != $correctFileSize) {
254
- $size = filesize($tempFiles[$counter]);
255
- @unlink($tempFiles[$counter]);
256
  $returnMessage = array(
257
  "Status" => self::STATUS_ERROR,
258
  "Message" => "Error downloading file - incorrect file size (downloaded: {$size}, correct: {$correctFileSize} )");
259
  }
260
- elseif (!file_exists($tempFiles[$counter])) {
261
- $returnMessage = array("Status" => self::STATUS_ERROR, "Message" => "Unable to locate downloaded file " . $tempFiles[$counter]);
262
  }
263
  return $returnMessage;
264
  }
265
 
266
- public function handleSuccess($APIresponse, $URLs, $PATHs, $ID, $compressionType) {
 
 
 
 
 
 
 
 
267
  $counter = $savedSpace = $originalSpace = $optimizedSpace = $averageCompression = 0;
268
  $NoBackup = true;
269
-
 
 
270
  //download each file from array and process it
271
  foreach ( $APIresponse as $fileData )
272
  {
273
  if ( $fileData->Status->Code == 2 ) //file was processed OK
274
  {
275
- if ( $counter == 0 )//save percent improvement for main file
276
  $percentImprovement = $fileData->PercentImprovement;
277
- else //count thumbnails only
278
- update_option( 'wp-short-pixel-thumbnail-count', get_option('wp-short-pixel-thumbnail-count') + 1 );
279
- $downloadResult = $this->handleDownload($fileData,$counter,$compressionType);
280
- //when the status is STATUS_UNCHANGED we just skip the array line for that one
281
  if ( $downloadResult['Status'] == self::STATUS_SUCCESS ) {
282
  $tempFiles[$counter] = $downloadResult['Message'];
283
  }
284
- elseif ( $downloadResult['Status'] <> self::STATUS_UNCHANGED )
 
285
  return array("Status" => $downloadResult['Status'], "Message" => $downloadResult['Message']);
 
 
 
 
 
 
286
  }
287
- else //there was an error while trying to download a file
288
  $tempFiles[$counter] = "";
289
-
290
  $counter++;
291
  }
292
 
293
  //figure out in what SubDir files should land
294
- $SubDir = $this->returnSubDir(get_attached_file($ID));
 
 
 
295
 
296
  //if backup is enabled - we try to save the images
297
  if( $this->_settings->backupImages )
298
  {
299
- $uploadDir = wp_upload_dir();
300
  $source = $PATHs; //array with final paths for these files
301
 
302
  if( !file_exists(SP_BACKUP_FOLDER) && !@mkdir(SP_BACKUP_FOLDER, 0777, true) ) {//creates backup folder if it doesn't exist
303
  return array("Status" => self::STATUS_FAIL, "Message" => "Backup folder does not exist and it cannot be created");
304
  }
305
  //create subdir in backup folder if needed
306
- @mkdir( SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir, 0777, true);
307
 
308
  foreach ( $source as $fileID => $filePATH )//create destination files array
309
  {
310
- $destination[$fileID] = SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . self::MB_basename($source[$fileID]);
311
  }
 
312
 
313
  //now that we have original files and where we should back them up we attempt to do just that
314
  if(is_writable(SP_BACKUP_FOLDER))
@@ -319,20 +418,29 @@ class ShortPixelAPI {
319
  {
320
  if ( !@copy($source[$fileID], $destination[$fileID]) )
321
  {//file couldn't be saved in backup folder
322
- ShortPixelAPI::SaveMessageinMetadata($ID, 'Cannot save file <i>' . self::MB_basename($source[$fileID]) . '</i> in backup directory');
323
- return array("Status" => self::STATUS_FAIL, "Message" => 'Cannot save file <i>' . self::MB_basename($source[$fileID]) . '</i> in backup directory');
 
 
 
324
  }
325
  }
326
  }
327
  $NoBackup = true;
328
  } else {//cannot write to the backup dir, return with an error
329
- ShortPixelAPI::SaveMessageinMetadata($ID, 'Cannot save file in backup directory');
330
- return array("Status" => self::STATUS_FAIL, "Message" => 'Cannot save file in backup directory');
 
 
 
331
  }
332
 
333
  }//end backup section
334
 
335
  $writeFailed = 0;
 
 
 
336
 
337
  if ( !empty($tempFiles) )
338
  {
@@ -340,23 +448,29 @@ class ShortPixelAPI {
340
  foreach ( $tempFiles as $tempFileID => $tempFilePATH )
341
  {
342
  if ( file_exists($tempFilePATH) && file_exists($PATHs[$tempFileID]) && is_writable($PATHs[$tempFileID]) ) {
343
- /*
344
- echo("COPY FROM " . $tempFilePATH . " TO " . $PATHs[$tempFileID] . " TIME " . date("Y-m-d H:i:s"));
345
- if(file_exists($PATHs[$tempFileID])) {
346
- echo " TARGET IS THERE, SIZE " . filesize($PATHs[$tempFileID]) . ", FILEMTIME: " . date("Y-m-d H:i:s", filemtime($PATHs[$tempFileID])) . ", UNLINKING ... "
347
- }
348
- unlink($PATHs[$tempFileID]);
349
- if(file_exists($PATHs[$tempFileID])) {
350
- echo " NOT UNLINKED ";
351
- }
352
- */
353
  copy($tempFilePATH, $PATHs[$tempFileID]);
354
- /*
355
- if(file_exists($PATHs[$tempFileID])) {
356
- echo " FILE COPIED, SIZE " . filesize($PATHs[$tempFileID]) . ", FILEMTIME: " . date("Y-m-d H:i:s", filemtime($PATHs[$tempFileID])) . " ";
 
 
 
 
357
  }
358
- */
359
- } else {
 
 
 
 
 
 
 
 
 
 
 
 
360
  $writeFailed++;
361
  }
362
  @unlink($tempFilePATH);
@@ -364,30 +478,19 @@ class ShortPixelAPI {
364
 
365
  if ( $writeFailed > 0 )//there was an error
366
  {
367
- ShortPixelAPI::SaveMessageinMetadata($ID, 'Error: optimized version of ' . $writeFailed . ' file(s) couldn\'t be updated.');
 
 
 
368
  update_option('bulkProcessingStatus', "error");
369
- return array("Status" => self::STATUS_FAIL, "Code" =>"write-fail", "Message" => 'Error: optimized version of ' . $writeFailed . ' file(s) couldn\'t be updated.');
370
- }
371
- else
372
- {//all files were copied, optimization data regarding the savings locally in DB
373
- $fileType = ( $this->_settings->compressionType ) ? "LossySize" : "LoselessSize";
374
- $savedSpace += $APIresponse[$tempFileID]->OriginalSize - $APIresponse[$tempFileID]->$fileType;
375
- $originalSpace += $APIresponse[$tempFileID]->OriginalSize;
376
- $optimizedSpace += $APIresponse[$tempFileID]->$fileType;
377
- $averageCompression += $fileData->PercentImprovement;
378
-
379
- //add the number of files with < 5% optimization
380
- if ( ( ( 1 - $APIresponse[$tempFileID]->$fileType/$APIresponse[$tempFileID]->OriginalSize ) * 100 ) < 5 ) {
381
- $this->_settings->under5Percent++;
382
- }
383
  }
384
  } elseif( 0 + $fileData->PercentImprovement < 5) {
385
  $this->_settings->under5Percent++;
386
  }
387
  //old average counting
388
  $this->_settings->savedSpace += $savedSpace;
389
- $averageCompression = $this->_settings->averageCompression * $this->_settings->fileCount;
390
- $averageCompression = $averageCompression / ($this->_settings->fileCount + count($APIresponse));
391
  $this->_settings->averageCompression = $averageCompression;
392
  $this->_settings->fileCount += count($APIresponse);
393
  //new average counting
@@ -395,71 +498,45 @@ class ShortPixelAPI {
395
  $this->_settings->totalOptimized += $optimizedSpace;
396
 
397
  //update metadata for this file
398
- $duplicates = self::getWPMLDuplicates($ID);
399
- foreach($duplicates as $_ID) {
400
- $meta = wp_get_attachment_metadata($_ID);
401
- $meta['ShortPixel']['type'] = self::getCompressionTypeName($compressionType);
402
- $meta['ShortPixel']['exifKept'] = $this->_settings->keepExif;
403
- $meta['ShortPixel']['date'] = date("Y-m-d");
404
- //thumbs were processed if settings or if they were explicitely requested
405
- $meta['ShortPixel']['thumbsOpt'] = (isset($meta['ShortPixel']['thumbsTodo']) || $this->_settings->processThumbnails) && isset($meta['sizes']) ? count($meta['sizes']) : 0;
406
- //if thumbsTodo - this means there was an explicit request to process thumbs for an image that was previously processed without
407
- // don't update the ShortPixelImprovement ratio as this is only calculated based on main image
408
- if(isset($meta['ShortPixel']['thumbsTodo']) && is_numeric($meta['ShortPixelImprovement'])) {
409
- unset($meta['ShortPixel']['thumbsTodo']);
410
- $percentImprovement = $meta['ShortPixelImprovement'];
411
- } else {
412
- $meta['ShortPixelImprovement'] = "".round($percentImprovement,2);
413
- }
414
- if($NoBackup) {
415
- $meta['ShortPixel']['NoBackup'] = true;
416
- }
417
- wp_update_attachment_metadata($_ID, $meta);
418
  }
 
 
 
 
 
 
 
 
 
419
  //we reset the retry counter in case of success
420
- update_option('wp-short-pixel-api-retries', 0);
421
 
422
- return array("Status" => self::STATUS_SUCCESS, "Message" => 'Success: No pixels remained unsqueezed :-)', "PercentImprovement" => $percentImprovement);
423
  }//end handleSuccess
424
-
425
- static public function getWPMLDuplicates( $id ) {
426
- global $wpdb;
427
-
428
- $parentId = get_post_meta ($id, '_icl_lang_duplicate_of', true );
429
- if($parentId) $id = $parentId;
430
-
431
- $duplicates = $wpdb->get_col( $wpdb->prepare( "
432
- SELECT pm.post_id FROM {$wpdb->postmeta} pm
433
- WHERE pm.meta_value = %s AND pm.meta_key = '_icl_lang_duplicate_of'
434
- ", $id ) );
435
 
436
- if(!in_array($id, $duplicates)) $duplicates[] = $id;
437
-
438
- $transTable = $wpdb->get_results("SELECT COUNT(1) hasTransTable FROM information_schema.tables WHERE table_schema='{$wpdb->dbname}' AND table_name='{$wpdb->prefix}icl_translations'");
439
- if(isset($transTable[0]->hasTransTable) && $transTable[0]->hasTransTable > 0) {
440
- $transGroupId = $wpdb->get_results("SELECT trid FROM {$wpdb->prefix}icl_translations WHERE element_id = {$id}");
441
- if(count($transGroupId)) {
442
- $transGroup = $wpdb->get_results("SELECT element_id FROM {$wpdb->prefix}icl_translations WHERE trid = " . $transGroupId[0]->trid);
443
- foreach($transGroup as $trans) {
444
- $duplicates[] = $trans->element_id;
445
- }
446
- }
447
- }
448
-
449
- return array_unique($duplicates);
450
- }
451
-
452
-
453
- static public function returnSubDir($file)//return subdir for that particular attached file
454
- {
455
- $Atoms = explode("/", $file);
456
- $Counter = count($Atoms);
457
- $SubDir = $Atoms[$Counter-3] . DIRECTORY_SEPARATOR . $Atoms[$Counter-2] . DIRECTORY_SEPARATOR;
458
-
459
- return $SubDir;
460
- }
461
-
462
- //a basename alternative that deals OK with multibyte charsets (e.g. Arabic)
463
  static public function MB_basename($Path){
464
  $Separator = " qq ";
465
  $Path = preg_replace("/[^ ]/u", $Separator."\$0".$Separator, $Path);
@@ -468,9 +545,13 @@ class ShortPixelAPI {
468
  return $Base;
469
  }
470
 
471
- //sometimes, the paths to the files as defined in metadata are wrong, we try to automatically correct them
 
 
 
 
472
  static public function CheckAndFixImagePaths($PATHs){
473
-
474
  $ErrorCount = 0;
475
  $uploadDir = wp_upload_dir();
476
  $Tmp = explode("/", $uploadDir['basedir']);
@@ -483,18 +564,19 @@ class ShortPixelAPI {
483
  if ( !file_exists($File) ){
484
  //$NewFile = $uploadDir['basedir'] . "/" . substr($File,strpos($File, $StichString));//+strlen($StichString));
485
  $NewFile = $uploadDir['basedir'] . substr($File,strpos($File, $StichString)+strlen($StichString));
486
- if ( file_exists($NewFile) )
487
  $PATHs[$Id] = $NewFile;
488
- else
489
  $ErrorCount++;
 
490
  }
491
  }
492
 
493
- if ( $ErrorCount > 0 )
494
  return false;
495
- else
496
  return $PATHs;
497
-
498
  }
499
 
500
  static public function getCompressionTypeName($compressionType) {
@@ -508,15 +590,4 @@ class ShortPixelAPI {
508
  unset($meta['ShortPixel']['WaitingProcessing']);
509
  wp_update_attachment_metadata($ID, $meta);
510
  }
511
-
512
- public function parseJSON($data) {
513
- if ( function_exists('json_decode') ) {
514
- $data = json_decode( $data );
515
- } else {
516
- require_once( 'JSON/JSON.php' );
517
- $json = new Services_JSON( );
518
- $data = $json->decode( $data );
519
- }
520
- return $data;
521
- }
522
  }
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
 
23
  private $_settings;
24
  private $_maxAttempts = 10;
28
  public function __construct($settings) {
29
  $this->_settings = $settings;
30
  $this->_apiEndPoint = $this->_settings->httpProto . '://api.shortpixel.com/v2/reducer.php';
31
+ //# TODO verifica- pare la oha: add_action('processImageAction', array(&$this, 'processImageAction'), 10, 4);
32
  }
33
 
34
+ //# TODO verifica- pare la oha
35
+ //#public function processImageAction($url, $filePaths, $itemHandler, $time) {
36
+ //# $this->processImage($URLs, $PATHs, $itemHandler, $time);
37
+ //#}
38
 
39
+ /**
40
+ * sends a compression request to the API
41
+ * @param array $URLs - list of urls to send to API
42
+ * @param Boolean $Blocking - true means it will wait for an answer
43
+ * @param ShortPixelMetaFacade $itemHandler - the Facade that manages different types of image metadatas: MediaLibrary (postmeta table), ShortPixel custom (shortpixel_meta table)
44
+ * @param int $compressionType 1 - lossy, 0 - lossless
45
+ * @return response from wp_remote_post or error
46
+ */
47
+ public function doRequests($URLs, $Blocking, $itemHandler, $compressionType = false) {
48
 
49
  $requestParameters = array(
50
  'plugin_version' => PLUGIN_VERSION,
68
  'body' => json_encode($requestParameters),
69
  'cookies' => array()
70
  );
71
+ //add this explicitely only for https, otherwise (for http) it slows down the request
72
  if($this->_settings->httpProto !== 'https') {
73
  unset($arguments['sslverify']);
74
  }
75
+
76
+ WpShortPixel::log("Calling API with params : " . json_encode($arguments));
77
+
78
  $response = wp_remote_post($this->_apiEndPoint, $arguments );
79
 
80
  //only if $Blocking is true analyze the response
81
  if ( $Blocking )
82
  {
83
+ WpShortPixel::log("API response : " . json_encode($response));
84
+
85
  //die(var_dump(array('URL: ' => $this->_apiEndPoint, '<br><br>REQUEST:' => $arguments, '<br><br>RESPONSE: ' => $response )));
86
  //there was an error, save this error inside file's SP optimization field
87
  if ( is_object($response) && get_class($response) == 'WP_Error' )
97
 
98
  if ( isset($errorMessage) )
99
  {//set details inside file so user can know what happened
100
+ $itemHandler->setError($errorCode, $errorMessage);
101
+ $itemHandler->incrementRetries();
102
+ //$meta = wp_get_attachment_metadata($ID);
103
+ //$meta['ShortPixelImprovement'] = 'Error: <i>' . $errorMessage . '</i>';
104
+ //unset($meta['ShortPixel']['WaitingProcessing']);
105
+ //wp_update_attachment_metadata($ID, $meta);
106
  return array("response" => array("code" => $errorCode, "message" => $errorMessage ));
107
  }
108
 
109
  return $response;//this can be an error or a good response
110
  }
111
+
112
  return $response;
113
  }
114
 
115
+ /**
116
+ * parse the JSON response
117
+ * @param $response
118
+ * @return parsed array
119
+ */
120
  public function parseResponse($response) {
121
  $data = $response['body'];
122
+ $data = ShortPixelTools::parseJSON($data);
123
  return (array)$data;
124
  }
125
 
126
+ /**
127
+ * handles the processing of the image using the ShortPixel API
128
+ * @param array $URLs - list of urls to send to API
129
+ * @param array $PATHs - list of local paths for the images
130
+ * @param ShortPixelMetaFacade $itemHandler - the Facade that manages different types of image metadatas: MediaLibrary (postmeta table), ShortPixel custom (shortpixel_meta table)
131
+ * @return status/message array
132
+ */
133
+ public function processImage($URLs, $PATHs, $itemHandler = null) {
134
+ return $this->processImageRecursive($URLs, $PATHs, $itemHandler, 0);
135
+ }
136
+
137
+ /**
138
+ * handles the processing of the image using the ShortPixel API - cals itself recursively until success
139
+ * @param array $URLs - list of urls to send to API
140
+ * @param array $PATHs - list of local paths for the images
141
+ * @param ShortPixelMetaFacade $itemHandler - the Facade that manages different types of image metadatas: MediaLibrary (postmeta table), ShortPixel custom (shortpixel_meta table)
142
+ * @param type $startTime - time of the first call
143
+ * @return status/message array
144
+ */
145
+ private function processImageRecursive($URLs, $PATHs, $itemHandler = null, $startTime = 0)
146
  {
147
+ //#$meta = wp_get_attachment_metadata($ID);
 
148
 
149
  $PATHs = self::CheckAndFixImagePaths($PATHs);//check for images to make sure they exist on disk
150
  if ( $PATHs === false ) {
151
  $msg = 'The file(s) do not exist on disk.';
152
+ //#$meta['ShortPixelImprovement'] = $msg;
153
+ //#wp_update_attachment_metadata($ID, $meta);
154
+ $itemHandler->setError(self::ERR_FILE_NOT_FOUND, $msg );
155
+ return array("Status" => self::STATUS_SKIP, "Message" => $msg, "Silent" => $itemHandler->getType() == ShortPixelMetaFacade::CUSTOM_TYPE ? 1 : 0);
156
  }
157
 
158
  //tries multiple times (till timeout almost reached) to fetch images.
159
  if($startTime == 0) {
160
  $startTime = time();
161
  }
162
+ $apiRetries = $this->_settings->apiRetries;
163
 
164
  if( time() - $startTime > MAX_EXECUTION_TIME)
165
  {//keeps track of time
166
  if ( $apiRetries > MAX_API_RETRIES )//we tried to process this time too many times, giving up...
167
  {
168
+ //#$meta['ShortPixelImprovement'] = 'Timed out while processing.';
169
+ //#unset($meta['ShortPixel']['WaitingProcessing']);
170
+ //#wp_update_attachment_metadata($ID, $meta);
171
+ $itemHandler->setError(self::ERR_TIMEOUT, 'Timed out while processing.');
172
+ $itemHandler->incrementRetries();
173
+ $this->_settings->apiRetries = 0; //fai added to solve a bug?
174
+ return array("Status" => self::STATUS_SKIP,
175
+ "Message" => ($itemHandler->getType() == ShortPixelMetaFacade::CUSTOM_TYPE ? "Image ID: " : "Media ID: ")
176
+ . $itemHandler->getId() .' Skip this image, try the next one.');
177
  }
178
  else
179
  {//we'll try again next time user visits a page on admin panel
180
  $apiRetries++;
181
+ $this->_settings->apiRetries = $apiRetries;
182
+ return array("Status" => self::STATUS_RETRY, "Message" => 'Timed out while processing. (pass '.$apiRetries.')',
183
+ "Count" => $apiRetries);
184
  }
185
  }
186
 
187
+ //#$compressionType = isset($meta['ShortPixel']['type']) ? ($meta['ShortPixel']['type'] == 'lossy' ? 1 : 0) : $this->_settings->compressionType;
188
+ $meta = $itemHandler->getMeta();
189
+ $compressionType = $meta->getCompressionType() !== null ? $meta->getCompressionType() : $this->_settings->compressionType;
190
+ $response = $this->doRequests($URLs, true, $itemHandler, $compressionType);//send requests to API
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.");
194
 
195
  $APIresponse = $this->parseResponse($response);//get the actual response from API, its an array
196
+
197
+ if ( isset($APIresponse[0]) ) //API returned image details
198
  {
199
+ foreach ( $APIresponse as $imageObject ) {//this part makes sure that all the sizes were processed and ready to be downloaded
200
+ if ( $imageObject->Status->Code == 0 || $imageObject->Status->Code == 1 ) {
 
 
201
  sleep(1);
202
+ return $this->processImageRecursive($URLs, $PATHs, $itemHandler, $startTime);
203
  }
204
  }
205
 
208
  {
209
  case 2:
210
  //handle image has been processed
211
+ if(!isset($firstImage->Status->QuotaExceeded)) {
212
  $this->_settings->quotaExceeded = 0;//reset the quota exceeded flag
213
  }
214
+ return $this->handleSuccess($APIresponse, $PATHs, $itemHandler, $compressionType);
 
215
  default:
216
  //handle error
217
+ if ( !file_exists($PATHs[0]) ) {
218
+ $itemHandler->incrementRetries(2);
219
+ $err = array("Status" => self::STATUS_NOT_FOUND, "Message" => "File not found on disk. "
220
+ . ($itemHandler->getType() == ShortPixelMetaFacade::CUSTOM_TYPE ? "Image " : "Media ")
221
+ . " ID: " . $itemHandler->getId());
222
+ }
223
  elseif ( isset($APIresponse[0]->Status->Message) ) {
224
  //return array("Status" => self::STATUS_FAIL, "Message" => "There was an error and your request was not processed (" . $APIresponse[0]->Status->Message . "). REQ: " . json_encode($URLs));
225
+ $err = array("Status" => self::STATUS_FAIL, "Code" => (isset($APIresponse[0]->Status->Code) ? $APIresponse[0]->Status->Code : ""),
226
+ "Message" => "There was an error and your request was not processed (" . $APIresponse[0]->Status->Message . ")");
227
+ } else {
228
+ $err = array("Status" => self::STATUS_FAIL, "Message" => "There was an error and your request was not processed");
229
  }
230
 
231
+ $itemHandler->incrementRetries();
232
+ $meta = $itemHandler->getMeta();
233
+ if($meta->getRetries() >= MAX_FAIL_RETRIES) {
234
+ $meta->setStatus($APIresponse[0]->Status->Code);
235
+ $meta->setMessage($APIresponse[0]->Status->Message);
236
+ $itemHandler->updateMeta();
237
+ }
238
+ return $err;
239
  }
240
  }
241
 
242
+ if(!isset($APIresponse['Status'])) {
243
+ WpShortPixel::log("API Response Status unfound : " . json_encode($APIresponse));
244
+ return array("Status" => self::STATUS_FAIL, "Message" => "Unecognized API response. Please contact support.");
245
+ } else {
246
+ switch($APIresponse['Status']->Code)
247
+ {
248
+ case -403:
249
+ @delete_option('bulkProcessingStatus');
250
+ $this->_settings->quotaExceeded = 1;
251
+ return array("Status" => self::STATUS_QUOTA_EXCEEDED, "Message" => "Quota exceeded.");
252
+ break;
253
+ }
254
+
255
+ //sometimes the response array can be different
256
+ if (is_numeric($APIresponse['Status']->Code)) {
257
+ return array("Status" => self::STATUS_FAIL, "Message" => $APIresponse['Status']->Message);
258
+ } else {
259
+ return array("Status" => self::STATUS_FAIL, "Message" => $APIresponse[0]->Status->Message);
260
+ }
261
  }
 
 
 
 
 
 
 
262
  }
263
 
264
+ /**
265
+ * sets the preferred protocol of URL using the globally set preferred protocol.
266
+ * If global protocol not set, sets it by testing the download of a http test image from ShortPixel site.
267
+ * If http works then it's http, otherwise sets https
268
+ * @param string $url
269
+ * @param bool $reset - forces recheck even if preferred protocol is already set
270
+ * @return url with the preferred protocol
271
+ */
272
  public function setPreferredProtocol($url, $reset = false) {
273
  //switch protocol based on the formerly detected working protocol
274
  if($this->_settings->downloadProto == '' || $reset) {
284
 
285
  }
286
 
287
+ /**
288
+ * handles the download of an optimized image from ShortPixel API
289
+ * @param type $fileData - info about the file
290
+ * @param int $compressionType - 1 - lossy, 0 - lossless
291
+ * @return status/message array
292
+ */
293
+ private function handleDownload($fileData, $compressionType){
294
  //var_dump($fileData);
295
  if($compressionType)
296
  {
311
  $fileURL = $this->setPreferredProtocol(urldecode($fileData->$fileType));
312
 
313
  $downloadTimeout = max(ini_get('max_execution_time') - 10, 15);
314
+ $tempFile = download_url($fileURL, $downloadTimeout);
315
  //var_dump($tempFiles);
316
 
317
+ if(is_wp_error( $tempFile ))
318
  { //try to switch the default protocol
319
  $fileURL = $this->setPreferredProtocol(urldecode($fileData->$fileType), true); //force recheck of the protocol
320
+ $tempFile = download_url($fileURL, $downloadTimeout);
321
  }
322
  //on success we return this
323
+ $returnMessage = array("Status" => self::STATUS_SUCCESS, "Message" => $tempFile);
324
 
325
+ if ( is_wp_error( $tempFile ) ) {
326
+ @unlink($tempFile);
327
  $returnMessage = array(
328
  "Status" => self::STATUS_ERROR,
329
+ "Message" => "Error downloading file ({$fileData->$fileType}) " . $tempFile->get_error_message());
330
  }
331
  //check response so that download is OK
332
+ elseif( filesize($tempFile) != $correctFileSize) {
333
+ $size = filesize($tempFile);
334
+ @unlink($tempFile);
335
  $returnMessage = array(
336
  "Status" => self::STATUS_ERROR,
337
  "Message" => "Error downloading file - incorrect file size (downloaded: {$size}, correct: {$correctFileSize} )");
338
  }
339
+ elseif (!file_exists($tempFile)) {
340
+ $returnMessage = array("Status" => self::STATUS_ERROR, "Message" => "Unable to locate downloaded file " . $tempFile);
341
  }
342
  return $returnMessage;
343
  }
344
 
345
+ /**
346
+ * handles a successful optimization, setting metadata and handling download for each file in the set
347
+ * @param type $APIresponse - the response from the API - contains the optimized images URLs to download
348
+ * @param type $PATHs - list of local paths for the files
349
+ * @param ShortPixelMetaFacade $itemHandler - the Facade that manages different types of image metadatas: MediaLibrary (postmeta table), ShortPixel custom (shortpixel_meta table)
350
+ * @param int $compressionType - 1 - lossy, 0 - lossless
351
+ * @return status/message array
352
+ */
353
+ private function handleSuccess($APIresponse, $PATHs, $itemHandler, $compressionType) {
354
  $counter = $savedSpace = $originalSpace = $optimizedSpace = $averageCompression = 0;
355
  $NoBackup = true;
356
+
357
+ $fileType = ( $compressionType ) ? "LossySize" : "LoselessSize";
358
+
359
  //download each file from array and process it
360
  foreach ( $APIresponse as $fileData )
361
  {
362
  if ( $fileData->Status->Code == 2 ) //file was processed OK
363
  {
364
+ if ( $counter == 0 ) { //save percent improvement for main file
365
  $percentImprovement = $fileData->PercentImprovement;
366
+ } else { //count thumbnails only
367
+ $this->_settings->thumbsCount = $this->_settings->thumbsCount + 1;
368
+ }
369
+ $downloadResult = $this->handleDownload($fileData,$compressionType);
370
  if ( $downloadResult['Status'] == self::STATUS_SUCCESS ) {
371
  $tempFiles[$counter] = $downloadResult['Message'];
372
  }
373
+ //when the status is STATUS_UNCHANGED we just skip the array line for that one
374
+ elseif ( $downloadResult['Status'] <> self::STATUS_UNCHANGED ) {
375
  return array("Status" => $downloadResult['Status'], "Message" => $downloadResult['Message']);
376
+ }
377
+ else { //this image is unchanged so won't be copied below, only the optimization stats need to be computed
378
+ $originalSpace += $fileData->OriginalSize;
379
+ $optimizedSpace += $fileData->$fileType;
380
+ }
381
+
382
  }
383
+ else { //there was an error while trying to download a file
384
  $tempFiles[$counter] = "";
385
+ }
386
  $counter++;
387
  }
388
 
389
  //figure out in what SubDir files should land
390
+ //#$SubDir = $this->returnSubDir(get_attached_file($ID));
391
+ $fullSubDir = str_replace(WP_CONTENT_DIR, "", dirname($itemHandler->getMeta()->getPath())) . DIRECTORY_SEPARATOR;
392
+ //die("Uploads base: " . SP_UPLOADS_BASE . " FullSubDir: ". $fullSubDir . " PATH: " . dirname($itemHandler->getMeta()->getPath()));
393
+ $SubDir = ShortPixelMetaFacade::returnSubDir($itemHandler->getMeta()->getPath(), $itemHandler->getType());
394
 
395
  //if backup is enabled - we try to save the images
396
  if( $this->_settings->backupImages )
397
  {
 
398
  $source = $PATHs; //array with final paths for these files
399
 
400
  if( !file_exists(SP_BACKUP_FOLDER) && !@mkdir(SP_BACKUP_FOLDER, 0777, true) ) {//creates backup folder if it doesn't exist
401
  return array("Status" => self::STATUS_FAIL, "Message" => "Backup folder does not exist and it cannot be created");
402
  }
403
  //create subdir in backup folder if needed
404
+ @mkdir( SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $fullSubDir, 0777, true);
405
 
406
  foreach ( $source as $fileID => $filePATH )//create destination files array
407
  {
408
+ $destination[$fileID] = SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $fullSubDir . self::MB_basename($source[$fileID]);
409
  }
410
+ //die("IZ BACKUP: " . SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . var_dump($destination));
411
 
412
  //now that we have original files and where we should back them up we attempt to do just that
413
  if(is_writable(SP_BACKUP_FOLDER))
418
  {
419
  if ( !@copy($source[$fileID], $destination[$fileID]) )
420
  {//file couldn't be saved in backup folder
421
+ $msg = 'Cannot save file <i>' . self::MB_basename($source[$fileID]) . '</i> in backup directory';
422
+ //#ShortPixelAPI::SaveMessageinMetadata($ID, $msg);
423
+ $itemHandler->setError(self::ERR_SAVE_BKP, $msg);
424
+ $itemHandler->incrementRetries();
425
+ return array("Status" => self::STATUS_FAIL, "Message" => $msg);
426
  }
427
  }
428
  }
429
  $NoBackup = true;
430
  } else {//cannot write to the backup dir, return with an error
431
+ //#ShortPixelAPI::SaveMessageinMetadata($ID, 'Cannot save file in backup directory');
432
+ $msg = 'Cannot save file in backup directory';
433
+ $itemHandler->setError(self::ERR_SAVE_BKP, $msg);
434
+ $itemHandler->incrementRetries();
435
+ return array("Status" => self::STATUS_FAIL, "Message" => $msg);
436
  }
437
 
438
  }//end backup section
439
 
440
  $writeFailed = 0;
441
+ $firstImage = true;
442
+ $width = $height = null;
443
+ $resize = $this->_settings->resizeImages;
444
 
445
  if ( !empty($tempFiles) )
446
  {
448
  foreach ( $tempFiles as $tempFileID => $tempFilePATH )
449
  {
450
  if ( file_exists($tempFilePATH) && file_exists($PATHs[$tempFileID]) && is_writable($PATHs[$tempFileID]) ) {
 
 
 
 
 
 
 
 
 
 
451
  copy($tempFilePATH, $PATHs[$tempFileID]);
452
+ if($firstImage) { //this is the main image
453
+ $firstImage = false;
454
+ if($resize) {
455
+ $size = getimagesize($PATHs[$tempFileID]);
456
+ $width = $size[0];
457
+ $height = $size[1];
458
+ }
459
  }
460
+ //Calculate the saved space
461
+ $fileData = $APIresponse[$tempFileID];
462
+ $savedSpace += $fileData->OriginalSize - $fileData->$fileType;
463
+ $originalSpace += $fileData->OriginalSize;
464
+ $optimizedSpace += $fileData->$fileType;
465
+ $averageCompression += $fileData->PercentImprovement;
466
+ WPShortPixel::log("HANDLE SUCCESS: Image " . $PATHs[$tempFileID] . " original size: ".$fileData->OriginalSize . " optimized: " . $fileData->$fileType);
467
+
468
+ //add the number of files with < 5% optimization
469
+ if ( ( ( 1 - $APIresponse[$tempFileID]->$fileType/$APIresponse[$tempFileID]->OriginalSize ) * 100 ) < 5 ) {
470
+ $this->_settings->under5Percent++;
471
+ }
472
+ }
473
+ else {
474
  $writeFailed++;
475
  }
476
  @unlink($tempFilePATH);
478
 
479
  if ( $writeFailed > 0 )//there was an error
480
  {
481
+ $msg = 'Optimized version of ' . $writeFailed . ' file(s) couldn\'t be updated.';
482
+ //#ShortPixelAPI::SaveMessageinMetadata($ID, 'Error: optimized version of ' . $writeFailed . ' file(s) couldn\'t be updated.');
483
+ $itemHandler->setError(self::ERR_SAVE, $msg);
484
+ $itemHandler->incrementRetries();
485
  update_option('bulkProcessingStatus', "error");
486
+ return array("Status" => self::STATUS_FAIL, "Code" =>"write-fail", "Message" => $msg);
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  }
488
  } elseif( 0 + $fileData->PercentImprovement < 5) {
489
  $this->_settings->under5Percent++;
490
  }
491
  //old average counting
492
  $this->_settings->savedSpace += $savedSpace;
493
+ $averageCompression = $this->_settings->averageCompression * $this->_settings->fileCount / ($this->_settings->fileCount + count($APIresponse));
 
494
  $this->_settings->averageCompression = $averageCompression;
495
  $this->_settings->fileCount += count($APIresponse);
496
  //new average counting
498
  $this->_settings->totalOptimized += $optimizedSpace;
499
 
500
  //update metadata for this file
501
+ $meta = $itemHandler->getMeta();
502
+ // die(var_dump($percentImprovement));
503
+ if($meta->getThumbsTodo()) {
504
+ $percentImprovement = $meta->getImprovementPercent();
505
+ }
506
+ $meta->setMessage($originalSpace
507
+ ? number_format(100.0 - 100.0 * $optimizedSpace / $originalSpace, 2)
508
+ : "Couldn't compute thumbs optimization percent. Main image: " . $percentImprovement);
509
+ WPShortPixel::log("HANDLE SUCCESS: Image optimization: ".$meta->getMessage());
510
+ $meta->setCompressionType($compressionType);
511
+ $meta->setCompressedSize(filesize($meta->getPath()));
512
+ $meta->setKeepExif($this->_settings->keepExif);
513
+ $meta->setTsOptimized(date("Y-m-d H:i:s"));
514
+ $meta->setThumbsOpt(($meta->getThumbsTodo() || $this->_settings->processThumbnails) ? count($meta->getThumbs()) : 0);
515
+ $meta->setThumbsTodo(false);
516
+ if($width && $height) {
517
+ $meta->setActualWidth($width);
518
+ $meta->setActualHeight($height);
 
 
519
  }
520
+ $meta->setRetries($meta->getRetries() + 1);
521
+ $meta->setBackup(!$NoBackup);
522
+ $meta->setStatus(2);
523
+
524
+ $itemHandler->updateMeta($meta);
525
+ if(!$originalSpace) { //das kann nicht sein, alles klar?!
526
+ throw new Exception("OriginalSpace = 0. APIResponse" . json_encode($APIresponse));
527
+ }
528
+
529
  //we reset the retry counter in case of success
530
+ $this->_settings->apiRetries = 0;
531
 
532
+ return array("Status" => self::STATUS_SUCCESS, "Message" => 'Success: No pixels remained unsqueezed :-)', "PercentImprovement" => $meta->getMessage());
533
  }//end handleSuccess
 
 
 
 
 
 
 
 
 
 
 
534
 
535
+ /**
536
+ * a basename alternative that deals OK with multibyte charsets (e.g. Arabic)
537
+ * @param string $Path
538
+ * @return string
539
+ */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
  static public function MB_basename($Path){
541
  $Separator = " qq ";
542
  $Path = preg_replace("/[^ ]/u", $Separator."\$0".$Separator, $Path);
545
  return $Base;
546
  }
547
 
548
+ /**
549
+ * sometimes, the paths to the files as defined in metadata are wrong, we try to automatically correct them
550
+ * @param type $PATHs
551
+ * @return boolean|string
552
+ */
553
  static public function CheckAndFixImagePaths($PATHs){
554
+
555
  $ErrorCount = 0;
556
  $uploadDir = wp_upload_dir();
557
  $Tmp = explode("/", $uploadDir['basedir']);
564
  if ( !file_exists($File) ){
565
  //$NewFile = $uploadDir['basedir'] . "/" . substr($File,strpos($File, $StichString));//+strlen($StichString));
566
  $NewFile = $uploadDir['basedir'] . substr($File,strpos($File, $StichString)+strlen($StichString));
567
+ if (file_exists($NewFile)) {
568
  $PATHs[$Id] = $NewFile;
569
+ } else {
570
  $ErrorCount++;
571
+ }
572
  }
573
  }
574
 
575
+ if ( $ErrorCount > 0 ) {
576
  return false;
577
+ } else {
578
  return $PATHs;
579
+ }
580
  }
581
 
582
  static public function getCompressionTypeName($compressionType) {
590
  unset($meta['ShortPixel']['WaitingProcessing']);
591
  wp_update_attachment_metadata($ID, $meta);
592
  }
 
 
 
 
 
 
 
 
 
 
 
593
  }
shortpixel_queue.php CHANGED
@@ -4,15 +4,6 @@ class ShortPixelQueue {
4
 
5
  private $ctrl;
6
  private $settings;
7
- private $startBulkId;
8
- private $stopBulkId;
9
- private $bulkCount;
10
- private $bulkPreviousPercent;
11
- private $bulkCurrentlyProcessed;
12
- private $bulkAlreadyDoneCount;
13
- private $lastBulkStartTime;
14
- private $lastBulkSuccessTime;
15
- private $bulkRunningTime;
16
 
17
  const BULK_NEVER = 0; //bulk never ran
18
  const BULK_RUNNING = 1; //bulk is running
@@ -23,31 +14,31 @@ class ShortPixelQueue {
23
  $this->ctrl = $controller;
24
  $this->settings = $settings;
25
  //init the option if needed
26
- if(!isset($_SESSION["wp-short-pixel-priorityQueue"])) {
 
27
  //take the priority list from the options (we persist there the priority IDs from the previous session)
28
  $prioQueueOpt = $this->settings->getOpt( 'wp-short-pixel-priorityQueue', array());//here we save the IDs for the files that need to be processed after an image upload for example
29
  $_SESSION["wp-short-pixel-priorityQueue"] = array();
30
  foreach($prioQueueOpt as $ID) {
31
- $meta = wp_get_attachment_metadata($ID);
 
 
 
 
 
 
32
  WPShortPixel::log("INIT: Item $ID from options has metadata: " .json_encode($meta));
33
- if(!isset($meta['ShortPixelImprovement'])) {
34
  $this->push($ID);
35
  }
36
  }
37
- $this->settings->setOpt('wp-short-pixel-priorityQueue', $_SESSION["wp-short-pixel-priorityQueue"]);
38
- WPShortPixel::log("INIT: Session queue not found, updated from Options with "
 
 
39
  .json_encode($_SESSION["wp-short-pixel-priorityQueue"]));
 
40
  }
41
-
42
- $this->startBulkId = $this->settings->getOpt( 'wp-short-pixel-query-id-start', 0);//current query ID used for postmeta queries
43
- $this->stopBulkId = $this->settings->getOpt( 'wp-short-pixel-query-id-stop', 0);//min ID used for postmeta queries
44
- $this->bulkCount = $this->settings->getOpt( "wp-short-pixel-bulk-count", 0);
45
- $this->bulkPreviousPercent = $this->settings->getOpt( "wp-short-pixel-bulk-previous-percent", 0);
46
- $this->bulkCurrentlyProcessed = $this->settings->getOpt( "wp-short-pixel-bulk-processed-items", 0);
47
- $this->bulkAlreadyDoneCount = $this->settings->getOpt( "wp-short-pixel-bulk-done-count", 0);
48
- $this->lastBulkStartTime = $this->settings->getOpt( 'wp-short-pixel-last-bulk-start-time', 0);//time of the last start of the bulk.
49
- $this->lastBulkSuccessTime = $this->settings->getOpt( 'wp-short-pixel-last-bulk-success-time', 0);//time of the last start of the bulk.
50
- $this->bulkRunningTime = $this->settings->getOpt( 'wp-short-pixel-bulk-running-time', 0);//how long the bulk ran that far.
51
  }
52
 
53
  //handling older
@@ -62,46 +53,46 @@ class ShortPixelQueue {
62
  public function skip($id) {
63
  if(is_array($this->settings->prioritySkip)) {
64
  $this->settings->prioritySkip = array_merge($this->settings->prioritySkip, array($id));
65
- } else {
66
  $this->settings->prioritySkip = array($id);
67
  }
68
- }
69
-
70
- public function allSkipped() {
71
- if( !is_array($this->settings->prioritySkip) ) return false;
72
- count(array_diff($_SESSION["wp-short-pixel-priorityQueue"], $this->settings->prioritySkip));
73
- }
74
-
75
- public function skippedCount() {
76
- return is_array($this->settings->prioritySkip) ? count($this->settings->prioritySkip) : 0;
77
- }
78
-
79
- public function isSkipped($id) {
80
- return is_array($this->settings->prioritySkip) && in_array($id, $this->settings->prioritySkip);
81
- }
82
-
83
- public function isPrio($id) {
84
- return is_array($_SESSION["wp-short-pixel-priorityQueue"]) && in_array($id, $_SESSION["wp-short-pixel-priorityQueue"]);
85
- }
86
-
87
- public function getSkipped() {
88
- return $this->settings->prioritySkip;
89
- }
90
-
91
- public function reverse() {
92
- $this->settings->priorityQueue = $_SESSION["wp-short-pixel-priorityQueue"] = array_reverse($_SESSION["wp-short-pixel-priorityQueue"]);
93
-
94
- }
95
-
96
- public function push($ID)//add an ID to priority queue
97
  {
98
  $priorityQueue = $_SESSION["wp-short-pixel-priorityQueue"]; //get_option("wp-short-pixel-priorityQueue");
99
  WPShortPixel::log("PUSH: Push ID $ID into queue ".json_encode($priorityQueue));
100
  array_push($priorityQueue, $ID);
101
  $prioQ = array_unique($priorityQueue);
102
  $_SESSION["wp-short-pixel-priorityQueue"] = $prioQ;
103
- //push also to the options queue, in case the session gets killed retrieve frm there
104
- $this->settings->setOpt('wp-short-pixel-priorityQueue', $prioQ);
105
 
106
  WPShortPixel::log("PUSH: Updated: ".json_encode($_SESSION["wp-short-pixel-priorityQueue"]));//get_option("wp-short-pixel-priorityQueue")));
107
  }
@@ -112,6 +103,28 @@ class ShortPixelQueue {
112
  $count = min(count($priorityQueue), $count);
113
  return(array_slice($priorityQueue, count($priorityQueue) - $count, $count));
114
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
  public function remove($ID)//remove an ID from priority queue
117
  {
@@ -133,34 +146,40 @@ class ShortPixelQueue {
133
  }
134
 
135
  public function removeFromFailed($ID) {
136
- $failed = explode(",", $this->settings->getOpt('wp-short-pixel-failed-imgs',''));
137
  $key = array_search($ID, $failed);
138
  if($key !== false) {
139
  unset($failed[$key]);
140
  $failed = array_values($failed);
141
- $this->settings->setOpt('wp-short-pixel-failed-imgs', implode(",", $failed) );
142
  }
143
  }
144
 
145
  public function addToFailed($ID) {
146
- $failed = $this->settings->getOpt('wp-short-pixel-failed-imgs','');
147
  if(!in_array($ID, explode(",", $failed))) {
148
- $this->settings->setOpt('wp-short-pixel-failed-imgs', (strlen($failed) ? $failed . "," : "") . $ID );
149
  }
150
  }
151
 
152
  public function getFailed() {
153
- $failed = $this->settings->getOpt('wp-short-pixel-failed-imgs','');
154
- $failed = "83";
155
  if(!strlen($failed)) return array();
156
  $ret = explode(",", $failed);
157
  $fails = array();
158
  foreach($ret as $fail) {
159
- $meta = wp_get_attachment_metadata($fail);
160
- if(!$meta || (isset($meta["ShortPixelImprovement"]) && is_numeric($meta["ShortPixelImprovement"]))){
161
- $this->removeFromFailed($fail);
 
 
162
  } else {
163
- $fails[] = $fail;
 
 
 
 
 
164
  }
165
  }
166
  return $fails;
@@ -168,16 +187,16 @@ class ShortPixelQueue {
168
 
169
  public function bulkRunning() {
170
  //$bulkProcessingStatus = get_option('bulkProcessingStatus');
171
- return $this->startBulkId > $this->stopBulkId;
172
  }
173
 
174
  public function bulkPaused() {
175
- WPShortPixel::log("Bulk Paused: " . get_option( 'wp-short-pixel-cancel-pointer'));
176
- return $this->settings->getOpt( 'wp-short-pixel-cancel-pointer', 0);
177
  }
178
 
179
  public function bulkRan() {
180
- return $this->settings->getOpt("wp-short-pixel-bulk-ever-ran", 0) != 0;
181
  }
182
 
183
  public function processing() {
@@ -186,60 +205,52 @@ class ShortPixelQueue {
186
  }
187
 
188
  public function getFlagBulkId() {
189
- return $this->settings->getOpt("wp-short-pixel-flag-id",0);
190
  }
191
 
192
  public function getStartBulkId() {
193
- return $this->startBulkId;
194
  }
195
 
196
  public function resetStartBulkId() {
197
- $this->setStartBulkId(WPShortPixel::getMaxMediaId());
198
  }
199
 
200
  public function setStartBulkId($start){
201
- $this->startBulkId = $start;
202
- $this->settings->setOpt("wp-short-pixel-query-id-start", $this->startBulkId);
203
  }
204
 
205
  public function getStopBulkId() {
206
- return $this->stopBulkId;
207
  }
208
 
209
  public function resetStopBulkId() {
210
- $this->stopBulkId = $this->ctrl->getMinMediaId();
211
- $this->settings->setOpt("wp-short-pixel-query-id-stop", $this->stopBulkId);
212
  }
213
 
214
  public function setBulkPreviousPercent() {
215
  //processable and already processed
216
- $res = $this->ctrl->countAllProcessableFiles($this->getFlagBulkId(), $this->stopBulkId);
217
- $this->bulkCount = $res["mainFiles"];
218
- $this->settings->setOpt("wp-short-pixel-bulk-count", $this->bulkCount);
219
 
220
  //if compression type changed, add also the images with the other compression type
221
- $this->bulkAlreadyDoneCount = $res["mainProcessedFiles"] - $res["mainProc".((0 + $this->ctrl->getCompressionType() == 1) ? "Lossless" : "Lossy")."Files"];
222
  // if the thumbnails are to be processed, add also the images that have thumbs not processed
223
  if($this->settings->processThumbnails) {
224
- $this->bulkAlreadyDoneCount -= $res["mainUnprocessedThumbs"];
225
  }
226
 
227
- //die(var_dump($res));
228
-
229
- $this->settings->setOpt("wp-short-pixel-bulk-done-count", $this->bulkAlreadyDoneCount);
230
  //percent already done
231
- $this->bulkPreviousPercent = round($this->bulkAlreadyDoneCount / ($this->bulkCount ? $this->bulkCount : 1) * 100);
232
- $this->settings->setOpt("wp-short-pixel-bulk-previous-percent", $this->bulkPreviousPercent);
233
  }
234
 
235
  public function getBulkToProcess() {
236
- return $this->bulkCount - $this->bulkAlreadyDoneCount;
237
  }
238
 
239
  public function flagBulkStart() {
240
- $this->settings->setOpt("wp-short-pixel-flag-id", $this->startBulkId);
241
- delete_option('bulkProcessingStatus');
242
- add_option('bulkProcessingStatus', 'running');//set bulk flag
243
  }
244
 
245
  public function startBulk() {
@@ -248,14 +259,15 @@ class ShortPixelQueue {
248
  $this->flagBulkStart(); //we use this to detect new added files while bulk is running
249
  $this->setBulkPreviousPercent();
250
  $this->resetBulkCurrentlyProcessed();
251
- $this->settings->setOpt( 'wp-short-pixel-bulk-ever-ran', 1);
252
  }
253
 
254
  public function pauseBulk() {
255
- $cancelPointer = $this->startBulkId;
256
  $bulkStartId = $this->getFlagBulkId();
257
- $this->settings->setOpt( 'wp-short-pixel-cancel-pointer', $cancelPointer);//we save this so we can resume bulk processing
258
- WPShortPixel::log("PAUSE: Pointer = ".get_option( 'wp-short-pixel-cancel-pointer'));
 
259
  //remove the bulk items from prio queue
260
  foreach($this->get() as $qItem) {
261
  if($qItem < $bulkStartId) {
@@ -265,46 +277,46 @@ class ShortPixelQueue {
265
  $this->stopBulk();
266
  }
267
 
 
 
 
 
 
 
268
  public function stopBulk() {
269
- $this->startBulkId = WPShortPixel::getMaxMediaId();
270
- $this->stopBulkId = $this->startBulkId;
271
- $this->settings->setOpt("wp-short-pixel-query-id-start", $this->startBulkId);
272
- $this->settings->setOpt("wp-short-pixel-query-id-stop", $this->stopBulkId);
273
- delete_option('bulkProcessingStatus');
274
- return $this->settings->getOpt('wp-short-pixel-bulk-ever-ran', 0);
275
  }
276
 
277
  public function resumeBulk() {
278
- $this->startBulkId = get_option( 'wp-short-pixel-cancel-pointer');
279
- $this->settings->setOpt("wp-short-pixel-query-id-start", $this->startBulkId);//start downwards from the biggest item ID
280
- $this->stopBulkId = $this->ctrl->getMinMediaId();
281
- $this->settings->setOpt("wp-short-pixel-query-id-stop", $this->stopBulkId);
282
  //$this->settings->setOpt("wp-short-pixel-flag-id", $this->startBulkId);//we use to detect new added files while bulk is running
283
- add_option('bulkProcessingStatus', 'running');//set bulk flag
284
- delete_option( 'wp-short-pixel-cancel-pointer');
285
- WPShortPixel::log("Resumed: (pause says: " . $this->bulkPaused() . ") Start from: " . $this->startBulkId . " to " . $this->stopBulkId);
286
  }
287
 
288
  public function resetBulkCurrentlyProcessed() {
289
- $this->bulkCurrentlyProcessed = 0;
290
- $this->settings->setOpt( "wp-short-pixel-bulk-processed-items", $this->bulkCurrentlyProcessed);
291
  }
292
 
293
  public function incrementBulkCurrentlyProcessed() {
294
- $this->bulkCurrentlyProcessed++;
295
- $this->settings->setOpt( "wp-short-pixel-bulk-processed-items", $this->bulkCurrentlyProcessed);
296
  }
297
 
298
  public function markBulkComplete() {
299
- delete_option('bulkProcessingStatus');
300
- delete_option( 'wp-short-pixel-cancel-pointer');
301
  }
302
 
303
  public static function resetBulk() {
304
  delete_option('bulkProcessingStatus');
305
  delete_option( 'wp-short-pixel-cancel-pointer');
306
  delete_option( "wp-short-pixel-flag-id");
307
- $startBulkId = $stopBulkId = WPShortPixel::getMaxMediaId();
308
  update_option( 'wp-short-pixel-query-id-stop', $startBulkId );
309
  update_option( 'wp-short-pixel-query-id-start', $startBulkId );
310
  delete_option( "wp-short-pixel-bulk-previous-percent");
@@ -327,26 +339,25 @@ class ShortPixelQueue {
327
  public function logBulkProgress() {
328
  $t = time();
329
  $this->incrementBulkCurrentlyProcessed();
330
- if($t - $this->lastBulkSuccessTime > 120) { //if break longer than two minutes we mark a pause in the bulk
331
- $this->bulkRunningTime += ($this->lastBulkSuccessTime - $this->lastBulkStartTime);
332
- $this->settings->setOpt('wp-short-pixel-bulk-running-time', $this->bulkRunningTime);
333
- $this->lastBulkStartTime = $this->lastBulkSuccessTime = $t;
334
- $this->settings->setOpt('wp-short-pixel-last-bulk-start-time', $t);
335
- $this->settings->setOpt('wp-short-pixel-last-bulk-success-time', $t);
336
  } else {
337
- $this->lastBulkSuccessTime = $t;
338
- $this->settings->setOpt('wp-short-pixel-last-bulk-success-time', $t);
339
  }
340
  }
341
 
342
  public function getBulkPercent() {
343
- WPShortPixel::log("QUEUE - BulkPrevPercent: " . $this->bulkPreviousPercent . " BulkCurrentlyProcessing: "
344
- . $this->bulkCurrentlyProcessed . " out of " . $this->getBulkToProcess());
 
345
 
346
  if($this->getBulkToProcess() <= 0) return ($this->processing () ? 99: 100);
347
  // return maximum 99%
348
- $percent = $this->bulkPreviousPercent + round($this->bulkCurrentlyProcessed / $this->getBulkToProcess()
349
- * (100 - $this->bulkPreviousPercent));
350
 
351
  WPShortPixel::log("QUEUE - Calculated Percent: " . $percent);
352
 
@@ -354,14 +365,14 @@ class ShortPixelQueue {
354
  }
355
 
356
  public function getDeltaBulkPercent() {
357
- return $this->getBulkPercent() - $this->bulkPreviousPercent;
358
  }
359
 
360
  public function getTimeRemaining (){
361
  $p = $this->getBulkPercent();
362
- $pAlready = $this->bulkCount == 0 ? 0 : round($this->bulkAlreadyDoneCount / $this->bulkCount * 100);
363
  // die("" . ($this->lastBulkSuccessTime - $this->lastBulkStartTime));
364
  if(($p - $pAlready) == 0) return 0;
365
- return round(((100 - $p) / ($p - $pAlready)) * ($this->bulkRunningTime + $this->lastBulkSuccessTime - $this->lastBulkStartTime)/60);
366
  }
367
  }
4
 
5
  private $ctrl;
6
  private $settings;
 
 
 
 
 
 
 
 
 
7
 
8
  const BULK_NEVER = 0; //bulk never ran
9
  const BULK_RUNNING = 1; //bulk is running
14
  $this->ctrl = $controller;
15
  $this->settings = $settings;
16
  //init the option if needed
17
+ if( !isset($_SESSION["wp-short-pixel-priorityQueue"]) //session is not defined
18
+ || !(is_admin() && function_exists("is_user_logged_in") && is_user_logged_in())) { //or we're not in the admin - re-init each time
19
  //take the priority list from the options (we persist there the priority IDs from the previous session)
20
  $prioQueueOpt = $this->settings->getOpt( 'wp-short-pixel-priorityQueue', array());//here we save the IDs for the files that need to be processed after an image upload for example
21
  $_SESSION["wp-short-pixel-priorityQueue"] = array();
22
  foreach($prioQueueOpt as $ID) {
23
+ if(ShortPixelMetaFacade::isCustomQueuedId($ID)) {
24
+ $meta = $this->ctrl->getSpMetaDao()->getMeta(ShortPixelMetaFacade::stripQueuedIdType($ID));
25
+ $todo = isset($meta) && ($meta->getStatus() == 0 || $meta->getStatus() == 1);
26
+ } else {
27
+ $meta = wp_get_attachment_metadata($ID);
28
+ $todo = !isset($meta['ShortPixelImprovement']);
29
+ }
30
  WPShortPixel::log("INIT: Item $ID from options has metadata: " .json_encode($meta));
31
+ if($todo) {
32
  $this->push($ID);
33
  }
34
  }
35
+ $this->settings->priorityQueue = $_SESSION["wp-short-pixel-priorityQueue"];
36
+
37
+ if(is_admin() && function_exists("is_user_logged_in") && is_user_logged_in()) {
38
+ WPShortPixel::log("INIT: Session queue not found, updated from Options with "
39
  .json_encode($_SESSION["wp-short-pixel-priorityQueue"]));
40
+ }
41
  }
 
 
 
 
 
 
 
 
 
 
42
  }
43
 
44
  //handling older
53
  public function skip($id) {
54
  if(is_array($this->settings->prioritySkip)) {
55
  $this->settings->prioritySkip = array_merge($this->settings->prioritySkip, array($id));
56
+ } else {
57
  $this->settings->prioritySkip = array($id);
58
  }
59
+ }
60
+
61
+ public function allSkipped() {
62
+ if( !is_array($this->settings->prioritySkip) ) return false;
63
+ count(array_diff($_SESSION["wp-short-pixel-priorityQueue"], $this->settings->prioritySkip));
64
+ }
65
+
66
+ public function skippedCount() {
67
+ return is_array($this->settings->prioritySkip) ? count($this->settings->prioritySkip) : 0;
68
+ }
69
+
70
+ public function isSkipped($id) {
71
+ return is_array($this->settings->prioritySkip) && in_array($id, $this->settings->prioritySkip);
72
+ }
73
+
74
+ public function isPrio($id) {
75
+ return is_array($_SESSION["wp-short-pixel-priorityQueue"]) && in_array($id, $_SESSION["wp-short-pixel-priorityQueue"]);
76
+ }
77
+
78
+ public function getSkipped() {
79
+ return $this->settings->prioritySkip;
80
+ }
81
+
82
+ public function reverse() {
83
+ $this->settings->priorityQueue = $_SESSION["wp-short-pixel-priorityQueue"] = array_reverse($_SESSION["wp-short-pixel-priorityQueue"]);
84
+
85
+ }
86
+
87
+ public function push($ID)//add an ID to priority queue
88
  {
89
  $priorityQueue = $_SESSION["wp-short-pixel-priorityQueue"]; //get_option("wp-short-pixel-priorityQueue");
90
  WPShortPixel::log("PUSH: Push ID $ID into queue ".json_encode($priorityQueue));
91
  array_push($priorityQueue, $ID);
92
  $prioQ = array_unique($priorityQueue);
93
  $_SESSION["wp-short-pixel-priorityQueue"] = $prioQ;
94
+ //push also to the options queue, in case the session gets killed retrieve from there
95
+ $this->settings->priorityQueue = $prioQ;
96
 
97
  WPShortPixel::log("PUSH: Updated: ".json_encode($_SESSION["wp-short-pixel-priorityQueue"]));//get_option("wp-short-pixel-priorityQueue")));
98
  }
103
  $count = min(count($priorityQueue), $count);
104
  return(array_slice($priorityQueue, count($priorityQueue) - $count, $count));
105
  }
106
+
107
+ public function getFromPrioAndCheck() {
108
+ $ids = array();
109
+ $removeIds = array();
110
+
111
+ $idsPrio = $this->get();
112
+ for($i = count($idsPrio) - 1, $cnt = 0; $i>=0 && $cnt < 3; $i--) {
113
+ if(!isset($idsPrio[$i])) continue; //saw this situation but then couldn't reproduce it to see the cause, so at least treat the effects.
114
+ $id = $idsPrio[$i];
115
+ if(!$this->isSkipped($id) && $this->ctrl->isValidMetaId($id)) {
116
+ $ids[] = $id; //valid ID
117
+ $cnt++;
118
+ } elseif(!$this->isSkipped($id)) {
119
+ $removeIds[] = $id;//not skipped, url not found, means it's absent, to remove
120
+ }
121
+ }
122
+ foreach($removeIds as $rId){
123
+ WPShortPixel::log("HIP: Unfound ID $rId Remove from Priority Queue: ".json_encode($this->get()));
124
+ $this->remove($rId);
125
+ }
126
+ return $ids;
127
+ }
128
 
129
  public function remove($ID)//remove an ID from priority queue
130
  {
146
  }
147
 
148
  public function removeFromFailed($ID) {
149
+ $failed = explode(",", $this->settings->failedImages);
150
  $key = array_search($ID, $failed);
151
  if($key !== false) {
152
  unset($failed[$key]);
153
  $failed = array_values($failed);
154
+ $this->settings->failedImages = implode(",", $failed) ;
155
  }
156
  }
157
 
158
  public function addToFailed($ID) {
159
+ $failed = $this->settings->failedImages;
160
  if(!in_array($ID, explode(",", $failed))) {
161
+ $this->settings->failedImages = (strlen($failed) ? $failed . "," : "") . $ID;
162
  }
163
  }
164
 
165
  public function getFailed() {
166
+ $failed = $this->settings->failedImages;
 
167
  if(!strlen($failed)) return array();
168
  $ret = explode(",", $failed);
169
  $fails = array();
170
  foreach($ret as $fail) {
171
+ if(ShortPixelMetaFacade::isCustomQueuedId($fail)) {
172
+ $meta = $this->ctrl->getSpMetaDao()->getMeta(ShortPixelMetaFacade::stripQueuedIdType($fail));
173
+ if($meta) {
174
+ $fails[] = (object)array("id" => ShortPixelMetaFacade::stripQueuedIdType($fail), "type" => ShortPixelMetaFacade::CUSTOM_TYPE, "meta" => $meta);
175
+ }
176
  } else {
177
+ $meta = wp_get_attachment_metadata($fail);
178
+ if(!$meta || (isset($meta["ShortPixelImprovement"]) && is_numeric($meta["ShortPixelImprovement"]))){
179
+ $this->removeFromFailed($fail);
180
+ } else {
181
+ $fails[] = (object)array("id" => $fail, "type" => ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE, "meta" => $meta);
182
+ }
183
  }
184
  }
185
  return $fails;
187
 
188
  public function bulkRunning() {
189
  //$bulkProcessingStatus = get_option('bulkProcessingStatus');
190
+ return $this->settings->startBulkId > $this->settings->stopBulkId;
191
  }
192
 
193
  public function bulkPaused() {
194
+ WPShortPixel::log("Bulk Paused: " . $this->settings->cancelPointer);
195
+ return $this->settings->cancelPointer;
196
  }
197
 
198
  public function bulkRan() {
199
+ return $this->settings->bulkEverRan != 0;
200
  }
201
 
202
  public function processing() {
205
  }
206
 
207
  public function getFlagBulkId() {
208
+ return $this->settings->flagId;
209
  }
210
 
211
  public function getStartBulkId() {
212
+ return $this->settings->startBulkId;
213
  }
214
 
215
  public function resetStartBulkId() {
216
+ $this->setStartBulkId(ShortPixelMetaFacade::getMaxMediaId());
217
  }
218
 
219
  public function setStartBulkId($start){
220
+ $this->settings->startBulkId = $start;
 
221
  }
222
 
223
  public function getStopBulkId() {
224
+ return $this->settings->stopBulkId;
225
  }
226
 
227
  public function resetStopBulkId() {
228
+ $this->settings->stopBulkId = ShortPixelMetaFacade::getMinMediaId();
 
229
  }
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
237
+ $this->settings->bulkAlreadyDoneCount = $res["mainProcessedFiles"] - $res["mainProc".((0 + $this->ctrl->getCompressionType() == 1) ? "Lossless" : "Lossy")."Files"];
238
  // if the thumbnails are to be processed, add also the images that have thumbs not processed
239
  if($this->settings->processThumbnails) {
240
+ $this->settings->bulkAlreadyDoneCount -= $res["mainUnprocessedThumbs"];
241
  }
242
 
 
 
 
243
  //percent already done
244
+ $this->settings->bulkPreviousPercent = round($this->settings->bulkAlreadyDoneCount / ($this->settings->bulkCount ? $this->settings->bulkCount : 1) * 100);
 
245
  }
246
 
247
  public function getBulkToProcess() {
248
+ return $this->settings->bulkCount - $this->settings->bulkAlreadyDoneCount;
249
  }
250
 
251
  public function flagBulkStart() {
252
+ $this->settings->flagId = $this->settings->startBulkId;
253
+ $this->settings->bulkProcessingStatus = 'running';//set bulk flag
 
254
  }
255
 
256
  public function startBulk() {
259
  $this->flagBulkStart(); //we use this to detect new added files while bulk is running
260
  $this->setBulkPreviousPercent();
261
  $this->resetBulkCurrentlyProcessed();
262
+ $this->settings->bulkEverRan = 1;
263
  }
264
 
265
  public function pauseBulk() {
266
+ $cancelPointer = $this->settings->startBulkId;
267
  $bulkStartId = $this->getFlagBulkId();
268
+ $this->settings->cancelPointer = $cancelPointer;//we save this so we can resume bulk processing
269
+ $this->settings->skipToCustom = NULL;
270
+ WPShortPixel::log("PAUSE: Pointer = ".$this->settings->cancelPointer);
271
  //remove the bulk items from prio queue
272
  foreach($this->get() as $qItem) {
273
  if($qItem < $bulkStartId) {
277
  $this->stopBulk();
278
  }
279
 
280
+ public function cancelBulk() {
281
+ $this->pauseBulk();
282
+ WPShortPixel::log("STOP, delete pointer.");
283
+ $this->settings->cancelPointer = NULL;
284
+ }
285
+
286
  public function stopBulk() {
287
+ $this->settings->startBulkId = ShortPixelMetaFacade::getMaxMediaId();
288
+ $this->settings->stopBulkId = $this->settings->startBulkId;
289
+ $this->settings->bulkProcessingStatus = null;
290
+ return $this->settings->bulkEverRan;
 
 
291
  }
292
 
293
  public function resumeBulk() {
294
+ $this->settings->startBulkId = $this->settings->cancelPointer;
295
+ $this->settings->stopBulkId = ShortPixelMetaFacade::getMinMediaId();
 
 
296
  //$this->settings->setOpt("wp-short-pixel-flag-id", $this->startBulkId);//we use to detect new added files while bulk is running
297
+ $this->settings->bulkProcessingStatus = 'running';//set bulk flag
298
+ $this->settings->cancelPointer = null;
299
+ WPShortPixel::log("Resumed: (pause says: " . $this->bulkPaused() . ") Start from: " . $this->settings->startBulkId . " to " . $this->settings->stopBulkId);
300
  }
301
 
302
  public function resetBulkCurrentlyProcessed() {
303
+ $this->settings->bulkCurrentlyProcessed = 0;
 
304
  }
305
 
306
  public function incrementBulkCurrentlyProcessed() {
307
+ $this->settings->bulkCurrentlyProcessed = $this->settings->bulkCurrentlyProcessed + 1;
 
308
  }
309
 
310
  public function markBulkComplete() {
311
+ $this->settings->bulkProcessingStatus = null;
312
+ $this->settings->cancelPointer = null;
313
  }
314
 
315
  public static function resetBulk() {
316
  delete_option('bulkProcessingStatus');
317
  delete_option( 'wp-short-pixel-cancel-pointer');
318
  delete_option( "wp-short-pixel-flag-id");
319
+ $startBulkId = $stopBulkId = ShortPixelMetaFacade::getMaxMediaId();
320
  update_option( 'wp-short-pixel-query-id-stop', $startBulkId );
321
  update_option( 'wp-short-pixel-query-id-start', $startBulkId );
322
  delete_option( "wp-short-pixel-bulk-previous-percent");
339
  public function logBulkProgress() {
340
  $t = time();
341
  $this->incrementBulkCurrentlyProcessed();
342
+ $successTime = $this->settings->lastBulkSuccessTime;
343
+ if($t - $successTime > 120) { //if break longer than two minutes we mark a pause in the bulk
344
+ $this->settings->bulkRunningTime += ($successTime - $this->settings->lastBulkStartTime);
345
+ $this->settings->lastBulkStartTime = $t;
346
+ $this->settings->lastBulkSuccessTime = $t;
 
347
  } else {
348
+ $this->settings->lastBulkSuccessTime = $t;
 
349
  }
350
  }
351
 
352
  public function getBulkPercent() {
353
+ $previousPercent = $this->settings->bulkPreviousPercent;
354
+ WPShortPixel::log("QUEUE - BulkPrevPercent: " . $previousPercent . " BulkCurrentlyProcessing: "
355
+ . $this->settings->bulkCurrentlyProcessed . " out of " . $this->getBulkToProcess());
356
 
357
  if($this->getBulkToProcess() <= 0) return ($this->processing () ? 99: 100);
358
  // return maximum 99%
359
+ $percent = $previousPercent + round($this->settings->bulkCurrentlyProcessed / $this->getBulkToProcess()
360
+ * (100 - $previousPercent));
361
 
362
  WPShortPixel::log("QUEUE - Calculated Percent: " . $percent);
363
 
365
  }
366
 
367
  public function getDeltaBulkPercent() {
368
+ return $this->getBulkPercent() - $this->settings->bulkPreviousPercent;
369
  }
370
 
371
  public function getTimeRemaining (){
372
  $p = $this->getBulkPercent();
373
+ $pAlready = $this->settings->bulkCount == 0 ? 0 : round($this->settings->bulkAlreadyDoneCount / $this->settings->bulkCount * 100);
374
  // die("" . ($this->lastBulkSuccessTime - $this->lastBulkStartTime));
375
  if(($p - $pAlready) == 0) return 0;
376
+ return round(((100 - $p) / ($p - $pAlready)) * ($this->settings->bulkRunningTime + $this->settings->lastBulkSuccessTime - $this->settings->lastBulkStartTime)/60);
377
  }
378
  }
shortpixel_view.php CHANGED
@@ -470,7 +470,7 @@ class ShortPixelView {
470
  <th scope="row"><label for="backupImages">Image backup</label></th>
471
  <td>
472
  <input name="backupImages" type="checkbox" id="backupImages" <?php echo( $checkedBackupImages );?>> Save and keep a backup of your original images in a separate folder.
473
- <p class="settings-info">You need to have backup active in order to be able to restore images to originals or to convert from Lossy to Lossless and back.</p>
474
  </td>
475
  </tr>
476
  <tr>
470
  <th scope="row"><label for="backupImages">Image backup</label></th>
471
  <td>
472
  <input name="backupImages" type="checkbox" id="backupImages" <?php echo( $checkedBackupImages );?>> Save and keep a backup of your original images in a separate folder.
473
+ <p class="settings-info">Usually recommended for safety.</p>
474
  </td>
475
  </tr>
476
  <tr>
wp-shortpixel-req.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once('shortpixel-debug.php');
3
+
4
+ require_once('wp-shortpixel-settings.php');
5
+ require_once('shortpixel_api.php');
6
+ require_once('shortpixel_queue.php');
7
+ //entities
8
+ require_once('class/model/shortpixel-entity.php');
9
+ require_once('class/model/shortpixel-meta.php');
10
+ require_once('class/model/shortpixel-folder.php');
11
+ //exceptions
12
+ require_once('class/model/sp-file-rights-exception.php');
13
+ //database access
14
+ require_once('class/db/shortpixel-db.php');
15
+ require_once('class/db/wp-shortpixel-db.php');
16
+ require_once('class/db/shortpixel-custom-meta-dao.php');
17
+ require_once('class/db/shortpixel-nextgen-adapter.php');
18
+ require_once('class/db/wp-shortpixel-media-library-adapter.php');
19
+ require_once('class/db/shortpixel-meta-facade.php');
20
+ //view
21
+ require_once('class/view/shortpixel_view.php');
22
+
23
+ require_once('class/shortpixel-tools.php');
24
+
25
+ require_once( ABSPATH . 'wp-admin/includes/image.php' );
26
+ include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
27
+
28
+ /*
29
+ if ( !is_plugin_active( 'wpmandrill/wpmandrill.php' ) //avoid conflicts with some plugins
30
+ && !is_plugin_active( 'wp-ses/wp-ses.php' )
31
+ && !is_plugin_active( 'wordfence/wordfence.php') ) {
32
+ require_once( ABSPATH . 'wp-includes/pluggable.php' );
33
+ }
34
+ */
35
+
36
+
wp-shortpixel-settings.php CHANGED
@@ -12,8 +12,12 @@ class WPShortPixelSettings {
12
  private $_resizeImages = false;
13
  private $_resizeWidth = 0;
14
  private $_resizeHeight = 0;
15
-
16
  private static $_optionsMap = array(
 
 
 
 
17
  'apiKey' => 'wp-short-pixel-apiKey',
18
  'verifiedKey' => 'wp-short-pixel-verifiedKey',
19
  'compressionType' => 'wp-short-pixel-compression',
@@ -21,25 +25,58 @@ class WPShortPixelSettings {
21
  'keepExif' => 'wp-short-pixel-keep-exif',
22
  'CMYKtoRGBconversion' => 'wp-short-pixel_cmyk2rgb',
23
  'backupImages' => 'wp-short-backup_images',
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  'fileCount' => 'wp-short-pixel-fileCount',
25
  'thumbsCount' => 'wp-short-pixel-thumbnail-count',
26
  'under5Percent' => 'wp-short-pixel-files-under-5-percent',
27
  'savedSpace' => 'wp-short-pixel-savedSpace',
28
  'averageCompression' => 'wp-short-pixel-averageCompression',
29
- 'apiRetries' => 'wp-short-pixel-api-retries',
30
- 'resizeImages' => 'wp-short-pixel-resize-images',
31
- 'resizeWidth' => 'wp-short-pixel-resize-width',
32
- 'resizeHeight' => 'wp-short-pixel-resize-height',
33
  'totalOptimized' => 'wp-short-pixel-total-optimized',
34
  'totalOriginal' => 'wp-short-pixel-total-original',
35
  'quotaExceeded' => 'wp-short-pixel-quota-exceeded',
36
  'httpProto' => 'wp-short-pixel-protocol',
37
  'downloadProto' => 'wp-short-pixel-download-protocol',
38
  'mediaAlert' => 'wp-short-pixel-media-alert',
39
- 'siteAuthUser' => 'wp-short-pixel-site-auth-user',
40
- 'siteAuthPass' => 'wp-short-pixel-site-auth-pass',
41
- 'prioritySkip' => 'wp-short-pixel-prioritySkip',
 
42
  'redirectedSettings' => 'wp-short-pixel-redirected-settings',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  '' => '',
44
  );
45
 
@@ -72,7 +109,7 @@ class WPShortPixelSettings {
72
  }
73
 
74
  public static function debugResetOptions() {
75
- delete_option('wp-short-pixel-apiKey');
76
  delete_option('wp-short-pixel-verifiedKey');
77
  delete_option('wp-short-pixel-compression');
78
  delete_option('wp-short-process_thumbnails');
@@ -96,7 +133,10 @@ class WPShortPixelSettings {
96
  delete_option( 'wp-short-pixel-resize-width');
97
  delete_option( 'wp-short-pixel-resize-height');
98
  delete_option( 'wp-short-pixel-dismissed-notices');
99
- delete_option( 'wp-short-pixel-redirected-settings');
 
 
 
100
  if(isset($_SESSION["wp-short-pixel-priorityQueue"])) {
101
  unset($_SESSION["wp-short-pixel-priorityQueue"]);
102
  }
@@ -116,11 +156,6 @@ class WPShortPixelSettings {
116
  }
117
 
118
 
119
-
120
-
121
-
122
-
123
-
124
  public function __get($name)
125
  {
126
  if (array_key_exists($name, self::$_optionsMap)) {
@@ -137,7 +172,11 @@ class WPShortPixelSettings {
137
 
138
  public function __set($name, $value) {
139
  if (array_key_exists($name, self::$_optionsMap)) {
140
- $this->setOpt(self::$_optionsMap[$name], $value);
 
 
 
 
141
  }
142
  }
143
 
12
  private $_resizeImages = false;
13
  private $_resizeWidth = 0;
14
  private $_resizeHeight = 0;
15
+
16
  private static $_optionsMap = array(
17
+ //This one is accessed also directly via get_option
18
+ 'frontBootstrap' => 'wp-short-pixel-front-bootstrap', //set to 1 when need the plugin active for logged in user in the front-end
19
+ 'lastBackAction' => 'wp-short-pixel-last-back-action', //when less than 10 min. passed from this timestamp, the front-bootstrap is ineffective.
20
+ //optimization options
21
  'apiKey' => 'wp-short-pixel-apiKey',
22
  'verifiedKey' => 'wp-short-pixel-verifiedKey',
23
  'compressionType' => 'wp-short-pixel-compression',
25
  'keepExif' => 'wp-short-pixel-keep-exif',
26
  'CMYKtoRGBconversion' => 'wp-short-pixel_cmyk2rgb',
27
  'backupImages' => 'wp-short-backup_images',
28
+ 'resizeImages' => 'wp-short-pixel-resize-images',
29
+ 'resizeWidth' => 'wp-short-pixel-resize-width',
30
+ 'resizeHeight' => 'wp-short-pixel-resize-height',
31
+ 'siteAuthUser' => 'wp-short-pixel-site-auth-user',
32
+ 'siteAuthPass' => 'wp-short-pixel-site-auth-pass',
33
+
34
+ //optimize other images than the ones in Media Library
35
+ 'includeNextGen' => 'wp-short-pixel-include-next-gen',
36
+ 'hasCustomFolders' => 'wp-short-pixel-has-custom-folders',
37
+ 'customBulkPaused' => 'wp-short-pixel-custom-bulk-paused',
38
+
39
+ //stats, notices, etc.
40
+ 'currentTotalFiles' => 'wp-short-pixel-current-total-files',
41
  'fileCount' => 'wp-short-pixel-fileCount',
42
  'thumbsCount' => 'wp-short-pixel-thumbnail-count',
43
  'under5Percent' => 'wp-short-pixel-files-under-5-percent',
44
  'savedSpace' => 'wp-short-pixel-savedSpace',
45
  'averageCompression' => 'wp-short-pixel-averageCompression',
46
+ 'apiRetries' => 'wp-short-pixel-api-retries',
 
 
 
47
  'totalOptimized' => 'wp-short-pixel-total-optimized',
48
  'totalOriginal' => 'wp-short-pixel-total-original',
49
  'quotaExceeded' => 'wp-short-pixel-quota-exceeded',
50
  'httpProto' => 'wp-short-pixel-protocol',
51
  'downloadProto' => 'wp-short-pixel-download-protocol',
52
  'mediaAlert' => 'wp-short-pixel-media-alert',
53
+ 'dismissedNotices' => 'wp-short-pixel-dismissed-notices',
54
+ 'activationDate' => 'wp-short-pixel-activation-date',
55
+ 'activationNotice' => 'wp-short-pixel-activation-notice',
56
+ 'mediaLibraryViewMode' => 'wp-short-pixel-view-mode',
57
  'redirectedSettings' => 'wp-short-pixel-redirected-settings',
58
+
59
+ //bulk state machine
60
+ 'bulkLastStatus' => 'wp-short-pixel-bulk-last-status',
61
+ 'startBulkId' => 'wp-short-pixel-query-id-start',
62
+ 'stopBulkId' => 'wp-short-pixel-query-id-stop',
63
+ 'bulkCount' => 'wp-short-pixel-bulk-count',
64
+ 'bulkPreviousPercent' => 'wp-short-pixel-bulk-previous-percent',
65
+ 'bulkCurrentlyProcessed' => 'wp-short-pixel-bulk-processed-items',
66
+ 'bulkAlreadyDoneCount' => 'wp-short-pixel-bulk-done-count',
67
+ 'lastBulkStartTime' => 'wp-short-pixel-last-bulk-start-time',
68
+ 'lastBulkSuccessTime' => 'wp-short-pixel-last-bulk-success-time',
69
+ 'bulkRunningTime' => 'wp-short-pixel-bulk-running-time',
70
+ 'cancelPointer' => 'wp-short-pixel-cancel-pointer',
71
+ 'skipToCustom' => 'wp-short-pixel-skip-to-custom',
72
+ 'bulkEverRan' => 'wp-short-pixel-bulk-ever-ran',
73
+ 'flagId' => 'wp-short-pixel-flag-id',
74
+ 'failedImages' => 'wp-short-pixel-failed-imgs',
75
+ 'bulkProcessingStatus' => 'bulkProcessingStatus',
76
+
77
+ 'priorityQueue' => 'wp-short-pixel-priorityQueue',
78
+ 'prioritySkip' => 'wp-short-pixel-prioritySkip',
79
+
80
  '' => '',
81
  );
82
 
109
  }
110
 
111
  public static function debugResetOptions() {
112
+ /* delete_option('wp-short-pixel-apiKey');
113
  delete_option('wp-short-pixel-verifiedKey');
114
  delete_option('wp-short-pixel-compression');
115
  delete_option('wp-short-process_thumbnails');
133
  delete_option( 'wp-short-pixel-resize-width');
134
  delete_option( 'wp-short-pixel-resize-height');
135
  delete_option( 'wp-short-pixel-dismissed-notices');
136
+ */
137
+ foreach(self::$_optionsMap as $key => $val) {
138
+ delete_option($val);
139
+ }
140
  if(isset($_SESSION["wp-short-pixel-priorityQueue"])) {
141
  unset($_SESSION["wp-short-pixel-priorityQueue"]);
142
  }
156
  }
157
 
158
 
 
 
 
 
 
159
  public function __get($name)
160
  {
161
  if (array_key_exists($name, self::$_optionsMap)) {
172
 
173
  public function __set($name, $value) {
174
  if (array_key_exists($name, self::$_optionsMap)) {
175
+ if($value !== null) {
176
+ $this->setOpt(self::$_optionsMap[$name], $value);
177
+ } else {
178
+ delete_option(self::$_optionsMap[$name]);
179
+ }
180
  }
181
  }
182
 
wp-shortpixel.php CHANGED
@@ -3,36 +3,33 @@
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: 3.3.8
7
  * Author: ShortPixel
8
  * Author URI: https://shortpixel.com
9
  */
10
 
11
- require_once('wp-shortpixel-settings.php');
12
- require_once('shortpixel_api.php');
13
- require_once('shortpixel_queue.php');
14
- require_once('shortpixel_view.php');
15
- require_once( ABSPATH . 'wp-admin/includes/image.php' );
16
- include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
17
-
18
- /*if ( !is_plugin_active( 'wpmandrill/wpmandrill.php' ) && !is_plugin_active( 'wp-ses/wp-ses.php' ) ) {
19
- require_once( ABSPATH . 'wp-includes/pluggable.php' );//to avoid conflict with wpmandrill plugin
20
- }
21
- */
22
-
23
  define('SP_RESET_ON_ACTIVATE', false); //if true TODO set false
24
 
25
  define('SP_AFFILIATE_CODE', '');
26
 
27
- define('PLUGIN_VERSION', "3.3.8");
28
  define('SP_MAX_TIMEOUT', 10);
29
  define('SP_VALIDATE_MAX_TIMEOUT', 15);
30
  define('SP_BACKUP', 'ShortpixelBackups');
31
- define('SP_BACKUP_FOLDER', WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . SP_BACKUP);
32
  define('MAX_API_RETRIES', 50);
33
  define('MAX_ERR_RETRIES', 5);
 
34
  $MAX_EXECUTION_TIME = ini_get('max_execution_time');
35
 
 
 
 
 
 
 
 
 
 
36
  /*
37
  if ( is_numeric($MAX_EXECUTION_TIME) && $MAX_EXECUTION_TIME > 10 )
38
  define('MAX_EXECUTION_TIME', $MAX_EXECUTION_TIME - 5 ); //in seconds
@@ -53,17 +50,26 @@ class WPShortPixel {
53
  private $_settings = null;
54
  private $prioQ = null;
55
  private $view = null;
 
 
 
 
 
56
 
57
  public function __construct() {
58
  if (!session_id()) {
59
  session_start();
60
  }
 
 
61
 
62
  $isAdminUser = current_user_can( 'manage_options' );
63
 
64
  $this->_affiliateSufix = (strlen(SP_AFFILIATE_CODE)) ? "/affiliate/" . SP_AFFILIATE_CODE : "";
65
  $this->_settings = new WPShortPixelSettings();
66
  $this->_apiInterface = new ShortPixelAPI($this->_settings);
 
 
67
  $this->prioQ = new ShortPixelQueue($this, $this->_settings);
68
  $this->view = new ShortPixelView($this);
69
 
@@ -72,7 +78,7 @@ class WPShortPixel {
72
  $this->setDefaultViewModeList();//set default mode as list. only @ first run
73
 
74
  //add hook for image upload processing
75
- add_filter( 'wp_generate_attachment_metadata', array( &$this, 'handleImageUpload' ), 10, 2 );
76
  add_filter( 'plugin_action_links_' . plugin_basename(__FILE__), array(&$this, 'generatePluginLinks'));//for plugin settings page
77
 
78
  //add_action( 'admin_footer', array(&$this, 'handleImageProcessing'));
@@ -80,6 +86,16 @@ class WPShortPixel {
80
  //Media custom column
81
  add_filter( 'manage_media_columns', array( &$this, 'columns' ) );//add media library column header
82
  add_action( 'manage_media_custom_column', array( &$this, 'generateCustomColumn' ), 10, 2 );//generate the media library column
 
 
 
 
 
 
 
 
 
 
83
  //custom hook
84
  add_action( 'shortpixel-optimize-now', array( &$this, 'optimizeNowHook' ), 10, 1);
85
 
@@ -87,6 +103,8 @@ class WPShortPixel {
87
  //add settings page
88
  add_action( 'admin_menu', array( &$this, 'registerSettingsPage' ) );//display SP in Settings menu
89
  add_action( 'admin_menu', array( &$this, 'registerAdminPage' ) );
 
 
90
 
91
  add_action( 'delete_attachment', array( &$this, 'handleDeleteAttachmentInBackup' ) );
92
  add_action( 'load-upload.php', array( &$this, 'handleCustomBulk'));
@@ -111,12 +129,18 @@ class WPShortPixel {
111
  add_action( 'wp_ajax_shortpixel_dismiss_media_alert', array(&$this, 'dismissMediaAlert'));
112
  //check quota
113
  add_action('admin_action_shortpixel_check_quota', array(&$this, 'handleCheckQuota'));
114
-
115
  //This adds the constants used in PHP to be available also in JS
116
  add_action( 'admin_footer', array( &$this, 'shortPixelJS') );
 
 
 
 
 
 
 
117
  //register a method to display admin notices if necessary
118
  add_action('admin_notices', array( &$this, 'displayAdminNotices'));
119
-
120
  $this->migrateBackupFolder();
121
 
122
  if(!$this->_settings->redirectedSettings && !$this->_settings->verifiedKey && (!function_exists("is_multisite") || !is_multisite())) {
@@ -136,6 +160,9 @@ class WPShortPixel {
136
  }
137
 
138
  function registerAdminPage( ) {
 
 
 
139
  add_media_page( 'ShortPixel Bulk Process', 'Bulk ShortPixel', 'edit_others_posts', 'wp-short-pixel-bulk', array( &$this, 'bulkProcess' ) );
140
  }
141
 
@@ -150,7 +177,7 @@ class WPShortPixel {
150
 
151
  public static function shortPixelDeactivatePlugin()//reset some params to avoid trouble for plugins that were activated/deactivated/activated
152
  {
153
- include_once dirname( __FILE__ ) . '/shortpixel_queue.php';
154
  ShortPixelQueue::resetBulk();
155
  ShortPixelQueue::resetPrio();
156
  WPShortPixelSettings::onDeactivate();
@@ -158,12 +185,12 @@ class WPShortPixel {
158
 
159
  public function displayAdminNotices() {
160
  if(!$this->_settings->verifiedKey) {
161
- $dismissed = $this->_settings->getOpt( 'wp-short-pixel-dismissed-notices', array());
162
  $now = time();
163
- $act = $this->_settings->getOpt( 'wp-short-pixel-activation-date', $now);
164
- if($this->_settings->getOpt( 'wp-short-pixel-activation-notice', false) && $this->_settings->redirectedSettings >= 2) {
165
  ShortPixelView::displayActivationNotice();
166
- delete_option('wp-short-pixel-activation-notice');
167
  }
168
  if( ($now > $act + 7200) && !isset($dismissed['2h'])) {
169
  ShortPixelView::displayActivationNotice('2h');
@@ -175,9 +202,9 @@ class WPShortPixel {
175
 
176
  public function dismissAdminNotice() {
177
  $noticeId = preg_replace('|[^a-z0-9]|i', '', $_GET['notice_id']);
178
- $dismissed = $this->_settings->getOpt( 'wp-short-pixel-dismissed-notices', array());
179
  $dismissed[$noticeId] = true;
180
- update_option( 'wp-short-pixel-dismissed-notices', $dismissed);
181
  die(json_encode(array("Status" => 'success', "Message" => 'Notice ID: ' . $noticeId . ' dismissed')));
182
  }
183
 
@@ -189,9 +216,9 @@ class WPShortPixel {
189
  //set default move as "list". only set once, it won't try to set the default mode again.
190
  public function setDefaultViewModeList()
191
  {
192
- if($this->_settings->getOpt('wp-short-pixel-view-mode') === false)
193
  {
194
- $this->_settings->setOpt('wp-short-pixel-view-mode', 1);
195
  if ( function_exists('get_currentuserinfo') )
196
  {
197
  global $current_user;
@@ -215,27 +242,27 @@ class WPShortPixel {
215
 
216
  function shortPixelJS() { ?>
217
  <script type="text/javascript" >
218
- jQuery(document).ready(function($){
219
- if(typeof ShortPixel !== 'undefined') {
220
- ShortPixel.setOptions({
221
- STATUS_SUCCESS: <?php echo ShortPixelAPI::STATUS_SUCCESS; ?>,
222
- STATUS_EMPTY_QUEUE: <?php echo self::BULK_EMPTY_QUEUE; ?>,
223
- STATUS_ERROR: <?php echo ShortPixelAPI::STATUS_ERROR; ?>,
224
- STATUS_FAIL: <?php echo ShortPixelAPI::STATUS_FAIL; ?>,
225
- STATUS_QUOTA_EXCEEDED: <?php echo ShortPixelAPI::STATUS_QUOTA_EXCEEDED; ?>,
226
- STATUS_SKIP: <?php echo ShortPixelAPI::STATUS_SKIP; ?>,
227
- STATUS_NO_KEY: <?php echo ShortPixelAPI::STATUS_NO_KEY; ?>,
228
- STATUS_RETRY: <?php echo ShortPixelAPI::STATUS_RETRY; ?>,
229
- WP_PLUGIN_URL: '<?php echo plugins_url( '', __FILE__ ); ?>',
230
- WP_ADMIN_URL: '<?php echo strtolower(admin_url()); ?>',
231
- API_KEY: "<?php echo $this->_settings->apiKey; ?>",
232
- MEDIA_ALERT: '<?php echo $this->_settings->mediaAlert ? "done" : "todo"; ?>'
233
- });
234
- }
235
- });
236
  </script> <?php
237
- wp_enqueue_style('short-pixel.css', plugins_url('/css/short-pixel.css',__FILE__) );
238
- wp_enqueue_script('short-pixel.js', plugins_url('/js/short-pixel.js',__FILE__) );
 
 
239
  }
240
 
241
  function toolbar_shortpixel_processing( $wp_admin_bar ) {
@@ -257,18 +284,16 @@ class WPShortPixel {
257
  //$blank = '_blank';
258
  //$icon = "shortpixel-alert.png";
259
  }
260
- $lastStatus = $this->_settings->getOpt( 'wp-short-pixel-bulk-last-status', array('Status' => ShortPixelAPI::STATUS_SUCCESS));
261
- if($lastStatus['Status'] != ShortPixelAPI::STATUS_SUCCESS) {
262
  $extraClasses = " shortpixel-alert shortpixel-processing";
263
  $tooltip = $lastStatus['Message'];
264
  }
265
- self::log("TB: Start: " . $this->prioQ->getStartBulkId() . ", stop: " . $this->prioQ->getStopBulkId() . " PrioQ: "
266
- .json_encode($this->prioQ->get()));
267
 
268
  $args = array(
269
  'id' => 'shortpixel_processing',
270
  'title' => '<div title="' . $tooltip . '" ><img src="'
271
- . plugins_url( 'img/'.$icon, __FILE__ ) . '" success-url="' . $successLink . '"><span class="shp-alert">!</span></div>',
272
  'href' => $link,
273
  'meta' => array('target'=> $blank, 'class' => 'shortpixel-toolbar-processing' . $extraClasses)
274
  );
@@ -303,7 +328,13 @@ class WPShortPixel {
303
  }
304
  }
305
 
306
- public function handleImageUpload($meta, $ID = null)
 
 
 
 
 
 
307
  {
308
  if( !$this->_settings->verifiedKey) {// no API Key set/verified -> do nothing here, just return
309
  return $meta;
@@ -319,7 +350,9 @@ class WPShortPixel {
319
  else
320
  {//the kind of file we can process. goody.
321
  $this->prioQ->push($ID);
322
- $URLsAndPATHs = $this->getURLsAndPATHs($ID, $meta);
 
 
323
  //send a processing request right after a file was uploaded, do NOT wait for response
324
  $this->_apiInterface->doRequests($URLsAndPATHs['URLs'], false, $ID);
325
  self::log("IMG: sent: " . json_encode($URLsAndPATHs));
@@ -327,25 +360,82 @@ class WPShortPixel {
327
  return $meta;
328
  }
329
 
330
- }//end handleImageUpload
331
 
332
- public function getCurrentBulkItemsCount(){
333
- global $wpdb;
334
-
335
- $startQueryID = $this->prioQ->getFlagBulkId();
336
- $endQueryID = $this->prioQ->getStopBulkId();
337
-
338
- if ( $startQueryID <= $endQueryID ) {
339
- return 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  }
341
- $queryPostMeta = "SELECT COUNT(DISTINCT post_id) items FROM " . $wpdb->prefix . "postmeta
342
- WHERE ( post_id <= $startQueryID AND post_id > $endQueryID ) AND (
343
- meta_key = '_wp_attached_file'
344
- OR meta_key = '_wp_attachment_metadata' )";
345
- $res = $wpdb->get_results($queryPostMeta);
346
- return $res[0]->items;
347
  }
348
 
 
 
 
 
 
 
 
 
 
 
 
349
  public function getBulkItemsFromDb(){
350
  global $wpdb;
351
 
@@ -357,8 +447,9 @@ class WPShortPixel {
357
  return false;
358
  }
359
  $idList = array();
 
360
  for ($sanityCheck = 0, $crtStartQueryID = $startQueryID;
361
- $crtStartQueryID >= $endQueryID && count($idList) < 3; $sanityCheck++) {
362
 
363
  self::log("GETDB: current StartID: " . $crtStartQueryID);
364
 
@@ -377,24 +468,27 @@ class WPShortPixel {
377
  foreach ( $resultsPostMeta as $itemMetaData ) {
378
  $crtStartQueryID = $itemMetaData->post_id;
379
  if(!in_array($crtStartQueryID, $idList) && self::isProcessable($crtStartQueryID)) {
380
- $meta = wp_get_attachment_metadata($crtStartQueryID);
381
- $compression = ShortPixelAPI::getCompressionTypeName($this->_settings->compressionType);
382
 
383
- if(! isset($meta["ShortPixelImprovement"]) || !is_numeric($meta["ShortPixelImprovement"])) {
 
384
  $idList[] = $crtStartQueryID;
385
  }
386
- elseif(isset($meta["ShortPixel"]["type"]) && $meta["ShortPixel"]["type"] != $compression) {//a different type of compression was chosen in settings
387
- if($this->doRestore($crtStartQueryID, $meta)) {
 
388
  $idList[] = $crtStartQueryID;
389
  } else {
390
  $skippedAlreadyProcessed++;
391
  }
392
  }
393
- elseif( $this->_settings->processThumbnails && isset($meta["ShortPixel"]["thumbsOpt"]) && $meta["ShortPixel"]["thumbsOpt"] == 0
394
- && isset($meta["sizes"]) && count($meta["sizes"]) > 0) { //thumbs were chosen in settings
395
  //if($crtStartQueryID == 44 || $crtStartQueryID == 49) {echo("No THuMBS?");die(var_dump($meta));}
396
- $meta["ShortPixel"]["thumbsTodo"] = true;
397
- wp_update_attachment_metadata($crtStartQueryID, $meta);
 
398
  $idList[] = $crtStartQueryID;
399
  }
400
  elseif($itemMetaData->meta_key == '_wp_attachment_metadata') { //count skipped
@@ -406,54 +500,52 @@ class WPShortPixel {
406
  //daca n-am adaugat niciuna pana acum, n-are sens sa mai selectez zona asta de id-uri in bulk-ul asta.
407
  $leapStart = $this->prioQ->getStartBulkId();
408
  $crtStartQueryID = $startQueryID = $itemMetaData->post_id - 1; //decrement it so we don't select it again
409
- $res = self::countAllProcessableFiles($leapStart, $crtStartQueryID);
410
  $skippedAlreadyProcessed += $res["mainProcessedFiles"] - $res["mainProc".($this->getCompressionType() == 1 ? "Lossy" : "Lossless")."Files"];
411
  $this->prioQ->setStartBulkId($startQueryID);
412
  } else {
413
  $crtStartQueryID--;
414
  }
415
  }
416
- return array("ids" => $idList, "skipped" => $skippedAlreadyProcessed);
417
  }
418
 
419
  /**
420
  * Get last added items from priority
421
  * @return type
422
  */
 
423
  public function getFromPrioAndCheck() {
424
- $ids = array();
425
- $removeIds = array();
426
-
427
- $idsPrio = $this->prioQ->get();
428
- for($i = count($idsPrio) - 1, $cnt = 0; $i>=0 && $cnt < 3; $i--) {
429
- $id = $idsPrio[$i];
430
- if(!$this->prioQ->isSkipped($id) && wp_get_attachment_url($id)) {
431
- $ids[] = $id; //valid ID
432
- } elseif(!$this->prioQ->isSkipped($id)) {
433
- $removeIds[] = $id;//not skipped, url not found, means it's absent, to remove
434
- }
435
- }
436
- foreach($removeIds as $rId){
437
- self::log("HIP: Unfound ID $rID Remove from Priority Queue: ".json_encode(get_option($this->prioQ->get())));
438
- $this->prioQ->remove($rId);
439
  }
440
- return $ids;
441
  }
442
 
443
  public function handleImageProcessing($ID = null) {
444
- //die("stop");
 
 
 
445
  //0: check key
446
  if( $this->_settings->verifiedKey == false) {
447
  if($ID == null){
448
  $ids = $this->getFromPrioAndCheck();
449
- $ID = (count($ids) > 0 ? $ids[0] : null);
450
  }
451
- $response = array("Status" => ShortPixelAPI::STATUS_NO_KEY, "ImageID" => $ID, "Message" => "Missing API Key");
452
- update_option( 'wp-short-pixel-bulk-last-status', $response);
453
  die(json_encode($response));
454
  }
455
 
 
 
 
 
 
456
  self::log("HIP: 0 Priority Queue: ".json_encode($this->prioQ->get()));
 
457
 
458
  //1: get 3 ids to process. Take them with priority from the queue
459
  $ids = $this->getFromPrioAndCheck();
@@ -461,16 +553,33 @@ class WPShortPixel {
461
  $bulkStatus = $this->prioQ->bulkRunning();
462
  if($bulkStatus =='running') {
463
  $res = $this->getBulkItemsFromDb();
464
- $bulkItems = $res['ids'];
465
  if($bulkItems){
466
  $ids = array_merge ($ids, $bulkItems);
467
  }
468
  }
469
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  if ($ids === false || count( $ids ) == 0 ){
471
  $bulkEverRan = $this->prioQ->stopBulk();
472
- $avg = self::getAverageCompression();
473
- $fileCount = get_option('wp-short-pixel-fileCount');
474
  $response = array("Status" => self::BULK_EMPTY_QUEUE,
475
  "Message" => 'Empty queue ' . $this->prioQ->getStartBulkId() . '->' . $this->prioQ->getStopBulkId(),
476
  "BulkStatus" => ($this->prioQ->bulkRunning()
@@ -482,13 +591,16 @@ class WPShortPixel {
482
  }
483
 
484
  self::log("HIP: 1 Prio Queue: ".json_encode($this->prioQ->get()));
 
485
 
486
  //2: Send up to 3 files to the server for processing
487
  for($i = 0; $i < min(3, count($ids)); $i++) {
488
- $ID = $ids[$i];
489
- $tmpMeta = wp_get_attachment_metadata($ID);
490
- $compType = (isset($tmpMeta['ShortPixel']['type']) ? ($tmpMeta['ShortPixel']['type'] == 'lossy' ? 1 : 0) : $this->_settings->compressionType);
491
- $URLsAndPATHs = $this->sendToProcessing($ID, $compType, isset($tmpMeta['ShortPixel']['thumbsTodo']));
 
 
492
  if($i == 0) { //save for later use
493
  $firstUrlAndPaths = $URLsAndPATHs;
494
  }
@@ -496,135 +608,229 @@ class WPShortPixel {
496
 
497
  self::log("HIP: 2 Prio Queue: ".json_encode($this->prioQ->get()));
498
  //3: Retrieve the file for the first element of the list
499
- $ID = $ids[0];
500
- $result = $this->_apiInterface->processImage($firstUrlAndPaths['URLs'], $firstUrlAndPaths['PATHs'], $ID);
501
- $result["ImageID"] = $ID;
 
 
 
502
 
503
  self::log("HIP: 3 Prio Queue: ".json_encode($this->prioQ->get()));
504
 
505
  //4: update counters and priority list
506
  if( $result["Status"] == ShortPixelAPI::STATUS_SUCCESS) {
507
- self::log("HIP: Image ID $ID optimized successfully: ".json_encode($result));
508
- $prio = $this->prioQ->remove($ID);
509
  //remove also from the failed list if it failed in the past
510
- $prio = $this->prioQ->removeFromFailed($ID);
511
- $meta = wp_get_attachment_metadata($ID);
512
- $result["Type"] = isset($meta['ShortPixel']['type']) ? $meta['ShortPixel']['type'] : '';
513
  $result["ThumbsTotal"] = isset($meta['sizes']) && is_array($meta['sizes']) ? count($meta['sizes']): 0;
514
  $result["ThumbsCount"] = isset($meta['ShortPixel']['thumbsOpt'])
515
  ? $meta['ShortPixel']['thumbsOpt'] //below is the fallback for old optimized images that don't have thumbsOpt
516
  : ($this->_settings->processThumbnails ? $result["ThumbsTotal"] : 0);
 
 
 
 
 
 
517
 
518
- $result["BackupEnabled"] = ($this->getBackupFolder(get_attached_file($ID)) ? true : false);//$this->_settings->backupImages;
 
519
 
520
- if(!$prio && $ID <= $this->prioQ->getStartBulkId()) {
521
-
522
- $this->advanceBulk($ID, $result);
 
 
 
 
 
523
 
524
  $thumb = $bkThumb = "";
525
- $percent = 0;
526
- if(isset($meta["ShortPixelImprovement"]) && isset($meta["file"])){
527
- $percent = $meta["ShortPixelImprovement"];
528
-
529
- $filePath = explode("/", $meta["file"]);
 
 
 
530
 
531
  //Get a suitable thumb
532
- if(isset($meta["sizes"]) && count($meta["sizes"])) {
533
- $thumb = (isset($meta["sizes"]["medium"]) ? $meta["sizes"]["medium"]["file"] : (isset($meta["sizes"]["thumbnail"]) ? $meta["sizes"]["thumbnail"]["file"]: ""));
534
- if(!strlen($thumb)) { //fallback to the first in the list
535
- $sizes = isset($meta["sizes"]) ? array_values($meta["sizes"]) : array();
536
- $thumb = count($sizes) ? $sizes[0]['file'] : '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
537
  }
538
- } else { //fallback to the image itself
539
- $thumb = is_array($filePath) ? $filePath[count($filePath) - 1] : $filePath;
540
- }
541
-
542
- if(strlen($thumb) && $this->_settings->backupImages && $this->_settings->processThumbnails) {
543
- $backupUrl = content_url() . "/uploads/" . SP_BACKUP . "/";
544
- $urlBkPath = $this->_apiInterface->returnSubDir(get_attached_file($ID));
545
- $bkThumb = $backupUrl . $urlBkPath . "/" . $thumb;
546
- }
547
- if(strlen($thumb)) {
548
- $uploadDir = wp_upload_dir();
549
- $uploadsUrl = $uploadDir["baseurl"] . "/";
550
- $urlPath = implode("/", array_slice($filePath, 0, count($filePath) - 1));
551
- $thumb = $uploadsUrl . $urlPath . "/" . $thumb;
552
  }
 
553
  $result["Thumb"] = $thumb;
554
  $result["BkThumb"] = $bkThumb;
555
  }
556
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
  }
558
  elseif ($result["Status"] == ShortPixelAPI::STATUS_ERROR) {
559
- $meta = wp_get_attachment_metadata($ID);
560
- if(isset($meta['ShortPixel']['Retries']) && $meta['ShortPixel']['Retries'] > MAX_ERR_RETRIES) {
561
- if(! $this->prioQ->remove($ID) ){
562
- $this->advanceBulk($ID, $result);
 
 
 
563
  }
564
- unset($meta['ShortPixel']);
565
- wp_update_attachment_metadata($ID, $meta);
 
566
  $result["Status"] = ShortPixelAPI::STATUS_SKIP;
567
- $result["Message"] .= " Retry limit reached. Skipping file ID " . $ID . ".";
568
  }
569
  else {
570
- if(!isset($meta['ShortPixel'])) { $meta['ShortPixel'] = array(); }
571
- $meta['ShortPixel']['Retries'] = isset($meta['ShortPixel']['Retries']) ? $meta['ShortPixel']['Retries'] + 1 : 1;
572
- wp_update_attachment_metadata($ID, $meta);
 
573
  }
574
  }
575
  elseif ($result["Status"] == ShortPixelAPI::STATUS_SKIP
576
  || $result["Status"] == ShortPixelAPI::STATUS_FAIL) {
577
- $prio = $this->prioQ->remove($ID);
578
- if(isset($result["Code"]) && $result["Code"] == "write-fail") {
 
 
 
 
 
579
  //put this one in the failed images list - to show the user at the end
580
- $prio = $this->prioQ->addToFailed($ID);
581
- }
582
- $this->advanceBulk($ID, $result);
583
- }
584
- elseif ($this->prioQ->isPrio($ID) && $result["Status"] == ShortPixelAPI::STATUS_QUOTA_EXCEEDED) {
585
- if(!$this->prioQ->skippedCount()) {
586
- $this->prioQ->reverse(); //for the first prio item with quota exceeded, revert the prio queue as probably the bottom ones were processed
587
- }
588
- if($this->prioQ->allSkipped()) {
589
- $result["Stop"] = true;
590
- } else {
591
- $result["Stop"] = false;
592
- $this->prioQ->skip($ID);
593
- }
594
- self::log("HIP: 5 Prio Skipped: ".json_encode($this->prioQ->getSkipped()));
595
- }
596
- if($result["Status"] !== ShortPixelAPI::STATUS_RETRY) {
597
- update_option( 'wp-short-pixel-bulk-last-status', $result);
 
 
 
 
 
 
 
598
  }
599
  die(json_encode($result));
600
  }
601
 
 
602
  private function advanceBulk($processedID, &$result) {
 
603
  if($processedID <= $this->prioQ->getStartBulkId()) {
604
  $this->prioQ->setStartBulkId($processedID - 1);
605
  $this->prioQ->logBulkProgress();
606
- $deltaBulkPercent = $this->prioQ->getDeltaBulkPercent();
607
- $msg = $this->bulkProgressMessage($deltaBulkPercent, $this->prioQ->getTimeRemaining());
608
- $result["BulkPercent"] = $this->prioQ->getBulkPercent();
609
- $result["BulkMsg"] = $msg;
610
  }
611
  }
612
 
613
- private function sendToProcessing($ID, $compressionType = false, $onlyThumbs = false) {
614
- $URLsAndPATHs = $this->getURLsAndPATHs($ID, NULL, $onlyThumbs);
615
- $this->_apiInterface->doRequests($URLsAndPATHs['URLs'], false, $ID, $compressionType === false ? $this->_settings->compressionType : $compressionType);//send a request, do NOT wait for response
616
- $meta = wp_get_attachment_metadata($ID);
617
- if(!is_array($meta)) {
618
- $meta = array("previous_meta" => $meta, 'ShortPixel' => array());
619
- }
620
- $meta['ShortPixel']['WaitingProcessing'] = true;
621
- wp_update_attachment_metadata($ID, $meta);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
622
  return $URLsAndPATHs;
623
  }
624
 
625
  public function handleManualOptimization() {
626
- $imageId = intval($_GET['image_id']);
627
- $this->optimizeNowHook($imageId);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
628
  //do_action('shortpixel-optimize-now', $imageId);
629
 
630
  }
@@ -633,7 +839,7 @@ class WPShortPixel {
633
  public function optimizeNowHook($imageId) {
634
  if(self::isProcessable($imageId)) {
635
  $this->prioQ->push($imageId);
636
- $this->sendToProcessing($imageId);
637
  $ret = array("Status" => ShortPixelAPI::STATUS_SUCCESS, "message" => "");
638
  } else {
639
  $ret = array("Status" => ShortPixelAPI::STATUS_SKIP, "message" => $imageId);
@@ -651,7 +857,7 @@ class WPShortPixel {
651
 
652
  public function getBackupFolder($file) {
653
  $fileExtension = strtolower(substr($file,strrpos($file,".")+1));
654
- $SubDir = $this->_apiInterface->returnSubDir($file);
655
 
656
  //sometimes the month of original file and backup can differ
657
  if ( !file_exists(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . ShortPixelAPI::MB_basename($file)) ) {
@@ -682,19 +888,23 @@ class WPShortPixel {
682
  $pathInfo = pathinfo($file);
683
 
684
  $bkFolder = $this->getBackupFolder($file);
 
685
  $bkFile = $bkFolder . ShortPixelAPI::MB_basename($file);
686
 
 
 
 
 
687
  //first check if the file is readable by the current user - otherwise it will be unaccessible for the web browser
688
  // - collect the thumbs paths in the process
689
  if(! $this->setFilePerms($bkFile) ) return false;
690
  $thumbsPaths = array();
691
  if( !empty($meta['file']) && is_array($meta["sizes"]) ) {
692
  foreach($meta["sizes"] as $size => $imageData) {
693
- if(isset($imageData['file'])) {
694
- $source = $bkFolder . $imageData['file'];
695
- $thumbsPaths[$source] = $pathInfo['dirname'] . DIRECTORY_SEPARATOR . $imageData['file'];
696
- if(! $this->setFilePerms($source)) return false;
697
- }
698
  }
699
  }
700
 
@@ -708,7 +918,7 @@ class WPShortPixel {
708
  @rename($source, $destination);
709
  }
710
 
711
- $duplicates = ShortPixelAPI::getWPMLDuplicates($attachmentID);
712
  foreach($duplicates as $ID) {
713
  $crtMeta = $attachmentID == $ID ? $meta : wp_get_attachment_metadata($ID);
714
  if(is_numeric($crtMeta["ShortPixelImprovement"]) && 0 + $crtMeta["ShortPixelImprovement"] < 5 && $this->_settings->under5Percent > 0) {
@@ -745,16 +955,23 @@ class WPShortPixel {
745
  }
746
 
747
  public function handleRedo() {
748
- $ID = intval($_GET['attachment_ID']);
749
- $type = ($_GET['type'] == 'lossless' ? 'lossless' : 'lossy'); //sanity check
 
 
 
 
 
 
 
750
 
751
  $meta = $this->doRestore($ID);
752
  //die(var_dump($meta));
753
  if($meta) { //restore succeeded
754
- $meta['ShortPixel'] = array("type" => $type);
755
  wp_update_attachment_metadata($ID, $meta);
756
  $this->prioQ->push($ID);
757
- $this->sendToProcessing($ID, $type == 'lossy' ? 1 : 0);
758
  $ret = array("Status" => ShortPixelAPI::STATUS_SUCCESS, "Message" => "");
759
  } else {
760
  $ret = array("Status" => ShortPixelAPI::STATUS_SKIP, "Message" => "Could not restore from backup: " . $ID);
@@ -772,7 +989,7 @@ class WPShortPixel {
772
  $meta['ShortPixel']['thumbsTodo'] = true;
773
  wp_update_attachment_metadata($ID, $meta);
774
  $this->prioQ->push($ID);
775
- $this->sendToProcessing($ID);
776
  $ret = array("Status" => ShortPixelAPI::STATUS_SUCCESS, "message" => "");
777
  } else {
778
  $ret = array("Status" => ShortPixelAPI::STATUS_SKIP, "message" => (isset($meta['ShortPixelImprovement']) ? "No thumbnails to optimize for ID: " : "Please optimize image for ID:") . $ID);
@@ -797,9 +1014,8 @@ class WPShortPixel {
797
 
798
  if(self::isProcessable($ID) != false)
799
  {
800
- $SubDir = $this->_apiInterface->returnSubDir($file);
801
  try {
802
- $SubDir = $this->_apiInterface->returnSubDir($file);
803
 
804
  @unlink(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . ShortPixelAPI::MB_basename($file));
805
 
@@ -809,9 +1025,7 @@ class WPShortPixel {
809
  //remove thumbs thumbnails
810
  if(isset($meta["sizes"])) {
811
  foreach($meta["sizes"] as $size => $imageData) {
812
- if(isset($imageData['file'])) {
813
- @unlink($filesPath . ShortPixelAPI::MB_basename($imageData['file']));//remove thumbs
814
- }
815
  }
816
  }
817
  }
@@ -830,19 +1044,29 @@ class WPShortPixel {
830
  return $quotaData;
831
  }
832
  //$tempus = microtime(true);
833
- $imageCount = $this->countAllProcessableFiles();
 
 
 
834
  //echo("Count took (seconds): " . (microtime(true) - $tempus));
835
  foreach($imageCount as $key => $val) {
836
  $quotaData[$key] = $val;
837
  }
838
- //$quotaData['totalFiles'] = $imageCount['totalFiles'];
839
- //$quotaData['totalProcessedFiles'] = $imageCount['totalProcessedFiles'];
840
- //$quotaData['mainFiles'] = $imageCount['mainFiles'];
841
- //$quotaData['mainProcessedFiles'] = $imageCount['mainProcessedFiles'];
 
 
 
 
 
842
 
843
  if($quotaData['APICallsQuotaNumeric'] + $quotaData['APICallsQuotaOneTimeNumeric'] > $quotaData['APICallsMadeNumeric'] + $quotaData['APICallsMadeOneTimeNumeric']) {
844
  $this->_settings->quotaExceeded = '0';
845
  $this->_settings->prioritySkip = NULL;
 
 
846
  ?><script>var shortPixelQuotaExceeded = 0;</script><?php
847
  }
848
  else {
@@ -851,7 +1075,59 @@ class WPShortPixel {
851
  }
852
  return $quotaData;
853
  }
854
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
855
  public function bulkProcess() {
856
  global $wpdb;
857
 
@@ -866,24 +1142,42 @@ class WPShortPixel {
866
  if(isset($_POST['bulkProcessPause']))
867
  {//pause an ongoing bulk processing, it might be needed sometimes
868
  $this->prioQ->pauseBulk();
 
 
 
 
 
 
 
 
 
 
 
869
  }
870
 
871
  if(isset($_POST["bulkProcess"]))
872
  {
873
  //set the thumbnails option
874
  if ( isset($_POST['thumbnails']) ) {
875
- update_option('wp-short-process_thumbnails', 1);
876
  } else {
877
- update_option('wp-short-process_thumbnails', 0);
878
  }
879
  $this->prioQ->startBulk();
 
880
  self::log("BULK: Start: " . $this->prioQ->getStartBulkId() . ", stop: " . $this->prioQ->getStopBulkId() . " PrioQ: "
881
  .json_encode($this->prioQ->get()));
882
  }//end bulk process was clicked
883
 
884
- if(isset($_POST["bulkProcessResume"]))
885
  {
886
  $this->prioQ->resumeBulk();
 
 
 
 
 
 
887
  }//resume was clicked
888
 
889
  //figure out all the files that could be processed
@@ -892,13 +1186,26 @@ class WPShortPixel {
892
  $allFiles = $wpdb->get_results($qry);
893
  //figure out the files that are left to be processed
894
  $qry_left = "SELECT count(*) FilesLeftToBeProcessed FROM " . $wpdb->prefix . "postmeta
895
- WHERE meta_key = '_wp_attached_file' AND post_id <= " . $this->prioQ->getStartBulkId();
896
  $filesLeft = $wpdb->get_results($qry_left);
897
-
898
- if ( $filesLeft[0]->FilesLeftToBeProcessed > 0 && $this->prioQ->bulkRunning() )//bulk processing was started and is still running
 
 
 
 
 
899
  {
 
 
 
 
 
 
 
900
  $msg = $this->bulkProgressMessage($this->prioQ->getDeltaBulkPercent(), $this->prioQ->getTimeRemaining());
901
- $this->view->displayBulkProcessingRunning($this->prioQ->getBulkPercent(), $msg);
 
902
 
903
  // $imagesLeft = $filesLeft[0]->FilesLeftToBeProcessed;
904
  // $totalImages = $allFiles[0]->FilesToBeProcessed;
@@ -909,7 +1216,7 @@ class WPShortPixel {
909
  if($this->prioQ->bulkRan() && !$this->prioQ->bulkPaused()) {
910
  $this->prioQ->markBulkComplete();
911
  }
912
-
913
  //image count
914
  //$imageCount = $this->countAllProcessableFiles();
915
  $imageOnlyThumbs = $quotaData['totalFiles'] - $quotaData['mainFiles'];
@@ -918,9 +1225,10 @@ class WPShortPixel {
918
 
919
  //average compression
920
  $averageCompression = self::getAverageCompression();
921
- $this->view->displayBulkProcessingForm($quotaData, $thumbsProcessedCount, $under5PercentCount,
922
- $this->prioQ->bulkRan(), $averageCompression, get_option('wp-short-pixel-fileCount'),
923
- self::formatBytes(get_option('wp-short-pixel-savedSpace')), $this->prioQ->bulkPaused() ? $this->prioQ->getBulkPercent() : false);
 
924
  }
925
  }
926
  //end bulk processing
@@ -935,7 +1243,7 @@ class WPShortPixel {
935
  } elseif ($minutes > 240) {
936
  $timeEst = "~ " . round($minutes / 60) . " hours left";
937
  } elseif ($minutes > 60) {
938
- $timeEst = "~ " . round($minutes / 60) . " hours " . round($minutes%60/10) * 10 . " min. left";
939
  } elseif ($minutes > 20) {
940
  $timeEst = "~ " . round($minutes / 10) * 10 . " minutes left";
941
  } else {
@@ -975,17 +1283,107 @@ class WPShortPixel {
975
  return count(scandir(SP_BACKUP_FOLDER)) > 2 ? false : true;
976
  }
977
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
978
  public function renderSettingsMenu() {
979
  if ( !current_user_can( 'manage_options' ) ) {
980
  wp_die('You do not have sufficient permissions to access this page.');
981
  }
982
 
 
 
 
983
  //die(var_dump($_POST));
984
  $noticeHTML = "";
985
  $notice = null;
986
-
 
 
987
  $this->_settings->redirectedSettings = 2;
988
-
989
  //by default we try to fetch the API Key from wp-config.php (if defined)
990
  if ( defined("SHORTPIXEL_API_KEY") && strlen(SHORTPIXEL_API_KEY) == 20)
991
  {
@@ -995,9 +1393,15 @@ class WPShortPixel {
995
  $_POST['key'] = SHORTPIXEL_API_KEY;
996
  }
997
 
998
- if(isset($_POST['save']) || (isset($_POST['validate']) && $_POST['validate'] == "validate")) {
999
- //handle API Key - common for save and validate
1000
- $_POST['key'] = trim(str_replace("*","",$_POST['key']));
 
 
 
 
 
 
1001
 
1002
  if ( strlen($_POST['key']) <> 20 )
1003
  {
@@ -1014,9 +1418,9 @@ class WPShortPixel {
1014
  if($validityData['APIKeyValid']) {
1015
  if(isset($_POST['validate']) && $_POST['validate'] == "validate") {
1016
  // delete last status if it was no valid key
1017
- $lastStatus = get_option( 'wp-short-pixel-bulk-last-status');
1018
- if(isset($lastStatus) && $lastStatus['Status'] == ShortPixelAPI::STATUS_NO_KEY) {
1019
- delete_option( 'wp-short-pixel-bulk-last-status');
1020
  }
1021
  //display notification
1022
  $urlParts = explode("/", get_site_url());
@@ -1034,7 +1438,7 @@ class WPShortPixel {
1034
  //test that the "uploads" have the right rights and also we can create the backup dir for ShortPixel
1035
  if ( !file_exists(SP_BACKUP_FOLDER) && !@mkdir(SP_BACKUP_FOLDER, 0777, true) )
1036
  $notice = array("status" => "error", "msg" => "There is something preventing us to create a new folder for backing up your original files.<BR>
1037
- Please make sure that folder <b>" . WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "uploads</b> has the necessary write and read rights.");
1038
  } else {
1039
  if(isset($_POST['validate'])) {
1040
  //display notification
@@ -1044,9 +1448,8 @@ class WPShortPixel {
1044
  }
1045
  }
1046
 
1047
-
1048
  //if save button - we process the rest of the form elements
1049
- if(isset($_POST['save'])) {
1050
  $this->_settings->compressionType = $_POST['compressionType'];
1051
  if(isset($_POST['thumbnails'])) { $this->_settings->processThumbnails = 1; } else { $this->_settings->processThumbnails = 0; }
1052
  if(isset($_POST['backupImages'])) { $this->_settings->backupImages = 1; } else { $this->_settings->backupImages = 0; }
@@ -1059,12 +1462,42 @@ class WPShortPixel {
1059
  $this->_settings->siteAuthUser = (isset($_POST['siteAuthUser']) ? $_POST['siteAuthUser']: $this->_settings->siteAuthUser);
1060
  $this->_settings->siteAuthPass = (isset($_POST['siteAuthPass']) ? $_POST['siteAuthPass']: $this->_settings->siteAuthPass);
1061
 
1062
- if($_POST['save'] == "Save and Go to Bulk Process") {
 
 
 
 
1063
  wp_redirect("upload.php?page=wp-short-pixel-bulk");
1064
  exit();
1065
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1066
  }
1067
  }
 
1068
  //now output headers. They were prevented with noheaders=true in the form url in order to be able to redirect if bulk was pressed
1069
  if(isset($_REQUEST['noheader'])) {
1070
  require_once(ABSPATH . 'wp-admin/admin-header.php');
@@ -1076,17 +1509,35 @@ class WPShortPixel {
1076
  }
1077
 
1078
  $quotaData = $this->checkQuotaAndAlert(isset($validityData) ? $validityData : null);
 
 
 
 
 
 
 
 
 
 
 
 
1079
 
 
 
 
 
 
 
1080
  if($this->_settings->verifiedKey) {
1081
  $fileCount = number_format($this->_settings->fileCount);
1082
  $savedSpace = self::formatBytes($this->_settings->savedSpace,2);
1083
- $averageCompression = self::getAverageCompression();
1084
  $savedBandwidth = self::formatBytes($this->_settings->savedSpace * 10000,2);
1085
  if (is_numeric($quotaData['APICallsQuota'])) {
1086
  $quotaData['APICallsQuota'] .= "/month";
1087
  }
1088
  $backupFolderSize = self::formatBytes(self::folderSize(SP_BACKUP_FOLDER));
1089
- $remainingImages = $quotaData['APICallsQuotaNumeric'] + $quotaData['APICallsQuotaOneTimeNumeric'] - $quotaData['APICallsMadeNumeric'] - $quotaData['APICallsMadeOneTimeNumeric'];
1090
  $remainingImages = ( $remainingImages < 0 ) ? 0 : number_format($remainingImages);
1091
  $totalCallsMade = number_format($quotaData['APICallsMadeNumeric'] + $quotaData['APICallsMadeOneTimeNumeric']);
1092
 
@@ -1094,16 +1545,31 @@ class WPShortPixel {
1094
  if(is_wp_error( $resources )) {
1095
  $resources = array();
1096
  }
1097
- $this->view->displaySettings(is_main_site() || (is_multisite() && !defined("SHORTPIXEL_API_KEY")),
1098
- $quotaData, $notice, $resources, $averageCompression, $savedSpace, $savedBandwidth,
1099
- $remainingImages, $totalCallsMade, $fileCount, $backupFolderSize);
 
1100
  } else {
1101
- $this->view->displaySettings(is_main_site() || (is_multisite() && !defined("SHORTPIXEL_API_KEY")),
1102
- $quotaData, $notice);
1103
  }
1104
 
1105
  }
1106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1107
  public function getAverageCompression(){
1108
  return $this->_settings->totalOptimized > 0
1109
  ? round(( 1 - ( $this->_settings->totalOptimized / $this->_settings->totalOriginal ) ) * 100, 2)
@@ -1138,7 +1604,7 @@ class WPShortPixel {
1138
  }
1139
  if($validate) {
1140
  $args['body']['DomainCheck'] = get_site_url();
1141
- $imageCount = $this->countAllProcessableFiles();
1142
  $args['body']['ImagesCount'] = $imageCount['mainFiles'];
1143
  $args['body']['ThumbsCount'] = $imageCount['totalFiles'] - $imageCount['mainFiles'];
1144
  $argsStr .= "&DomainCheck={$args['body']['DomainCheck']}&ImagesCount={$imageCount['mainFiles']}&ThumbsCount={$args['body']['ThumbsCount']}";
@@ -1188,7 +1654,7 @@ class WPShortPixel {
1188
  $response = wp_remote_get($requestURL, $args);
1189
  $comm[] = array("sent" => "POST: " . $requestURL, "args" => $args, "received" => $response);
1190
  }
1191
-
1192
  $defaultData = array(
1193
  "APIKeyValid" => false,
1194
  "Message" => 'API Key could not be validated due to a connectivity error.<BR>Your firewall may be blocking us. Please contact your hosting provider and ask them to allow connections from your site to IP 176.9.106.46.<BR> If you still cannot validate your API Key after this, please <a href="https://shortpixel.com/contact" target="_blank">contact us</a> and we will try to help. ',
@@ -1212,7 +1678,7 @@ class WPShortPixel {
1212
  }
1213
 
1214
  $data = $response['body'];
1215
- $data = $this->parseJSON($data);
1216
 
1217
  if(empty($data)) { return $defaultData; }
1218
 
@@ -1226,10 +1692,10 @@ class WPShortPixel {
1226
  else
1227
  $this->_settings->quotaExceeded = 1;//activate quota limiting
1228
 
1229
- //if a not valid status exists, delete it
1230
- $lastStatus = $this->_settings->getOpt( 'wp-short-pixel-bulk-last-status', array('Status' => ShortPixelAPI::STATUS_SUCCESS));
1231
- if($lastStatus['Status'] == ShortPixelAPI::STATUS_NO_KEY) {
1232
- delete_option('wp-short-pixel-bulk-last-status');
1233
  }
1234
 
1235
  return array(
@@ -1242,6 +1708,7 @@ class WPShortPixel {
1242
  "APICallsQuotaNumeric" => $data->APICallsQuota,
1243
  "APICallsMadeOneTimeNumeric" => $data->APICallsMadeOneTime,
1244
  "APICallsQuotaOneTimeNumeric" => $data->APICallsQuotaOneTime,
 
1245
  "APILastRenewalDate" => $data->DateSubscription,
1246
  "DomainCheck" => (isset($data->DomainCheck) ? $data->DomainCheck : null)
1247
  );
@@ -1257,9 +1724,6 @@ class WPShortPixel {
1257
  $quotaExceeded = $this->_settings->quotaExceeded;
1258
  $renderData = array("id" => $id, "showActions" => current_user_can( 'manage_options' ));
1259
 
1260
- $data2 = $data;
1261
-
1262
-
1263
  if($invalidKey) { //invalid key - let the user first register and only then
1264
  $renderData['status'] = 'invalidKey';
1265
  $this->view->renderCustomColumn($id, $renderData);
@@ -1315,16 +1779,16 @@ class WPShortPixel {
1315
  }
1316
  elseif(isset($data['ShortPixel']['WaitingProcessing'])) {
1317
  $renderData['status'] = $quotaExceeded ? 'quotaExceeded' : 'retry';
1318
- $renderData['message'] = "<img src=\"" . plugins_url( 'img/loading.gif', __FILE__ ) . "\" class='sp-loading-small'>&nbsp;Image waiting to be processed.";
1319
- if($id > $this->prioQ->getFlagBulkId()) $this->prioQ->push($id); //should be there but just to make sure
1320
  }
1321
  else { //finally
1322
  $renderData['status'] = $quotaExceeded ? 'quotaExceeded' : 'optimizeNow';
1323
  $sizes = isset($data['sizes']) ? count($data['sizes']) : 0;
1324
  $renderData['thumbsTotal'] = $sizes;
1325
  $renderData['message'] = ($fileExtension == "pdf" ? 'PDF' : 'Image') . ' not processed.';
1326
- }
1327
-
1328
  $this->view->renderCustomColumn($id, $renderData);
1329
  }
1330
  }
@@ -1337,23 +1801,58 @@ class WPShortPixel {
1337
  return $defaults;
1338
  }
1339
 
1340
- public function generatePluginLinks($links) {
1341
- $in = '<a href="options-general.php?page=wp-shortpixel">Settings</a>';
1342
- array_unshift($links, $in);
1343
- return $links;
 
 
1344
  }
1345
 
1346
- public function parseJSON($data) {
1347
- if ( function_exists('json_decode') ) {
1348
- $data = json_decode( $data );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1349
  } else {
1350
- require_once( 'JSON/JSON.php' );
1351
- $json = new Services_JSON( );
1352
- $data = $json->decode( $data );
 
 
 
 
1353
  }
1354
- return $data;
1355
  }
1356
 
 
 
 
 
 
1357
 
1358
  static public function formatBytes($bytes, $precision = 2) {
1359
  $units = array('B', 'KB', 'MB', 'GB', 'TB');
@@ -1374,7 +1873,7 @@ class WPShortPixel {
1374
 
1375
  static public function isProcessablePath($path) {
1376
  $pathParts = pathinfo($path);
1377
- if( isset($pathParts['extension']) && in_array(strtolower($pathParts['extension']), array('jpg', 'jpeg', 'gif', 'png', 'pdf'))) {
1378
  return true;
1379
  } else {
1380
  return false;
@@ -1383,50 +1882,14 @@ class WPShortPixel {
1383
 
1384
 
1385
  //return an array with URL(s) and PATH(s) for this file
1386
- public function getURLsAndPATHs($ID, $meta = NULL, $onlyThumbs = false) {
1387
-
1388
- if ( !parse_url(WP_CONTENT_URL, PHP_URL_SCHEME) ) {//no absolute URLs used -> we implement a hack
1389
- $url = get_site_url() . wp_get_attachment_url($ID);//get the file URL
1390
- }
1391
- else {
1392
- $url = wp_get_attachment_url($ID);//get the file URL
1393
- }
1394
- $urlList[] = $url;
1395
- $path = get_attached_file($ID);//get the full file PATH
1396
- $filePath[] = $path;
1397
-
1398
- if ( $meta == NULL ) {
1399
- $meta = wp_get_attachment_metadata($ID);
1400
- }
1401
-
1402
- //it is NOT a PDF file and thumbs are processable
1403
- if ( strtolower(substr($filePath[0],strrpos($filePath[0], ".")+1)) != "pdf"
1404
- && ($this->_settings->processThumbnails || $onlyThumbs)
1405
- && isset($meta['sizes']) && is_array($meta['sizes']))
1406
- {
1407
- foreach( $meta['sizes'] as $thumbnailInfo ) {
1408
- if(isset($thumbnailInfo['file'])) {
1409
- $urlList[] = str_replace(ShortPixelAPI::MB_basename($urlList[0]), $thumbnailInfo['file'], $url);
1410
- $filePath[] = str_replace(ShortPixelAPI::MB_basename($filePath[0]), $thumbnailInfo['file'], $path);
1411
- }
1412
- }
1413
- }
1414
- if(!isset($meta['sizes']) || !is_array($meta['sizes'])) {
1415
- self::log("getURLsAndPATHs: no meta sizes for ID $ID : " . json_encode($meta));
1416
- }
1417
-
1418
- if($onlyThumbs) { //remove the main image
1419
- array_shift($urlList);
1420
- array_shift($filePath);
1421
- }
1422
- return array("URLs" => $urlList, "PATHs" => $filePath);
1423
  }
1424
 
1425
 
1426
  public static function deleteDir($dirPath) {
1427
- if (substr($dirPath, strlen($dirPath) - 1, 1) !=
1428
- '/') {
1429
- $dirPath .= '/';
1430
  }
1431
  $files = glob($dirPath . '*', GLOB_MARK);
1432
  foreach ($files as $file) {
@@ -1446,7 +1909,7 @@ class WPShortPixel {
1446
  } else {
1447
  return $total_size;
1448
  }
1449
- $cleanPath = rtrim($path, '/'). '/';
1450
  foreach($files as $t) {
1451
  if ($t<>"." && $t<>"..")
1452
  {
@@ -1464,200 +1927,34 @@ class WPShortPixel {
1464
  return $total_size;
1465
  }
1466
 
1467
- public static function getMaxMediaId() {
1468
- global $wpdb;
1469
- $queryMax = "SELECT max(post_id) as QueryID FROM " . $wpdb->prefix . "postmeta";
1470
- $resultQuery = $wpdb->get_results($queryMax);
1471
- return $resultQuery[0]->QueryID;
1472
- }
1473
-
1474
- public function getMinMediaId() {
1475
- global $wpdb;
1476
- $queryMax = "SELECT min(post_id) as QueryID FROM " . $wpdb->prefix . "postmeta";
1477
- $resultQuery = $wpdb->get_results($queryMax);
1478
- return $resultQuery[0]->QueryID;
1479
- }
1480
-
1481
- protected function getOptimalChunkSize() {
1482
- global $wpdb;
1483
- $cnt = $wpdb->get_results("SELECT count(*) posts FROM " . $wpdb->prefix . "posts");
1484
- $posts = isset($cnt) && count($cnt) > 0 ? $cnt[0]->posts : 0;
1485
- if($posts > 100000) {
1486
- return 20000;
1487
- } elseif ($posts > 50000) {
1488
- return 5000;
1489
- } elseif($posts > 10000) {
1490
- return 2000;
1491
- } else {
1492
- return 500;
1493
- }
1494
- }
1495
-
1496
- protected function getPostIdsChunk($minId, $maxId, $pointer, $limit) {
1497
- global $wpdb;
1498
-
1499
- $ids = array();
1500
- $idList = $wpdb->get_results("SELECT ID, post_mime_type FROM " . $wpdb->prefix . "posts
1501
- WHERE ( ID <= $maxId AND ID > $minId )
1502
- LIMIT $pointer,$limit");
1503
- if ( empty($idList) ) {
1504
- return null;
1505
- }
1506
- foreach($idList as $item) {
1507
- if($item->post_mime_type != '') {
1508
- $ids[] = $item->ID;
1509
- }
1510
- }
1511
- return $ids;
1512
- }
1513
-
1514
- //count all the processable files in media library (while limiting the results to max 10000)
1515
- public function countAllProcessableFiles($maxId = PHP_INT_MAX, $minId = 0){
1516
- global $wpdb;
1517
-
1518
- $totalFiles = $mainFiles = $processedMainFiles = $processedTotalFiles =
1519
- $procLossyMainFiles = $procLossyTotalFiles = $procLosslessMainFiles = $procLosslessTotalFiles = $mainUnprocessedThumbs = 0;
1520
- $filesMap = $processedFilesMap = array();
1521
- $limit = $this->getOptimalChunkSize();
1522
- $pointer = 0;
1523
- $filesWithErrors = array();
1524
-
1525
- //count all the files, main and thumbs
1526
- while ( 1 ) {
1527
- /* ALTERNATE CODE WITH JOIN - seems less efficient than select ids / select in
1528
- $filesList= $wpdb->get_results("SELECT pm.* FROM " . $wpdb->prefix . "postmeta pm
1529
- INNER JOIN " . $wpdb->prefix . "posts p on p.ID = pm.post_id
1530
- WHERE ( p.ID <= $maxId AND p.ID > $minId ) and p.post_mime_type <> ''
1531
- AND ( pm.meta_key = '_wp_attached_file' OR pm.meta_key = '_wp_attachment_metadata' )
1532
- LIMIT $pointer,$limit");
1533
- if ( empty($filesList) ) //we parsed all the results
1534
- break;
1535
- */
1536
- $ids = $this->getPostIdsChunk($minId, $maxId, $pointer, $limit);
1537
- if($ids === null) {
1538
- break; //we parsed all the results
1539
- }
1540
- elseif(count($ids) == 0) {
1541
- $pointer += $limit;
1542
- continue;
1543
- }
1544
-
1545
- $filesList= $wpdb->get_results("SELECT * FROM " . $wpdb->prefix . "postmeta
1546
- WHERE post_id IN (" . implode(',', $ids) . ")
1547
- AND ( meta_key = '_wp_attached_file' OR meta_key = '_wp_attachment_metadata' )");
1548
-
1549
- foreach ( $filesList as $file )
1550
- {
1551
- if ( $file->meta_key == "_wp_attached_file" )
1552
- {//count pdf files only
1553
- $extension = substr($file->meta_value, strrpos($file->meta_value,".") + 1 );
1554
- if ( $extension == "pdf" && !isset($filesMap[$file->meta_value]))
1555
- {
1556
- $totalFiles++;
1557
- $mainFiles++;
1558
- $filesMap[$file->meta_value] = 1;
1559
- }
1560
- }
1561
- else //_wp_attachment_metadata
1562
- {
1563
- $attachment = unserialize($file->meta_value);
1564
- //processable
1565
- $isProcessable = false;
1566
- if(isset($attachment['file']) && !isset($filesMap[$attachment['file']]) && self::isProcessablePath($attachment['file'])){
1567
- $isProcessable = true;
1568
- if ( isset($attachment['sizes']) ) {
1569
- $totalFiles += count($attachment['sizes']);
1570
- }
1571
- if ( isset($attachment['file']) )
1572
- {
1573
- $totalFiles++;
1574
- $mainFiles++;
1575
- $filesMap[$attachment['file']] = 1;
1576
- }
1577
- }
1578
- //processed
1579
- if (isset($attachment['ShortPixelImprovement'])
1580
- && ($attachment['ShortPixelImprovement'] > 0 || $attachment['ShortPixelImprovement'] === 0.0 || $attachment['ShortPixelImprovement'] === "0")
1581
- //for PDFs there is no file field so just let it pass.
1582
- && (!isset($attachment['file']) || !isset($processedFilesMap[$attachment['file']])) ) {
1583
-
1584
- //add main file to counts
1585
- $processedMainFiles++;
1586
- $processedTotalFiles++;
1587
- $type = isset($attachment['ShortPixel']['type']) ? $attachment['ShortPixel']['type'] : null;
1588
- if($type == 'lossy') {
1589
- $procLossyMainFiles++;
1590
- $procLossyTotalFiles++;
1591
- } else {
1592
- $procLosslessMainFiles++;
1593
- $procLosslessTotalFiles++;
1594
- }
1595
-
1596
- //get the thumbs processed for that attachment
1597
- $thumbs = $allThumbs = 0;
1598
- if ( isset($attachment['ShortPixel']['thumbsOpt']) ) {
1599
- $thumbs = $attachment['ShortPixel']['thumbsOpt'];
1600
- }
1601
- elseif ( isset($attachment['sizes']) ) {
1602
- $thumbs = count($attachment['sizes']);
1603
- }
1604
- if ( isset($attachment['sizes']) && count($attachment['sizes']) > $thumbs) {
1605
- $mainUnprocessedThumbs++;
1606
- }
1607
-
1608
- //increment with thumbs processed
1609
- $processedTotalFiles += $thumbs;
1610
- if($type == 'lossy') {
1611
- $procLossyTotalFiles += $thumbs;
1612
- } else {
1613
- $procLosslessTotalFiles += $thumbs;
1614
- }
1615
-
1616
- if ( isset($attachment['file']) ) {
1617
- $processedFilesMap[$attachment['file']] = 1;
1618
- }
1619
- }
1620
- elseif($isProcessable && isset($attachment['ShortPixelImprovement']) && count($filesWithErrors) < 50) {
1621
- $filePath = explode("/", $attachment["file"]);
1622
- $name = is_array($filePath)? $filePath[count($filePath) - 1] : $file->post_id;
1623
- $filesWithErrors[$file->post_id] = array('Name' => $name, 'Message' => $attachment['ShortPixelImprovement']);
1624
- }
1625
-
1626
- }
1627
- }
1628
- unset($filesList);
1629
- $pointer += $limit;
1630
-
1631
- }//end while
1632
-
1633
- return array("totalFiles" => $totalFiles, "mainFiles" => $mainFiles,
1634
- "totalProcessedFiles" => $processedTotalFiles, "mainProcessedFiles" => $processedMainFiles,
1635
- "totalProcLossyFiles" => $procLossyTotalFiles, "mainProcLossyFiles" => $procLossyMainFiles,
1636
- "totalProcLosslessFiles" => $procLosslessTotalFiles, "mainProcLosslessFiles" => $procLosslessMainFiles,
1637
- "mainUnprocessedThumbs" => $mainUnprocessedThumbs,
1638
- "filesWithErrors" => $filesWithErrors
1639
- );
1640
- }
1641
-
1642
  public function migrateBackupFolder() {
1643
- $oldBackupFolder = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'ShortpixelBackups';
1644
 
1645
- if(!file_exists($oldBackupFolder)) return; //if old backup folder does not exist then there is nothing to do
1646
 
1647
  if(!file_exists(SP_BACKUP_FOLDER)) {
1648
- //we check that the backup folder exists, if not we create it so we can copy into it
1649
- if(!mkdir(SP_BACKUP_FOLDER, 0777, true)) return;
1650
- }
1651
 
1652
- $scannedDirectory = array_diff(scandir($oldBackupFolder), array('..', '.'));
1653
- foreach($scannedDirectory as $file) {
1654
- @rename($oldBackupFolder.DIRECTORY_SEPARATOR.$file, SP_BACKUP_FOLDER.DIRECTORY_SEPARATOR.$file);
 
 
 
 
 
1655
  }
1656
- $scannedDirectory = array_diff(scandir($oldBackupFolder), array('..', '.'));
1657
- if(empty($scannedDirectory)) {
1658
- @rmdir($oldBackupFolder);
 
 
 
 
 
1659
  }
1660
-
1661
  return;
1662
  }
1663
 
@@ -1691,7 +1988,7 @@ class WPShortPixel {
1691
  */
1692
  public static function encrypt($pure_string, $encryption_key)
1693
  {
1694
- if(!function_exists("mcrypt_get_iv_size") || !function_exists('utf8_encode')) {
1695
  return "";
1696
  }
1697
  $iv_size = \mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
@@ -1725,7 +2022,6 @@ class WPShortPixel {
1725
  return $this->_settings;
1726
  }
1727
 
1728
-
1729
  public function getResizeImages() {
1730
  return $this->_settings->resizeImages;
1731
  }
@@ -1746,20 +2042,57 @@ class WPShortPixel {
1746
  public function getCompressionType() {
1747
  return $this->_settings->compressionType;
1748
  }
 
 
 
 
 
 
 
1749
 
1750
  }
1751
 
1752
- function onInit() {
1753
- if ( ! is_admin() || !is_user_logged_in() || ! (current_user_can( 'manage_options' ) || current_user_can( 'upload_files' )) ) {
1754
- return;
1755
- }
1756
- $pluginInstance = new WPShortPixel;
1757
  global $pluginInstance;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1758
  }
1759
 
1760
- if ( !function_exists( 'vc_action' ) || vc_action() !== 'vc_inline' ) { //handle incompatibility with Visual Composer
1761
- add_action( 'init', 'onInit');
 
 
 
 
 
 
 
 
 
 
 
 
 
1762
 
 
 
 
 
 
1763
  register_activation_hook( __FILE__, array( 'WPShortPixel', 'shortPixelActivatePlugin' ) );
1764
  register_deactivation_hook( __FILE__, array( 'WPShortPixel', 'shortPixelDeactivatePlugin' ) );
1765
 
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.0.0
7
  * Author: ShortPixel
8
  * Author URI: https://shortpixel.com
9
  */
10
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  define('SP_RESET_ON_ACTIVATE', false); //if true TODO set false
12
 
13
  define('SP_AFFILIATE_CODE', '');
14
 
15
+ define('PLUGIN_VERSION', "4.0.0");
16
  define('SP_MAX_TIMEOUT', 10);
17
  define('SP_VALIDATE_MAX_TIMEOUT', 15);
18
  define('SP_BACKUP', 'ShortpixelBackups');
 
19
  define('MAX_API_RETRIES', 50);
20
  define('MAX_ERR_RETRIES', 5);
21
+ define('MAX_FAIL_RETRIES', 3);
22
  $MAX_EXECUTION_TIME = ini_get('max_execution_time');
23
 
24
+ require_once(ABSPATH . 'wp-admin/includes/file.php');
25
+
26
+ $sp__uploads = wp_upload_dir();
27
+ define('SP_UPLOADS_BASE', $sp__uploads['basedir']);
28
+ define('SP_UPLOADS_NAME', basename(is_main_site() ? SP_UPLOADS_BASE : dirname(dirname(SP_UPLOADS_BASE))));
29
+ define('SP_UPLOADS_BASE_REL', str_replace(get_home_path(),"", $sp__uploads['basedir']));
30
+ $sp__backupBase = is_main_site() ? SP_UPLOADS_BASE : dirname(dirname(SP_UPLOADS_BASE));
31
+ define('SP_BACKUP_FOLDER', $sp__backupBase . DIRECTORY_SEPARATOR . SP_BACKUP);
32
+
33
  /*
34
  if ( is_numeric($MAX_EXECUTION_TIME) && $MAX_EXECUTION_TIME > 10 )
35
  define('MAX_EXECUTION_TIME', $MAX_EXECUTION_TIME - 5 ); //in seconds
50
  private $_settings = null;
51
  private $prioQ = null;
52
  private $view = null;
53
+
54
+ private $hasNextGen = false;
55
+ private $spMetaDao = null;
56
+
57
+ public static $PROCESSABLE_EXTENSIONS = array('jpg', 'jpeg', 'gif', 'png', 'pdf');
58
 
59
  public function __construct() {
60
  if (!session_id()) {
61
  session_start();
62
  }
63
+
64
+ require_once('wp-shortpixel-req.php');
65
 
66
  $isAdminUser = current_user_can( 'manage_options' );
67
 
68
  $this->_affiliateSufix = (strlen(SP_AFFILIATE_CODE)) ? "/affiliate/" . SP_AFFILIATE_CODE : "";
69
  $this->_settings = new WPShortPixelSettings();
70
  $this->_apiInterface = new ShortPixelAPI($this->_settings);
71
+ $this->hasNextGen = ShortPixelNextGenAdapter::hasNextGen();
72
+ $this->spMetaDao = new ShortPixelCustomMetaDao(new WpShortPixelDb(), $this->_settings->hasCustomFolders);
73
  $this->prioQ = new ShortPixelQueue($this, $this->_settings);
74
  $this->view = new ShortPixelView($this);
75
 
78
  $this->setDefaultViewModeList();//set default mode as list. only @ first run
79
 
80
  //add hook for image upload processing
81
+ //add_filter( 'wp_generate_attachment_metadata', array( &$this, 'handleMediaLibraryImageUpload' ), 10, 2 ); // now external
82
  add_filter( 'plugin_action_links_' . plugin_basename(__FILE__), array(&$this, 'generatePluginLinks'));//for plugin settings page
83
 
84
  //add_action( 'admin_footer', array(&$this, 'handleImageProcessing'));
86
  //Media custom column
87
  add_filter( 'manage_media_columns', array( &$this, 'columns' ) );//add media library column header
88
  add_action( 'manage_media_custom_column', array( &$this, 'generateCustomColumn' ), 10, 2 );//generate the media library column
89
+ //for NextGen
90
+ if($this->_settings->hasCustomFolders) {
91
+ add_filter( 'ngg_manage_images_columns', array( &$this, 'nggColumns' ) );
92
+ add_filter( 'ngg_manage_images_number_of_columns', array( &$this, 'nggCountColumns' ) );
93
+ add_filter( 'ngg_manage_images_column_7_header', array( &$this, 'nggColumnHeader' ) );
94
+ add_filter( 'ngg_manage_images_column_7_content', array( &$this, 'nggColumnContent' ) );
95
+ // hook on the NextGen gallery list update
96
+ add_action('ngg_update_addgallery_page', array( &$this, 'addNextGenGalleriesToCustom'));
97
+ }
98
+
99
  //custom hook
100
  add_action( 'shortpixel-optimize-now', array( &$this, 'optimizeNowHook' ), 10, 1);
101
 
103
  //add settings page
104
  add_action( 'admin_menu', array( &$this, 'registerSettingsPage' ) );//display SP in Settings menu
105
  add_action( 'admin_menu', array( &$this, 'registerAdminPage' ) );
106
+
107
+ add_action('wp_ajax_shortpixel_browse_content', array(&$this, 'browseContent'));
108
 
109
  add_action( 'delete_attachment', array( &$this, 'handleDeleteAttachmentInBackup' ) );
110
  add_action( 'load-upload.php', array( &$this, 'handleCustomBulk'));
129
  add_action( 'wp_ajax_shortpixel_dismiss_media_alert', array(&$this, 'dismissMediaAlert'));
130
  //check quota
131
  add_action('admin_action_shortpixel_check_quota', array(&$this, 'handleCheckQuota'));
 
132
  //This adds the constants used in PHP to be available also in JS
133
  add_action( 'admin_footer', array( &$this, 'shortPixelJS') );
134
+
135
+ if($this->_settings->frontBootstrap) {
136
+ //also need to have it in the front footer then
137
+ add_action( 'wp_footer', array( &$this, 'shortPixelJS') );
138
+ //need to add the nopriv action for when items exist in the queue and no user is logged in
139
+ add_action( 'wp_ajax_nopriv_shortpixel_image_processing', array( &$this, 'handleImageProcessing') );
140
+ }
141
  //register a method to display admin notices if necessary
142
  add_action('admin_notices', array( &$this, 'displayAdminNotices'));
143
+
144
  $this->migrateBackupFolder();
145
 
146
  if(!$this->_settings->redirectedSettings && !$this->_settings->verifiedKey && (!function_exists("is_multisite") || !is_multisite())) {
160
  }
161
 
162
  function registerAdminPage( ) {
163
+ if(count($this->spMetaDao->hasFoldersTable() && $this->spMetaDao->getFolders())) {
164
+ add_media_page( 'Other Media Optimized by ShortPixel', 'Other Media', 'edit_others_posts', 'wp-short-pixel-custom', array( &$this, 'listCustomMedia' ) );
165
+ }
166
  add_media_page( 'ShortPixel Bulk Process', 'Bulk ShortPixel', 'edit_others_posts', 'wp-short-pixel-bulk', array( &$this, 'bulkProcess' ) );
167
  }
168
 
177
 
178
  public static function shortPixelDeactivatePlugin()//reset some params to avoid trouble for plugins that were activated/deactivated/activated
179
  {
180
+ include_once dirname( __FILE__ ) . '/wp-shortpixel-req.php';
181
  ShortPixelQueue::resetBulk();
182
  ShortPixelQueue::resetPrio();
183
  WPShortPixelSettings::onDeactivate();
185
 
186
  public function displayAdminNotices() {
187
  if(!$this->_settings->verifiedKey) {
188
+ $dismissed = $this->_settings->dismissedNotices ? $this->_settings->dismissedNotices : array();
189
  $now = time();
190
+ $act = $this->_settings->activationDate ? $this->_settings->activationDate : $now;
191
+ if($this->_settings->activationNotice && $this->_settings->redirectedSettings >= 2) {
192
  ShortPixelView::displayActivationNotice();
193
+ $this->_settings->activationNotice = null;
194
  }
195
  if( ($now > $act + 7200) && !isset($dismissed['2h'])) {
196
  ShortPixelView::displayActivationNotice('2h');
202
 
203
  public function dismissAdminNotice() {
204
  $noticeId = preg_replace('|[^a-z0-9]|i', '', $_GET['notice_id']);
205
+ $dismissed = $this->_settings->dismissedNotices ? $this->_settings->dismissedNotices : array();
206
  $dismissed[$noticeId] = true;
207
+ $this->_settings->dismissedNotices = $dismissed;
208
  die(json_encode(array("Status" => 'success', "Message" => 'Notice ID: ' . $noticeId . ' dismissed')));
209
  }
210
 
216
  //set default move as "list". only set once, it won't try to set the default mode again.
217
  public function setDefaultViewModeList()
218
  {
219
+ if($this->_settings->mediaLibraryViewMode === false)
220
  {
221
+ $this->_settings->mediaLibraryViewMode = 1;
222
  if ( function_exists('get_currentuserinfo') )
223
  {
224
  global $current_user;
242
 
243
  function shortPixelJS() { ?>
244
  <script type="text/javascript" >
245
+ var ShortPixelConstants = {
246
+ STATUS_SUCCESS: <?php echo ShortPixelAPI::STATUS_SUCCESS; ?>,
247
+ STATUS_EMPTY_QUEUE: <?php echo self::BULK_EMPTY_QUEUE; ?>,
248
+ STATUS_ERROR: <?php echo ShortPixelAPI::STATUS_ERROR; ?>,
249
+ STATUS_FAIL: <?php echo ShortPixelAPI::STATUS_FAIL; ?>,
250
+ STATUS_QUOTA_EXCEEDED: <?php echo ShortPixelAPI::STATUS_QUOTA_EXCEEDED; ?>,
251
+ STATUS_SKIP: <?php echo ShortPixelAPI::STATUS_SKIP; ?>,
252
+ STATUS_NO_KEY: <?php echo ShortPixelAPI::STATUS_NO_KEY; ?>,
253
+ STATUS_RETRY: <?php echo ShortPixelAPI::STATUS_RETRY; ?>,
254
+ WP_PLUGIN_URL: '<?php echo plugins_url( '', __FILE__ ); ?>',
255
+ WP_ADMIN_URL: '<?php echo admin_url(); ?>',
256
+ API_KEY: "<?php echo $this->_settings->apiKey; ?>",
257
+ MEDIA_ALERT: '<?php echo $this->_settings->mediaAlert ? "done" : "todo"; ?>',
258
+ FRONT_BOOTSTRAP: <?php echo $this->_settings->frontBootstrap && (time() - $this->_settings->lastBackAction > 60) ? 1 : 0; ?>,
259
+ AJAX_URL: '<?php echo admin_url('admin-ajax.php'); ?>'
260
+ };
 
 
261
  </script> <?php
262
+ wp_enqueue_style('short-pixel.css', plugins_url('/res/css/short-pixel.css',__FILE__) );
263
+ wp_enqueue_script('short-pixel.js', plugins_url('/res/js/short-pixel.js',__FILE__) );
264
+ wp_enqueue_script('jquery.knob.js', plugins_url('/res/js/jquery.knob.js',__FILE__) );
265
+ wp_enqueue_script('jquery.tooltip.js', plugins_url('/res/js/jquery.tooltip.js',__FILE__) );
266
  }
267
 
268
  function toolbar_shortpixel_processing( $wp_admin_bar ) {
284
  //$blank = '_blank';
285
  //$icon = "shortpixel-alert.png";
286
  }
287
+ $lastStatus = $this->_settings->bulkLastStatus;
288
+ if($lastStatus && $lastStatus['Status'] != ShortPixelAPI::STATUS_SUCCESS) {
289
  $extraClasses = " shortpixel-alert shortpixel-processing";
290
  $tooltip = $lastStatus['Message'];
291
  }
 
 
292
 
293
  $args = array(
294
  'id' => 'shortpixel_processing',
295
  'title' => '<div title="' . $tooltip . '" ><img src="'
296
+ . plugins_url( 'res/img/'.$icon, __FILE__ ) . '" success-url="' . $successLink . '"><span class="shp-alert">!</span></div>',
297
  'href' => $link,
298
  'meta' => array('target'=> $blank, 'class' => 'shortpixel-toolbar-processing' . $extraClasses)
299
  );
328
  }
329
  }
330
 
331
+ /**
332
+ * this is hooked onto the MediaLibrary image upload
333
+ * @param array $meta - the wordpress postmeta structure
334
+ * @param type $ID - the Media Library ID
335
+ * @return the meta structure updated with ShortPixel info if case
336
+ */
337
+ public function handleMediaLibraryImageUpload($meta, $ID = null)
338
  {
339
  if( !$this->_settings->verifiedKey) {// no API Key set/verified -> do nothing here, just return
340
  return $meta;
350
  else
351
  {//the kind of file we can process. goody.
352
  $this->prioQ->push($ID);
353
+ $itemHandler = new ShortPixelMetaFacade($ID);
354
+ $itemHandler->setRawMeta($meta);
355
+ $URLsAndPATHs = $this->getURLsAndPATHs($itemHandler);
356
  //send a processing request right after a file was uploaded, do NOT wait for response
357
  $this->_apiInterface->doRequests($URLsAndPATHs['URLs'], false, $ID);
358
  self::log("IMG: sent: " . json_encode($URLsAndPATHs));
360
  return $meta;
361
  }
362
 
363
+ }//end handleMediaLibraryImageUpload
364
 
365
+ /**
366
+ * this is hooked onto the NextGen upload
367
+ * @param type $image
368
+ */
369
+ public function handleNextGenImageUpload($image) {
370
+ if($this->_settings->includeNextGen == 1) {
371
+ $imageFsPath = ShortPixelNextGenAdapter::getImageAbspath($image);
372
+
373
+ $customFolders = $this->spMetaDao->getFolders();
374
+
375
+ $folderId = -1;
376
+ foreach($customFolders as $folder) {
377
+ if(strpos($imageFsPath, $folder->getPath()) === 0) {
378
+ $folderId = $folder->getId();
379
+ break;
380
+ }
381
+ }
382
+ if($folderId == -1) { //if not found, create
383
+ $galleryPath = dirname($imageFsPath);
384
+ $folder = new ShortPixelFolder(array("path" => $galleryPath));
385
+ $folderMsg = $this->spMetaDao->saveFolder($folder);
386
+ $folderId = $folder->getId();
387
+ self::log("NG Image Upload: created folder from path $galleryPath : Folder info: " . json_encode($folder));
388
+ }
389
+ $pathParts = explode(DIRECTORY_SEPARATOR, trim($imageFsPath));
390
+ //Add the main image
391
+ $meta = new ShortPixelMeta();
392
+ $meta->setPath($imageFsPath);
393
+ $meta->setName($pathParts[count($pathParts) - 1]);
394
+ $meta->setFolderId($folderId);
395
+ $meta->setExtMetaId($image->pid); // do this only for main image, not for thumbnais.
396
+ $meta->setCompressionType($this->_settings->compressionType);
397
+ $meta->setKeepExif($this->_settings->keepExif);
398
+ $meta->setCmyk2rgb($this->_settings->CMYKtoRGBconversion);
399
+ $meta->setResize($this->_settings->resizeImages);
400
+ $meta->setResizeWidth($this->_settings->resizeWidth);
401
+ $meta->setResizeHeight($this->_settings->resizeHeight);
402
+ $ID = $this->spMetaDao->addImage($meta);
403
+ $meta->setId($ID);
404
+ $this->prioQ->push('C-' . $ID);
405
+ //add the thumb image if exists
406
+ $pathParts[] = "thumbs_" . $pathParts[count($pathParts) - 1];
407
+ $pathParts[count($pathParts) - 2] = "thumbs";
408
+ $thumbPath = implode(DIRECTORY_SEPARATOR, $pathParts);
409
+ if(file_exists($thumbPath)) {
410
+ $metaThumb = new ShortPixelMeta();
411
+ $metaThumb->setPath($thumbPath);
412
+ $metaThumb->setName($pathParts[count($pathParts) - 1]);
413
+ $metaThumb->setFolderId($folderId);
414
+ $metaThumb->setCompressionType($this->_settings->compressionType);
415
+ $metaThumb->setKeepExif($this->_settings->keepExif);
416
+ $metaThumb->setCmyk2rgb($this->_settings->CMYKtoRGBconversion);
417
+ $metaThumb->setResize($this->_settings->resizeImages);
418
+ $metaThumb->setResizeWidth($this->_settings->resizeWidth);
419
+ $metaThumb->setResizeHeight($this->_settings->resizeHeight);
420
+ $ID = $this->spMetaDao->addImage($metaThumb);
421
+ $metaThumb->setId($ID);
422
+ $this->prioQ->push('C-' . $ID);
423
+ }
424
+ return $meta;
425
  }
 
 
 
 
 
 
426
  }
427
 
428
+ public function optimizeCustomImage($id) {
429
+ $meta = $this->spMetaDao->getMeta($id);
430
+ if($meta->getStatus() != 2) {
431
+ $meta->setStatus(1);
432
+ $meta->setRetries(0);
433
+ $this->spMetaDao->update($meta);
434
+ $this->prioQ->push('C-' . $id);
435
+ }
436
+ }
437
+
438
+ //TODO muta in bulkProvider
439
  public function getBulkItemsFromDb(){
440
  global $wpdb;
441
 
447
  return false;
448
  }
449
  $idList = array();
450
+ $itemList = array();
451
  for ($sanityCheck = 0, $crtStartQueryID = $startQueryID;
452
+ $crtStartQueryID >= $endQueryID && count($itemList) < 3; $sanityCheck++) {
453
 
454
  self::log("GETDB: current StartID: " . $crtStartQueryID);
455
 
468
  foreach ( $resultsPostMeta as $itemMetaData ) {
469
  $crtStartQueryID = $itemMetaData->post_id;
470
  if(!in_array($crtStartQueryID, $idList) && self::isProcessable($crtStartQueryID)) {
471
+ $item = new ShortPixelMetaFacade($crtStartQueryID);
472
+ $meta = $item->getMeta();//wp_get_attachment_metadata($crtStartQueryID);
473
 
474
+ if($meta->getStatus() != 2) {
475
+ $itemList[] = $item;
476
  $idList[] = $crtStartQueryID;
477
  }
478
+ elseif($meta->getCompressionType() !== null && $meta->getCompressionType() != $this->_settings->compressionType) {//a different type of compression was chosen in settings
479
+ if($this->doRestore($crtStartQueryID)) {
480
+ $itemList[] = $item = new ShortPixelMetaFacade($crtStartQueryID); //force reload after restore
481
  $idList[] = $crtStartQueryID;
482
  } else {
483
  $skippedAlreadyProcessed++;
484
  }
485
  }
486
+ elseif( $this->_settings->processThumbnails && $meta->getThumbsOpt() !== null
487
+ && $meta->getThumbsOpt() == 0 && count($meta->getThumbs()) > 0) { //thumbs were chosen in settings
488
  //if($crtStartQueryID == 44 || $crtStartQueryID == 49) {echo("No THuMBS?");die(var_dump($meta));}
489
+ $meta->setThumbsTodo(true);
490
+ $item->updateMeta($meta);//wp_update_attachment_metadata($crtStartQueryID, $meta);
491
+ $itemList[] = $item;
492
  $idList[] = $crtStartQueryID;
493
  }
494
  elseif($itemMetaData->meta_key == '_wp_attachment_metadata') { //count skipped
500
  //daca n-am adaugat niciuna pana acum, n-are sens sa mai selectez zona asta de id-uri in bulk-ul asta.
501
  $leapStart = $this->prioQ->getStartBulkId();
502
  $crtStartQueryID = $startQueryID = $itemMetaData->post_id - 1; //decrement it so we don't select it again
503
+ $res = WpShortPixelMediaLbraryAdapter::countAllProcessableFiles($leapStart, $crtStartQueryID);
504
  $skippedAlreadyProcessed += $res["mainProcessedFiles"] - $res["mainProc".($this->getCompressionType() == 1 ? "Lossy" : "Lossless")."Files"];
505
  $this->prioQ->setStartBulkId($startQueryID);
506
  } else {
507
  $crtStartQueryID--;
508
  }
509
  }
510
+ return array("items" => $itemList, "skipped" => $skippedAlreadyProcessed);
511
  }
512
 
513
  /**
514
  * Get last added items from priority
515
  * @return type
516
  */
517
+ //TODO muta in bulkProvider - prio
518
  public function getFromPrioAndCheck() {
519
+ $items = array();
520
+ foreach ($this->prioQ->getFromPrioAndCheck() as $id) {
521
+ $items[] = new ShortPixelMetaFacade($id);
 
 
 
 
 
 
 
 
 
 
 
 
522
  }
523
+ return $items;
524
  }
525
 
526
  public function handleImageProcessing($ID = null) {
527
+ //if(rand(1,2) == 2) {
528
+ // header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
529
+ // die("za stop");
530
+ //}
531
  //0: check key
532
  if( $this->_settings->verifiedKey == false) {
533
  if($ID == null){
534
  $ids = $this->getFromPrioAndCheck();
535
+ $itemHandler = (count($ids) > 0 ? $ids[0] : null);
536
  }
537
+ $response = array("Status" => ShortPixelAPI::STATUS_NO_KEY, "ImageID" => $itemHandler ? $itemHandler->getId() : "-1", "Message" => "Missing API Key");
538
+ $this->_settings->bulkLastStatus = $response;
539
  die(json_encode($response));
540
  }
541
 
542
+ if($this->_settings->frontBootstrap && is_admin() && !ShortPixelTools::requestIsFrontendAjax()) {
543
+ //if in backend, and front-end is activated, mark processing from backend to shut off the front-end for 10 min.
544
+ $this->_settings->lastBackAction = time();
545
+ }
546
+
547
  self::log("HIP: 0 Priority Queue: ".json_encode($this->prioQ->get()));
548
+ self::log("HIP: 0 Skipped: ".json_encode($this->prioQ->getSkipped()));
549
 
550
  //1: get 3 ids to process. Take them with priority from the queue
551
  $ids = $this->getFromPrioAndCheck();
553
  $bulkStatus = $this->prioQ->bulkRunning();
554
  if($bulkStatus =='running') {
555
  $res = $this->getBulkItemsFromDb();
556
+ $bulkItems = $res['items'];
557
  if($bulkItems){
558
  $ids = array_merge ($ids, $bulkItems);
559
  }
560
  }
561
  }
562
+
563
+ self::log("HIP: 0 Bulk ran: " . $this->prioQ->bulkRan());
564
+ $customIds = false;
565
+ if(count($ids) < 3 && $this->prioQ->bulkRan() && $this->_settings->hasCustomFolders
566
+ && (!$this->_settings->cancelPointer || $this->_settings->skipToCustom)
567
+ && !$this->_settings->customBulkPaused)
568
+ { //take from custom images if any left to optimize - only if bulk was ever started
569
+ $customIds = $this->spMetaDao->getPendingMetas( 3 - count($ids));
570
+ if(is_array($customIds)) {
571
+ $ids = array_merge($ids, array_map(function($item) {
572
+ return new ShortPixelMetaFacade("C-" . $item->id);
573
+ }, $customIds));
574
+ }
575
+ }
576
+ // var_dump($ids);
577
+ // die("za stop 2");
578
+
579
  if ($ids === false || count( $ids ) == 0 ){
580
  $bulkEverRan = $this->prioQ->stopBulk();
581
+ $avg = $this->getAverageCompression();
582
+ $fileCount = $this->_settings->fileCount;
583
  $response = array("Status" => self::BULK_EMPTY_QUEUE,
584
  "Message" => 'Empty queue ' . $this->prioQ->getStartBulkId() . '->' . $this->prioQ->getStopBulkId(),
585
  "BulkStatus" => ($this->prioQ->bulkRunning()
591
  }
592
 
593
  self::log("HIP: 1 Prio Queue: ".json_encode($this->prioQ->get()));
594
+ self::log("HIP: 1 Selected IDs count: ".count($ids));
595
 
596
  //2: Send up to 3 files to the server for processing
597
  for($i = 0; $i < min(3, count($ids)); $i++) {
598
+ $itemHandler = $ids[$i];
599
+ $tmpMeta = $itemHandler->getMeta();
600
+ //$compType = (isset($tmpMeta['ShortPixel']['type']) ? ($tmpMeta['ShortPixel']['type'] == 'lossy' ? 1 : 0) : $this->_settings->compressionType);
601
+ $compType = ($tmpMeta->getCompressionType() !== null ? $tmpMeta->getCompressionType() : $this->_settings->compressionType);
602
+ //$URLsAndPATHs = $this->sendToProcessing($ID, $compType, isset($tmpMeta['ShortPixel']['thumbsTodo']));
603
+ $URLsAndPATHs = $this->sendToProcessing($itemHandler, $compType, $tmpMeta->getThumbsTodo());
604
  if($i == 0) { //save for later use
605
  $firstUrlAndPaths = $URLsAndPATHs;
606
  }
608
 
609
  self::log("HIP: 2 Prio Queue: ".json_encode($this->prioQ->get()));
610
  //3: Retrieve the file for the first element of the list
611
+ $itemHandler = $ids[0];
612
+ $itemId = $itemHandler->getQueuedId();
613
+ $result = $this->_apiInterface->processImage($firstUrlAndPaths['URLs'], $firstUrlAndPaths['PATHs'], $itemHandler);
614
+ $result["ImageID"] = $itemId;
615
+ $meta = $itemHandler->getMeta();
616
+ $result["Filename"] = basename($meta->getPath());
617
 
618
  self::log("HIP: 3 Prio Queue: ".json_encode($this->prioQ->get()));
619
 
620
  //4: update counters and priority list
621
  if( $result["Status"] == ShortPixelAPI::STATUS_SUCCESS) {
622
+ self::log("HIP: Image ID " . $itemId . " optimized successfully: ".json_encode($result));
623
+ $prio = $this->prioQ->remove($itemId);
624
  //remove also from the failed list if it failed in the past
625
+ $prio = $this->prioQ->removeFromFailed($itemId);
626
+ /* $result["Type"] = isset($meta['ShortPixel']['type']) ? $meta['ShortPixel']['type'] : '';
 
627
  $result["ThumbsTotal"] = isset($meta['sizes']) && is_array($meta['sizes']) ? count($meta['sizes']): 0;
628
  $result["ThumbsCount"] = isset($meta['ShortPixel']['thumbsOpt'])
629
  ? $meta['ShortPixel']['thumbsOpt'] //below is the fallback for old optimized images that don't have thumbsOpt
630
  : ($this->_settings->processThumbnails ? $result["ThumbsTotal"] : 0);
631
+ */
632
+ $result["Type"] = $meta->getCompressionType() !== null ? ShortPixelAPI::getCompressionTypeName($meta->getCompressionType()) : '';
633
+ $result["ThumbsTotal"] = $meta->getThumbs() && is_array($meta->getThumbs()) ? count($meta->getThumbs()): 0;
634
+ $result["ThumbsCount"] = $meta->getThumbsOpt()
635
+ ? $meta->getThumbsOpt() //below is the fallback for old optimized images that don't have thumbsOpt
636
+ : ($this->_settings->processThumbnails ? $result["ThumbsTotal"] : 0);
637
 
638
+ //$result["BackupEnabled"] = ($this->getBackupFolder(get_attached_file($ID)) ? true : false);//$this->_settings->backupImages;
639
+ $result["BackupEnabled"] = ($this->getBackupFolder($meta->getPath()) ? true : false);//$this->_settings->backupImages;
640
 
641
+ if(!$prio && $itemId <= $this->prioQ->getStartBulkId()) {
642
+ $this->advanceBulk($itemId, $result);
643
+ $this->setBulkInfo($itemId, $result);
644
+ }
645
+
646
+ $result["AverageCompression"] = $this->getAverageCompression();
647
+
648
+ if($itemHandler->getType() == ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE) {
649
 
650
  $thumb = $bkThumb = "";
651
+ //$percent = 0;
652
+ $percent = $meta->getImprovementPercent();
653
+ //if(isset($meta["ShortPixelImprovement"]) && isset($meta["file"])){
654
+ if($percent){
655
+ //$percent = $meta["ShortPixelImprovement"];
656
+
657
+ //$filePath = explode("/", $meta["file"]);
658
+ $filePath = explode("/", $meta->getPath());
659
 
660
  //Get a suitable thumb
661
+ //if(isset($meta["sizes"]) && count($meta["sizes"])) {
662
+ //$sizes = $meta["sizes"];
663
+ $sizes = $meta->getThumbs();
664
+ if('pdf' == strtolower(pathinfo($result["Filename"], PATHINFO_EXTENSION))) {
665
+ // echo($result["Filename"] . " ESTE --> "); die(var_dump(strtolower(pathinfo($result["Filename"], PATHINFO_EXTENSION))));
666
+ $thumb = plugins_url( 'shortpixel-image-optimiser/res/img/logo-pdf.png' );
667
+ $bkThumb = '';
668
+ } else {
669
+ if(count($sizes)) {
670
+ $thumb = (isset($sizes["medium"]) ? $sizes["medium"]["file"] : (isset($sizes["thumbnail"]) ? $sizes["thumbnail"]["file"]: ""));
671
+ if(!strlen($thumb)) { //fallback to the first in the list
672
+ $sizeVals = array_values($sizes);
673
+ $thumb = count($sizeVals) ? $sizeVals[0]['file'] : '';
674
+ }
675
+ } else { //fallback to the image itself
676
+ $thumb = is_array($filePath) ? $filePath[count($filePath) - 1] : $filePath;
677
+ }
678
+
679
+ if(strlen($thumb) && $this->_settings->backupImages && $this->_settings->processThumbnails) {
680
+ $backupUrl = content_url() . "/" . SP_UPLOADS_NAME . "/" . SP_BACKUP . "/";
681
+ //$urlBkPath = $this->_apiInterface->returnSubDir(get_attached_file($ID));
682
+ $urlBkPath = ShortPixelMetaFacade::returnSubDir($meta->getPath(), ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE);
683
+ $bkThumb = $backupUrl . $urlBkPath . $thumb;
684
+ }
685
+ if(strlen($thumb)) {
686
+ $uploadsUrl = content_url() . "/";
687
+ $urlPath = ShortPixelMetaFacade::returnSubDir($meta->getPath(), ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE);
688
+ //$urlPath = implode("/", array_slice($filePath, 0, count($filePath) - 1));
689
+ $thumb = $uploadsUrl . $urlPath . $thumb;
690
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
691
  }
692
+
693
  $result["Thumb"] = $thumb;
694
  $result["BkThumb"] = $bkThumb;
695
  }
696
  }
697
+ elseif( is_array($customIds)) { // this item is from custom bulk
698
+ foreach($customIds as $customId) {
699
+ $contentUrl = network_site_url("/") . "/wp-content";
700
+ if($customId->id == $itemHandler->getId()) {
701
+ if('pdf' == strtolower(pathinfo($meta->getName(), PATHINFO_EXTENSION))) {
702
+ $result["Thumb"] = plugins_url( 'shortpixel-image-optimiser/res/img/logo-pdf.png' );
703
+ $result["BkThumb"] = "";
704
+ } else {
705
+ $result["Thumb"] = $thumb = $contentUrl . "/" . $meta->getWebPath();
706
+ if($this->_settings->backupImages) {
707
+ $result["BkThumb"] = str_replace($contentUrl, $contentUrl. "/" . SP_UPLOADS_NAME . "/" . SP_BACKUP, $thumb);
708
+ }
709
+ }
710
+ $this->setBulkInfo($itemId, $result);
711
+ break;
712
+ }
713
+ }
714
+ }
715
  }
716
  elseif ($result["Status"] == ShortPixelAPI::STATUS_ERROR) {
717
+ //$meta = wp_get_attachment_metadata($ID);
718
+ //if(isset($meta['ShortPixel']['Retries']) && $meta['ShortPixel']['Retries'] > MAX_ERR_RETRIES) {
719
+ if($meta->getRetries() > MAX_ERR_RETRIES) {
720
+ //if(! $this->prioQ->remove($ID) ){
721
+ if(! $this->prioQ->remove($itemId) ){
722
+ //$this->advanceBulk($ID, $result);
723
+ $this->advanceBulk($meta->getId(), $result);
724
  }
725
+ //unset($meta['ShortPixel']);
726
+ //wp_update_attachment_metadata($ID, $meta);
727
+ $itemHandler->deleteMeta(); //this deletes only the ShortPixel fields from meta, in case of WP Media library
728
  $result["Status"] = ShortPixelAPI::STATUS_SKIP;
729
+ $result["Message"] .= " Retry limit reached. Skipping file ID " . $itemId . ".";
730
  }
731
  else {
732
+ //if(!isset($meta['ShortPixel'])) { $meta['ShortPixel'] = array(); }
733
+ //$meta['ShortPixel']['Retries'] = isset($meta['ShortPixel']['Retries']) ? $meta['ShortPixel']['Retries'] + 1 : 1;
734
+ //wp_update_attachment_metadata($ID, $meta);
735
+ $itemHandler->incrementRetries();
736
  }
737
  }
738
  elseif ($result["Status"] == ShortPixelAPI::STATUS_SKIP
739
  || $result["Status"] == ShortPixelAPI::STATUS_FAIL) {
740
+ $meta = $itemHandler->getMeta();
741
+ //$prio = $this->prioQ->remove($ID);
742
+ $prio = $this->prioQ->remove($itemId);
743
+ if(isset($result["Code"])
744
+ && ( $result["Code"] == "write-fail" //could not write
745
+ //|| $result["Code"] == -201)) {
746
+ || (in_array(0+$result["Code"], array(-201)) && $meta->getRetries() >= 3))) { //for -201 (invalid image format) we retry only 3 times.
747
  //put this one in the failed images list - to show the user at the end
748
+ $prio = $this->prioQ->addToFailed($itemHandler->getQueuedId());
749
+ }
750
+ $this->advanceBulk($meta->getId(), $result);
751
+ if($itemHandler->getType() == ShortPixelMetaFacade::CUSTOM_TYPE) {
752
+ $result["CustomImageLink"] = trailingslashit(network_site_url("/")) . "wp-content/" . $meta->getWebPath();
753
+ }
754
+ }
755
+ elseif ($this->prioQ->isPrio($itemId) && $result["Status"] == ShortPixelAPI::STATUS_QUOTA_EXCEEDED) {
756
+ if(!$this->prioQ->skippedCount()) {
757
+ $this->prioQ->reverse(); //for the first prio item with quota exceeded, revert the prio queue as probably the bottom ones were processed
758
+ }
759
+ if($this->prioQ->allSkipped()) {
760
+ $result["Stop"] = true;
761
+ } else {
762
+ $result["Stop"] = false;
763
+ $this->prioQ->skip($itemId);
764
+ }
765
+ self::log("HIP: 5 Prio Skipped: ".json_encode($this->prioQ->getSkipped()));
766
+ }
767
+ elseif($result["Status"] == ShortPixelAPI::STATUS_RETRY && is_array($customIds)) {
768
+ $result["CustomImageLink"] = $thumb = trailingslashit(network_site_url("/")) . "wp-content/" . $meta->getWebPath();
769
+ }
770
+
771
+ if($result["Status"] !== ShortPixelAPI::STATUS_RETRY) {
772
+ $this->_settings->bulkLastStatus = $result;
773
  }
774
  die(json_encode($result));
775
  }
776
 
777
+
778
  private function advanceBulk($processedID, &$result) {
779
+ //echo("ADVANCE BULK: ");die(var_dump($result));
780
  if($processedID <= $this->prioQ->getStartBulkId()) {
781
  $this->prioQ->setStartBulkId($processedID - 1);
782
  $this->prioQ->logBulkProgress();
 
 
 
 
783
  }
784
  }
785
 
786
+ private function setBulkInfo($processedID, &$result) {
787
+ $deltaBulkPercent = $this->prioQ->getDeltaBulkPercent();
788
+ $minutesRemaining = $this->prioQ->getTimeRemaining();
789
+ $pendingMeta = $this->_settings->hasCustomFolders ? $this->spMetaDao->getPendingMetaCount() : 0;
790
+ $percent = $this->prioQ->getBulkPercent();
791
+ if(0 + $pendingMeta > 0) {
792
+ $customMeta = $this->spMetaDao->getCustomMetaCount();
793
+ $totalPercent = round(($percent * $this->_settings->currentTotalFiles + ($customMeta - $pendingMeta) * 100) / ($this->_settings->currentTotalFiles + $customMeta));
794
+ $minutesRemaining = round($minutesRemaining * (100 - $totalPercent) / max(1, 100 - $percent));
795
+ $percent = $totalPercent;
796
+ }
797
+ $result["BulkPercent"] = $percent;
798
+ $result["BulkMsg"] = $this->bulkProgressMessage($deltaBulkPercent, $minutesRemaining);
799
+ }
800
+
801
+ private function sendToProcessing($itemHandler, $compressionType = false, $onlyThumbs = false) {
802
+ $URLsAndPATHs = $this->getURLsAndPATHs($itemHandler, NULL, $onlyThumbs);
803
+ //echo("URLS: "); die(var_dump($URLsAndPATHs));
804
+ $this->_apiInterface->doRequests($URLsAndPATHs['URLs'], false, $itemHandler,
805
+ $compressionType === false ? $this->_settings->compressionType : $compressionType);//send a request, do NOT wait for response
806
+ $itemHandler->setWaitingProcessing();
807
+ //$meta = wp_get_attachment_metadata($ID);
808
+ //$meta['ShortPixel']['WaitingProcessing'] = true;
809
+ //wp_update_attachment_metadata($ID, $meta);
810
  return $URLsAndPATHs;
811
  }
812
 
813
  public function handleManualOptimization() {
814
+ $imageId = $_GET['image_id'];
815
+ switch(substr($imageId, 0, 2)) {
816
+ case "N-":
817
+ return "Add the gallery to the custom folders list in ShortPixel settings.";
818
+ // Later
819
+ if(class_exists("C_Image_Mapper")) { //this is a NextGen image but not added to our tables, so add it now.
820
+ $image_mapper = C_Image_Mapper::get_instance();
821
+ $image = $image_mapper->find(intval(substr($imageId, 2)));
822
+ if($image) {
823
+ $this->handleNextGenImageUpload($image, true);
824
+ return array("Status" => ShortPixelAPI::STATUS_SUCCESS, "message" => "");
825
+ }
826
+ }
827
+ return array("Status" => ShortPixelAPI::STATUS_FAIL, "message" => "NextGen image not found");
828
+ break;
829
+ case "C-":
830
+ throw new Exception("HandleManualOptimization for custom images not implemented");
831
+ default:
832
+ $this->optimizeNowHook(intval($imageId));
833
+ }
834
  //do_action('shortpixel-optimize-now', $imageId);
835
 
836
  }
839
  public function optimizeNowHook($imageId) {
840
  if(self::isProcessable($imageId)) {
841
  $this->prioQ->push($imageId);
842
+ $this->sendToProcessing(new ShortPixelMetaFacade($imageId));
843
  $ret = array("Status" => ShortPixelAPI::STATUS_SUCCESS, "message" => "");
844
  } else {
845
  $ret = array("Status" => ShortPixelAPI::STATUS_SKIP, "message" => $imageId);
857
 
858
  public function getBackupFolder($file) {
859
  $fileExtension = strtolower(substr($file,strrpos($file,".")+1));
860
+ $SubDir = ShortPixelMetaFacade::returnSubDir($file, ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE);
861
 
862
  //sometimes the month of original file and backup can differ
863
  if ( !file_exists(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . ShortPixelAPI::MB_basename($file)) ) {
888
  $pathInfo = pathinfo($file);
889
 
890
  $bkFolder = $this->getBackupFolder($file);
891
+ $bkNewFile = str_replace(dirname(SP_BACKUP_FOLDER), SP_BACKUP_FOLDER, $file);
892
  $bkFile = $bkFolder . ShortPixelAPI::MB_basename($file);
893
 
894
+ if(file_exists($bkNewFile)) {
895
+ $bkFile = $bkNewFile;
896
+ }
897
+
898
  //first check if the file is readable by the current user - otherwise it will be unaccessible for the web browser
899
  // - collect the thumbs paths in the process
900
  if(! $this->setFilePerms($bkFile) ) return false;
901
  $thumbsPaths = array();
902
  if( !empty($meta['file']) && is_array($meta["sizes"]) ) {
903
  foreach($meta["sizes"] as $size => $imageData) {
904
+ $source = $bkFolder . $imageData['file'];
905
+ if(!file_exists($source)) continue; // if thumbs were not optimized, then the backups will not be there.
906
+ $thumbsPaths[$source] = $pathInfo['dirname'] . DIRECTORY_SEPARATOR . $imageData['file'];
907
+ if(! $this->setFilePerms($source)) return false;
 
908
  }
909
  }
910
 
918
  @rename($source, $destination);
919
  }
920
 
921
+ $duplicates = ShortPixelMetaFacade::getWPMLDuplicates($attachmentID);
922
  foreach($duplicates as $ID) {
923
  $crtMeta = $attachmentID == $ID ? $meta : wp_get_attachment_metadata($ID);
924
  if(is_numeric($crtMeta["ShortPixelImprovement"]) && 0 + $crtMeta["ShortPixelImprovement"] < 5 && $this->_settings->under5Percent > 0) {
955
  }
956
 
957
  public function handleRedo() {
958
+ $ID = $_GET['attachment_ID'];
959
+ $mediaType = ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE;
960
+ if(strpos($ID, 'C-') === 0) {
961
+ die("handleRedo not implemented for Custom media");
962
+ $ID = substr($ID, 2);
963
+ $mediaType = ShortPixelMetaFacade::CUSTOM_TYPE;
964
+ }
965
+ $ID = intval($ID);
966
+ $compressionType = ($_GET['type'] == 'lossless' ? 'lossless' : 'lossy'); //sanity check
967
 
968
  $meta = $this->doRestore($ID);
969
  //die(var_dump($meta));
970
  if($meta) { //restore succeeded
971
+ $meta['ShortPixel'] = array("type" => $compressionType);
972
  wp_update_attachment_metadata($ID, $meta);
973
  $this->prioQ->push($ID);
974
+ $this->sendToProcessing(new ShortPixelMetaFacade($ID), $compressionType == 'lossy' ? 1 : 0);
975
  $ret = array("Status" => ShortPixelAPI::STATUS_SUCCESS, "Message" => "");
976
  } else {
977
  $ret = array("Status" => ShortPixelAPI::STATUS_SKIP, "Message" => "Could not restore from backup: " . $ID);
989
  $meta['ShortPixel']['thumbsTodo'] = true;
990
  wp_update_attachment_metadata($ID, $meta);
991
  $this->prioQ->push($ID);
992
+ $this->sendToProcessing(new ShortPixelMetaFacade($ID));
993
  $ret = array("Status" => ShortPixelAPI::STATUS_SUCCESS, "message" => "");
994
  } else {
995
  $ret = array("Status" => ShortPixelAPI::STATUS_SKIP, "message" => (isset($meta['ShortPixelImprovement']) ? "No thumbnails to optimize for ID: " : "Please optimize image for ID:") . $ID);
1014
 
1015
  if(self::isProcessable($ID) != false)
1016
  {
 
1017
  try {
1018
+ $SubDir = ShortPixelMetaFacade::returnSubDir($file, ShortPixelMetaFacade::MEDIA_LIBRARY_TYPE);
1019
 
1020
  @unlink(SP_BACKUP_FOLDER . DIRECTORY_SEPARATOR . $SubDir . ShortPixelAPI::MB_basename($file));
1021
 
1025
  //remove thumbs thumbnails
1026
  if(isset($meta["sizes"])) {
1027
  foreach($meta["sizes"] as $size => $imageData) {
1028
+ @unlink($filesPath . ShortPixelAPI::MB_basename($imageData['file']));//remove thumbs
 
 
1029
  }
1030
  }
1031
  }
1044
  return $quotaData;
1045
  }
1046
  //$tempus = microtime(true);
1047
+ $imageCount = WpShortPixelMediaLbraryAdapter::countAllProcessableFiles();
1048
+
1049
+ $this->_settings->currentTotalFiles = $imageCount['totalFiles'];
1050
+
1051
  //echo("Count took (seconds): " . (microtime(true) - $tempus));
1052
  foreach($imageCount as $key => $val) {
1053
  $quotaData[$key] = $val;
1054
  }
1055
+
1056
+ if($this->_settings->hasCustomFolders) {
1057
+ $customImageCount = $this->spMetaDao->countAllProcessableFiles();
1058
+ foreach($customImageCount as $key => $val) {
1059
+ $quotaData[$key] = isset($quotaData[$key])
1060
+ ? (is_array($quotaData[$key]) ? array_merge($quotaData[$key], $val) : $quotaData[$key] + $val)
1061
+ : $val;
1062
+ }
1063
+ }
1064
 
1065
  if($quotaData['APICallsQuotaNumeric'] + $quotaData['APICallsQuotaOneTimeNumeric'] > $quotaData['APICallsMadeNumeric'] + $quotaData['APICallsMadeOneTimeNumeric']) {
1066
  $this->_settings->quotaExceeded = '0';
1067
  $this->_settings->prioritySkip = NULL;
1068
+ self::log("CHECK QUOTA: Skipped: ".json_encode($this->prioQ->getSkipped()));
1069
+
1070
  ?><script>var shortPixelQuotaExceeded = 0;</script><?php
1071
  }
1072
  else {
1075
  }
1076
  return $quotaData;
1077
  }
1078
+
1079
+ public function isValidMetaId($id) {
1080
+ return substr($id, 0, 2 ) == "C-" ? $this->spMetaDao->getMeta(substr($id, 2)) : wp_get_attachment_url($id);
1081
+ }
1082
+
1083
+ public function listCustomMedia() {
1084
+ if( ! class_exists( 'ShortPixelListTable' ) ) {
1085
+ require_once('class/view/shortpixel-list-table.php');
1086
+ }
1087
+ if(isset($_REQUEST['refresh']) && esc_attr($_REQUEST['refresh']) == 1) {
1088
+ $notice = null;
1089
+ $this->refreshCustomFolders($notice);
1090
+ }
1091
+ if(isset($_REQUEST['action']) && esc_attr($_REQUEST['action']) == 'optimize' && isset($_REQUEST['image'])) {
1092
+ //die(ShortPixelMetaFacade::queuedId(ShortPixelMetaFacade::CUSTOM_TYPE, $_REQUEST['image']));
1093
+ $this->prioQ->push(ShortPixelMetaFacade::queuedId(ShortPixelMetaFacade::CUSTOM_TYPE, $_REQUEST['image']));
1094
+ }
1095
+ $customMediaListTable = new ShortPixelListTable($this, $this->spMetaDao, $this->hasNextGen);
1096
+ ?>
1097
+ <div class="wrap">
1098
+ <h2>
1099
+ <div style="float:right;">
1100
+ <a href="upload.php?page=wp-short-pixel-custom&refresh=1" id="refresh" class="button button-primary" title="Refresh custom folders content">
1101
+ Refresh folders
1102
+ </a>
1103
+ </div>
1104
+ Other Media optimized by ShortPixel
1105
+ </h2>
1106
+
1107
+ <div id="poststuff">
1108
+ <div id="post-body" class="metabox-holder columns-2">
1109
+ <div id="post-body-content">
1110
+ <div class="meta-box-sortables ui-sortable">
1111
+ <form method="post" class="shortpixel-table">
1112
+ <?php
1113
+ $items = $customMediaListTable->prepare_items();
1114
+ $customMediaListTable->display();
1115
+ //push to the processing list the pending ones, just in case
1116
+ $count = $this->spMetaDao->getCustomMetaCount();
1117
+ foreach ($items as $item) {
1118
+ if($item->status == 1){
1119
+ $this->prioQ->push(ShortPixelMetaFacade::queuedId(ShortPixelMetaFacade::CUSTOM_TYPE, $item->id));
1120
+ }
1121
+ }
1122
+ ?>
1123
+ </form>
1124
+ </div>
1125
+ </div>
1126
+ </div>
1127
+ <br class="clear">
1128
+ </div>
1129
+ </div> <?php
1130
+ }
1131
  public function bulkProcess() {
1132
  global $wpdb;
1133
 
1142
  if(isset($_POST['bulkProcessPause']))
1143
  {//pause an ongoing bulk processing, it might be needed sometimes
1144
  $this->prioQ->pauseBulk();
1145
+ if($this->_settings->hasCustomFolders && $this->spMetaDao->getPendingMetaCount()) {
1146
+ $this->_settings->customBulkPaused = 1;
1147
+ }
1148
+ }
1149
+
1150
+ if(isset($_POST['bulkProcessStop']))
1151
+ {//stop an ongoing bulk processing
1152
+ $this->prioQ->cancelBulk();
1153
+ if($this->_settings->hasCustomFolders && $this->spMetaDao->getPendingMetaCount()) {
1154
+ $this->_settings->customBulkPaused = 1;
1155
+ }
1156
  }
1157
 
1158
  if(isset($_POST["bulkProcess"]))
1159
  {
1160
  //set the thumbnails option
1161
  if ( isset($_POST['thumbnails']) ) {
1162
+ $this->_settings->processThumbnails = 1;
1163
  } else {
1164
+ $this->_settings->processThumbnails = 0;
1165
  }
1166
  $this->prioQ->startBulk();
1167
+ $this->_settings->customBulkPaused = 0;
1168
  self::log("BULK: Start: " . $this->prioQ->getStartBulkId() . ", stop: " . $this->prioQ->getStopBulkId() . " PrioQ: "
1169
  .json_encode($this->prioQ->get()));
1170
  }//end bulk process was clicked
1171
 
1172
+ if(isset($_POST["bulkProcessResume"]))
1173
  {
1174
  $this->prioQ->resumeBulk();
1175
+ $this->_settings->customBulkPaused = 0;
1176
+ }//resume was clicked
1177
+
1178
+ if(isset($_POST["skipToCustom"]))
1179
+ {
1180
+ $this->_settings->skipToCustom = true;
1181
  }//resume was clicked
1182
 
1183
  //figure out all the files that could be processed
1186
  $allFiles = $wpdb->get_results($qry);
1187
  //figure out the files that are left to be processed
1188
  $qry_left = "SELECT count(*) FilesLeftToBeProcessed FROM " . $wpdb->prefix . "postmeta
1189
+ WHERE meta_key = '_wp_attached_file' AND post_id <= " . (0 + $this->prioQ->getStartBulkId());
1190
  $filesLeft = $wpdb->get_results($qry_left);
1191
+
1192
+ //check the custom bulk
1193
+ $pendingMeta = $this->_settings->hasCustomFolders ? $this->spMetaDao->getPendingMetaCount() : 0;
1194
+
1195
+ if ( ($filesLeft[0]->FilesLeftToBeProcessed > 0 && $this->prioQ->bulkRunning())
1196
+ || (0 + $pendingMeta > 0 && !$this->_settings->customBulkPaused && $this->prioQ->bulkRan())//bulk processing was started
1197
+ && (!$this->prioQ->bulkPaused() || $this->_settings->skipToCustom)) //bulk not paused or if paused, user pressed Process Custom button
1198
  {
1199
+ /*$percent = $this->prioQ->getBulkPercent();
1200
+ if(0 + $pendingMeta > 0) {
1201
+ $customMeta = $this->spMetaDao->getCustomMetaCount();
1202
+ $percent = round(($percent * $quotaData["totalFiles"] + ($customMeta - $pendingMeta) * 100) / ($quotaData["totalFiles"] + $customMeta));
1203
+ $percent = round($quotaData["totalProcessedFiles"] / $quotaData["totalFiles"]);
1204
+ }*/
1205
+ $percent = min(99, round($quotaData["totalProcessedFiles"] *100.0 / $quotaData["totalFiles"]));
1206
  $msg = $this->bulkProgressMessage($this->prioQ->getDeltaBulkPercent(), $this->prioQ->getTimeRemaining());
1207
+ $this->view->displayBulkProcessingRunning($percent, $msg, $quotaData['APICallsRemaining'], $this->getAverageCompression(),
1208
+ ($pendingMeta !== null ? ($this->prioQ->bulkRunning() ? 3 : 2) : 1));
1209
 
1210
  // $imagesLeft = $filesLeft[0]->FilesLeftToBeProcessed;
1211
  // $totalImages = $allFiles[0]->FilesToBeProcessed;
1216
  if($this->prioQ->bulkRan() && !$this->prioQ->bulkPaused()) {
1217
  $this->prioQ->markBulkComplete();
1218
  }
1219
+
1220
  //image count
1221
  //$imageCount = $this->countAllProcessableFiles();
1222
  $imageOnlyThumbs = $quotaData['totalFiles'] - $quotaData['mainFiles'];
1225
 
1226
  //average compression
1227
  $averageCompression = self::getAverageCompression();
1228
+ $percent = $this->prioQ->bulkPaused() ? round($quotaData["totalProcessedFiles"] *100.0 / $quotaData["totalFiles"]) : false;
1229
+ $this->view->displayBulkProcessingForm($quotaData, $thumbsProcessedCount, $under5PercentCount,
1230
+ $this->prioQ->bulkRan(), $averageCompression, $this->_settings->fileCount,
1231
+ self::formatBytes($this->_settings->savedSpace), $percent, $pendingMeta);
1232
  }
1233
  }
1234
  //end bulk processing
1243
  } elseif ($minutes > 240) {
1244
  $timeEst = "~ " . round($minutes / 60) . " hours left";
1245
  } elseif ($minutes > 60) {
1246
+ $timeEst = "~ " . round($minutes / 60) . " hours " . round($minutes % 60 / 10) * 10 . " min. left";
1247
  } elseif ($minutes > 20) {
1248
  $timeEst = "~ " . round($minutes / 10) * 10 . " minutes left";
1249
  } else {
1283
  return count(scandir(SP_BACKUP_FOLDER)) > 2 ? false : true;
1284
  }
1285
 
1286
+ public function browseContent() {
1287
+ if ( !current_user_can( 'manage_options' ) ) {
1288
+ wp_die('You do not have sufficient permissions to access this page.');
1289
+ }
1290
+
1291
+ $root = $this->getCustomFolderBase();
1292
+
1293
+ $postDir = rawurldecode($root.(isset($_POST['dir']) ? $_POST['dir'] : null ));
1294
+
1295
+ // set checkbox if multiSelect set to true
1296
+ $checkbox = ( isset($_POST['multiSelect']) && $_POST['multiSelect'] == 'true' ) ? "<input type='checkbox' />" : null;
1297
+ $onlyFolders = ( isset($_POST['onlyFolders']) && $_POST['onlyFolders'] == 'true' ) ? true : false;
1298
+ $onlyFiles = ( isset($_POST['onlyFiles']) && $_POST['onlyFiles'] == 'true' ) ? true : false;
1299
+
1300
+ if( file_exists($postDir) ) {
1301
+
1302
+ $files = scandir($postDir);
1303
+ $returnDir = substr($postDir, strlen($root));
1304
+
1305
+ natcasesort($files);
1306
+
1307
+ if( count($files) > 2 ) { // The 2 accounts for . and ..
1308
+ echo "<ul class='jqueryFileTree'>";
1309
+ foreach( $files as $file ) {
1310
+
1311
+ if($file == 'ShortpixelBackups') continue;
1312
+
1313
+ $htmlRel = htmlentities($returnDir . $file);
1314
+ $htmlName = htmlentities($file);
1315
+ $ext = preg_replace('/^.*\./', '', $file);
1316
+
1317
+ if( file_exists($postDir . $file) && $file != '.' && $file != '..' ) {
1318
+ if( is_dir($postDir . $file) && (!$onlyFiles || $onlyFolders) )
1319
+ echo "<li class='directory collapsed'>{$checkbox}<a rel='" .$htmlRel. "/'>" . $htmlName . "</a></li>";
1320
+ else if (!$onlyFolders || $onlyFiles)
1321
+ echo "<li class='file ext_{$ext}'>{$checkbox}<a rel='" . $htmlRel . "'>" . $htmlName . "</a></li>";
1322
+ }
1323
+ }
1324
+
1325
+ echo "</ul>";
1326
+ }
1327
+ }
1328
+ die();
1329
+ }
1330
+
1331
+ public function getCustomFolderBase() {
1332
+ if(is_main_site()) {
1333
+ return WP_CONTENT_DIR;
1334
+ } else {
1335
+ $up = wp_upload_dir();
1336
+ return $up['basedir'];
1337
+ }
1338
+ }
1339
+
1340
+ protected function refreshCustomFolders(&$notice, $ignore = false) {
1341
+ $customFolders = array();
1342
+ if($this->_settings->hasCustomFolders) {
1343
+ $customFolders = $this->spMetaDao->getFolders();
1344
+ foreach($customFolders as $folder) {
1345
+ if($folder->getPath() === $ignore) continue;
1346
+ try {
1347
+ $mt = $folder->getFolderContentsChangeDate();
1348
+ if($mt > strtotime($folder->getTsUpdated())) {
1349
+ $fileList = $folder->getFileList(strtotime($folder->getTsUpdated()));
1350
+ $this->spMetaDao->batchInsertImages($fileList, $folder->getId());
1351
+ $folder->setTsUpdated(date("Y-m-d H:i:s", $mt));
1352
+ $folder->setFileCount($folder->countFiles());
1353
+ $this->spMetaDao->update($folder);
1354
+ }
1355
+ //echo ("mt: " . $mt);
1356
+ //die(var_dump($folder));
1357
+ } catch(SpFileRightsException $ex) {
1358
+ if(is_array($notice)) {
1359
+ if($notice['status'] == 'error') {
1360
+ $notice['msg'] .= " " . $ex->getMessage();
1361
+ }
1362
+ } else {
1363
+ $notice = array("status" => "error", "msg" => $ex->getMessage());
1364
+ }
1365
+ }
1366
+ }
1367
+ }
1368
+ return $customFolders;
1369
+ }
1370
+
1371
  public function renderSettingsMenu() {
1372
  if ( !current_user_can( 'manage_options' ) ) {
1373
  wp_die('You do not have sufficient permissions to access this page.');
1374
  }
1375
 
1376
+ wp_enqueue_style('sp-file-tree.css', plugins_url('/res/css/sp-file-tree.css',__FILE__) );
1377
+ wp_enqueue_script('sp-file-tree.js', plugins_url('/res/js/sp-file-tree.js',__FILE__) );
1378
+
1379
  //die(var_dump($_POST));
1380
  $noticeHTML = "";
1381
  $notice = null;
1382
+ $folderMsg = false;
1383
+ $addedFolder = false;
1384
+
1385
  $this->_settings->redirectedSettings = 2;
1386
+
1387
  //by default we try to fetch the API Key from wp-config.php (if defined)
1388
  if ( defined("SHORTPIXEL_API_KEY") && strlen(SHORTPIXEL_API_KEY) == 20)
1389
  {
1393
  $_POST['key'] = SHORTPIXEL_API_KEY;
1394
  }
1395
 
1396
+ //check all custom folders and update meta table if files appeared
1397
+ $customFolders = $this->refreshCustomFolders($notice, isset($_POST['removeFolder']) ? $_POST['removeFolder'] : null);
1398
+
1399
+ if( isset($_POST['save']) || isset($_POST['saveAdv'])
1400
+ || (isset($_POST['validate']) && $_POST['validate'] == "validate")
1401
+ || isset($_POST['removeFolder'])) {
1402
+
1403
+ //handle API Key - common for save and validate.
1404
+ $_POST['key'] = trim(str_replace("*", "", isset($_POST['key']) ? $_POST['key'] : $this->_settings->apiKey)); //the API key might not be set if the editing is disabled.
1405
 
1406
  if ( strlen($_POST['key']) <> 20 )
1407
  {
1418
  if($validityData['APIKeyValid']) {
1419
  if(isset($_POST['validate']) && $_POST['validate'] == "validate") {
1420
  // delete last status if it was no valid key
1421
+ $lastStatus = $this->_settings->bulkLastStatus;
1422
+ if(isset($lastStatus['Status']) && $lastStatus['Status'] == ShortPixelAPI::STATUS_NO_KEY) {
1423
+ $this->_settings->bulkLastStatus = null;
1424
  }
1425
  //display notification
1426
  $urlParts = explode("/", get_site_url());
1438
  //test that the "uploads" have the right rights and also we can create the backup dir for ShortPixel
1439
  if ( !file_exists(SP_BACKUP_FOLDER) && !@mkdir(SP_BACKUP_FOLDER, 0777, true) )
1440
  $notice = array("status" => "error", "msg" => "There is something preventing us to create a new folder for backing up your original files.<BR>
1441
+ Please make sure that folder <b>" . WP_CONTENT_DIR . DIRECTORY_SEPARATOR . SP_UPLOADS_NAME . "</b> has the necessary write and read rights.");
1442
  } else {
1443
  if(isset($_POST['validate'])) {
1444
  //display notification
1448
  }
1449
  }
1450
 
 
1451
  //if save button - we process the rest of the form elements
1452
+ if(isset($_POST['save']) || isset($_POST['saveAdv'])) {
1453
  $this->_settings->compressionType = $_POST['compressionType'];
1454
  if(isset($_POST['thumbnails'])) { $this->_settings->processThumbnails = 1; } else { $this->_settings->processThumbnails = 0; }
1455
  if(isset($_POST['backupImages'])) { $this->_settings->backupImages = 1; } else { $this->_settings->backupImages = 0; }
1462
  $this->_settings->siteAuthUser = (isset($_POST['siteAuthUser']) ? $_POST['siteAuthUser']: $this->_settings->siteAuthUser);
1463
  $this->_settings->siteAuthPass = (isset($_POST['siteAuthPass']) ? $_POST['siteAuthPass']: $this->_settings->siteAuthPass);
1464
 
1465
+ $uploadDir = wp_upload_dir();
1466
+ $uploadPath = $uploadDir["basedir"];
1467
+
1468
+ if( isset($_POST['save']) && $_POST['save'] == "Save and Go to Bulk Process"
1469
+ || isset($_POST['saveAdv']) && $_POST['saveAdv'] == "Save and Go to Bulk Process") {
1470
  wp_redirect("upload.php?page=wp-short-pixel-bulk");
1471
  exit();
1472
  }
1473
+
1474
+ if(isset($_POST['nextGen'])) {
1475
+ WpShortPixelDb::checkCustomTables(); // check if custom tables are created, if not, create them
1476
+ $prevNextGen = $this->_settings->includeNextGen;
1477
+ $this->_settings->includeNextGen = 1;
1478
+ $ret = $this->addNextGenGalleriesToCustom($prevNextGen);
1479
+ $folderMsg = $ret["message"];
1480
+ $customFolders = $ret["customFolders"];
1481
+ } else {
1482
+ $this->_settings->includeNextGen = 0;
1483
+ }
1484
+ if(isset($_POST['addCustomFolder']) && strlen($_POST['addCustomFolder']) > 0) {
1485
+ $folderMsg = $this->spMetaDao->newFolderFromPath($_POST['addCustomFolder'], $uploadPath, $this->getCustomFolderBase());
1486
+ if(!$folderMsg) {
1487
+ $notice = array("status" => "success", "msg" => 'Folder added successfully.');
1488
+ }
1489
+ $customFolders = $this->spMetaDao->getFolders();
1490
+ $this->_settings->hasCustomFolders = true;
1491
+ }
1492
+ if(isset($_POST['frontBootstrap'])) { $this->_settings->frontBootstrap = 1; } else { $this->_settings->frontBootstrap = 0; }
1493
+ }
1494
+ if(isset($_POST['removeFolder']) && strlen(($_POST['removeFolder']))) {
1495
+ $this->spMetaDao->removeFolder($_POST['removeFolder']);
1496
+ $customFolders = $this->spMetaDao->getFolders();
1497
+ $_POST["saveAdv"] = true;
1498
  }
1499
  }
1500
+
1501
  //now output headers. They were prevented with noheaders=true in the form url in order to be able to redirect if bulk was pressed
1502
  if(isset($_REQUEST['noheader'])) {
1503
  require_once(ABSPATH . 'wp-admin/admin-header.php');
1509
  }
1510
 
1511
  $quotaData = $this->checkQuotaAndAlert(isset($validityData) ? $validityData : null);
1512
+
1513
+ if($this->hasNextGen) {
1514
+ $ngg = array_map(function($item){
1515
+ return str_replace(DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, get_home_path() . $item);
1516
+ }, ShortPixelNextGenAdapter::getGalleries());
1517
+ //die(var_dump($ngg));
1518
+ for($i = 0; $i < count($customFolders); $i++) {
1519
+ if(in_array($customFolders[$i]->getPath(), $ngg )) {
1520
+ $customFolders[$i]->setType("NextGen");
1521
+ }
1522
+ }
1523
+ }
1524
 
1525
+ $showApiKey = is_main_site() || (function_exists("is_multisite") && is_multisite() && !defined("SHORTPIXEL_API_KEY"));
1526
+ $editApiKey = ( is_main_site() && function_exists("is_multisite") && is_multisite()
1527
+ || !function_exists("is_multisite")
1528
+ || !is_multisite() )
1529
+ && !defined("SHORTPIXEL_API_KEY");
1530
+
1531
  if($this->_settings->verifiedKey) {
1532
  $fileCount = number_format($this->_settings->fileCount);
1533
  $savedSpace = self::formatBytes($this->_settings->savedSpace,2);
1534
+ $averageCompression = $this->getAverageCompression();
1535
  $savedBandwidth = self::formatBytes($this->_settings->savedSpace * 10000,2);
1536
  if (is_numeric($quotaData['APICallsQuota'])) {
1537
  $quotaData['APICallsQuota'] .= "/month";
1538
  }
1539
  $backupFolderSize = self::formatBytes(self::folderSize(SP_BACKUP_FOLDER));
1540
+ $remainingImages = $quotaData['APICallsRemaining'];
1541
  $remainingImages = ( $remainingImages < 0 ) ? 0 : number_format($remainingImages);
1542
  $totalCallsMade = number_format($quotaData['APICallsMadeNumeric'] + $quotaData['APICallsMadeOneTimeNumeric']);
1543
 
1545
  if(is_wp_error( $resources )) {
1546
  $resources = array();
1547
  }
1548
+ $this->view->displaySettings($showApiKey, $editApiKey,
1549
+ $quotaData, $notice, $resources, $averageCompression, $savedSpace, $savedBandwidth, $remainingImages,
1550
+ $totalCallsMade, $fileCount, $backupFolderSize, $customFolders,
1551
+ $folderMsg, $folderMsg ? $addedFolder : false, isset($_POST['saveAdv']));
1552
  } else {
1553
+ $this->view->displaySettings($showApiKey, $editApiKey, $quotaData, $notice);
 
1554
  }
1555
 
1556
  }
1557
 
1558
+ public function addNextGenGalleriesToCustom($silent) {
1559
+ $customFolders = array();
1560
+ $folderMsg = "";
1561
+ if($this->_settings->includeNextGen) {
1562
+ //add the NextGen galleries to custom folders
1563
+ $ngGalleries = ShortPixelNextGenAdapter::getGalleries();
1564
+ foreach($ngGalleries as $gallery) {
1565
+ $folderMsg = $this->spMetaDao->newFolderFromPath($gallery, get_home_path(), $this->getCustomFolderBase());
1566
+ $this->_settings->hasCustomFolders = true;
1567
+ }
1568
+ $customFolders = $this->spMetaDao->getFolders();
1569
+ }
1570
+ return array("message" => $silent? "" : $folderMsg, "customFolders" => $customFolders);
1571
+ }
1572
+
1573
  public function getAverageCompression(){
1574
  return $this->_settings->totalOptimized > 0
1575
  ? round(( 1 - ( $this->_settings->totalOptimized / $this->_settings->totalOriginal ) ) * 100, 2)
1604
  }
1605
  if($validate) {
1606
  $args['body']['DomainCheck'] = get_site_url();
1607
+ $imageCount = WpShortPixelMediaLbraryAdapter::countAllProcessableFiles();
1608
  $args['body']['ImagesCount'] = $imageCount['mainFiles'];
1609
  $args['body']['ThumbsCount'] = $imageCount['totalFiles'] - $imageCount['mainFiles'];
1610
  $argsStr .= "&DomainCheck={$args['body']['DomainCheck']}&ImagesCount={$imageCount['mainFiles']}&ThumbsCount={$args['body']['ThumbsCount']}";
1654
  $response = wp_remote_get($requestURL, $args);
1655
  $comm[] = array("sent" => "POST: " . $requestURL, "args" => $args, "received" => $response);
1656
  }
1657
+
1658
  $defaultData = array(
1659
  "APIKeyValid" => false,
1660
  "Message" => 'API Key could not be validated due to a connectivity error.<BR>Your firewall may be blocking us. Please contact your hosting provider and ask them to allow connections from your site to IP 176.9.106.46.<BR> If you still cannot validate your API Key after this, please <a href="https://shortpixel.com/contact" target="_blank">contact us</a> and we will try to help. ',
1678
  }
1679
 
1680
  $data = $response['body'];
1681
+ $data = ShortPixelTools::parseJSON($data);
1682
 
1683
  if(empty($data)) { return $defaultData; }
1684
 
1692
  else
1693
  $this->_settings->quotaExceeded = 1;//activate quota limiting
1694
 
1695
+ //if a non-valid status exists, delete it
1696
+ $lastStatus = $this->_settings->bulkLastStatus = null;
1697
+ if($lastStatus && $lastStatus['Status'] == ShortPixelAPI::STATUS_NO_KEY) {
1698
+ $this->_settings->bulkLastStatus = null;
1699
  }
1700
 
1701
  return array(
1708
  "APICallsQuotaNumeric" => $data->APICallsQuota,
1709
  "APICallsMadeOneTimeNumeric" => $data->APICallsMadeOneTime,
1710
  "APICallsQuotaOneTimeNumeric" => $data->APICallsQuotaOneTime,
1711
+ "APICallsRemaining" => $data->APICallsQuota + $data->APICallsQuotaOneTime - $data->APICallsMade - $data->APICallsMadeOneTime,
1712
  "APILastRenewalDate" => $data->DateSubscription,
1713
  "DomainCheck" => (isset($data->DomainCheck) ? $data->DomainCheck : null)
1714
  );
1724
  $quotaExceeded = $this->_settings->quotaExceeded;
1725
  $renderData = array("id" => $id, "showActions" => current_user_can( 'manage_options' ));
1726
 
 
 
 
1727
  if($invalidKey) { //invalid key - let the user first register and only then
1728
  $renderData['status'] = 'invalidKey';
1729
  $this->view->renderCustomColumn($id, $renderData);
1779
  }
1780
  elseif(isset($data['ShortPixel']['WaitingProcessing'])) {
1781
  $renderData['status'] = $quotaExceeded ? 'quotaExceeded' : 'retry';
1782
+ $renderData['message'] = "<img src=\"" . plugins_url( 'res/img/loading.gif', __FILE__ ) . "\" class='sp-loading-small'>&nbsp;Image waiting to be processed.";
1783
+ if($id > $this->prioQ->getFlagBulkId() || !$this->prioQ->bulkRunning()) $this->prioQ->push($id); //should be there but just to make sure
1784
  }
1785
  else { //finally
1786
  $renderData['status'] = $quotaExceeded ? 'quotaExceeded' : 'optimizeNow';
1787
  $sizes = isset($data['sizes']) ? count($data['sizes']) : 0;
1788
  $renderData['thumbsTotal'] = $sizes;
1789
  $renderData['message'] = ($fileExtension == "pdf" ? 'PDF' : 'Image') . ' not processed.';
1790
+ }
1791
+
1792
  $this->view->renderCustomColumn($id, $renderData);
1793
  }
1794
  }
1801
  return $defaults;
1802
  }
1803
 
1804
+ public function nggColumns( $defaults ) {
1805
+ $this->nggColumnIndex = count($defaults) + 1;
1806
+ add_filter( 'ngg_manage_images_column_' . $this->nggColumnIndex . '_header', array( &$this, 'nggColumnHeader' ) );
1807
+ add_filter( 'ngg_manage_images_column_' . $this->nggColumnIndex . '_content', array( &$this, 'nggColumnContent' ), 10, 2 );
1808
+ $defaults['wp-shortPixelNgg'] = 'ShortPixel Compression';
1809
+ return $defaults;
1810
  }
1811
 
1812
+ public function nggCountColumns( $count ) {
1813
+ return $count + 1;
1814
+ }
1815
+
1816
+ public function nggColumnHeader( $default ) {
1817
+ return 'ShortPixel Compression';
1818
+ }
1819
+
1820
+ public function nggColumnContent( $unknown, $picture ) {
1821
+
1822
+ $meta = $this->spMetaDao->getMetaForPath($picture->imagePath);
1823
+ if($meta) {
1824
+ switch($meta->getStatus()) {
1825
+ case "0": echo("<div id='sp-msg-C-{$meta->getId()}' class='column-wp-shortPixel' style='color: #928B1E'>Waiting</div>"); break;
1826
+ case "1": echo("<div id='sp-msg-C-{$meta->getId()}' class='column-wp-shortPixel' style='color: #1919E2'>Pending</div>"); break;
1827
+ case "2": $this->view->renderCustomColumn("C-" . $meta->getId(), array(
1828
+ 'showActions' => false && current_user_can( 'manage_options' ),
1829
+ 'status' => 'imgOptimized',
1830
+ 'type' => ShortPixelAPI::getCompressionTypeName($meta->getCompressionType()),
1831
+ 'percent' => $meta->getImprovementPercent(),
1832
+ 'bonus' => $meta->getImprovementPercent() < 5,
1833
+ 'thumbsOpt' => 0,
1834
+ 'thumbsTotal' => 0,
1835
+ 'backup' => true
1836
+ ));
1837
+ break;
1838
+ }
1839
  } else {
1840
+ $this->view->renderCustomColumn($meta ? "C-" . $meta->getId() : "N-" . $picture->pid, array(
1841
+ 'showActions' => false && current_user_can( 'manage_options' ),
1842
+ 'status' => 'optimizeNow',
1843
+ 'thumbsOpt' => 0,
1844
+ 'thumbsTotal' => 0,
1845
+ 'message' => "Not optimized"
1846
+ ));
1847
  }
1848
+ // return var_dump($meta);
1849
  }
1850
 
1851
+ public function generatePluginLinks($links) {
1852
+ $in = '<a href="options-general.php?page=wp-shortpixel">Settings</a>';
1853
+ array_unshift($links, $in);
1854
+ return $links;
1855
+ }
1856
 
1857
  static public function formatBytes($bytes, $precision = 2) {
1858
  $units = array('B', 'KB', 'MB', 'GB', 'TB');
1873
 
1874
  static public function isProcessablePath($path) {
1875
  $pathParts = pathinfo($path);
1876
+ if( isset($pathParts['extension']) && in_array(strtolower($pathParts['extension']), self::$PROCESSABLE_EXTENSIONS)) {
1877
  return true;
1878
  } else {
1879
  return false;
1882
 
1883
 
1884
  //return an array with URL(s) and PATH(s) for this file
1885
+ public function getURLsAndPATHs($itemHandler, $meta = NULL, $onlyThumbs = false) {
1886
+ return $itemHandler->getURLsAndPATHs($this->_settings->processThumbnails, $onlyThumbs);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1887
  }
1888
 
1889
 
1890
  public static function deleteDir($dirPath) {
1891
+ if (substr($dirPath, strlen($dirPath) - 1, 1) != DIRECTORY_SEPARATOR) {
1892
+ $dirPath .= DIRECTORY_SEPARATOR;
 
1893
  }
1894
  $files = glob($dirPath . '*', GLOB_MARK);
1895
  foreach ($files as $file) {
1909
  } else {
1910
  return $total_size;
1911
  }
1912
+ $cleanPath = rtrim($path, DIRECTORY_SEPARATOR). DIRECTORY_SEPARATOR;
1913
  foreach($files as $t) {
1914
  if ($t<>"." && $t<>"..")
1915
  {
1927
  return $total_size;
1928
  }
1929
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1930
  public function migrateBackupFolder() {
1931
+ $oldBackupFolder = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . SP_BACKUP;
1932
 
1933
+ if(file_exists($oldBackupFolder)) { //if old backup folder does not exist then there is nothing to do
1934
 
1935
  if(!file_exists(SP_BACKUP_FOLDER)) {
1936
+ //we check that the backup folder exists, if not we create it so we can copy into it
1937
+ if(!mkdir(SP_BACKUP_FOLDER, 0777, true)) return;
1938
+ }
1939
 
1940
+ $scannedDirectory = array_diff(scandir($oldBackupFolder), array('..', '.'));
1941
+ foreach($scannedDirectory as $file) {
1942
+ @rename($oldBackupFolder.DIRECTORY_SEPARATOR.$file, SP_BACKUP_FOLDER.DIRECTORY_SEPARATOR.$file);
1943
+ }
1944
+ $scannedDirectory = array_diff(scandir($oldBackupFolder), array('..', '.'));
1945
+ if(empty($scannedDirectory)) {
1946
+ @rmdir($oldBackupFolder);
1947
+ }
1948
  }
1949
+ //now if the backup folder does not contain the uploads level, create it
1950
+ if(!is_dir(SP_BACKUP_FOLDER.DIRECTORY_SEPARATOR.SP_UPLOADS_NAME)) {
1951
+ @rename(SP_BACKUP_FOLDER, SP_BACKUP_FOLDER."_tmp");
1952
+ @mkdir(SP_BACKUP_FOLDER);
1953
+ @rename(SP_BACKUP_FOLDER."_tmp", SP_BACKUP_FOLDER.DIRECTORY_SEPARATOR.SP_UPLOADS_NAME);
1954
+ if(!file_exists(SP_BACKUP_FOLDER)) {//just in case..
1955
+ @rename(SP_BACKUP_FOLDER."_tmp", SP_BACKUP_FOLDER);
1956
+ }
1957
  }
 
1958
  return;
1959
  }
1960
 
1988
  */
1989
  public static function encrypt($pure_string, $encryption_key)
1990
  {
1991
+ if(!function_exists("mcrypt_get_iv_size") || !function_exists('utf8_encode')) {
1992
  return "";
1993
  }
1994
  $iv_size = \mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
2022
  return $this->_settings;
2023
  }
2024
 
 
2025
  public function getResizeImages() {
2026
  return $this->_settings->resizeImages;
2027
  }
2042
  public function getCompressionType() {
2043
  return $this->_settings->compressionType;
2044
  }
2045
+ public function hasNextGen() {
2046
+ return $this->hasNextGen;
2047
+ }
2048
+
2049
+ public function getSpMetaDao() {
2050
+ return $this->spMetaDao;
2051
+ }
2052
 
2053
  }
2054
 
2055
+
2056
+ function shortpixelInit() {
 
 
 
2057
  global $pluginInstance;
2058
+ //is admin, is logged in - :) seems funny but it's not, ajax scripts are admin even if no admin is logged in.
2059
+ $prio = get_option('wp-short-pixel-priorityQueue');
2060
+ if (!isset($pluginInstance)
2061
+ && (($prio && is_array($prio) && count($prio) && get_option('wp-short-pixel-front-bootstrap'))
2062
+ || is_admin()
2063
+ && (function_exists("is_user_logged_in") && is_user_logged_in())
2064
+ && ( current_user_can( 'manage_options' )
2065
+ || current_user_can( 'upload_files' )
2066
+ || current_user_can( 'edit_posts' )
2067
+ )
2068
+ )
2069
+ )
2070
+ {
2071
+ $pluginInstance = new WPShortPixel;
2072
+ }
2073
  }
2074
 
2075
+ function handleImageUploadHook($meta, $ID = null) {
2076
+ global $pluginInstance;
2077
+ if(!isset($pluginInstance)) {
2078
+ $pluginInstance = new WPShortPixel;
2079
+ }
2080
+ return $pluginInstance->handleMediaLibraryImageUpload($meta, $ID);
2081
+ }
2082
+
2083
+ function shortpixelNggAdd($image) {
2084
+ global $pluginInstance;
2085
+ if(!isset($pluginInstance)) {
2086
+ $pluginInstance = new WPShortPixel;
2087
+ }
2088
+ $pluginInstance->handleNextGenImageUpload($image);
2089
+ }
2090
 
2091
+ if ( !function_exists( 'vc_action' ) || vc_action() !== 'vc_inline' ) { //handle incompatibility with Visual Composer
2092
+ add_action( 'init', 'shortpixelInit');
2093
+ add_action('ngg_added_new_image', 'shortpixelNggAdd');
2094
+ add_filter( 'wp_generate_attachment_metadata', 'handleImageUploadHook', 10, 2 );
2095
+
2096
  register_activation_hook( __FILE__, array( 'WPShortPixel', 'shortPixelActivatePlugin' ) );
2097
  register_deactivation_hook( __FILE__, array( 'WPShortPixel', 'shortPixelDeactivatePlugin' ) );
2098