Brizy – Page Builder - Version 2.2.9.1

Version Description

  • 2021-03-09 =
  • New: Form input file label is now editable
  • Improved: Sections with Membership enabled now have an indicator icon
  • Fixed: Some images could experience blurriness
  • Fixed: Clicking Global Blocks conditions options would do nothing
  • Fixed: Carousel content flicker
  • Fixed: Carousel options in responsive mode
  • Fixed: Form Submit align option
  • Fixed: Form Custom HTML template copy to clipboard
  • Fixed: Form Integrations Popup scroll in Safari
  • Fixed: Accordion drag and drop
  • Fixed: Accordion adjusting browser scroll position even when not needed
  • Fixed: Slider Prev and Next buttons outline
  • Fixed: Slider padding option
  • Fixed: Image sizes
  • Fixed: Timeline border CSS
  • Fixed: PostContent CSS
  • Fixed: WooCommerce Add to Cart CSS
  • Fixed: Typography option issues in Safari
  • Fixed: Slider option CSS
  • Fixed: Saving a block or a layout would fail if the browser did not support screenshot saving
  • Fixed: Browser back button not working after clicking an anchor to a block on the page
  • Fixed: Do not render Terms page on checkout if it created with Brizy
  • Fixed: White label prefix for multi-site
  • Fixed: Compatibility with BBpress plugin
  • Fixed: Page shop as front page template issue
  • Fixed: Exclude rule
  • Fixed: Dynamic content in Membership block
  • Fixed: Get post lists API
  • Fixed: Compatibility with ThemeFuse old themes
  • Fixed: Avoid duplicate global blocks on create blocks
  • Fixed: Add script dependencies from placeholder
  • Fixed: Preview for autosaves
  • Fixed: Archive element on Author archive template not available
  • Fixed: Possibility to crop an image by its Dynamic URL
  • Fixed: Updated .POT file
  • Fixed: Added an alert comment when removing a function used in other plugins
  • Fixed: Global Blocks would not appear in category pages made with Brizy Template
  • Fixed: PostContent CSS
  • Fixed: WooCommerce Add to Cart CSS
Download this release

Release Info

Developer themefusecom
Plugin Icon 128x128 Brizy – Page Builder
Version 2.2.9.1
Comparing to
See all releases

Code changes from version 2.2.9 to 2.2.9.1

Files changed (54) hide show
  1. README.md +2 -2
  2. brizy.php +2 -2
  3. readme.txt +2 -2
  4. vendor/autoload.php +7 -0
  5. vendor/composer/ClassLoader.php +477 -0
  6. vendor/composer/InstalledVersions.php +24 -0
  7. vendor/composer/LICENSE +21 -0
  8. vendor/composer/autoload_classmap.php +10 -0
  9. vendor/composer/autoload_files.php +13 -0
  10. vendor/composer/autoload_namespaces.php +12 -0
  11. vendor/composer/autoload_psr4.php +16 -0
  12. vendor/composer/autoload_real.php +75 -0
  13. vendor/composer/autoload_static.php +107 -0
  14. vendor/composer/installed.json +128 -0
  15. vendor/composer/installed.php +24 -0
  16. vendor/composer/platform_check.php +26 -0
  17. vendor/shortpixel/shortpixel-php/LICENSE +21 -0
  18. vendor/shortpixel/shortpixel-php/lib/ShortPixel.php +459 -0
  19. vendor/shortpixel/shortpixel-php/lib/ShortPixel/Client.php +542 -0
  20. vendor/shortpixel/shortpixel-php/lib/ShortPixel/Commander.php +244 -0
  21. vendor/shortpixel/shortpixel-php/lib/ShortPixel/Exception.php +37 -0
  22. vendor/shortpixel/shortpixel-php/lib/ShortPixel/Lock.php +103 -0
  23. vendor/shortpixel/shortpixel-php/lib/ShortPixel/Persister.php +23 -0
  24. vendor/shortpixel/shortpixel-php/lib/ShortPixel/Result.php +444 -0
  25. vendor/shortpixel/shortpixel-php/lib/ShortPixel/SPCache.php +67 -0
  26. vendor/shortpixel/shortpixel-php/lib/ShortPixel/SPLog.php +154 -0
  27. vendor/shortpixel/shortpixel-php/lib/ShortPixel/Settings.php +111 -0
  28. vendor/shortpixel/shortpixel-php/lib/ShortPixel/Source.php +166 -0
  29. vendor/shortpixel/shortpixel-php/lib/ShortPixel/notify/ProgressNotifier.php +105 -0
  30. vendor/shortpixel/shortpixel-php/lib/ShortPixel/notify/ProgressNotifierFileQ.php +43 -0
  31. vendor/shortpixel/shortpixel-php/lib/ShortPixel/notify/ProgressNotifierMemcache.php +43 -0
  32. vendor/shortpixel/shortpixel-php/lib/ShortPixel/persist/ExifPersister.php +135 -0
  33. vendor/shortpixel/shortpixel-php/lib/ShortPixel/persist/PNGMetadataExtractor.php +389 -0
  34. vendor/shortpixel/shortpixel-php/lib/ShortPixel/persist/PNGReader.php +95 -0
  35. vendor/shortpixel/shortpixel-php/lib/ShortPixel/persist/TextPersister.php +644 -0
  36. vendor/shortpixel/shortpixel-php/lib/cmdShortpixelOptimize.php +327 -0
  37. vendor/shortpixel/shortpixel-php/lib/data/shortpixel.crt +29 -0
  38. vendor/shortpixel/shortpixel-php/lib/no-composer.php +12 -0
  39. vendor/shortpixel/shortpixel-php/lib/shortpixel-php-req.php +41 -0
  40. vendor/shortpixel/shortpixel-php/phpunit.xml +13 -0
  41. vendor/symfony/deprecation-contracts/LICENSE +19 -0
  42. vendor/symfony/deprecation-contracts/function.php +27 -0
  43. vendor/symfony/dotenv/.gitattributes +4 -0
  44. vendor/symfony/dotenv/Dotenv.php +571 -0
  45. vendor/symfony/dotenv/Exception/ExceptionInterface.php +21 -0
  46. vendor/symfony/dotenv/Exception/FormatException.php +34 -0
  47. vendor/symfony/dotenv/Exception/FormatExceptionContext.php +49 -0
  48. vendor/symfony/dotenv/Exception/PathException.php +25 -0
  49. vendor/symfony/dotenv/LICENSE +19 -0
  50. vendor/symfony/dotenv/Tests/DotenvTest.php +499 -0
  51. vendor/symfony/polyfill-ctype/Ctype.php +227 -0
  52. vendor/symfony/polyfill-ctype/LICENSE +19 -0
  53. vendor/symfony/polyfill-ctype/bootstrap.php +50 -0
  54. vendor/symfony/polyfill-ctype/bootstrap80.php +46 -0
README.md CHANGED
@@ -3,7 +3,7 @@ Contributors: themefuse<br>
3
  Requires at least: 4.5<br>
4
  Tested up to: 5.6<br>
5
  Requires PHP: 5.6<br>
6
- Stable tag: 2.2.9<br>
7
  License: GPLv3<br>
8
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
9
 
@@ -118,7 +118,7 @@ $bodyHtml = apply_filters( 'brizy_content', $html->get_body(), Brizy_Editor_Proj
118
 
119
  ## Changelog
120
 
121
- ### 2.2.9 - 2021-03-09
122
  * New: Form input file label is now editable
123
  * Improved: Sections with Membership enabled now have an indicator icon
124
  * Fixed: Some images could experience blurriness
3
  Requires at least: 4.5<br>
4
  Tested up to: 5.6<br>
5
  Requires PHP: 5.6<br>
6
+ Stable tag: 2.2.9.1<br>
7
  License: GPLv3<br>
8
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
9
 
118
 
119
  ## Changelog
120
 
121
+ ### 2.2.9.1 - 2021-03-09
122
  * New: Form input file label is now editable
123
  * Improved: Sections with Membership enabled now have an indicator icon
124
  * Fixed: Some images could experience blurriness
brizy.php CHANGED
@@ -5,7 +5,7 @@
5
  * Plugin URI: https://brizy.io/
6
  * Author: Brizy.io
7
  * Author URI: https://brizy.io/
8
- * Version: 2.2.9
9
  * Text Domain: brizy
10
  * License: GPLv3
11
  * Domain Path: /languages
@@ -19,7 +19,7 @@ if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && stripos( $_SERVER['HTTP_X_FO
19
 
20
  define( 'BRIZY_DEVELOPMENT', false );
21
  define( 'BRIZY_LOG', false );
22
- define( 'BRIZY_VERSION', '2.2.9' );
23
  define( 'BRIZY_EDITOR_VERSION', BRIZY_DEVELOPMENT ? 'dev' : '173-wp' );
24
  define( 'BRIZY_SYNC_VERSION', '173' );
25
  define( 'BRIZY_FILE', __FILE__ );
5
  * Plugin URI: https://brizy.io/
6
  * Author: Brizy.io
7
  * Author URI: https://brizy.io/
8
+ * Version: 2.2.9.1
9
  * Text Domain: brizy
10
  * License: GPLv3
11
  * Domain Path: /languages
19
 
20
  define( 'BRIZY_DEVELOPMENT', false );
21
  define( 'BRIZY_LOG', false );
22
+ define( 'BRIZY_VERSION', '2.2.9.1' );
23
  define( 'BRIZY_EDITOR_VERSION', BRIZY_DEVELOPMENT ? 'dev' : '173-wp' );
24
  define( 'BRIZY_SYNC_VERSION', '173' );
25
  define( 'BRIZY_FILE', __FILE__ );
readme.txt CHANGED
@@ -4,7 +4,7 @@ Tags: page builder, website builder, brizy, editor, visual editor, unyson, wysiw
4
  Requires at least: 4.5
5
  Tested up to: 5.6
6
  Requires PHP: 5.6
7
- Stable tag: 2.2.9
8
  License: GPLv3
9
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
10
 
@@ -133,7 +133,7 @@ Don't worry if you make a mistake or delete something that you shouldn't have. W
133
 
134
  == Changelog ==
135
 
136
- = 2.2.9 - 2021-03-09 =
137
  * New: Form input file label is now editable
138
  * Improved: Sections with Membership enabled now have an indicator icon
139
  * Fixed: Some images could experience blurriness
4
  Requires at least: 4.5
5
  Tested up to: 5.6
6
  Requires PHP: 5.6
7
+ Stable tag: 2.2.9.1
8
  License: GPLv3
9
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
10
 
133
 
134
  == Changelog ==
135
 
136
+ = 2.2.9.1 - 2021-03-09 =
137
  * New: Form input file label is now editable
138
  * Improved: Sections with Membership enabled now have an indicator icon
139
  * Fixed: Some images could experience blurriness
vendor/autoload.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload.php @generated by Composer
4
+
5
+ require_once __DIR__ . '/composer/autoload_real.php';
6
+
7
+ return ComposerAutoloaderInit830e2064261ef899b6a9d442bbe99fee::getLoader();
vendor/composer/ClassLoader.php ADDED
@@ -0,0 +1,477 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Composer.
5
+ *
6
+ * (c) Nils Adermann <naderman@naderman.de>
7
+ * Jordi Boggiano <j.boggiano@seld.be>
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ namespace Composer\Autoload;
14
+
15
+ /**
16
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17
+ *
18
+ * $loader = new \Composer\Autoload\ClassLoader();
19
+ *
20
+ * // register classes with namespaces
21
+ * $loader->add('Symfony\Component', __DIR__.'/component');
22
+ * $loader->add('Symfony', __DIR__.'/framework');
23
+ *
24
+ * // activate the autoloader
25
+ * $loader->register();
26
+ *
27
+ * // to enable searching the include path (eg. for PEAR packages)
28
+ * $loader->setUseIncludePath(true);
29
+ *
30
+ * In this example, if you try to use a class in the Symfony\Component
31
+ * namespace or one of its children (Symfony\Component\Console for instance),
32
+ * the autoloader will first look for the class under the component/
33
+ * directory, and it will then fallback to the framework/ directory if not
34
+ * found before giving up.
35
+ *
36
+ * This class is loosely based on the Symfony UniversalClassLoader.
37
+ *
38
+ * @author Fabien Potencier <fabien@symfony.com>
39
+ * @author Jordi Boggiano <j.boggiano@seld.be>
40
+ * @see https://www.php-fig.org/psr/psr-0/
41
+ * @see https://www.php-fig.org/psr/psr-4/
42
+ */
43
+ class ClassLoader
44
+ {
45
+ private $vendorDir;
46
+
47
+ // PSR-4
48
+ private $prefixLengthsPsr4 = array();
49
+ private $prefixDirsPsr4 = array();
50
+ private $fallbackDirsPsr4 = array();
51
+
52
+ // PSR-0
53
+ private $prefixesPsr0 = array();
54
+ private $fallbackDirsPsr0 = array();
55
+
56
+ private $useIncludePath = false;
57
+ private $classMap = array();
58
+ private $classMapAuthoritative = false;
59
+ private $missingClasses = array();
60
+ private $apcuPrefix;
61
+
62
+ private static $registeredLoaders = array();
63
+
64
+ public function __construct($vendorDir = null)
65
+ {
66
+ $this->vendorDir = $vendorDir;
67
+ }
68
+
69
+ public function getPrefixes()
70
+ {
71
+ if (!empty($this->prefixesPsr0)) {
72
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
73
+ }
74
+
75
+ return array();
76
+ }
77
+
78
+ public function getPrefixesPsr4()
79
+ {
80
+ return $this->prefixDirsPsr4;
81
+ }
82
+
83
+ public function getFallbackDirs()
84
+ {
85
+ return $this->fallbackDirsPsr0;
86
+ }
87
+
88
+ public function getFallbackDirsPsr4()
89
+ {
90
+ return $this->fallbackDirsPsr4;
91
+ }
92
+
93
+ public function getClassMap()
94
+ {
95
+ return $this->classMap;
96
+ }
97
+
98
+ /**
99
+ * @param array $classMap Class to filename map
100
+ */
101
+ public function addClassMap(array $classMap)
102
+ {
103
+ if ($this->classMap) {
104
+ $this->classMap = array_merge($this->classMap, $classMap);
105
+ } else {
106
+ $this->classMap = $classMap;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Registers a set of PSR-0 directories for a given prefix, either
112
+ * appending or prepending to the ones previously set for this prefix.
113
+ *
114
+ * @param string $prefix The prefix
115
+ * @param array|string $paths The PSR-0 root directories
116
+ * @param bool $prepend Whether to prepend the directories
117
+ */
118
+ public function add($prefix, $paths, $prepend = false)
119
+ {
120
+ if (!$prefix) {
121
+ if ($prepend) {
122
+ $this->fallbackDirsPsr0 = array_merge(
123
+ (array) $paths,
124
+ $this->fallbackDirsPsr0
125
+ );
126
+ } else {
127
+ $this->fallbackDirsPsr0 = array_merge(
128
+ $this->fallbackDirsPsr0,
129
+ (array) $paths
130
+ );
131
+ }
132
+
133
+ return;
134
+ }
135
+
136
+ $first = $prefix[0];
137
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
138
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
139
+
140
+ return;
141
+ }
142
+ if ($prepend) {
143
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
144
+ (array) $paths,
145
+ $this->prefixesPsr0[$first][$prefix]
146
+ );
147
+ } else {
148
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
149
+ $this->prefixesPsr0[$first][$prefix],
150
+ (array) $paths
151
+ );
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Registers a set of PSR-4 directories for a given namespace, either
157
+ * appending or prepending to the ones previously set for this namespace.
158
+ *
159
+ * @param string $prefix The prefix/namespace, with trailing '\\'
160
+ * @param array|string $paths The PSR-4 base directories
161
+ * @param bool $prepend Whether to prepend the directories
162
+ *
163
+ * @throws \InvalidArgumentException
164
+ */
165
+ public function addPsr4($prefix, $paths, $prepend = false)
166
+ {
167
+ if (!$prefix) {
168
+ // Register directories for the root namespace.
169
+ if ($prepend) {
170
+ $this->fallbackDirsPsr4 = array_merge(
171
+ (array) $paths,
172
+ $this->fallbackDirsPsr4
173
+ );
174
+ } else {
175
+ $this->fallbackDirsPsr4 = array_merge(
176
+ $this->fallbackDirsPsr4,
177
+ (array) $paths
178
+ );
179
+ }
180
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
181
+ // Register directories for a new namespace.
182
+ $length = strlen($prefix);
183
+ if ('\\' !== $prefix[$length - 1]) {
184
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
185
+ }
186
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
187
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
188
+ } elseif ($prepend) {
189
+ // Prepend directories for an already registered namespace.
190
+ $this->prefixDirsPsr4[$prefix] = array_merge(
191
+ (array) $paths,
192
+ $this->prefixDirsPsr4[$prefix]
193
+ );
194
+ } else {
195
+ // Append directories for an already registered namespace.
196
+ $this->prefixDirsPsr4[$prefix] = array_merge(
197
+ $this->prefixDirsPsr4[$prefix],
198
+ (array) $paths
199
+ );
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Registers a set of PSR-0 directories for a given prefix,
205
+ * replacing any others previously set for this prefix.
206
+ *
207
+ * @param string $prefix The prefix
208
+ * @param array|string $paths The PSR-0 base directories
209
+ */
210
+ public function set($prefix, $paths)
211
+ {
212
+ if (!$prefix) {
213
+ $this->fallbackDirsPsr0 = (array) $paths;
214
+ } else {
215
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Registers a set of PSR-4 directories for a given namespace,
221
+ * replacing any others previously set for this namespace.
222
+ *
223
+ * @param string $prefix The prefix/namespace, with trailing '\\'
224
+ * @param array|string $paths The PSR-4 base directories
225
+ *
226
+ * @throws \InvalidArgumentException
227
+ */
228
+ public function setPsr4($prefix, $paths)
229
+ {
230
+ if (!$prefix) {
231
+ $this->fallbackDirsPsr4 = (array) $paths;
232
+ } else {
233
+ $length = strlen($prefix);
234
+ if ('\\' !== $prefix[$length - 1]) {
235
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
236
+ }
237
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
238
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Turns on searching the include path for class files.
244
+ *
245
+ * @param bool $useIncludePath
246
+ */
247
+ public function setUseIncludePath($useIncludePath)
248
+ {
249
+ $this->useIncludePath = $useIncludePath;
250
+ }
251
+
252
+ /**
253
+ * Can be used to check if the autoloader uses the include path to check
254
+ * for classes.
255
+ *
256
+ * @return bool
257
+ */
258
+ public function getUseIncludePath()
259
+ {
260
+ return $this->useIncludePath;
261
+ }
262
+
263
+ /**
264
+ * Turns off searching the prefix and fallback directories for classes
265
+ * that have not been registered with the class map.
266
+ *
267
+ * @param bool $classMapAuthoritative
268
+ */
269
+ public function setClassMapAuthoritative($classMapAuthoritative)
270
+ {
271
+ $this->classMapAuthoritative = $classMapAuthoritative;
272
+ }
273
+
274
+ /**
275
+ * Should class lookup fail if not found in the current class map?
276
+ *
277
+ * @return bool
278
+ */
279
+ public function isClassMapAuthoritative()
280
+ {
281
+ return $this->classMapAuthoritative;
282
+ }
283
+
284
+ /**
285
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
286
+ *
287
+ * @param string|null $apcuPrefix
288
+ */
289
+ public function setApcuPrefix($apcuPrefix)
290
+ {
291
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
292
+ }
293
+
294
+ /**
295
+ * The APCu prefix in use, or null if APCu caching is not enabled.
296
+ *
297
+ * @return string|null
298
+ */
299
+ public function getApcuPrefix()
300
+ {
301
+ return $this->apcuPrefix;
302
+ }
303
+
304
+ /**
305
+ * Registers this instance as an autoloader.
306
+ *
307
+ * @param bool $prepend Whether to prepend the autoloader or not
308
+ */
309
+ public function register($prepend = false)
310
+ {
311
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
312
+
313
+ if (null === $this->vendorDir) {
314
+ //no-op
315
+ } elseif ($prepend) {
316
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
317
+ } else {
318
+ unset(self::$registeredLoaders[$this->vendorDir]);
319
+ self::$registeredLoaders[$this->vendorDir] = $this;
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Unregisters this instance as an autoloader.
325
+ */
326
+ public function unregister()
327
+ {
328
+ spl_autoload_unregister(array($this, 'loadClass'));
329
+
330
+ if (null !== $this->vendorDir) {
331
+ unset(self::$registeredLoaders[$this->vendorDir]);
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Loads the given class or interface.
337
+ *
338
+ * @param string $class The name of the class
339
+ * @return bool|null True if loaded, null otherwise
340
+ */
341
+ public function loadClass($class)
342
+ {
343
+ if ($file = $this->findFile($class)) {
344
+ includeFile($file);
345
+
346
+ return true;
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Finds the path to the file where the class is defined.
352
+ *
353
+ * @param string $class The name of the class
354
+ *
355
+ * @return string|false The path if found, false otherwise
356
+ */
357
+ public function findFile($class)
358
+ {
359
+ // class map lookup
360
+ if (isset($this->classMap[$class])) {
361
+ return $this->classMap[$class];
362
+ }
363
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
364
+ return false;
365
+ }
366
+ if (null !== $this->apcuPrefix) {
367
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
368
+ if ($hit) {
369
+ return $file;
370
+ }
371
+ }
372
+
373
+ $file = $this->findFileWithExtension($class, '.php');
374
+
375
+ // Search for Hack files if we are running on HHVM
376
+ if (false === $file && defined('HHVM_VERSION')) {
377
+ $file = $this->findFileWithExtension($class, '.hh');
378
+ }
379
+
380
+ if (null !== $this->apcuPrefix) {
381
+ apcu_add($this->apcuPrefix.$class, $file);
382
+ }
383
+
384
+ if (false === $file) {
385
+ // Remember that this class does not exist.
386
+ $this->missingClasses[$class] = true;
387
+ }
388
+
389
+ return $file;
390
+ }
391
+
392
+ /**
393
+ * Returns the currently registered loaders indexed by their corresponding vendor directories.
394
+ *
395
+ * @return self[]
396
+ */
397
+ public static function getRegisteredLoaders()
398
+ {
399
+ return self::$registeredLoaders;
400
+ }
401
+
402
+ private function findFileWithExtension($class, $ext)
403
+ {
404
+ // PSR-4 lookup
405
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
406
+
407
+ $first = $class[0];
408
+ if (isset($this->prefixLengthsPsr4[$first])) {
409
+ $subPath = $class;
410
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
411
+ $subPath = substr($subPath, 0, $lastPos);
412
+ $search = $subPath . '\\';
413
+ if (isset($this->prefixDirsPsr4[$search])) {
414
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
415
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
416
+ if (file_exists($file = $dir . $pathEnd)) {
417
+ return $file;
418
+ }
419
+ }
420
+ }
421
+ }
422
+ }
423
+
424
+ // PSR-4 fallback dirs
425
+ foreach ($this->fallbackDirsPsr4 as $dir) {
426
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
427
+ return $file;
428
+ }
429
+ }
430
+
431
+ // PSR-0 lookup
432
+ if (false !== $pos = strrpos($class, '\\')) {
433
+ // namespaced class name
434
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
435
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
436
+ } else {
437
+ // PEAR-like class name
438
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
439
+ }
440
+
441
+ if (isset($this->prefixesPsr0[$first])) {
442
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
443
+ if (0 === strpos($class, $prefix)) {
444
+ foreach ($dirs as $dir) {
445
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
446
+ return $file;
447
+ }
448
+ }
449
+ }
450
+ }
451
+ }
452
+
453
+ // PSR-0 fallback dirs
454
+ foreach ($this->fallbackDirsPsr0 as $dir) {
455
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
456
+ return $file;
457
+ }
458
+ }
459
+
460
+ // PSR-0 include paths.
461
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
462
+ return $file;
463
+ }
464
+
465
+ return false;
466
+ }
467
+ }
468
+
469
+ /**
470
+ * Scope isolated include.
471
+ *
472
+ * Prevents access to $this/self from included files.
473
+ */
474
+ function includeFile($file)
475
+ {
476
+ include $file;
477
+ }
vendor/composer/InstalledVersions.php CHANGED
@@ -89,19 +89,43 @@ private static $installed = array (
89
  ),
90
  'reference' => '9ce61fd297fd2922fe771debea8b24dfd219a49a',
91
  ),
 
 
 
 
 
 
 
 
 
92
  'symfony/deprecation-contracts' =>
93
  array (
 
 
94
  'aliases' =>
95
  array (
96
  0 => '2.3.x-dev',
97
  ),
 
 
 
 
 
 
 
 
 
 
98
  ),
99
  'symfony/polyfill-ctype' =>
100
  array (
 
 
101
  'aliases' =>
102
  array (
103
  0 => '1.22.x-dev',
104
  ),
 
105
  ),
106
  'twig/twig' =>
107
  array (
89
  ),
90
  'reference' => '9ce61fd297fd2922fe771debea8b24dfd219a49a',
91
  ),
92
+ 'shortpixel/shortpixel-php' =>
93
+ array (
94
+ 'pretty_version' => 'dev-master',
95
+ 'version' => 'dev-master',
96
+ 'aliases' =>
97
+ array (
98
+ ),
99
+ 'reference' => 'b54bbdc934ca3a688dd374002772bb0f637f454e',
100
+ ),
101
  'symfony/deprecation-contracts' =>
102
  array (
103
+ 'pretty_version' => 'dev-main',
104
+ 'version' => 'dev-main',
105
  'aliases' =>
106
  array (
107
  0 => '2.3.x-dev',
108
  ),
109
+ 'reference' => 'c154763cf2c552cc07ad9388c8a62e80adc2864f',
110
+ ),
111
+ 'symfony/dotenv' =>
112
+ array (
113
+ 'pretty_version' => '5.x-dev',
114
+ 'version' => '5.9999999.9999999.9999999-dev',
115
+ 'aliases' =>
116
+ array (
117
+ ),
118
+ 'reference' => 'f4f1b78f7b50aa7f1de3b038041063fbbab3218e',
119
  ),
120
  'symfony/polyfill-ctype' =>
121
  array (
122
+ 'pretty_version' => 'dev-main',
123
+ 'version' => 'dev-main',
124
  'aliases' =>
125
  array (
126
  0 => '1.22.x-dev',
127
  ),
128
+ 'reference' => 'c6c942b1ac76c82448322025e084cadc56048b4e',
129
  ),
130
  'twig/twig' =>
131
  array (
vendor/composer/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Copyright (c) Nils Adermann, Jordi Boggiano
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished
9
+ to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+
vendor/composer/autoload_classmap.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_classmap.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
10
+ );
vendor/composer/autoload_files.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_files.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
10
+ '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
11
+ '8ec4222c68e580a23520eef4abe4380f' => $vendorDir . '/shortpixel/shortpixel-php/lib/ShortPixel.php',
12
+ 'c93afce03290e70ec0d051b69a50edb0' => $vendorDir . '/shortpixel/shortpixel-php/lib/ShortPixel/Exception.php',
13
+ );
vendor/composer/autoload_namespaces.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_namespaces.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ 'Twig_' => array($vendorDir . '/twig/twig/lib'),
10
+ 'Gaufrette' => array($vendorDir . '/knplabs/gaufrette/src'),
11
+ 'Brizy' => array($vendorDir . '/bagrinsergiu/brizy-migration-utils/src'),
12
+ );
vendor/composer/autoload_psr4.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_psr4.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ 'enshrined\\svgSanitize\\' => array($vendorDir . '/enshrined/svg-sanitize/src'),
10
+ 'Twig\\' => array($vendorDir . '/twig/twig/src'),
11
+ 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
12
+ 'Symfony\\Component\\Dotenv\\' => array($vendorDir . '/symfony/dotenv'),
13
+ 'ShortPixel\\' => array($vendorDir . '/shortpixel/shortpixel-php/lib/ShortPixel'),
14
+ 'BrizyMerge\\' => array($vendorDir . '/bagrinsergiu/brizy-merge-page-assets/lib'),
15
+ 'BrizyMergeTests\\' => array($vendorDir . '/bagrinsergiu/brizy-merge-page-assets/tests'),
16
+ );
vendor/composer/autoload_real.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_real.php @generated by Composer
4
+
5
+ class ComposerAutoloaderInit830e2064261ef899b6a9d442bbe99fee
6
+ {
7
+ private static $loader;
8
+
9
+ public static function loadClassLoader($class)
10
+ {
11
+ if ('Composer\Autoload\ClassLoader' === $class) {
12
+ require __DIR__ . '/ClassLoader.php';
13
+ }
14
+ }
15
+
16
+ /**
17
+ * @return \Composer\Autoload\ClassLoader
18
+ */
19
+ public static function getLoader()
20
+ {
21
+ if (null !== self::$loader) {
22
+ return self::$loader;
23
+ }
24
+
25
+ require __DIR__ . '/platform_check.php';
26
+
27
+ spl_autoload_register(array('ComposerAutoloaderInit830e2064261ef899b6a9d442bbe99fee', 'loadClassLoader'), true, true);
28
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
29
+ spl_autoload_unregister(array('ComposerAutoloaderInit830e2064261ef899b6a9d442bbe99fee', 'loadClassLoader'));
30
+
31
+ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
32
+ if ($useStaticLoader) {
33
+ require __DIR__ . '/autoload_static.php';
34
+
35
+ call_user_func(\Composer\Autoload\ComposerStaticInit830e2064261ef899b6a9d442bbe99fee::getInitializer($loader));
36
+ } else {
37
+ $map = require __DIR__ . '/autoload_namespaces.php';
38
+ foreach ($map as $namespace => $path) {
39
+ $loader->set($namespace, $path);
40
+ }
41
+
42
+ $map = require __DIR__ . '/autoload_psr4.php';
43
+ foreach ($map as $namespace => $path) {
44
+ $loader->setPsr4($namespace, $path);
45
+ }
46
+
47
+ $classMap = require __DIR__ . '/autoload_classmap.php';
48
+ if ($classMap) {
49
+ $loader->addClassMap($classMap);
50
+ }
51
+ }
52
+
53
+ $loader->register(true);
54
+
55
+ if ($useStaticLoader) {
56
+ $includeFiles = Composer\Autoload\ComposerStaticInit830e2064261ef899b6a9d442bbe99fee::$files;
57
+ } else {
58
+ $includeFiles = require __DIR__ . '/autoload_files.php';
59
+ }
60
+ foreach ($includeFiles as $fileIdentifier => $file) {
61
+ composerRequire830e2064261ef899b6a9d442bbe99fee($fileIdentifier, $file);
62
+ }
63
+
64
+ return $loader;
65
+ }
66
+ }
67
+
68
+ function composerRequire830e2064261ef899b6a9d442bbe99fee($fileIdentifier, $file)
69
+ {
70
+ if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
71
+ require $file;
72
+
73
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
74
+ }
75
+ }
vendor/composer/autoload_static.php ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_static.php @generated by Composer
4
+
5
+ namespace Composer\Autoload;
6
+
7
+ class ComposerStaticInit830e2064261ef899b6a9d442bbe99fee
8
+ {
9
+ public static $files = array (
10
+ '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
11
+ '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
12
+ '8ec4222c68e580a23520eef4abe4380f' => __DIR__ . '/..' . '/shortpixel/shortpixel-php/lib/ShortPixel.php',
13
+ 'c93afce03290e70ec0d051b69a50edb0' => __DIR__ . '/..' . '/shortpixel/shortpixel-php/lib/ShortPixel/Exception.php',
14
+ );
15
+
16
+ public static $prefixLengthsPsr4 = array (
17
+ 'e' =>
18
+ array (
19
+ 'enshrined\\svgSanitize\\' => 22,
20
+ ),
21
+ 'T' =>
22
+ array (
23
+ 'Twig\\' => 5,
24
+ ),
25
+ 'S' =>
26
+ array (
27
+ 'Symfony\\Polyfill\\Ctype\\' => 23,
28
+ 'Symfony\\Component\\Dotenv\\' => 25,
29
+ 'ShortPixel\\' => 11,
30
+ ),
31
+ 'B' =>
32
+ array (
33
+ 'BrizyMerge\\' => 11,
34
+ 'BrizyMergeTests\\' => 16,
35
+ ),
36
+ );
37
+
38
+ public static $prefixDirsPsr4 = array (
39
+ 'enshrined\\svgSanitize\\' =>
40
+ array (
41
+ 0 => __DIR__ . '/..' . '/enshrined/svg-sanitize/src',
42
+ ),
43
+ 'Twig\\' =>
44
+ array (
45
+ 0 => __DIR__ . '/..' . '/twig/twig/src',
46
+ ),
47
+ 'Symfony\\Polyfill\\Ctype\\' =>
48
+ array (
49
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
50
+ ),
51
+ 'Symfony\\Component\\Dotenv\\' =>
52
+ array (
53
+ 0 => __DIR__ . '/..' . '/symfony/dotenv',
54
+ ),
55
+ 'ShortPixel\\' =>
56
+ array (
57
+ 0 => __DIR__ . '/..' . '/shortpixel/shortpixel-php/lib/ShortPixel',
58
+ ),
59
+ 'BrizyMerge\\' =>
60
+ array (
61
+ 0 => __DIR__ . '/..' . '/bagrinsergiu/brizy-merge-page-assets/lib',
62
+ ),
63
+ 'BrizyMergeTests\\' =>
64
+ array (
65
+ 0 => __DIR__ . '/..' . '/bagrinsergiu/brizy-merge-page-assets/tests',
66
+ ),
67
+ );
68
+
69
+ public static $prefixesPsr0 = array (
70
+ 'T' =>
71
+ array (
72
+ 'Twig_' =>
73
+ array (
74
+ 0 => __DIR__ . '/..' . '/twig/twig/lib',
75
+ ),
76
+ ),
77
+ 'G' =>
78
+ array (
79
+ 'Gaufrette' =>
80
+ array (
81
+ 0 => __DIR__ . '/..' . '/knplabs/gaufrette/src',
82
+ ),
83
+ ),
84
+ 'B' =>
85
+ array (
86
+ 'Brizy' =>
87
+ array (
88
+ 0 => __DIR__ . '/..' . '/bagrinsergiu/brizy-migration-utils/src',
89
+ ),
90
+ ),
91
+ );
92
+
93
+ public static $classMap = array (
94
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
95
+ );
96
+
97
+ public static function getInitializer(ClassLoader $loader)
98
+ {
99
+ return \Closure::bind(function () use ($loader) {
100
+ $loader->prefixLengthsPsr4 = ComposerStaticInit830e2064261ef899b6a9d442bbe99fee::$prefixLengthsPsr4;
101
+ $loader->prefixDirsPsr4 = ComposerStaticInit830e2064261ef899b6a9d442bbe99fee::$prefixDirsPsr4;
102
+ $loader->prefixesPsr0 = ComposerStaticInit830e2064261ef899b6a9d442bbe99fee::$prefixesPsr0;
103
+ $loader->classMap = ComposerStaticInit830e2064261ef899b6a9d442bbe99fee::$classMap;
104
+
105
+ }, null, ClassLoader::class);
106
+ }
107
+ }
vendor/composer/installed.json CHANGED
@@ -263,6 +263,64 @@
263
  "homepage": "https://select2.org/",
264
  "install-path": "../select2/select2"
265
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  {
267
  "name": "symfony/deprecation-contracts",
268
  "version": "dev-main",
@@ -330,6 +388,76 @@
330
  ],
331
  "install-path": "../symfony/deprecation-contracts"
332
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
  {
334
  "name": "symfony/polyfill-ctype",
335
  "version": "dev-main",
263
  "homepage": "https://select2.org/",
264
  "install-path": "../select2/select2"
265
  },
266
+ {
267
+ "name": "shortpixel/shortpixel-php",
268
+ "version": "dev-master",
269
+ "version_normalized": "dev-master",
270
+ "source": {
271
+ "type": "git",
272
+ "url": "https://github.com/short-pixel-optimizer/shortpixel-php.git",
273
+ "reference": "b54bbdc934ca3a688dd374002772bb0f637f454e"
274
+ },
275
+ "dist": {
276
+ "type": "zip",
277
+ "url": "https://api.github.com/repos/short-pixel-optimizer/shortpixel-php/zipball/b54bbdc934ca3a688dd374002772bb0f637f454e",
278
+ "reference": "b54bbdc934ca3a688dd374002772bb0f637f454e",
279
+ "shasum": ""
280
+ },
281
+ "require": {
282
+ "ext-curl": "*",
283
+ "ext-json": "*",
284
+ "lib-curl": ">=7.20.0",
285
+ "php": ">=5.3.0"
286
+ },
287
+ "require-dev": {
288
+ "phpunit/phpunit": "~4.0",
289
+ "symfony/yaml": "~2.0"
290
+ },
291
+ "time": "2021-01-27T09:52:48+00:00",
292
+ "type": "library",
293
+ "installation-source": "source",
294
+ "autoload": {
295
+ "files": [
296
+ "lib/ShortPixel.php",
297
+ "lib/ShortPixel/Exception.php"
298
+ ],
299
+ "psr-4": {
300
+ "ShortPixel\\": "lib/ShortPixel/"
301
+ }
302
+ },
303
+ "notification-url": "https://packagist.org/downloads/",
304
+ "license": [
305
+ "MIT"
306
+ ],
307
+ "authors": [
308
+ {
309
+ "name": "Simon Duduica",
310
+ "email": "simon@shortpixel.com"
311
+ }
312
+ ],
313
+ "description": "ShortPixel PHP SDK. Read more at https://shortpixel.com/api-tools",
314
+ "homepage": "https://shortpixel.com/api",
315
+ "keywords": [
316
+ "api",
317
+ "compress",
318
+ "images",
319
+ "optimize",
320
+ "shortpixel"
321
+ ],
322
+ "install-path": "../shortpixel/shortpixel-php"
323
+ },
324
  {
325
  "name": "symfony/deprecation-contracts",
326
  "version": "dev-main",
388
  ],
389
  "install-path": "../symfony/deprecation-contracts"
390
  },
391
+ {
392
+ "name": "symfony/dotenv",
393
+ "version": "5.x-dev",
394
+ "version_normalized": "5.9999999.9999999.9999999-dev",
395
+ "source": {
396
+ "type": "git",
397
+ "url": "https://github.com/symfony/dotenv.git",
398
+ "reference": "f4f1b78f7b50aa7f1de3b038041063fbbab3218e"
399
+ },
400
+ "dist": {
401
+ "type": "zip",
402
+ "url": "https://api.github.com/repos/symfony/dotenv/zipball/f4f1b78f7b50aa7f1de3b038041063fbbab3218e",
403
+ "reference": "f4f1b78f7b50aa7f1de3b038041063fbbab3218e",
404
+ "shasum": ""
405
+ },
406
+ "require": {
407
+ "php": ">=7.2.5",
408
+ "symfony/deprecation-contracts": "^2.1"
409
+ },
410
+ "require-dev": {
411
+ "symfony/process": "^4.4|^5.0"
412
+ },
413
+ "time": "2021-01-11T10:34:08+00:00",
414
+ "type": "library",
415
+ "installation-source": "source",
416
+ "autoload": {
417
+ "psr-4": {
418
+ "Symfony\\Component\\Dotenv\\": ""
419
+ },
420
+ "exclude-from-classmap": [
421
+ "/Tests/"
422
+ ]
423
+ },
424
+ "notification-url": "https://packagist.org/downloads/",
425
+ "license": [
426
+ "MIT"
427
+ ],
428
+ "authors": [
429
+ {
430
+ "name": "Fabien Potencier",
431
+ "email": "fabien@symfony.com"
432
+ },
433
+ {
434
+ "name": "Symfony Community",
435
+ "homepage": "https://symfony.com/contributors"
436
+ }
437
+ ],
438
+ "description": "Registers environment variables from a .env file",
439
+ "homepage": "https://symfony.com",
440
+ "keywords": [
441
+ "dotenv",
442
+ "env",
443
+ "environment"
444
+ ],
445
+ "funding": [
446
+ {
447
+ "url": "https://symfony.com/sponsor",
448
+ "type": "custom"
449
+ },
450
+ {
451
+ "url": "https://github.com/fabpot",
452
+ "type": "github"
453
+ },
454
+ {
455
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
456
+ "type": "tidelift"
457
+ }
458
+ ],
459
+ "install-path": "../symfony/dotenv"
460
+ },
461
  {
462
  "name": "symfony/polyfill-ctype",
463
  "version": "dev-main",
vendor/composer/installed.php CHANGED
@@ -65,19 +65,43 @@
65
  ),
66
  'reference' => '9ce61fd297fd2922fe771debea8b24dfd219a49a',
67
  ),
 
 
 
 
 
 
 
 
 
68
  'symfony/deprecation-contracts' =>
69
  array (
 
 
70
  'aliases' =>
71
  array (
72
  0 => '2.3.x-dev',
73
  ),
 
 
 
 
 
 
 
 
 
 
74
  ),
75
  'symfony/polyfill-ctype' =>
76
  array (
 
 
77
  'aliases' =>
78
  array (
79
  0 => '1.22.x-dev',
80
  ),
 
81
  ),
82
  'twig/twig' =>
83
  array (
65
  ),
66
  'reference' => '9ce61fd297fd2922fe771debea8b24dfd219a49a',
67
  ),
68
+ 'shortpixel/shortpixel-php' =>
69
+ array (
70
+ 'pretty_version' => 'dev-master',
71
+ 'version' => 'dev-master',
72
+ 'aliases' =>
73
+ array (
74
+ ),
75
+ 'reference' => 'b54bbdc934ca3a688dd374002772bb0f637f454e',
76
+ ),
77
  'symfony/deprecation-contracts' =>
78
  array (
79
+ 'pretty_version' => 'dev-main',
80
+ 'version' => 'dev-main',
81
  'aliases' =>
82
  array (
83
  0 => '2.3.x-dev',
84
  ),
85
+ 'reference' => 'c154763cf2c552cc07ad9388c8a62e80adc2864f',
86
+ ),
87
+ 'symfony/dotenv' =>
88
+ array (
89
+ 'pretty_version' => '5.x-dev',
90
+ 'version' => '5.9999999.9999999.9999999-dev',
91
+ 'aliases' =>
92
+ array (
93
+ ),
94
+ 'reference' => 'f4f1b78f7b50aa7f1de3b038041063fbbab3218e',
95
  ),
96
  'symfony/polyfill-ctype' =>
97
  array (
98
+ 'pretty_version' => 'dev-main',
99
+ 'version' => 'dev-main',
100
  'aliases' =>
101
  array (
102
  0 => '1.22.x-dev',
103
  ),
104
+ 'reference' => 'c6c942b1ac76c82448322025e084cadc56048b4e',
105
  ),
106
  'twig/twig' =>
107
  array (
vendor/composer/platform_check.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // platform_check.php @generated by Composer
4
+
5
+ $issues = array();
6
+
7
+ if (!(PHP_VERSION_ID >= 70205)) {
8
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.5". You are running ' . PHP_VERSION . '.';
9
+ }
10
+
11
+ if ($issues) {
12
+ if (!headers_sent()) {
13
+ header('HTTP/1.1 500 Internal Server Error');
14
+ }
15
+ if (!ini_get('display_errors')) {
16
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
17
+ fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
18
+ } elseif (!headers_sent()) {
19
+ echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
20
+ }
21
+ }
22
+ trigger_error(
23
+ 'Composer detected issues in your platform: ' . implode(' ', $issues),
24
+ E_USER_ERROR
25
+ );
26
+ }
vendor/shortpixel/shortpixel-php/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License
2
+
3
+ Copyright (c) 2014-2016 Shortpixel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
vendor/shortpixel/shortpixel-php/lib/ShortPixel.php ADDED
@@ -0,0 +1,459 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ShortPixel;
4
+
5
+ class ShortPixel {
6
+ const LIBRARY_CODE = "sp-sdk";
7
+ const VERSION = "1.7.1";
8
+ const DEBUG_LOG = false;
9
+
10
+ const MAX_ALLOWED_FILES_PER_CALL = 10;
11
+ const MAX_ALLOWED_FILES_PER_WEB_CALL = 30;
12
+ const MAX_API_ALLOWED_FILES_PER_WEB_CALL = 150;
13
+ const CLIENT_MAX_BODY_SIZE = 48; // in MBytes.
14
+ const MAX_RETRIES = 6;
15
+
16
+ const LOSSY_EXIF_TAG = "SPXLY";
17
+ const LOSSLESS_EXIF_TAG = "SPXLL";
18
+
19
+ const RESIZE_OUTER = 1;
20
+ const RESIZE_INNER = 3;
21
+
22
+ private static $key = NULL;
23
+ private static $client = NULL;
24
+ private static $options = array(
25
+ "lossy" => 1, // 1 - lossy, 2 - glossy, 0 - lossless
26
+ "keep_exif" => 0, // 1 - EXIF is preserved, 0 - EXIF is removed
27
+ "resize" => 0, // 0 - don't resize, 1 - outer resize, 3 - inner resize
28
+ "resize_width" => null, // in pixels. null means no resize
29
+ "resize_height" => null, // in pixels. null means no resize
30
+ "cmyk2rgb" => 1, // convert CMYK to RGB: 1 yes, 0 no
31
+ "convertto" => "", // if '+webp' then also the WebP version will be generated, if +avif then also the AVIF version will be generated. Specify both with +webp|+avif
32
+ "user" => "", //set the user needed for HTTP AUTH of the base_url
33
+ "pass" => "", //se the pass needed for HTTP AUTH of the base_url
34
+ // **** return options ****
35
+ "notify_me" => null, // should contain full URL of of notification script (notify.php) - to be implemented
36
+ "wait" => 30, // seconds
37
+ // **** local options ****
38
+ "total_wait" => 30, //seconds
39
+ "base_url" => null, // base url of the images - used to generate the path for toFile by extracting from original URL and using the remaining path as relative path to base_path
40
+ "base_source_path" => "", // base path of the local files
41
+ "base_path" => false, // base path to save the files
42
+ "backup_path" => false, // backup path, relative to the optimization folder (base_source_path)
43
+ // **** persist options ****
44
+ "persist_type" => null, // null - don't persist, otherwise "text" (.shortpixel text file in each folder), "exif" (mark in the EXIF that the image has been optimized) or "mysql" (to be implemented)
45
+ "persist_name" => ".shortpixel",
46
+ "notify_progress" => false,
47
+ "cache_time" => 0 // number of seconds to cache the folder results - the *Persister classes will cache the getTodo results and retrieve them from memcache if it's available.
48
+ //"persist_user" => "user", // only for mysql
49
+ //"persist_pass" => "pass" // only for mysql
50
+ // "" => null,
51
+ );
52
+ private static $curlOptions = array();
53
+
54
+ public static $PROCESSABLE_EXTENSIONS = array('jpg', 'jpeg', 'jpe', 'jfif', 'jif', 'gif', 'png', 'pdf');
55
+
56
+ private static $persistersRegistry = array();
57
+
58
+ /**
59
+ * @param $key - the ShortPixel API Key
60
+ */
61
+ public static function setKey($key) {
62
+ self::$key = $key;
63
+ self::$client = NULL;
64
+ }
65
+
66
+ /**
67
+ * @param $options - set the ShortPxiel options. Options defaults are the following:
68
+ * "lossy" => 1, // 1 - lossy, 0 - lossless
69
+ "keep_exif" => 0, // 1 - EXIF is preserved, 0 - EXIF is removed
70
+ "resize_width" => null, // in pixels. null means no resize
71
+ "resize_height" => null,
72
+ "cmyk2rgb" => 1,
73
+ "convertto" => "", // if '+webp' then also the WebP version will be generated, if +avif then also the AVIF version will be generated. Specify both with +webp|+avif
74
+ "notify_me" => null, // should contain full URL of of notification script (notify.php)- TO BE IMPLEMENTED
75
+ "wait" => 30,
76
+ //local options
77
+ "total_wait" => 30,
78
+ "base_url" => null, // base url of the images - used to generate the path for toFile by extracting from original URL and using the remaining path as relative path to base_path
79
+ "base_path" => "/tmp", // base path for the saved files
80
+ */
81
+ public static function setOptions($options) {
82
+ self::$options = array_merge(self::$options, $options);
83
+ }
84
+
85
+ /**
86
+ * add custom cURL options. These provided options will take precedence to the default options that are passed to all cURL calls but not to others that are specific to each call (CURLOPT_TIMEOUT, CURLOPT_CUSTOMREQUEST)
87
+ * or to library's user agent.
88
+ * @param array $curlOptions Key-value pairs
89
+ */
90
+ public static function setCurlOptions($curlOptions) {
91
+ self::$curlOptions = $curlOptions + self::$curlOptions;
92
+ }
93
+
94
+ /**
95
+ * @return the API Key in use
96
+ */
97
+ public static function getKey() {
98
+ return self::$key;
99
+ }
100
+
101
+ /**
102
+ * @param $name - option name
103
+ * @return the option value or false if not found
104
+ */
105
+ public static function opt($name) {
106
+ return isset(self::$options[$name]) ? self::$options[$name] : false;
107
+ }
108
+
109
+ /**
110
+ * @return the current options array
111
+ */
112
+ public static function options() {
113
+ return self::$options;
114
+ }
115
+
116
+ /**
117
+ * @return Client singleton
118
+ * @throws AccountException
119
+ */
120
+ public static function getClient() {
121
+ if (!self::$key) {
122
+ throw new AccountException("Provide an API key with ShortPixel\setKey(...)", -6);
123
+ }
124
+
125
+ if (!self::$client) {
126
+ self::$client = new Client(self::$curlOptions);
127
+ }
128
+
129
+ return self::$client;
130
+ }
131
+
132
+ public static function getPersister($context = null) {
133
+ if(!self::$options["persist_type"]) {
134
+ return null;
135
+ }
136
+ if($context && isset(self::$persistersRegistry[self::$options["persist_type"] . $context])) {
137
+ return self::$persistersRegistry[self::$options["persist_type"] . $context];
138
+ }
139
+
140
+ $persister = null;
141
+ switch(self::$options["persist_type"]) {
142
+ case "exif":
143
+ $persister = new persist\ExifPersister(self::$options);
144
+ break;
145
+ case "mysql":
146
+ return null;
147
+ case "text":
148
+ $persister = new persist\TextPersister(self::$options);
149
+ break;
150
+ default:
151
+ throw new PersistException("Unknown persist type: " . self::$options["persist_type"]);
152
+ }
153
+
154
+ if($context) {
155
+ self::$persistersRegistry[self::$options["persist_type"] . $context] = $persister;
156
+ }
157
+ return $persister;
158
+ }
159
+
160
+ static public function isProcessable($path) {
161
+ return in_array(strtolower(pathinfo($path, PATHINFO_EXTENSION)), \ShortPixel\ShortPixel::$PROCESSABLE_EXTENSIONS);
162
+ }
163
+
164
+ static public function log($msg) {
165
+ if(ShortPixel::DEBUG_LOG) {
166
+ @file_put_contents(__DIR__ . '/splog.txt', date("Y-m-d H:i:s") . " - " . $msg . " \n\n", FILE_APPEND);
167
+ }
168
+ }
169
+ }
170
+
171
+ ShortPixel::setOptions(array('base_path' => sys_get_temp_dir()));
172
+
173
+
174
+ /**
175
+ * stub for ShortPixel::setKey()
176
+ * @param $key - the ShortPixel API Key
177
+ */
178
+ function setKey($key) {
179
+ return ShortPixel::setKey($key);
180
+ }
181
+
182
+ /**
183
+ * stub for ShortPixel::setOptions()
184
+ * @return the current options array
185
+ */
186
+ function setOptions($options) {
187
+ return ShortPixel::setOptions($options);
188
+ }
189
+
190
+ /**
191
+ * stub for ShortPixel::setOptions()
192
+ * @return the current options array
193
+ */
194
+ function setCurlOptions($options) {
195
+ return ShortPixel::setCurlOptions($options);
196
+ }
197
+
198
+ /**
199
+ * stub for ShortPixel::opt()
200
+ * @param $name - name of the option
201
+ * @return the option
202
+ */
203
+ function opt($name) {
204
+ return ShortPixel::opt($name);
205
+ }
206
+
207
+ /**
208
+ * Stub for Source::fromFiles
209
+ * @param $path - the file path on the local drive
210
+ * @return Commander - the class that handles the optimization commands
211
+ * @throws ClientException
212
+ */
213
+ function fromFiles($path) {
214
+ $source = new Source();
215
+ return $source->fromFiles($path);
216
+ }
217
+
218
+ function fromFile($path) {
219
+ return fromFiles($path);
220
+ }
221
+
222
+ /**
223
+ * Stub for Source::folderInfo
224
+ * @param $path - the file path on the local drive
225
+ * @param bool $recurse - boolean - go into subfolders or not
226
+ * @param bool $fileList - return the list of files with optimization status (only current folder, not subfolders)
227
+ * @param array $exclude - array of folder names that you want to exclude from the optimization
228
+ * @param bool $persistPath - the path where to look for the metadata, if different from the $path
229
+ * @param int $recurseDepth - how many subfolders deep to go. Defaults to PHP_INT_MAX
230
+ * @param bool $retrySkipped - if true, all skipped files will be reset to pending with retries = 0
231
+ * @return object|void (object)array('status', 'total', 'succeeded', 'pending', 'same', 'failed')
232
+ * @throws PersistException
233
+ */
234
+ function folderInfo($path, $recurse = true, $fileList = false, $exclude = array(), $persistPath = false, $recurseDepth = PHP_INT_MAX, $retrySkipped = false) {
235
+ $source = new Source();
236
+ return $source->folderInfo($path, $recurse, $fileList, $exclude, $persistPath, $recurseDepth, $retrySkipped);
237
+ }
238
+
239
+ /**
240
+ * Stub for Source::fromFolder
241
+ * @param $path - the file path on the local drive
242
+ * @param $maxFiles - maximum number of files to select from the folder
243
+ * @param $exclude - exclude files based on regex patterns
244
+ * @param $persistPath - the path where to store the metadata, if different from the $path (usually the target path)
245
+ * @param $maxTotalFileSize - max summed up file size in MB
246
+ * @return Commander - the class that handles the optimization commands
247
+ * @throws ClientException
248
+ */
249
+ function fromFolder($path, $maxFiles = ShortPixel::MAX_ALLOWED_FILES_PER_CALL, $exclude = array(), $persistPath = false, $maxTotalFileSize = ShortPixel::CLIENT_MAX_BODY_SIZE, $recurseDepth = PHP_INT_MAX) {
250
+ $source = new Source();
251
+ return $source->fromFolder($path, $maxFiles, $exclude, $persistPath, $maxTotalFileSize, $recurseDepth);
252
+ }
253
+
254
+ /**
255
+ * Stub for Source::fromWebFolder
256
+ * @param $path - the file path on the local drive
257
+ * @param $webPath - the corresponding web path for the file path
258
+ * @param array $exclude - exclude files based on regex patterns
259
+ * @param bool $persistFolder - the path where to store the metadata, if different from the $path (usually the target path)
260
+ * @param int $recurseDepth - how many subfolders deep to go. Defaults to PHP_INT_MAX
261
+ * @return Commander - the class that handles the optimization commands
262
+ * @throws ClientException
263
+ */
264
+ function fromWebFolder($path, $webPath, $exclude = array(), $persistFolder = false, $recurseDepth = PHP_INT_MAX) {
265
+ $source = new Source();
266
+ return $source->fromWebFolder($path, $webPath, $exclude, $persistFolder, $recurseDepth);
267
+ }
268
+
269
+ function fromBuffer($name, $contents) {
270
+ $source = new Source();
271
+ return $source->fromBuffer($name, $contents);
272
+ }
273
+
274
+ /**
275
+ * Stub for Source::fromUrls
276
+ * @param $urls - the array of urls to be optimized
277
+ * @return Commander - the class that handles the optimization commands
278
+ * @throws ClientException
279
+ */
280
+ function fromUrls($urls) {
281
+ $source = new Source();
282
+ return $source->fromUrls($urls);
283
+ }
284
+
285
+ /**
286
+ */
287
+ function isOptimized($path) {
288
+ $persist = ShortPixel::getPersister($path);
289
+ if($persist) {
290
+ return $persist->isOptimized($path);
291
+ } else {
292
+ throw new Exception("No persister available");
293
+ }
294
+ }
295
+
296
+ function validate() {
297
+ try {
298
+ ShortPixel::getClient()->request("post");
299
+ } catch (ClientException $e) {
300
+ return true;
301
+ }
302
+ }
303
+
304
+ function recurseCopy($source, $dest) {
305
+ foreach (
306
+ $iterator = new \RecursiveIteratorIterator(
307
+ new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS),
308
+ \RecursiveIteratorIterator::SELF_FIRST) as $item
309
+ ) {
310
+ $target = $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName();
311
+ if ($item->isDir()) {
312
+ if(!@mkdir($target)) {
313
+ throw new PersistException("Could not create directory $target. Please check rights.");
314
+ }
315
+ } else {
316
+ if(!@copy($item, $target)) {
317
+ throw new PersistException("Could not copy file $item to $target. Please check rights.");
318
+ }
319
+ }
320
+ }
321
+ }
322
+
323
+ function delTree($dir, $keepBase = true) {
324
+ $files = array_diff(scandir($dir), array('.','..'));
325
+ foreach ($files as $file) {
326
+ (is_dir("$dir/$file")) ? delTree("$dir/$file", false) : unlink("$dir/$file");
327
+ }
328
+ return $keepBase ? true : rmdir($dir);
329
+ }
330
+
331
+ /**
332
+ * a basename alternative that deals OK with multibyte charsets (e.g. Arabic)
333
+ * @param string $Path
334
+ * @return string
335
+ */
336
+ function MB_basename($Path, $suffix = false){
337
+ $Separator = " qq ";
338
+ $qqPath = preg_replace("/[^ ]/u", $Separator."\$0".$Separator, $Path);
339
+ if(!$qqPath) { //this is not an UTF8 string!!
340
+ $pathElements = explode('/', $Path);
341
+ $fileName = end($pathElements);
342
+ $pos = strpos($fileName, $suffix);
343
+ if($pos !== false) {
344
+ return substr($fileName, 0, $pos);
345
+ }
346
+ return $fileName;
347
+ }
348
+ $suffix = preg_replace("/[^ ]/u", $Separator."\$0".$Separator, $suffix);
349
+ $Base = basename($qqPath, $suffix);
350
+ $Base = str_replace($Separator, "", $Base);
351
+ return $Base;
352
+ }
353
+
354
+ if(!function_exists('mb_detect_encoding')) {
355
+ function mb_detect_encoding($string, $enc=null) {
356
+
357
+ static $list = array('utf-8', 'iso-8859-1', 'windows-1251');
358
+
359
+ foreach ($list as $item) {
360
+ $sample = @iconv($item, $item, $string);
361
+ if (md5($sample) == md5($string)) {
362
+ if ($enc == $item) { return true; } else { return $item; }
363
+ }
364
+ }
365
+ return null;
366
+ }
367
+ }
368
+
369
+ function spdbg($var, $msg) {
370
+ echo("DEBUG $msg : "); var_dump($var);
371
+ }
372
+
373
+ function spdbgd($var, $msg) {
374
+ die(spdbg($var, $msg));
375
+ }
376
+
377
+ function normalizePath($path) {
378
+ $patterns = array('~/{2,}~', '~/(\./)+~', '~([^/\.]+/(?R)*\.{2,}/)~', '~\.\./~');
379
+ $replacements = array('/', '/', '', '');
380
+ return preg_replace($patterns, $replacements, $path);
381
+ }
382
+
383
+ function getMemcache() {
384
+ $mc = false;
385
+ if(class_exists('\Memcached')) {
386
+ $mc = new \Memcached();
387
+ $mc->addServer('127.0.0.1', '11211');
388
+ if(!@$mc->getStats()) {
389
+ $mc = false;
390
+ }
391
+ }
392
+ elseif(class_exists('\Memcache')) {
393
+ $mc = new \Memcache();
394
+ $mc->addServer('127.0.0.1', '11211');
395
+ if(!@$mc->connect('127.0.0.1', '11211')) {
396
+ $mc = false;
397
+ } else {
398
+ $mc->close();
399
+ }
400
+ }
401
+ return $mc;
402
+ }
403
+
404
+ if ( ! function_exists( 'json_last_error_msg' ) ) {
405
+ /**
406
+ * Retrieves the error string of the last json_encode() or json_decode() call.
407
+ *
408
+ * @since 4.4.0
409
+ *
410
+ * @internal This is a compatibility function for PHP <5.5
411
+ *
412
+ * @return bool|string Returns the error message on success, "No Error" if no error has occurred,
413
+ * or false on failure.
414
+ */
415
+ function json_last_error_msg()
416
+ {
417
+ // See https://core.trac.wordpress.org/ticket/27799.
418
+ if (!function_exists('json_last_error')) {
419
+ return false;
420
+ }
421
+
422
+ $last_error_code = json_last_error();
423
+
424
+ // Just in case JSON_ERROR_NONE is not defined.
425
+ $error_code_none = defined('JSON_ERROR_NONE') ? JSON_ERROR_NONE : 0;
426
+
427
+ switch (true) {
428
+ case $last_error_code === $error_code_none:
429
+ return 'No error';
430
+
431
+ case defined('JSON_ERROR_DEPTH') && JSON_ERROR_DEPTH === $last_error_code:
432
+ return 'Maximum stack depth exceeded';
433
+
434
+ case defined('JSON_ERROR_STATE_MISMATCH') && JSON_ERROR_STATE_MISMATCH === $last_error_code:
435
+ return 'State mismatch (invalid or malformed JSON)';
436
+
437
+ case defined('JSON_ERROR_CTRL_CHAR') && JSON_ERROR_CTRL_CHAR === $last_error_code:
438
+ return 'Control character error, possibly incorrectly encoded';
439
+
440
+ case defined('JSON_ERROR_SYNTAX') && JSON_ERROR_SYNTAX === $last_error_code:
441
+ return 'Syntax error';
442
+
443
+ case defined('JSON_ERROR_UTF8') && JSON_ERROR_UTF8 === $last_error_code:
444
+ return 'Malformed UTF-8 characters, possibly incorrectly encoded';
445
+
446
+ case defined('JSON_ERROR_RECURSION') && JSON_ERROR_RECURSION === $last_error_code:
447
+ return 'Recursion detected';
448
+
449
+ case defined('JSON_ERROR_INF_OR_NAN') && JSON_ERROR_INF_OR_NAN === $last_error_code:
450
+ return 'Inf and NaN cannot be JSON encoded';
451
+
452
+ case defined('JSON_ERROR_UNSUPPORTED_TYPE') && JSON_ERROR_UNSUPPORTED_TYPE === $last_error_code:
453
+ return 'Type is not supported';
454
+
455
+ default:
456
+ return 'An unknown error occurred';
457
+ }
458
+ }
459
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/Client.php ADDED
@@ -0,0 +1,542 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ShortPixel;
4
+
5
+
6
+ class Client {
7
+
8
+ public static function API_DOMAIN() {
9
+ return "api.shortpixel.com";
10
+ //* DEVELOPMENT !! */ return "devapi2.shortpixel.com";
11
+ }
12
+
13
+ private $options, $customOptions, $logger;
14
+
15
+ public static function API_URL() {
16
+ return "https://" . self::API_DOMAIN();
17
+
18
+ }
19
+ public static function API_ENDPOINT() {
20
+ return self::API_URL() . "/v2/reducer.php";
21
+ }
22
+
23
+ public static function API_UPLOAD_ENDPOINT() {
24
+ return self::API_URL() . "/v2/post-reducer.php";
25
+ }
26
+
27
+ public static function API_STATUS_ENDPOINT() {
28
+ return self::API_URL() . "/v2/api-status.php";
29
+ }
30
+
31
+ public static function IMAGE_STATUS_ENDPOINT() {
32
+ return self::API_URL() . "/v2/image-status.php";
33
+ }
34
+
35
+ public static function CLEANUP_ENDPOINT() {
36
+ return self::API_URL() . "/v2/cleanup.php";
37
+ }
38
+
39
+ public static function userAgent() {
40
+ $curl = curl_version();
41
+ return "ShortPixel/" . ShortPixel::VERSION . " PHP/" . PHP_VERSION . " curl/" . $curl["version"];
42
+ }
43
+
44
+ private static function caBundle() {
45
+ return dirname(__DIR__) . "/data/shortpixel.crt";
46
+ }
47
+
48
+ function __construct($curlOptions) {
49
+ $this->customOptions = $curlOptions;
50
+ $this->logger = SPLog::Get(SPLog::PRODUCER_CLIENT);
51
+ $this->options = $curlOptions + array(
52
+ CURLOPT_RETURNTRANSFER => true,
53
+ CURLOPT_BINARYTRANSFER => true,
54
+ CURLOPT_HEADER => true,
55
+ CURLOPT_TIMEOUT => 60,
56
+ //CURLOPT_CAINFO => self::caBundle(),
57
+ CURLOPT_SSL_VERIFYPEER => false, //TODO true
58
+ CURLOPT_SSL_VERIFYHOST => false, //TODO remove
59
+ CURLOPT_USERAGENT => self::userAgent(),
60
+ );
61
+ }
62
+
63
+ /**
64
+ * Does the CURL request to the ShortPixel API
65
+ * @param $method 'post' or 'get'
66
+ * @param null $body - the POST fields
67
+ * @param array $header - HTTP headers
68
+ * @return array - metadata from the API
69
+ * @throws ConnectionException
70
+ */
71
+ function request($method, $body = NULL, $header = array()){
72
+ if ($body) {
73
+ foreach($body as $key => $val) {
74
+ if($val === null) {
75
+ unset($body[$key]);
76
+ }
77
+ }
78
+ }
79
+
80
+ ShortPixel::log("REQUEST BODY: " . json_encode($body));
81
+
82
+ $retUrls = array("body" => array(), "headers" => array(), "fileMappings" => array());
83
+ $retPend = array("body" => array(), "headers" => array(), "fileMappings" => array());
84
+ $retFiles = array("body" => array(), "headers" => array(), "fileMappings" => array());
85
+
86
+ if(isset($body["urllist"])) {
87
+ $retUrls = $this->requestInternal($method, $body, $header);
88
+ }
89
+ if(isset($body["pendingURLs"])) {
90
+ unset($body["urllist"]);
91
+ //some files might have already been processed as relaunches in the given max time
92
+ foreach($retUrls["body"] as $url) {
93
+ //first remove it from the files list as the file was uploaded properly
94
+ if($url->Status->Code != -102 && $url->Status->Code != -106) {
95
+ $notExpired[] = $url;
96
+ if(!isset($body["pendingURLs"][$url->OriginalURL])) {
97
+ $lala = "cucu";
98
+ } else
99
+ $unsetPath = $body["pendingURLs"][$url->OriginalURL];
100
+ if(isset($body["files"]) && ($key = array_search($unsetPath, $body["files"])) !== false) {
101
+ unset($body["files"][$key]);
102
+ }
103
+ }
104
+ //now from the pendingURLs if we already have an answer with urllist
105
+ if(isset($body["pendingURLs"][$url->OriginalURL])) {
106
+ $retUrls["fileMappings"][$url->OriginalURL] = $body["pendingURLs"][$url->OriginalURL];
107
+ unset($body["pendingURLs"][$url->OriginalURL]);
108
+ }
109
+ }
110
+ if(count($body["pendingURLs"])) {
111
+ $retPend = $this->requestInternal($method, $body, $header);
112
+ if(isset($retPend['body']->Status->Code) && $retPend['body']->Status->Code < 0) { //something's wrong (API key?)
113
+ throw new ClientException($retPend['body']->Status->Message, $retPend['body']->Status->Code);
114
+
115
+ }
116
+ if(isset($body["files"])) {
117
+ $notExpired = array();
118
+ foreach($retPend['body'] as $detail) {
119
+ if($detail->Status->Code != -102) { // -102 is expired, means we need to resend the image through post
120
+ $notExpired[] = $detail;
121
+ $unsetPath = $body["pendingURLs"][$detail->OriginalURL];
122
+ if(($key = array_search($unsetPath, $body["files"])) !== false) {
123
+ unset($body["files"][$key]);
124
+ }
125
+ }
126
+ }
127
+ $retPend['body'] = $notExpired;
128
+ }
129
+ }
130
+ }
131
+ if (isset($body["files"]) && count($body["files"]) ||
132
+ isset($body["buffers"]) && count($body["buffers"])) {
133
+ unset($body["pendingURLs"]);
134
+ $retFiles = $this->requestInternal($method, $body, $header);
135
+ }
136
+
137
+ if(!isset($retUrls["body"]->Status) && !isset($retPend["body"]->Status) && !isset($retFiles["body"]->Status)
138
+ && (!is_array($retUrls["body"]) || !is_array($retPend["body"]) || !is_array($retFiles["body"]))) {
139
+ throw new Exception("Request inconsistent status. Please contact support.");
140
+ }
141
+
142
+ $body = isset($retUrls["body"]->Status)
143
+ ? $retUrls["body"]
144
+ : (isset($retPend["body"]->Status)
145
+ ? $retPend["body"]
146
+ : (isset($retFiles["body"]->Status)
147
+ ? $retFiles["body"] :
148
+ array_merge($retUrls["body"], $retPend["body"], $retFiles["body"])));
149
+
150
+ $theReturn = (object) array("body" => $body,
151
+ "headers" => array_unique(array_merge($retUrls["headers"], $retPend["headers"], $retFiles["headers"])),
152
+ "fileMappings" => array_merge($retUrls["fileMappings"], $retPend["fileMappings"], $retFiles["fileMappings"]));
153
+ ShortPixel::log("REQUEST RETURNS: " . json_encode($theReturn));
154
+ return $theReturn;
155
+ }
156
+
157
+ function requestInternal($method, $body = NULL, $header = array()){
158
+ $request = curl_init();
159
+ curl_setopt_array($request, $this->options);
160
+
161
+ $files = $urls = false;
162
+
163
+ if (isset($body["urllist"])) { //images are sent as a list of URLs
164
+ $this->prepareJSONRequest(self::API_ENDPOINT(), $request, $body, $method, $header);
165
+ }
166
+ elseif(isset($body["pendingURLs"])) {
167
+ //prepare the pending items request
168
+ $urls = array();
169
+ $fileCount = 1;
170
+ foreach($body["pendingURLs"] as $url => $path) {
171
+ $urls["url" . $fileCount] = $url;
172
+ $fileCount++;
173
+ }
174
+ $pendingURLs = $body["pendingURLs"];
175
+ unset($body["pendingURLs"]);
176
+ $body["file_urls"] = $urls;
177
+ $this->prepareJSONRequest(self::API_UPLOAD_ENDPOINT(), $request, $body, $method, $header);
178
+ }
179
+ elseif (isset($body["files"]) || isset($body["buffers"])) {
180
+ $files = $this->prepareMultiPartRequest($request, $body, $header);
181
+ }
182
+ else {
183
+ return array("body" => array(), "headers" => array(), "fileMappings" => array());
184
+ }
185
+
186
+ //spdbgd(rawurldecode($body['urllist'][1]), "body");
187
+
188
+ list($details, $headers, $status, $response) = $this->sendRequest($request,6);
189
+
190
+ //TODO delete later
191
+ /* for($i = 0; $i < 6; $i++) { //curl_setopt($request, CURLOPT_TIMEOUT, 120);curl_setopt($request, CURLOPT_VERBOSE, true);
192
+ $response = curl_exec($request);
193
+ if(!curl_errno($request)) {
194
+ break;
195
+ } else {
196
+ ShortPixel::log("CURL ERROR: " . curl_error($request) . " (BODY: $response)");
197
+ }
198
+ }
199
+
200
+ if(curl_errno($request)) {
201
+ throw new ConnectionException("Error while connecting: " . curl_error($request) . "");
202
+ }
203
+ if (!is_string($response)) {
204
+ $message = sprintf("%s (#%d)", curl_error($request), curl_errno($request));
205
+ curl_close($request);
206
+ throw new ConnectionException("Error while connecting: " . $message);
207
+ }
208
+
209
+ $status = curl_getinfo($request, CURLINFO_HTTP_CODE);
210
+ $headerSize = curl_getinfo($request, CURLINFO_HEADER_SIZE);
211
+ curl_close($request);
212
+
213
+ $headers = self::parseHeaders(substr($response, 0, $headerSize));
214
+ $body = substr($response, $headerSize);
215
+
216
+ $details = json_decode($body);
217
+
218
+ if (!$details) {
219
+ $message = sprintf("Error while parsing response (Status: %s): %s (#%d)", $status,
220
+ PHP_VERSION_ID >= 50500 ? json_last_error_msg() : "Error",
221
+ json_last_error());
222
+ $details = (object) array(
223
+ "raw" => $body,
224
+ "error" => "ParseError",
225
+ "message" => $message . "( " . $body . ")",
226
+ "Status" => (object)array("Code" => -1, "Message" => "ParseError: " . $message)
227
+ );
228
+ }
229
+ */
230
+ if(getenv("SHORTPIXEL_DEBUG")) {
231
+ $info = "DETAILS\n";
232
+ if(is_array($details)) {
233
+ foreach($details as $det) {
234
+ $info .= $det->Status->Code . " " . $det->OriginalURL . (isset($det->localPath) ? "({$det->localPath})" : "" ) . "\n";
235
+ }
236
+ } else {
237
+ $info = $response;
238
+ }
239
+ }
240
+
241
+ $fileMappings = array();
242
+ if($files) {
243
+ $fileMappings = array();
244
+ foreach($details as $detail) {
245
+ if(isset($detail->Key) && isset($files[$detail->Key])){
246
+ $fileMappings[$detail->OriginalURL] = $files[$detail->Key];
247
+ }
248
+ }
249
+ } elseif($urls) {
250
+ $fileMappings = $pendingURLs;
251
+ }
252
+
253
+ if(getenv("SHORTPIXEL_DEBUG")) {
254
+ $info .= "FILE MAPPINGS\n";
255
+ foreach($fileMappings as $key => $val) {
256
+ $info .= "$key -> $val\n";
257
+ }
258
+ }
259
+
260
+ if ($status >= 200 && $status <= 299) {
261
+ return array("body" => $details, "headers" => $headers, "fileMappings" => $fileMappings);
262
+ }
263
+
264
+ throw Exception::create($details->message, $details->error, $status);
265
+ }
266
+
267
+ protected function sendRequest($request, $tries) {
268
+ for($i = 0; $i < $tries; $i++) { //curl_setopt($request, CURLOPT_TIMEOUT, 120);curl_setopt($request, CURLOPT_VERBOSE, true);
269
+ $response = curl_exec($request);
270
+ $this->logger->log(SPLog::PRODUCER_CLIENT, "RAW RESPONSE: " . $response);
271
+ if(!curl_errno($request)) {
272
+ break;
273
+ } else {
274
+ ShortPixel::log("CURL ERROR: " . curl_error($request) . " (BODY: $response)");
275
+ }
276
+ }
277
+
278
+ if(curl_errno($request)) {
279
+ throw new ConnectionException("Error while connecting: " . curl_error($request) . "");
280
+ }
281
+ if (!is_string($response)) {
282
+ $message = sprintf("%s (#%d)", curl_error($request), curl_errno($request));
283
+ curl_close($request);
284
+ throw new ConnectionException("Error while connecting: " . $message);
285
+ }
286
+
287
+ $status = curl_getinfo($request, CURLINFO_HTTP_CODE);
288
+ $headerSize = curl_getinfo($request, CURLINFO_HEADER_SIZE);
289
+ curl_close($request);
290
+
291
+ $headers = self::parseHeaders(substr($response, 0, $headerSize));
292
+ $body = substr($response, $headerSize);
293
+
294
+ $details = json_decode($body);
295
+
296
+ if (!$details) {
297
+ $message = sprintf("Error while parsing response (Status: %s): %s (#%d)", $status,
298
+ PHP_VERSION_ID >= 50500 ? json_last_error_msg() : "Error",
299
+ json_last_error());
300
+ $details = (object) array(
301
+ "raw" => $body,
302
+ "error" => "ParseError",
303
+ "message" => $message . "( " . $body . ")",
304
+ "Status" => (object)array("Code" => -1, "Message" => "ParseError: " . $message)
305
+ );
306
+ ShortPixel::log("JSON Error while parsing response: " . json_encode($details));
307
+ }
308
+ return array($details, $headers, $status, $response);
309
+ }
310
+
311
+ protected function prepareJSONRequest($endpoint, $request, $body, $method, $header) {
312
+ //to escape the + from "+webp"
313
+ if(isset($body["convertto"]) && $body["convertto"]) {
314
+ $converts = explode('|', $body["convertto"]);
315
+ $body["convertto"] = implode('|', array_map('urlencode', $converts ));
316
+ }
317
+ // if(isset($body["urllist"])) {
318
+ // aici folosim ceva de genul: parse_url si apoi pe partea de path: str_replace('%2F', '/', rawurlencode($this->filePath)
319
+ // $body["urllist"] = array_map('rawurlencode', $body["urllist"]);
320
+ // }
321
+ $body = json_encode($body);
322
+
323
+ array_push($header, "Content-Type: application/json");
324
+ curl_setopt($request, CURLOPT_URL, $endpoint);
325
+ curl_setopt($request, CURLOPT_CUSTOMREQUEST, strtoupper($method));
326
+ curl_setopt($request, CURLOPT_HTTPHEADER, $header);
327
+ if ($body) {
328
+ curl_setopt($request, CURLOPT_POSTFIELDS, $body);
329
+ }
330
+ }
331
+
332
+
333
+ protected function prepareMultiPartRequest($request, $body, $header) {
334
+ $files = array();
335
+ $fileCount = 1;
336
+ //to escape the + from "+webp"
337
+ if($body["convertto"]) {
338
+ $body["convertto"] = urlencode($body["convertto"]);
339
+ }
340
+ if(isset($body["files"])) {
341
+ foreach($body["files"] as $filePath) {
342
+ $files["file" . $fileCount] = $filePath;
343
+ $fileCount++;
344
+ }
345
+ }
346
+ $buffers = array();
347
+ if(isset($body["buffers"])) {
348
+ foreach($body["buffers"] as $name => $contents) {
349
+ $files["file" . $fileCount] = $name;
350
+ $buffers["file" . $fileCount] = $contents;
351
+ $fileCount++;
352
+ }
353
+ unset($body["buffers"]);
354
+ }
355
+ $body["file_paths"] = json_encode($files);
356
+ unset($body["files"]);
357
+ curl_setopt($request, CURLOPT_URL, Client::API_UPLOAD_ENDPOINT());
358
+ $this->curl_custom_postfields($request, $body, $files, $header, $buffers);
359
+ return $files;
360
+ }
361
+
362
+ function curl_custom_postfields($ch, array $assoc = array(), array $files = array(), $header = array(), $buffers = array()) {
363
+
364
+ // invalid characters for "name" and "filename"
365
+ static $disallow = array("\0", "\"", "\r", "\n");
366
+
367
+ // build normal parameters
368
+ foreach ($assoc as $k => $v) {
369
+ $k = str_replace($disallow, "_", $k);
370
+ $body[] = implode("\r\n", array(
371
+ "Content-Disposition: form-data; name=\"{$k}\"",
372
+ "",
373
+ filter_var($v),
374
+ ));
375
+ }
376
+
377
+ // build file parameters
378
+ $fileContents = array();
379
+ foreach ($files as $k => $v) {
380
+ switch (true) {
381
+ case true === $v = realpath(filter_var($v)):
382
+ case is_file($v):
383
+ case is_readable($v):
384
+ $fileContents[$k] = file_get_contents($v);
385
+ // continue; // or return false, throw new InvalidArgumentException
386
+ }
387
+ }
388
+ $fileContents = array_merge($fileContents, $buffers);
389
+
390
+ foreach ($fileContents as $k => $data) {
391
+ $pp = explode(DIRECTORY_SEPARATOR, $files[$k]);
392
+ $v = end($pp);
393
+ $k = str_replace($disallow, "_", $k);
394
+ $v = str_replace($disallow, "_", $v);
395
+ $body[] = implode("\r\n", array(
396
+ "Content-Disposition: form-data; name=\"{$k}\"; filename=\"{$v}\"",
397
+ "Content-Type: application/octet-stream",
398
+ "",
399
+ $data,
400
+ ));
401
+ }
402
+
403
+ // generate safe boundary
404
+ do {
405
+ $boundary = "---------------------" . md5(mt_rand() . microtime());
406
+ } while (preg_grep("/{$boundary}/", $body));
407
+
408
+ // add boundary for each parameters
409
+ array_walk($body, function (&$part) use ($boundary) {
410
+ $part = "--{$boundary}\r\n{$part}";
411
+ });
412
+
413
+ // add final boundary
414
+ $body[] = "--{$boundary}--";
415
+ $body[] = "";
416
+
417
+ // set options
418
+ return @curl_setopt_array($ch, array(
419
+ CURLOPT_POST => true,
420
+ CURLOPT_BINARYTRANSFER => true,
421
+ CURLOPT_RETURNTRANSFER => true,
422
+ CURLOPT_TIMEOUT => 300, //to be able to handle via post large files up to 48M which might take a long time to upload.
423
+ CURLOPT_POSTFIELDS => implode("\r\n", $body),
424
+ CURLOPT_HTTPHEADER => array_merge(array(
425
+ "Expect: 100-continue",
426
+ "Content-Type: multipart/form-data; boundary={$boundary}", // change Content-Type
427
+ ), $header),
428
+ ));
429
+ }
430
+
431
+ protected static function parseHeaders($headers) {
432
+ if (!is_array($headers)) {
433
+ $headers = explode("\r\n", $headers);
434
+ }
435
+
436
+ $res = array();
437
+ foreach ($headers as $header) {
438
+ if (empty($header)) continue;
439
+ $split = explode(":", $header, 2);
440
+ if (count($split) === 2) {
441
+ $res[strtolower($split[0])] = trim($split[1]);
442
+ }
443
+ }
444
+ return $res;
445
+ }
446
+
447
+ function download($sourceURL, $target, $expectedSize = false) {
448
+ $targetTemp = substr($target, 0, 245) . ".sptemp";
449
+ $fp = @fopen ($targetTemp, 'w+'); // open file handle
450
+ if(!$fp) {
451
+ //file cannot be opened, probably no rights or path disappeared
452
+ if(!is_dir(dirname($target))) {
453
+ throw new ClientException("The file path cannot be found.", -15);
454
+ } else {
455
+ throw new ClientException("Temp file cannot be created inside " . dirname($targetTemp) . ". Please check rights.", -16);
456
+ }
457
+ }
458
+
459
+ $ch = curl_init($sourceURL);
460
+ // curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // enable if you want
461
+ curl_setopt_array($ch, $this->customOptions);
462
+ curl_setopt($ch, CURLOPT_FILE, $fp); // output to file
463
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); //previously 1. Changed it because it conflicts with some clients open_basedir (php.ini) settings (https://secure.helpscout.net/conversation/859529984/16086?folderId=1117588)
464
+ curl_setopt($ch, CURLOPT_TIMEOUT, 10000); // some large value to allow curl to run for a long time
465
+ curl_setopt($ch, CURLOPT_USERAGENT, $this->options[CURLOPT_USERAGENT]);
466
+ // curl_setopt($ch, CURLOPT_VERBOSE, true); // Enable this line to see debug prints
467
+ curl_exec($ch);
468
+
469
+ curl_close($ch); // closing curl handle
470
+ fclose($fp); // closing file handle
471
+ $actualSize = filesize($targetTemp);
472
+ if(!$expectedSize || $expectedSize == $actualSize) {
473
+ if(!@rename($targetTemp, $target)) {
474
+ @unlink($targetTemp);
475
+ throw new ClientException("File cannot be renamed. Please check rights.", -16);
476
+ }
477
+ } else {
478
+ // ATENTIE!!!!! daca s-a oprit aici e un caz de fisier cu dimensiunea diferita, de verificat
479
+ @unlink($targetTemp);
480
+ return -$actualSize; //will retry
481
+ }
482
+ return true;
483
+ }
484
+
485
+ function apiStatus($key, $domainToCheck = false, $imgCount = 0, $thumbsCount = 0) {
486
+ $request = curl_init();
487
+ curl_setopt_array($request, $this->options);
488
+ //$this->prepareJSONRequest(self::API_STATUS_ENDPOINT(), $request, array('key' => $key), 'post', array());
489
+ curl_setopt($request, CURLOPT_URL, self::API_STATUS_ENDPOINT());
490
+ curl_setopt($request, CURLOPT_CUSTOMREQUEST, 'POST');
491
+ curl_setopt($request, CURLOPT_HTTPHEADER, array());
492
+ $params = array('key' => $key);
493
+ if($domainToCheck) {
494
+ $params['DomainCheck'] = $domainToCheck;
495
+ $params['ImagesCount'] = $imgCount;
496
+ $params['ThumbsCount'] = $thumbsCount;
497
+
498
+ }
499
+ curl_setopt($request, CURLOPT_POSTFIELDS, $params);
500
+ return $this->sendRequest($request, 1);
501
+ }
502
+
503
+ /**
504
+ * Method that checks the status of an image being optimized
505
+ * @param $key
506
+ * @param $url
507
+ * @return array
508
+ * @throws ConnectionException
509
+ */
510
+ function imageStatus($key, $url) {
511
+ $request = curl_init();
512
+ curl_setopt_array($request, $this->options);
513
+ //$this->prepareJSONRequest(self::API_STATUS_ENDPOINT(), $request, array('key' => $key), 'post', array());
514
+ curl_setopt($request, CURLOPT_URL, self::IMAGE_STATUS_ENDPOINT());
515
+ curl_setopt($request, CURLOPT_CUSTOMREQUEST, 'POST');
516
+ curl_setopt($request, CURLOPT_HTTPHEADER, array());
517
+ $params = array('key' => $key, 'url' => $url);
518
+ curl_setopt($request, CURLOPT_POSTFIELDS, json_encode($params));
519
+ return $this->sendRequest($request, 1);
520
+ }
521
+
522
+ /**
523
+ * method that dumps the image from the optimization queue so the optimized version isn't available any more.
524
+ * Useful when you MIGHT need to optimize another image with the same URL - but with different contents - in the next
525
+ * hour and you don't want to have to keep a status to tell you if you need to use refresh() or not...
526
+ * @param $key
527
+ * @param $urllist
528
+ * @return array
529
+ * @throws ConnectionException
530
+ */
531
+ function imageCleanup($key, $urllist) {
532
+ $request = curl_init();
533
+ curl_setopt_array($request, $this->options);
534
+ //$this->prepareJSONRequest(self::API_STATUS_ENDPOINT(), $request, array('key' => $key), 'post', array());
535
+ curl_setopt($request, CURLOPT_URL, self::CLEANUP_ENDPOINT());
536
+ curl_setopt($request, CURLOPT_CUSTOMREQUEST, 'POST');
537
+ curl_setopt($request, CURLOPT_HTTPHEADER, array());
538
+ $params = array('key' => $key, 'urllist' => $urllist);
539
+ curl_setopt($request, CURLOPT_POSTFIELDS, json_encode($params));
540
+ return $this->sendRequest($request, 1);
541
+ }
542
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/Commander.php ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * User: simon
4
+ * Date: 04.04.2016
5
+ * Time: 14:01
6
+ */
7
+
8
+ namespace ShortPixel;
9
+
10
+ /**
11
+ * Class Commander - handles optimization commands such as lossless/lossy, resize, wait, notify_me etc.
12
+ * @package ShortPixel
13
+ */
14
+ class Commander {
15
+ private $data, $source, $commands, $logger;
16
+
17
+ public function __construct($data, Source $source) {
18
+ $this->source = $source;
19
+ $this->data = $data;
20
+ $this->logger = SPLog::Get(SPLog::PRODUCER_CTRL);
21
+ //$options = ShortPixel::options();
22
+ $this->commands = array();//('lossy' => 0 + $options["lossy"]);
23
+ if(isset($data['refresh']) && $data['refresh']) {
24
+ $this->refresh();
25
+ }
26
+ }
27
+
28
+ /**
29
+ * @param int $type 1 - lossy (default), 2 - glossy, 0 - lossless
30
+ * @return $this
31
+ */
32
+ public function optimize($type = 1) {
33
+ $this->commands = array_merge($this->commands, array("lossy" => $type));
34
+ return $this;
35
+ }
36
+
37
+ /**
38
+ * resize the image - performs an outer resize (meaning the image will preserve aspect ratio and have the smallest sizes that allow a rectangle with given width and height to fit inside the resized image)
39
+ * @param $width
40
+ * @param $height
41
+ * @param bool $inner - default, false, true to resize to maximum width or height (both smaller or equal)
42
+ * @return $this
43
+ */
44
+ public function resize($width, $height, $inner = false) {
45
+ $this->commands = array_merge($this->commands, array("resize" => ($inner ? ShortPixel::RESIZE_INNER : ShortPixel::RESIZE_OUTER), "resize_width" => $width, "resize_height" => $height));
46
+ return $this;
47
+ }
48
+
49
+ /**
50
+ * @param bool|true $keep
51
+ * @return $this
52
+ */
53
+ public function keepExif($keep = true) {
54
+ $this->commands = array_merge($this->commands, array("keep_exif" => $keep ? 1 : 0));
55
+ return $this;
56
+ }
57
+
58
+
59
+
60
+ /**
61
+ * @param bool|true $generate - default true, meaning generates WebP.
62
+ * @return $this
63
+ */
64
+ public function generateWebP($generate = true) {
65
+ $convertto = isset($this->commands['convertto']) ? explode('|', $this->commands['convertto']) : array();
66
+ $convertto[] = '+webp';
67
+ $this->commands = array_merge($this->commands, array("convertto" => implode('|', array_unique($convertto))));
68
+ return $this;
69
+ }
70
+
71
+ /**
72
+ * @param bool|true $generate - default true, meaning generates WebP.
73
+ * @return $this
74
+ */
75
+ public function generateAVIF($generate = true) {
76
+ $convertto = isset($this->commands['convertto']) ? explode('|', $this->commands['convertto']) : array();
77
+ $convertto[] = '+avif';
78
+ $this->commands = array_merge($this->commands, array("convertto" => implode('|', array_unique($convertto))));
79
+ return $this;
80
+ }
81
+
82
+ /**
83
+ * @param bool|true $refresh - if true, tells the server to discard the already optimized image and redo the optimization with the new settings.
84
+ * @return $this
85
+ */
86
+ public function refresh($refresh = true) {
87
+ $this->commands = array_merge($this->commands, array("refresh" => $refresh ? 1 : 0));
88
+ return $this;
89
+ }
90
+
91
+ /**
92
+ * will wait for the optimization to finish but not more than $seconds. The wait on the ShortPixel Server side can be a maximum of 30 seconds, for longer waits subsequent server requests will be sent.
93
+ * @param int $seconds
94
+ * @return $this
95
+ */
96
+ public function wait($seconds = 30) {
97
+ $seconds = max(0, intval($seconds));
98
+ $this->commands = array_merge($this->commands, array("wait" => min($seconds, 30), "total_wait" => $seconds));
99
+ return $this;
100
+ }
101
+
102
+ /**
103
+ * Not yet implemented
104
+ * @param $callbackURL the full url of the notify.php script that handles the notification postback
105
+ * @return mixed
106
+ */
107
+ public function notifyMe($callbackURL) {
108
+ throw new ClientException("NotifyMe not yet implemented");
109
+ $this->commands = array_merge($this->commands, array("notify_me" => $callbackURL));
110
+ return $this->execute();
111
+ }
112
+
113
+ /**
114
+ * call forwarder to Result - when a command is not understood by the Commander it could be a Result method like toFiles or toBuffer
115
+ * @param $method
116
+ * @param $args
117
+ * @return mixed
118
+ * @throws ClientException
119
+ */
120
+ public function __call($method, $args) {
121
+ if (method_exists("ShortPixel\Result", $method)) {
122
+ //execute the commands and forward to Result
123
+ if(isset($this->data["files"]) && !count($this->data["files"]) ||
124
+ isset($this->data["urllist"]) && !count($this->data["urllist"]) ||
125
+ isset($this->data["buffers"]) && !count($this->data["buffers"])) {
126
+ //empty data - no files, no need to send anything, just return an empty result
127
+ return (object) array(
128
+ 'status' => array('code' => 2, 'message' => 'success'),
129
+ 'succeeded' => array(),
130
+ 'pending' => array(),
131
+ 'failed' => array(),
132
+ 'same' => array());
133
+ }
134
+ for($i = 0; $i < 6; $i++) {
135
+ $return = $this->execute(true);
136
+ $this->logger->log(SPLog::PRODUCER_CTRL, "EXECUTE RETURNED: ", $return);
137
+ if(!isset($return->body->Status->Code) || !in_array($return->body->Status->Code, array(-305, -404, -500))) {
138
+ break;
139
+ }
140
+ // error -404: The maximum number of URLs in the optimization queue reached, wait a bit and retry.
141
+ // error -500: maintenance mode
142
+ sleep((10 + 3 * $i) * ($return->body->Status->Code == -500 ? 6 : 1)); //sleep six times longer if maintenance mode. This gives about 15 minutes in total, then it will throw exception.
143
+ }
144
+
145
+ if(isset($return->body->Status->Code) && $return->body->Status->Code < 0) {
146
+ ShortPixel::log("ERROR THROWN: " . $return->body->Status->Message . (isset($return->body->raw) ? "(Server sent: " . substr($return->body->raw, 0, 200) . "...)" : "") . " CODE: " . $return->body->Status->Code);
147
+ throw new AccountException($return->body->Status->Message . (isset($return->body->raw) ? "(Server sent: " . substr($return->body->raw, 0, 200) . "...)" : ""), $return->body->Status->Code);
148
+ }
149
+ return call_user_func_array(array(new Result($this, $return), $method), $args);
150
+ }
151
+ else {
152
+ throw new ClientException('Unknown function '.__CLASS__.':'.$method, E_USER_ERROR);
153
+ }
154
+ }
155
+
156
+ /**
157
+ * @internal
158
+ * @param bool|false $wait
159
+ * @return mixed
160
+ * @throws AccountException
161
+ */
162
+ public function execute($wait = false){
163
+ if($wait && !isset($this->commands['wait'])) {
164
+ $this->commands = array_merge($this->commands, array("wait" => ShortPixel::opt("wait"), "total_wait" => ShortPixel::opt("total_wait")));
165
+ }
166
+ ShortPixel::log("EXECUTE OPTIONS: " . json_encode(ShortPixel::options()) . " COMMANDS: " . json_encode($this->commands) . " DATA: " . json_encode($this->data));
167
+ return ShortPixel::getClient()->request("post", array_merge(ShortPixel::options(), $this->commands, $this->data));
168
+ }
169
+
170
+ /**
171
+ * @internal
172
+ * @param $pending
173
+ * @return bool|mixed
174
+ * @throws ClientException
175
+ */
176
+ public function relaunch($ctx) {
177
+ ShortPixel::log("RELAUNCH CTX: " . json_encode($ctx) . " COMMANDS: " . json_encode($this->commands) . " DATA: " . json_encode($this->data));
178
+ if(!count($ctx->body) &&
179
+ (isset($this->data["files"]) && !count($this->data["files"]) ||
180
+ isset($this->data["urllist"]) && !count($this->data["urllist"]))) return false; //nothing to do
181
+
182
+ //decrease the total wait and exit while if time expired
183
+ $this->commands["total_wait"] = max(0, $this->commands["total_wait"] - min($this->commands["wait"], 30));
184
+ if($this->commands['total_wait'] == 0) return false;
185
+
186
+ $pendingURLs = array();
187
+ //currently we relaunch only if we have the URLs that for posted files should be returned in the first pass.
188
+ $type = isset($ctx->body[0]->OriginalURL) ? 'URL' : 'FILE';
189
+ foreach($ctx->body as $pend) {
190
+ if($type == 'URL') {
191
+ if($pend->OriginalURL && !in_array($pend->OriginalURL, $pendingURLs)) {
192
+ $pendingURLs[$pend->OriginalURL] = $pend->OriginalFile;
193
+ }
194
+ } else {
195
+ //for now
196
+ throw new ClientException("Not implemented (Commander->execute())");
197
+ }
198
+ }
199
+ $this->commands["refresh"] = 0;
200
+ if($type == 'URL' && count($pendingURLs)) {
201
+ $this->data["pendingURLs"] = $pendingURLs;
202
+ //$this->data["fileMappings"] = $ctx->fileMappings;
203
+ }
204
+ return $this->execute();
205
+
206
+ }
207
+
208
+ public function getCommands() {
209
+ return $this->commands;
210
+ }
211
+
212
+ public function getData() {
213
+ return $this->data;
214
+ }
215
+
216
+ /* public function setCommand($key, $value) {
217
+ return $this->commands[$key] = $value;
218
+ }
219
+ */
220
+
221
+ public function isDone($item) {
222
+ //remove from local files list
223
+ if(isset($this->data["files"]) && is_array($this->data["files"])) {
224
+ if (isset($item->OriginalFile)) {
225
+ $this->data["files"] = array_diff($this->data["files"], array($item->OriginalFile));
226
+ }
227
+ elseif (isset($item->SavedFile)) {
228
+ $this->data["files"] = array_diff($this->data["files"], array($item->SavedFile));
229
+ }
230
+ elseif(isset($item->OriginalURL) && isset($this->data["pendingURLs"][$item->OriginalURL])) {
231
+ $this->data["files"] = array_diff($this->data["files"], array($this->data["pendingURLs"][$item->OriginalURL]));
232
+ }
233
+ }
234
+ //remove from pending URLs
235
+ if(isset($item->OriginalURL)) {
236
+ if(isset($this->data["pendingURLs"][$item->OriginalURL])) {
237
+ unset($this->data["pendingURLs"][$item->OriginalURL]);
238
+ }
239
+ elseif(isset($this->data["urllist"]) && in_array($item->OriginalURL, $this->data["urllist"])) {
240
+ $this->data["urllist"] = array_values(array_diff($this->data["urllist"], array($item->OriginalURL)));
241
+ }
242
+ }
243
+ }
244
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/Exception.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ShortPixel;
4
+
5
+ class Exception extends \Exception {
6
+ public static function create($message, $type, $status) {
7
+ if ($status == 401 || $status == 429) {
8
+ $klass = "ShortPixel\AccountException";
9
+ } else if($status >= 400 && $status <= 499) {
10
+ $klass = "ShortPixel\ClientException";
11
+ } else if($status >= 500 && $status <= 599) {
12
+ $klass = "ShortPixel\ServerException";
13
+ } else {
14
+ $klass = "ShortPixel\Exception";
15
+ }
16
+
17
+ if (empty($message)) $message = "No message was provided";
18
+ return new $klass($type . ": " . $message, $status);
19
+ }
20
+
21
+ function __construct($message, $code = 0, $parent = NULL, $type = NULL, $status = NULL) {
22
+ if ($status) {
23
+ parent::__construct($message . " (HTTP " . $status . "/" . $type . ")", $code, $parent);
24
+ } else {
25
+ parent::__construct($message, $code, $parent);
26
+ }
27
+ }
28
+ }
29
+
30
+ class AccountException extends Exception {}
31
+ class ClientException extends Exception {
32
+ const NO_FILE_FOUND = -1;
33
+ }
34
+ class ServerException extends Exception {}
35
+ class ConnectionException extends Exception {}
36
+ class PersistException extends Exception {}
37
+
vendor/shortpixel/shortpixel-php/lib/ShortPixel/Lock.php ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * User: simon
4
+ * Date: 21.12.2017
5
+ * Time: 23:41
6
+ */
7
+ namespace ShortPixel;
8
+
9
+ class Lock {
10
+ const FOLDER_LOCK_FILE = '.sp-lock';
11
+
12
+ private $processId, $targetFolder, $clearLock, $releaseTo, $timeout;
13
+
14
+ /**
15
+ * @param $processId
16
+ * @param $targetFolder
17
+ * @param bool|false $clearLock
18
+ * @param string|false $releaseTo a string that if found in the lock file, will make lock() release the lock instead of updating it
19
+ * - use together with requestLock to pass the lock between concurent processes with different priority (call requestLock with the same value as $requester on the script that should take the lock).
20
+ */
21
+ function __construct($processId, $targetFolder, $clearLock = false, $releaseTo = false, $timeout = 360) {
22
+ $this->processId = $processId;
23
+ $this->targetFolder = $targetFolder;
24
+ $this->clearLock = $clearLock;
25
+ $this->releaseTo = $releaseTo;
26
+ $this->timeout = $timeout;
27
+ $this->logger = SPLog::Get(SPLog::PRODUCER_PERSISTER);
28
+ }
29
+
30
+ function setTimeout($timeout) {
31
+ $this->timeout = $timeout;
32
+ }
33
+
34
+ function lockFile() {
35
+ return $this->targetFolder . '/' . self::FOLDER_LOCK_FILE;
36
+ }
37
+
38
+ function readLock() {
39
+ if(file_exists($this->lockFile())) {
40
+ $lock = file_get_contents($this->targetFolder . '/' . self::FOLDER_LOCK_FILE);
41
+ return explode("=", $lock);
42
+ }
43
+ return false;
44
+ }
45
+
46
+ function lock() {
47
+ //check if the folder is not locked by another ShortPixel process
48
+ if(!$this->clearLock && ($lock = $this->readLock()) !== false) {
49
+ $time = explode('!', $lock[1]);
50
+ if(count($lock) >= 2 && $lock[0] != $this->processId && $time[0] > time() - (isset($time[1]) ? $time[1] : $this->timeout)) {
51
+ //a lock was placed on the file and it's not yet expired as per its set timeout
52
+ throw new \Exception($this->getLockMsg($lock, $this->targetFolder), -19);
53
+ }
54
+ elseif(count($lock) >= 4 && $lock[2] == $this->releaseTo) {
55
+ // a request to release the lock was received
56
+ unlink($this->lockFile());
57
+ throw new \Exception("A lock release was requested by " . $this->releaseTo, -20);
58
+ }
59
+ }
60
+ if(FALSE === @file_put_contents($this->lockFile(), $this->processId . "=" . time() . '!' . $this->timeout . (strlen($this->releaseTo) ? "=" . $this->releaseTo : ''))) {
61
+ throw new ClientException("Could not write lock file " . $this->lockFile() . ". Please check rights.", -16);
62
+ }
63
+ $this->logger->log(SPLog::PRODUCER_PERSISTER, "{$this->processId} locked " . dirname($this->lockFile()) . " for {$this->timeout} sec.");
64
+ }
65
+
66
+ function requestLock($requester) {
67
+ if(($lock = $this->readLock()) !== false) {
68
+ if(isset($lock[2]) && $lock[2] == $requester) {
69
+ //the script that locked the folder will accept a request from $requester to give the lock
70
+ //mark in the lock a request to release it
71
+ if(FALSE === @file_put_contents($this->lockFile(), $lock[0] . "=" . $lock[1] . "=" . $requester . "=true")) {
72
+ throw new ClientException("Could not update lock file " . $this->lockFile() . ". Please check rights.", -16);
73
+ }
74
+ } else {
75
+ //the script will not accept a request from $requester, maybe the lock is old?
76
+ $this->lock();
77
+ return;
78
+ }
79
+ }
80
+ //now wait for the other process to release the lock, a bit more than its expiry time - in case it was left there...
81
+ $expiry = max(1, 365 - (time() - $lock[1]));
82
+ for($i = 0; $i < $expiry; $i++) {
83
+ if(file_exists($this->lockFile())) {
84
+ sleep(1);
85
+ } else {
86
+ break;
87
+ }
88
+ }
89
+ $this->lock();
90
+ }
91
+
92
+ function unlock() {
93
+ if(($lock = $this->readLock()) !== false) {
94
+ if($lock[0] == $this->processId) {
95
+ unlink($this->lockFile());
96
+ }
97
+ }
98
+ }
99
+
100
+ function getLockMsg($lock, $folder) {
101
+ return SPLog::format("The folder is locked by a different ShortPixel process ({$lock[0]}). Exiting. \n\n\033[31mIf you're SURE no other ShortPixel process is running, you can remove the lock with \n\n >\033[34m rm " . $folder . '/' . self::FOLDER_LOCK_FILE . " \033[0m \n");
102
+ }
103
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/Persister.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * User: simon
4
+ * Date: 19.08.2016
5
+ * Time: 18:01
6
+ */
7
+ namespace ShortPixel;
8
+
9
+ interface Persister {
10
+
11
+ static function IGNORED_BY_DEFAULT();
12
+ function __construct($options);
13
+ function isOptimized($path);
14
+ function getOptimizationData($path);
15
+ function info($path, $recurse = true, $fileList = false, $exclude = array(), $persistPath = false);
16
+ function getTodo($path, $count, $exclude = array(), $persistFolder = false, $maxTotalFileSize = false, $recurseDepth = PHP_INT_MAX);
17
+ function getNextTodo($path, $count);
18
+ function doneGet();
19
+ function setPending($path, $optData);
20
+ function setOptimized($path, $optData);
21
+ function setFailed($path, $optData);
22
+ function setSkipped($path, $optData);
23
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/Result.php ADDED
@@ -0,0 +1,444 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ShortPixel;
4
+ use ShortPixel\notify\ProgressNotifier;
5
+
6
+ /**
7
+ * Class Result - handles the result of the optimization (saves to file or returns a buffer, etc)
8
+ * @package ShortPixel
9
+ */
10
+ class Result {
11
+ protected $commander, $ctx, $logger;
12
+
13
+ public function __construct($commander, $context) {
14
+ $this->commander = $commander;
15
+ $this->ctx = $context;
16
+ $this->logger = SPLog::Get(SPLog::PRODUCER_RESULT);
17
+ }
18
+
19
+ /**
20
+ * returns the metadata provided by the optimizer
21
+ * @return mixed
22
+ */
23
+ public function ctx() {
24
+ return $this->ctx;
25
+ }
26
+
27
+ public function toBuffer() {
28
+ return $this->ctx;
29
+ }
30
+
31
+ /**
32
+ * @param null $path - path to save the file to
33
+ * @param null $fileName - filename of the saved file. If it's an array, then one entry for each optimized file, in the same order.
34
+ * @param null $bkPath - the path to save a backup of the original file
35
+ * @return object containig lists with succeeded, pending, failed and same items (same means the image did not need optimization)
36
+ * @throws AccountException
37
+ * @throws ClientException
38
+ */
39
+ public function toFiles($path = null, $fileName = null, $bkPath = null) {
40
+
41
+ // echo(" PATH: $path BkPath: $bkPath");
42
+ // spdbgd($this->ctx, 'context');
43
+ $thisDir = str_replace(DIRECTORY_SEPARATOR, '/', (getcwd() ? getcwd() : __DIR__));
44
+
45
+ if($path) {
46
+ if( (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && preg_match('/^[a-zA-Z]:\//', $path) === 0) //it's Windows and no drive letter X:
47
+ || (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' && substr($path, 0, 1) !== '/')) { //it's not Windows and doesn't start with a /
48
+ $path = (ShortPixel::opt("base_path") ?: $thisDir) . '/' . $path;
49
+ }
50
+ }
51
+ if(!$bkPath && ShortPixel::opt("backup_path")) {
52
+ $bkPath = ShortPixel::opt("backup_path");
53
+ }
54
+ if($bkPath && strpos($bkPath,'/') !== 0) { //it's a relative path
55
+ $bkPath = normalizePath(rtrim($path, '/') . '/' . $bkPath);
56
+ }
57
+ $i = 0;
58
+ $succeeded = $pending = $failed = $same = array();
59
+
60
+ $cmds = array_merge(ShortPixel::options(), $this->commander->getCommands());
61
+
62
+ while(true) {
63
+ $items = $this->ctx->body;
64
+ if(!is_array($items) || count($items) == 0) {
65
+ throw new AccountException("Result received no items to save!", -1);
66
+ // return (object)array( 'status' => array('code' => 2, 'message' => 'Folder completely optimized'));
67
+ }
68
+ //check API key errors
69
+ if(isset($items->Status->Code) && $items->Status->Code < 0) {
70
+ throw new AccountException($items->Status->Message, $items->Status->Code);
71
+ }
72
+ // No API level error
73
+ $retry = false;
74
+ foreach($items as $item) {
75
+
76
+ $targetPath = $path; $originalPath = $baseUrl = false;
77
+ $this->logger->log(SPLog::PRODUCER_RESULT, "RESULT toFiles while CTX:", $this->ctx->fileMappings);
78
+
79
+ if($this->ctx->fileMappings && count($this->ctx->fileMappings)) { // it was optimized from a local file, fileMappings contains the mappings from the local files to the internal ShortPixel URLs
80
+ $originalPath = isset($this->ctx->fileMappings[$item->OriginalURL]) ? $this->ctx->fileMappings[$item->OriginalURL] : false;
81
+ //
82
+ $this->logger->log(SPLog::PRODUCER_RESULT, "ORIGINAL PATH:" . $originalPath);
83
+ if(ShortPixel::opt("base_source_path") && $originalPath) {
84
+ $origPathParts = explode('/', str_replace(ShortPixel::opt("base_source_path"). "/", "", $originalPath));
85
+ $origFileName = $origPathParts[count($origPathParts) - 1];
86
+ unset($origPathParts[count($origPathParts) - 1]);
87
+ $relativePath = implode('/', $origPathParts);
88
+ } elseif($originalPath) {
89
+ $origPathParts = explode('/', $originalPath);
90
+ $origFileName = $origPathParts[count($origPathParts) - 1];
91
+ $relativePath = "";
92
+ } elseif(isset($item->OriginalFileName)) {
93
+ $origFileName = $item->OriginalFileName;
94
+ $relativePath = "";
95
+ } else {
96
+ throw new ClientException("Cannot determine a filename to save to." . $item->OriginalURL . " CTX: " . json_encode($this->ctx->fileMappings));
97
+ }
98
+ } elseif(isset($item->OriginalURL)) { // it was optimized from a URL
99
+ $baseUrl = ShortPixel::opt("base_url");
100
+ if($baseUrl && strlen($baseUrl)) {
101
+ $origURLParts = explode('/', trim(rawurldecode(str_replace($baseUrl, "", $item->OriginalURL)), '/'));
102
+ $origFileName = $origURLParts[count($origURLParts) - 1];
103
+ unset($origURLParts[count($origURLParts) - 1]);
104
+ $relativePath = implode('/', $origURLParts);
105
+ } else {
106
+ $origURLParts = explode('/', $item->OriginalURL);
107
+ $origFileName = $origURLParts[count($origURLParts) - 1];
108
+ $relativePath = "";
109
+ }
110
+ $originalPath = ShortPixel::opt("base_source_path") . '/' . (strlen($relativePath) ? $relativePath . '/' : '') . $origFileName;
111
+ } else { // something is wrong
112
+ throw(new ClientException("Malformed response. Please contact support."));
113
+ }
114
+ if(!$targetPath) { //se pare ca trebuie oricum
115
+ $targetPath = (ShortPixel::opt("base_path") ?: $thisDir) . '/' . $relativePath;
116
+ } elseif(ShortPixel::opt("base_source_path") && strlen($relativePath)) {
117
+ $targetPath .= '/' . $relativePath;
118
+ }
119
+
120
+ if($fileName) {
121
+ if(is_array($fileName)) {
122
+ if(!isset($fileName[$i])) {
123
+ throw new ClientException('Names array contains less names than the files sent to optimization');
124
+ }
125
+ $fn = $fileName[$i];
126
+ } else {
127
+ if($i > 0) {
128
+ $dotExt = '.' . pathinfo($fileName, PATHINFO_EXTENSION);
129
+ $fn = basename($fileName, $dotExt) . "_" . $i . $dotExt;
130
+ } else {
131
+ $fn = $fileName;
132
+ }
133
+ }
134
+ } else {
135
+ $fn = $origFileName;
136
+ }
137
+ $target = $targetPath . '/' . $fn;
138
+
139
+ if($originalPath) {
140
+ $item->OriginalFile = $originalPath;
141
+ if(!mb_detect_encoding($originalPath, 'UTF-8', true)) { $item->OriginalFileUTF8 = utf8_encode($originalPath); }
142
+ }
143
+ $item->SavedFile = $target;
144
+ if(!mb_detect_encoding($target, 'UTF-8', true)) { $item->SavedFileUTF8 = utf8_encode($target); }
145
+
146
+ //TODO: that one is a hack until the API waiting bug is fixed. Afterwards, just throw an exception
147
+ if( $item->Status->Code == 2
148
+ && ( $item->LossySize == 0 && $item->LoselessSize == 0
149
+ || $item->WebPLossyURL != 'NA' && ($item->WebPLossySize == 'NA' || !$item->WebPLossySize )
150
+ || $item->WebPLosslessURL != 'NA' && ($item->WebPLosslessSize == 'NA' || !$item->WebPLosslessSize)
151
+ || $item->AVIFLossyURL != 'NA' && ($item->AVIFLossySize == 'NA' || !$item->AVIFLossySize )
152
+ || $item->AVIFLosslessURL != 'NA' && ($item->AVIFLosslessSize == 'NA' || !$item->AVIFLosslessSize))) {
153
+ $item->Status->Code = 1;
154
+ }
155
+
156
+ if($item->Status->Code == 1) {
157
+ $found = $this->findItem($item, $pending, "OriginalURL");
158
+ if($found === false) {
159
+ $item->Retries = 1;
160
+ $pending[] = $item;
161
+ $this->persist($item, $cmds, 'pending');
162
+ } else {
163
+ $pending[$found]->Retries += 1;
164
+ }
165
+ $this->logger->log(SPLog::PRODUCER_RESULT, "PENDING, RETRIES:", $pending[$found]->Retries);
166
+ continue;
167
+ }
168
+ elseif ($item->Status->Code != 2) {
169
+ $this->removeItem($item, $pending, "OriginalURL");
170
+ if($item->Status->Code == -113) {
171
+ throw new AccountException("Too many inaccessible URLs from the same domain, please check accessibility and try again.", "-113");
172
+ }
173
+ if($item->Status->Code == -102 || $item->Status->Code == -106) {
174
+ // -102 is expired, means we need to resend the image through post
175
+ // -106 is file was not downloaded due to access restrictions - if these are uploaded files it looks like a bug in the API
176
+ // TODO find and fix
177
+ if(isset($this->ctx->fileMappings[$item->OriginalURL])) {
178
+ unset($this->ctx->fileMappings[$item->OriginalURL]);
179
+ }
180
+ $item->OriginalURL = false;
181
+ }
182
+
183
+ if($item->Status->Code == -201 || $item->Status->Code == -202) { //unrecoverable, no need to retry
184
+ $st = 'skip';
185
+ } else { //will persist as 'pending' if retries < MAX_RETRIES
186
+ $st = 'error';
187
+ }
188
+ $status = $this->persist($item, $cmds, $st);
189
+ if($status == 'pending') {
190
+ $retry = true;
191
+ } else {
192
+ $failed[] = $item;
193
+ $this->commander->isDone($item);
194
+ $this->removeItem($item, $pending, "OriginalURL");
195
+ }
196
+ $this->logger->log(SPLog::PRODUCER_RESULT, "FAILED WITH CODE:", $item->Status->Code);
197
+ continue;
198
+ }
199
+ //IF file is locally accessible, the source file size should be the same as the size downloaded by (or posted to) ShortPixel servers
200
+ elseif(clearstatcache(true, $originalPath) || file_exists($originalPath) && filesize($originalPath) != $item->OriginalSize) {
201
+ $item->Status->Code = -110;
202
+ $item->Status->Message = "Wrong original size. Expected (local source file): " . filesize($originalPath) . " downloaded by ShortPixel: " . $item->OriginalSize;
203
+ $status = $this->persist($item, $cmds, 'error');
204
+ if($status == 'pending') {
205
+ $retry = true;
206
+ } else {
207
+ $failed[] = $item;
208
+ $this->commander->isDone($item);
209
+ $this->removeItem($item, $pending, "OriginalURL");
210
+ }
211
+ $this->logger->log(SPLog::PRODUCER_RESULT, "ERROR:", $item->Status->Message);
212
+ continue;
213
+ }
214
+ elseif($item->PercentImprovement == 0) {
215
+ //sometimes the percent is 0 and the size is different (by some octets) so put the correct size in place
216
+ if(file_exists($originalPath)) {
217
+ clearstatcache(true, $originalPath);
218
+ if($cmds["lossy"] > 0) {
219
+ $item->LossySize = filesize($originalPath);
220
+ } else {
221
+ $item->LoselessSize = filesize($originalPath);
222
+ }
223
+ if(!file_exists($target)) {
224
+ //this is a case when the target is different from the original path, so just copy the file over
225
+ @copy($originalPath, $target);
226
+ }
227
+ }
228
+ $this->checkSaveWebP($item, $target, $cmds);
229
+ $this->checkSaveAVIF($item, $target, $cmds);
230
+ $same[] = $item;
231
+ $this->removeItem($item, $pending, "OriginalURL");
232
+ $this->persist($item, $cmds);
233
+ $this->commander->isDone($item);
234
+ $this->logger->log(SPLog::PRODUCER_RESULT, "IMPROVEMENT 0");
235
+ continue;
236
+ }
237
+
238
+ if(!is_dir($targetPath) && !@mkdir($targetPath, 0777, true)) { //create the folder
239
+ throw new ClientException("The destination path $targetPath cannot be found.");
240
+ }
241
+
242
+ //Now that's an optimized image indeed
243
+ try {
244
+ $bkCrtFilePath = false;
245
+ if($bkPath && $originalPath) {
246
+ $bkCrtPath = rtrim($bkPath, '/') . '/' . (strlen($relativePath) ? $relativePath . '/' : '');
247
+ if(!is_dir($bkCrtPath) && !@mkdir($bkCrtPath, 0777, true)) {
248
+ throw new Exception("Cannot create backup folder " . $bkCrtPath, -1);
249
+ }
250
+ $bkCrtFilePath = $bkCrtPath . MB_basename($originalPath);
251
+ if(!file_exists($bkCrtFilePath)) { //just make sure we're not overwriting the original in any case
252
+ if (!copy($originalPath, $bkCrtFilePath) && !file_exists($bkCrtFilePath)) {
253
+ throw new Exception("Cannot copy to backup folder " . $bkCrtPath, -1);
254
+ }
255
+ }
256
+ }
257
+
258
+ //try to figure out a backup URL
259
+ if($baseUrl) {
260
+ if($originalPath !== $targetPath) {
261
+ $item->BackupURL = $item->OriginalURL;
262
+ } elseif($bkCrtFilePath) {
263
+ $deltaPos = strspn($path ^ $bkPath, "\0");
264
+ $delta = substr($path, $deltaPos);
265
+ $bkDelta = substr($bkPath, $deltaPos);
266
+ if(strpos($baseUrl, $delta) > 0) {
267
+ //only in this case there can be a backup URL (the backup path is inside the web root)
268
+ $item->BackupURL = str_replace($delta, $bkDelta, $baseUrl) . '/' . (strlen($relativePath) ? $relativePath . '/' : '') . MB_basename($originalPath);
269
+ }
270
+ }
271
+ }
272
+
273
+ $optURL = $cmds["lossy"] > 0 ? $item->LossyURL : $item->LosslessURL; //this works also for glossy (2)
274
+ $optSize = $cmds["lossy"] > 0 ? $item->LossySize : $item->LoselessSize;
275
+
276
+ $downloadOK = ShortPixel::getClient()->download($optURL, $target, $optSize);
277
+ if($downloadOK <= 0) { // the size is wrong - probably in metadata, retry the image altogether
278
+ $found = $this->findItem($item, $pending, "OriginalURL");
279
+ if($found === false) {
280
+ $item->Status->Code = 1;
281
+ $item->Status->Message = "Pending";
282
+ $item->Retries = 1;
283
+ $pending[] = $item;
284
+ $this->persist($item, $cmds, 'pending');
285
+ } elseif($pending[$found]->Retries <= 3) {
286
+ $pending[$found]->Retries += 1;
287
+ } else {
288
+ $item->Status->Code = -1;
289
+ $item->Status->Message = "Wrong size, expected $optSize downloaded " . (-$downloadOK);
290
+ $failed[] = $item;
291
+ $this->commander->isDone($item);
292
+ $this->removeItem($item, $pending, "OriginalURL");
293
+ }
294
+ $this->logger->log(SPLog::PRODUCER_RESULT, "DOWNLOAD NOK? " . $downloadOK . " URL: " . $optURL, $item->Status->Message);
295
+ continue;
296
+ }
297
+ $this->checkSaveWebP($item, $target, $cmds);
298
+ $this->checkSaveAVIF($item, $target, $cmds);
299
+ }
300
+ catch(ClientException $e) {
301
+ $failed[] = $item;
302
+ $item->Status->Message = $e->getMessage();
303
+ $this->persist($item, $cmds, 'error');
304
+ $this->logger->log(SPLog::PRODUCER_RESULT, "CLIENT EXCEPTION:", $e->getMessage());
305
+ continue;
306
+ }
307
+
308
+ $succeeded[] = $item;
309
+ $this->persist($item, $cmds);
310
+
311
+ //remove from pending
312
+ $this->removeItem($item, $pending, "OriginalURL"); //TODO check if fromURL and if not, use file path
313
+ //tell the commander that the item is done so it won't be relaunched
314
+ $this->commander->isDone($item);
315
+ $i++;
316
+ }
317
+
318
+ //For the pending items relaunch, or if any item that needs to be retried from file (-102 or -106)
319
+ if($retry || count($pending)) {
320
+ if(!$item->OriginalURL) foreach($pending as $pend) {
321
+ if(!isset($this->ctx->fileMappings[$pend->OriginalURL])) {
322
+ $pend = false;
323
+ }
324
+ }
325
+ $this->logger->log(SPLog::PRODUCER_RESULT, "RELAUNCH");
326
+ $this->ctx = $this->commander->relaunch((object)array("body" => $pending, "headers" => $this->ctx->headers, "fileMappings" => $this->ctx->fileMappings));
327
+ } else {
328
+ break;
329
+ }
330
+ if($this->ctx == false) { //time's up
331
+ break;
332
+ }
333
+ }
334
+
335
+ $message = ShortPixel::opt('persist_type') == 'text' ? 'pending' : ($pending ? 'pending' : ($failed ? 'error' : 'success'));
336
+ $ret = (object) array(
337
+ 'status' => array(
338
+ 'code' => ($message == 'success' ? 2 : 1),
339
+ 'message' => $message),
340
+ 'succeeded' => $succeeded,
341
+ 'pending' => $pending,
342
+ 'failed' => $failed,
343
+ 'same' => $same
344
+ );
345
+ if($cmds['notify_progress']) {
346
+ //notify the progress to whom it may concern :)
347
+ $notifier = ProgressNotifier::constructNotifier($path);
348
+ $notifier->recordProgress($ret);
349
+ }
350
+
351
+ return $ret;
352
+ }
353
+
354
+ private function checkSaveWebP($item, $target, $cmds)
355
+ {
356
+ if (isset($item->WebPLossyURL) && $item->WebPLossyURL !== 'NA') { //a WebP image was generated as per the options, download and save it too
357
+ $webpTarget = $targetWebPFile = dirname($target) . '/' . MB_basename($target, '.' . pathinfo($target, PATHINFO_EXTENSION)) . ".webp";
358
+ $optWebPURL = $cmds["lossy"] > 0 ? $item->WebPLossyURL : $item->WebPLosslessURL;
359
+ ShortPixel::getClient()->download($optWebPURL, $webpTarget);
360
+ $item->WebPSavedFile = $webpTarget;
361
+ }
362
+ }
363
+
364
+ private function checkSaveAVIF($item, $target, $cmds)
365
+ {
366
+ if (isset($item->AVIFLossyURL) && $item->AVIFLossyURL !== 'NA') { //an AVIF image was generated as per the options, download and save it too
367
+ $avifTarget = $targetAVIFFile = dirname($target) . '/' . MB_basename($target, '.' . pathinfo($target, PATHINFO_EXTENSION)) . ".avif";
368
+ $optAVIFURL = $cmds["lossy"] > 0 ? $item->AVIFLossyURL : $item->AVIFLosslessURL;
369
+ ShortPixel::getClient()->download($optAVIFURL, $avifTarget);
370
+ $item->AVIFSavedFile = $avifTarget;
371
+ }
372
+ }
373
+
374
+ private function persist($item, $cmds, $status = 'success') {
375
+ $pers = ShortPixel::getPersister();
376
+ if($pers) {
377
+ $optParams = $this->optimizationParams($item, $cmds);
378
+ if($status == 'pending') {
379
+ $optParams['message'] = $item->OriginalURL;
380
+ return $pers->setPending($item->SavedFile, $optParams);
381
+ } elseif ($status == 'error') {
382
+ $optParams['message'] = $item->Status->Message;
383
+ return $pers->setFailed($item->SavedFile, $optParams);
384
+ } elseif ($status == 'skip') {
385
+ $optParams['message'] = $item->Status->Message;
386
+ return $pers->setSkipped($item->SavedFile, $optParams, 'skip');
387
+ } else {
388
+ return $pers->setOptimized($item->SavedFile, $optParams);
389
+ }
390
+ }
391
+ }
392
+
393
+ private function optimizationParams($item, $cmds) {
394
+ $optimizedSize = $item->Status->Code == 2 ? ($cmds["lossy"] > 0 ? $item->LossySize : $item->LoselessSize) : 0;
395
+
396
+ return array(
397
+ "compressionType" => $cmds["lossy"] == 1 ? 'lossy' : ($cmds["lossy"] == 2 ? 'glossy' : 'lossless'),
398
+ "keepExif" => isset($cmds['keep_exif']) ? $cmds['keep_exif'] : ShortPixel::opt("keep_exif"),
399
+ "cmyk2rgb" => isset($cmds['cmyk2rgb']) ? $cmds['cmyk2rgb'] : ShortPixel::opt("cmyk2rgb"),
400
+ "resize" => isset($cmds['resize'])
401
+ ? $cmds['resize']
402
+ : ((isset($cmds['resize_width']) && $cmds['resize_width'] > 0)
403
+ ? 1 : (ShortPixel::opt("resize_width") ? 1 : 0)),
404
+ "resizeWidth" => isset($cmds['resize_width']) ? $cmds['resize_width'] : ShortPixel::opt("resize_width"),
405
+ "resizeHeight" => isset($cmds['resize_height']) ? $cmds['resize_height'] : ShortPixel::opt("resize_height"),
406
+ "percent" => isset($item->PercentImprovement) ? number_format(100.0 - 100.0 * $optimizedSize / $item->OriginalSize, 2) : 0, //$item->PercentImprovement : 0,
407
+ "optimizedSize" => $optimizedSize,
408
+ "changeDate" => time(),
409
+ "message" => null //will be set by persist() if 'error'
410
+ );
411
+ }
412
+
413
+ /**
414
+ * finds if an array contains an item, comparing the property given as key
415
+ * @param $item
416
+ * @param $arr
417
+ * @param $key
418
+ * @return the position that was removed, false if not found
419
+ */
420
+ private function findItem($item, $arr, $key) {
421
+ for($j = 0; $j < count($arr); $j++) {
422
+ if($arr[$j]->$key == $item->$key) {
423
+ return $j;
424
+ }
425
+ }
426
+ return false;
427
+ }
428
+
429
+ /**
430
+ * removes the item if found in array (with findItem)
431
+ * @param $item
432
+ * @param $arr
433
+ * @param $key
434
+ * @return true if removed, false if not found
435
+ */
436
+ private function removeItem($item, &$arr, $key) {
437
+ $j = $this->findItem($item, $arr, $key);
438
+ if($j !== false) {
439
+ array_splice($arr, $j);
440
+ return true;
441
+ }
442
+ return false;
443
+ }
444
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/SPCache.php ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * User: simon
4
+ * Date: 27.07.2018
5
+ */
6
+
7
+ namespace ShortPixel;
8
+
9
+ /**
10
+ * Class SPCache will cache in memcached the objects received, if memcached is available
11
+ * @package ShortPixel
12
+ */
13
+ class SPCache {
14
+ private static $instance;
15
+ private $mc;
16
+ private $local;
17
+ private $time;
18
+
19
+ private function __construct() {
20
+ $this->time = \ShortPixel\opt("cache_time");
21
+ $this->mc = \ShortPixel\getMemcache();
22
+ if(!$this->mc) {
23
+ $this->local = array();
24
+ }
25
+ }
26
+
27
+ public function fetch($key) {
28
+ if($this->mc) {
29
+ return $this->mc->get($key);
30
+ } elseif(isset($this->local[$key]) && time() - $this->local[$key]['time'] < $this->time) {
31
+ return $this->local[$key]['value'];
32
+ }
33
+ return false;
34
+ }
35
+
36
+ public function store($key, $value) {
37
+ if($this->time) {
38
+ if($this->mc) {
39
+ return $this->mc->set($key, $value, $this->time);
40
+ } else {
41
+ $this->local[$key] = array('value' => $value, 'time' => time());
42
+ }
43
+ }
44
+ return false;
45
+ }
46
+
47
+ public function delete($key) {
48
+ if($this->mc) {
49
+ return $this->mc->delete($key);
50
+ } elseif(isset($this->local[$key])) {
51
+ unset($this->local[$key]);
52
+ }
53
+ return false;
54
+ }
55
+
56
+ /**
57
+ * returns the current logger. If the logger is not set to log from that producer or if the log is not initialized, will return a dummy logger which doesn't log.
58
+ * @return SPCache
59
+ */
60
+ public static function Get() {
61
+ if(!isset(self::$instance)) {
62
+ self::$instance = new SPCache();
63
+ }
64
+ return self::$instance;
65
+ }
66
+
67
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/SPLog.php ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * User: simon
4
+ * Date: 26.02.2018
5
+ */
6
+ namespace ShortPixel;
7
+
8
+ /**
9
+ * Class SPLog logs messages or not based on which source (producer)
10
+ * @package ShortPixel
11
+ */
12
+ class SPLog {
13
+ const PRODUCER_NONE = 0;
14
+ const PRODUCER_CMD = 1; //0b00000001;
15
+ const PRODUCER_CMD_VERBOSE = 2; //0b00000010;
16
+ const PRODUCER_PERSISTER = 4; //0b00000100;
17
+ const PRODUCER_CLIENT = 8; //0b00001000;
18
+ const PRODUCER_RESULT = 16; //0b00010000;
19
+ const PRODUCER_WEB = 32; //0b00100000;
20
+ const PRODUCER_CTRL = 64; //0b01000000;
21
+
22
+ const FLAG_NONE = 0;
23
+ const FLAG_MEMORY = 1;
24
+
25
+ const TARGET_CONSOLE = 1;
26
+ const TARGET_FILE = 2;
27
+
28
+ private static $instance, $dummy;
29
+
30
+ private $processId;
31
+ private $target;
32
+ private $targetName;
33
+ private $acceptedProducers;
34
+ private $time;
35
+ private $loggedAlready;
36
+ private $flags;
37
+
38
+ private function __construct($processId, $acceptedProducers, $target, $targetName, $flags = self::FLAG_NONE) {
39
+ $this->processId = $processId;
40
+ $this->acceptedProducers = $acceptedProducers;
41
+ $this->target = $target;
42
+ $this->targetName = $targetName;
43
+ $this->time = microtime(true);
44
+ $this->loggedAlready = array();
45
+ $this->flags = $flags;
46
+ }
47
+
48
+ /**
49
+ * formats a log message
50
+ * @param $processId
51
+ * @param $msg
52
+ * @param $time
53
+ * @return string
54
+ */
55
+ public static function format($msg, $processId = false, $time = false, $flags = self::FLAG_NONE) {
56
+ return "\n" . ($processId ? "$processId@" : "")
57
+ . date("Y-m-d H:i:s")
58
+ . ($time ? " (" . number_format(microtime(true) - $time, 2) . "s)" : "")
59
+ . ($flags | self::FLAG_MEMORY ? " (M: " . number_format(memory_get_usage()) . ")" : ""). " > $msg\n";
60
+ }
61
+
62
+ /**
63
+ * Log the message if the logger is configured to log from this producer
64
+ * @param $producer SPLog::PRODUCER_* - the source of logging ( one of the SPLog::PRODUCER_* values )
65
+ * @param $msg $string the actual message
66
+ * @param bool $object
67
+ */
68
+ public function log($producer, $msg, $object = false) {
69
+ if(!($this->acceptedProducers & $producer)) { return; }
70
+
71
+ $msgFmt = self::format($msg, $this->processId, $this->time, $this->flags);
72
+ if($object) {
73
+ $msgFmt .= " " . json_encode($object);
74
+ }
75
+ $this->logRaw($msgFmt);
76
+ }
77
+
78
+ /**
79
+ * Log only the first call with that key
80
+ * @param $key
81
+ * @param $producer
82
+ * @param $msg
83
+ * @param $object
84
+ */
85
+ public function logFirst($key, $producer, $msg, $object = false) {
86
+ if(!($this->acceptedProducers & $producer)) { return; }
87
+
88
+ if(!in_array($key, $this->loggedAlready)) {
89
+ $this->loggedAlready[] = $key;
90
+ $this->log($producer, $msg, $object);
91
+ }
92
+
93
+ }
94
+
95
+ public function clearLogged($producer, $key) {
96
+ if(!($this->acceptedProducers & $producer)) { return; }
97
+
98
+ if (($idx = array_search($key, $this->loggedAlready)) !== false) {
99
+ unset($this->loggedAlready[$idx]);
100
+ }
101
+ }
102
+
103
+ /**
104
+ * logs a message regardless of the producer setting and without formatting
105
+ * @param $msg
106
+ */
107
+ public function logRaw($msg){
108
+ switch($this->target) {
109
+ case self::TARGET_CONSOLE:
110
+ echo($msg);
111
+ break;
112
+ case self::TARGET_FILE:
113
+
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Log the message if the logger is configured to log from this producer AND EXIT ANYWAY
119
+ * @param $producer the source of logging ( one of the SPLog::PRODUCER_* values )
120
+ * @param $msg the actual message
121
+ * @param bool $object
122
+ */
123
+ public function bye($producer, $msg, $object = false) {
124
+ $this->log($producer, $msg, $object); echo("\n");die();
125
+ }
126
+
127
+ /**
128
+ * init the logger singleton
129
+ * @param $processId
130
+ * @param $acceptedProducers - the producers from which the logger will log, ignoring gracefully the others
131
+ * @param int $target - the log type
132
+ * @param bool|false $targetName the log name if needed
133
+ * @return SPLog the newly created logger instance
134
+ */
135
+ public static function Init($processId, $acceptedProducers, $target = self::TARGET_CONSOLE, $targetName = false, $flags = SPLog::FLAG_NONE) {
136
+ self::$instance = new SPLog($processId, $acceptedProducers, $target, $targetName, $flags);
137
+ return self::$instance;
138
+ }
139
+
140
+ /**
141
+ * returns the current logger. If the logger is not set to log from that producer or if the log is not initialized, will return a dummy logger which doesn't log.
142
+ * @param $producer
143
+ * @return SPLog
144
+ */
145
+ public static function Get($producer) {
146
+ if( !(self::$instance && ($producer & self::$instance->acceptedProducers)) ) {
147
+ if(!isset(self::$dummy)) {
148
+ self::$dummy = new SPLog(0, self::PRODUCER_NONE, self::TARGET_CONSOLE, false);
149
+ }
150
+ return self::$dummy;
151
+ }
152
+ return self::$instance;
153
+ }
154
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/Settings.php ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ShortPixel;
4
+
5
+
6
+ class Settings {
7
+ const FOLDER_INI_NAME = '.sp-options';
8
+ private $INI_PATH;
9
+ private $settings;
10
+
11
+ function __construct($iniPath = false) {
12
+ $this->INI_PATH = $iniPath;
13
+ $this->settings = array();
14
+ if(file_exists($this->INI_PATH)) {
15
+ $this->settings = parse_ini_file($this->INI_PATH);
16
+ }
17
+ }
18
+
19
+ function get($key) {
20
+ return(isset($this->settings[$key]) ? $this->settings[$key] : false);
21
+ }
22
+
23
+ function persistApiKeyAndSettings($data) {
24
+ if(isset($data['API_KEY']) && strlen($data['API_KEY']) == 20) {
25
+ if(file_exists($this->INI_PATH)) {
26
+ unlink($this->INI_PATH);
27
+ }
28
+ $strSettings = "[SHORTPIXEL]\nAPI_KEY=" . $data['API_KEY'] . "\n";
29
+ $settings = $this->post2options($data);
30
+ foreach($settings as $key => $val) {
31
+ $strSettings .= $key . '=' . $val . "\n";
32
+ }
33
+ $settings['API_KEY'] = $data['API_KEY'];
34
+
35
+ if(!@file_put_contents($this->INI_PATH, $strSettings)) {
36
+ return array("error" => "Could not write properties file " . $this->INI_PATH . ". Please check rights.");
37
+ }
38
+ $this->settings = $settings;
39
+ return array("success" => "API Key set: " . $data['API_KEY']);
40
+ } else {
41
+ return array("error" => "API Key should be 20 characters long.");
42
+ }
43
+ }
44
+
45
+ function post2options($post) {
46
+ $data = array();
47
+ if(isset($post['type'])) $data['lossy'] = $post['type'] == 'lossy' ? 1 : ($post['type'] == 'glossy' ? 2 : 0);
48
+ $data['keep_exif'] = isset($post['removeExif']) ? 0 : 1;
49
+ $data['cmyk2rgb'] = isset($post['cmyk2rgb']) ? 1 : 0;
50
+ $data['resize'] = isset($post['resize']) ? ($post['resize_type'] == 'outer' ? 1 : 3) : 0;
51
+ if($data['resize'] && isset($post['width'])) $data['resize_width'] = $post['width'];
52
+ if($data['resize'] && isset($post['height'])) $data['resize_height'] = $post['height'];
53
+
54
+ $convertto = isset($post['webp']) ? '|+webp' : '';
55
+ $convertto .= isset($post['avif']) ? '|+avif' : '';
56
+ $data['convertto'] = '' . substr($convertto, 1);
57
+
58
+ if(isset($post['backup_path'])) {
59
+ $data['backup_path'] = $post['backup_path'];
60
+ }
61
+ if(isset($post['exclude'])) {
62
+ $data['exclude'] = $post['exclude'];
63
+ }
64
+ if(isset($post['user']) && isset($post['pass'])) {
65
+ $data['user'] = $post['user'];
66
+ $data['pass'] = $post['pass'];
67
+ }
68
+ if(isset($post['base_url']) && strlen($post['base_url'])) {
69
+ $data['base_url'] = rtrim($post['base_url'], '/');
70
+ } elseif (isset($post['change_base_url']) && strlen($post['change_base_url'])) {
71
+ $data['base_url'] = rtrim($post['change_base_url'], '/');
72
+ }
73
+ return $data;
74
+ }
75
+
76
+ static function pathToRelative($path, $reference) {
77
+ $pa = explode('/', trim($path, '/'));
78
+ $ra = explode('/', trim($reference, '/'));
79
+ $res = array();
80
+ for($i = 0, $same = true; $i < max(count($pa), count($ra)); $i++) {
81
+ if($same && isset($pa[$i]) && isset($ra[$i]) && $pa[$i] == $ra[$i]) continue;
82
+ $same = false;
83
+ if(isset($ra[$i])) array_unshift($res, '..');
84
+ if(isset($pa[$i])) $res[] = $pa[$i];
85
+ }
86
+ return implode('/', $res);
87
+ }
88
+
89
+ function persistFolderSettings($data, $path) {
90
+ $strSettings = "[SHORTPIXEL]\n";
91
+ foreach($this->post2options($data) as $key => $val) {
92
+ if(!in_array($key, array("API_KEY", "folder", "")))
93
+ $strSettings .= $key . '=' . (is_numeric($val) ? $val : '"' . $val . '"') . "\n";
94
+ }
95
+ return @file_put_contents($path . '/' . self::FOLDER_INI_NAME, $strSettings);
96
+ }
97
+
98
+ function addOptions($options) {
99
+ array_merge($this->settings, $options);
100
+ }
101
+
102
+ function readOptions($path) {
103
+ $options = $this->settings;
104
+ if($path && file_exists($path . '/' . self::FOLDER_INI_NAME)) {
105
+ $options = array_merge($options, parse_ini_file($path . '/' . self::FOLDER_INI_NAME));
106
+ }
107
+ unset($options['API_KEY']);
108
+ return $options;
109
+
110
+ }
111
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/Source.php ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ShortPixel;
4
+
5
+ class Source {
6
+ private $urls;
7
+
8
+ /**
9
+ * @param $paths
10
+ * @param $basePath - common base path used to determine the subfolders that will be created in the destination
11
+ * @param null $pending
12
+ * @param bool $refresh
13
+ * @return Commander - the class that handles the optimization commands
14
+ * @throws ClientException
15
+ * @internal param $path - the file path on the local drive
16
+ */
17
+ public function fromFiles($paths, $basePath = null, $pending = null, $refresh = false) {
18
+ if(!is_array($paths)) {
19
+ $paths = array($paths);
20
+ }
21
+ if(count($paths) > ShortPixel::MAX_ALLOWED_FILES_PER_CALL) {
22
+ throw new ClientException("Maximum 10 local images allowed per call.");
23
+ }
24
+ $files = array();
25
+ foreach($paths as $path) {
26
+ if (!file_exists($path)) throw new ClientException("File not found: " . $path);
27
+ $files[] = $path;
28
+ }
29
+ $data = array(
30
+ "plugin_version" => ShortPixel::LIBRARY_CODE . " " . ShortPixel::VERSION,
31
+ "key" => ShortPixel::getKey(),
32
+ "files" => $files
33
+ );
34
+ if($refresh) { //don't put it in the array above because false will overwrite the commands refresh. If only set when true, will just force a refresh when needed.
35
+ $data["refresh"] = 1;
36
+ }
37
+ if($pending && count($pending)) {
38
+ $data["pendingURLs"] = $pending;
39
+ }
40
+
41
+ return new Commander($data, $this);
42
+ }
43
+
44
+ /**
45
+ * returns the optimization counters of the folder and subfolders
46
+ * @param $path - the file path on the local drive
47
+ * @param bool $recurse - boolean - go into subfolders or not
48
+ * @param bool $fileList - return the list of files with optimization status (only current folder, not subfolders)
49
+ * @param array $exclude - array of folder names that you want to exclude from the optimization
50
+ * @param bool $persistPath - the path where to look for the metadata, if different from the $path
51
+ * @param int $recurseDepth - how many subfolders deep to go. Defaults to PHP_INT_MAX
52
+ * @param bool $retrySkipped - if true, all skipped files will be reset to pending with retries = 0
53
+ * @return object|void (object)array('status', 'total', 'succeeded', 'pending', 'same', 'failed')
54
+ * @throws PersistException
55
+ */
56
+ public function folderInfo($path, $recurse = true, $fileList = false, $exclude = array(), $persistPath = false, $recurseDepth = PHP_INT_MAX, $retrySkipped = false){
57
+ $persister = ShortPixel::getPersister($path);
58
+ if(!$persister) {
59
+ throw new PersistException("Persist is not enabled in options, needed for fetching folder info");
60
+ }
61
+ return $persister->info($path, $recurse, $fileList, $exclude, $persistPath, $recurseDepth, $retrySkipped);
62
+ }
63
+
64
+ /**
65
+ * processes a chunk of MAX_ALLOWED files from the folder, based on the persisted information about which images are processed and which not. This information is offered by the Persister object.
66
+ * @param $path - the folder path on the local drive
67
+ * @param int $maxFiles - maximum number of files to select from the folder
68
+ * @param array $exclude - exclude files based on regex patterns
69
+ * @param bool $persistFolder - the path where to store the metadata, if different from the $path (usually the target path)
70
+ * @param int $maxTotalFileSize - max summed up file size in MB
71
+ * @param int $recurseDepth - how many subfolders deep to go. Defaults to PHP_INT_MAX
72
+ * @return Commander - the class that handles the optimization commands
73
+ * @throws ClientException
74
+ * @throws PersistException
75
+ */
76
+ public function fromFolder($path, $maxFiles = 0, $exclude = array(), $persistFolder = false, $maxTotalFileSize = ShortPixel::CLIENT_MAX_BODY_SIZE, $recurseDepth = PHP_INT_MAX) {
77
+ if($maxFiles == 0) {
78
+ $maxFiles = ShortPixel::MAX_ALLOWED_FILES_PER_CALL;
79
+ }
80
+ //sanitize
81
+ $maxFiles = max(1, min(ShortPixel::MAX_ALLOWED_FILES_PER_CALL, intval($maxFiles)));
82
+
83
+ $persister = ShortPixel::getPersister($path);
84
+ if(!$persister) {
85
+ throw new PersistException("Persist_type is not enabled in options, needed for folder optimization");
86
+ }
87
+ $paths = $persister->getTodo($path, $maxFiles, $exclude, $persistFolder, $maxTotalFileSize, $recurseDepth);
88
+ if($paths) {
89
+ ShortPixel::setOptions(array("base_source_path" => $path));
90
+ return $this->fromFiles($paths->files, null, $paths->filesPending);
91
+ }
92
+ throw new ClientException("Couldn't find any processable file at given path ($path).", 2);
93
+ }
94
+
95
+ /**
96
+ * processes a chunk of MAX_ALLOWED URLs from a folder that is accessible via web at the $webPath location,
97
+ * based on the persisted information about which images are processed and which not. This information is offered by the Persister object.
98
+ * @param $path - the folder path on the local drive
99
+ * @param $webPath - the web URL of the folder
100
+ * @param array $exclude - exclude files based on regex patterns
101
+ * @param bool $persistFolder - the path where to store the metadata, if different from the $path (usually the target path)
102
+ * @param int $recurseDepth - how many subfolders deep to go. Defaults to PHP_INT_MAX
103
+ * @return Commander - the class that handles the optimization commands
104
+ * @throws ClientException
105
+ * @throws PersistException
106
+ */
107
+ public function fromWebFolder($path, $webPath, $exclude = array(), $persistFolder = false, $recurseDepth = PHP_INT_MAX) {
108
+
109
+ $path = rtrim($path, '/');
110
+ $webPath = rtrim($webPath, '/');
111
+ $persister = ShortPixel::getPersister();
112
+ if($persister === null) {
113
+ //cannot optimize from folder without persister.
114
+ throw new PersistException("Persist_type is not enabled in options, needed for folder optimization");
115
+ }
116
+ $paths = $persister->getTodo($path, ShortPixel::MAX_ALLOWED_FILES_PER_WEB_CALL, $exclude, $persistFolder, $recurseDepth);
117
+ $repl = (object)array("path" => $path, "web" => $webPath);
118
+ if($paths && count($paths->files)) {
119
+ $items = array_merge($paths->files, array_values($paths->filesPending)); //not impossible to have filesPending - for example optimized partially without webPath then added it
120
+ array_walk(
121
+ $items,
122
+ function(&$item, $key, $repl){
123
+ $item = implode('/', array_map('rawurlencode', explode('/', str_replace($repl->path, '', $item))));
124
+ $item = $repl->web . $item;
125
+ }, $repl);
126
+ ShortPixel::setOptions(array("base_url" => $webPath, "base_source_path" => $path));
127
+
128
+ return $this->fromUrls($items);
129
+ }
130
+ //folder is either empty, either fully optimized, in both cases it's optimized :)
131
+ throw new ClientException("Couldn't find any processable file at given path ($path).", 2);
132
+ }
133
+
134
+ public function fromBuffer($name, $contents) {
135
+ return new Commander(array(
136
+ "plugin_version" => ShortPixel::LIBRARY_CODE . " " . ShortPixel::VERSION,
137
+ "key" => ShortPixel::getKey(),
138
+ "buffers" => array($name => $contents),
139
+ // don't add it if false, otherwise will overwrite the refresh command //"refresh" => false
140
+ ), $this);
141
+ }
142
+
143
+ /**
144
+ * @param $urls - the array of urls to be optimized
145
+ * @return Commander - the class that handles the optimization commands
146
+ * @throws ClientException
147
+ */
148
+ public function fromUrls($urls) {
149
+ if(!is_array($urls)) {
150
+ $urls = array($urls);
151
+ }
152
+ if(count($urls) > ShortPixel::MAX_API_ALLOWED_FILES_PER_WEB_CALL) {
153
+ throw new ClientException("Maximum 100 images allowed per call.");
154
+ }
155
+
156
+ $this->urls = array_map ('utf8_encode', $urls);
157
+ $data = array(
158
+ "plugin_version" => ShortPixel::LIBRARY_CODE . " " . ShortPixel::VERSION,
159
+ "key" => ShortPixel::getKey(),
160
+ "urllist" => $this->urls,
161
+ // don't add it if false, otherwise will overwrite the refresh command //"refresh" => false
162
+ );
163
+
164
+ return new Commander($data, $this);
165
+ }
166
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/notify/ProgressNotifier.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * User: simon
4
+ * Date: 16.03.2018
5
+ */
6
+ namespace ShortPixel\notify;
7
+
8
+ abstract class ProgressNotifier {
9
+ protected $path;
10
+
11
+ public function __construct($path) {
12
+ $this->path = $path;
13
+ }
14
+
15
+ public function recordProgress($info, $replace = false) {
16
+ $data = $this->getData();
17
+ $data = $data ? $data : new \stdClass();
18
+ if(isset($info->status)) {
19
+ $data->status = $info->status;
20
+ }
21
+ if(isset($info->total)) {
22
+ $data->total = 0 + $info->total;
23
+ }
24
+ if($replace) {
25
+ $data->succeeded = $data->failed = $data->same = 0;
26
+ }
27
+ if(isset($info->failed)) {
28
+ $data->failed = (isset($data->failed) ? $data->failed : 0) + (is_array($info->failed) ? count($info->failed) : 0 + $info->failed);
29
+ }
30
+ if(isset($info->same)) {
31
+ $data->same = (isset($data->same) ? $data->same : 0) + (is_array($info->same) ? count($info->same) : 0 + $info->same);
32
+ }
33
+ $succeeded = array();
34
+ if(isset($info->succeeded)) {
35
+ $data->succeeded = (isset($data->succeeded) ? $data->succeeded : 0) + (is_array($info->succeeded) ? count($info->succeeded) : 0 + $info->succeeded);
36
+ if(is_array($info->succeeded)) {
37
+ $succeeded = $info->succeeded;
38
+ }
39
+ }
40
+ $data->succeededList = array_slice(array_merge($succeeded, (isset($data->succeededList) ? $data->succeededList : array())), 0, 20);
41
+ if(!count($data->succeededList)) unset($data->succeededList);
42
+
43
+ $same = array();
44
+ if(isset($info->same)) {
45
+ $data->same = (isset($data->same) ? $data->same : 0) + (is_array($info->same) ? count($info->same) : 0 + $info->same);
46
+ if(is_array($info->same)) {
47
+ $same = $info->same;
48
+ }
49
+ }
50
+ $data->sameList = array_slice(array_merge($same, (isset($data->sameList) ? $data->sameList : array())), 0, 20);
51
+ if(!count($data->sameList)) unset($data->sameList);
52
+
53
+ $failed = array();
54
+ if(isset($info->failed)) {
55
+ $data->failed = (isset($data->failed) ? $data->failed : 0) + (is_array($info->failed) ? count($info->failed) : 0 + $info->failed);
56
+ if(is_array($info->failed)) {
57
+ for($i = 0; $i < count($info->failed); $i++) {
58
+ $info->failed[$i]->TimeStamp = date("Y-m-d H:i:s");
59
+ }
60
+ $failed = $info->failed;
61
+ }
62
+ }
63
+ $data->failedList = array_slice(array_merge($failed, (isset($data->failedList) ? $data->failedList : array())), 0, 100);
64
+ if(!count($data->failedList)) unset($data->failedList);
65
+
66
+ $this->setData($data);
67
+ }
68
+
69
+ public abstract function getData();
70
+ public abstract function setData($data);
71
+ public abstract function set($type, $data);
72
+ public abstract function get($type);
73
+
74
+ public function enqueueFailedImages(){
75
+
76
+ }
77
+ public function getFailedImages(){
78
+
79
+ }
80
+
81
+ /**
82
+ * Add to a queue info about the last optimized images. The queue is limited to maximum 20 images - the newest
83
+ * @return mixed
84
+ */
85
+ public function enqueueDoneImages() {
86
+
87
+ }
88
+
89
+ /**
90
+ * @return array - list of the last maximum 20 images optimized.
91
+ */
92
+ public function getDoneImages() {
93
+
94
+ }
95
+
96
+ public static function constructNotifier($path) {
97
+ $mc = \ShortPixel\getMemcache();
98
+ if($mc) {
99
+ $notifier = new ProgressNotifierMemcache($path);
100
+ $notifier->setMemcache($mc);
101
+ return $notifier;
102
+ }
103
+ return new ProgressNotifierFileQ($path);
104
+ }
105
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/notify/ProgressNotifierFileQ.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * User: simon
4
+ * Date: 16.03.2018
5
+ */
6
+ namespace ShortPixel\notify;
7
+
8
+ class ProgressNotifierFileQ extends ProgressNotifier {
9
+
10
+ public function get($type)
11
+ {
12
+ $data = $this->getData();
13
+ return isset($data[$type]) ? $data[$type] : false;
14
+ }
15
+
16
+ public function set($type, $value)
17
+ {
18
+ $data = $this->getData();
19
+ if(!is_array($data)) {
20
+ $data = [];
21
+ }
22
+ $data[$type] = $value;
23
+ $this->setData($data);
24
+ }
25
+
26
+ const PROGRESS_FILE_NAME = '.sp-progress';
27
+
28
+ public function getFilePath() {
29
+ return rtrim( $this->path, '/\\' ) . '/' . self::PROGRESS_FILE_NAME;
30
+ }
31
+
32
+ public function getData() {
33
+ $file = $this->getFilePath();
34
+ if(file_exists($file)) {
35
+ return json_decode(file_get_contents($file));
36
+ }
37
+ return false;
38
+ }
39
+
40
+ function setData($data) {
41
+ file_put_contents($this->getFilePath(), json_encode($data));
42
+ }
43
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/notify/ProgressNotifierMemcache.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * User: simon
4
+ * Date: 16.03.2018
5
+ */
6
+ namespace ShortPixel\notify;
7
+
8
+ class ProgressNotifierMemcache extends ProgressNotifier {
9
+ protected $mc;
10
+ protected $key;
11
+
12
+ public function __construct($path) {
13
+ parent::__construct($path);
14
+ $this->key = md5($path);
15
+ }
16
+
17
+ public function setMemcache($mc) {
18
+ $this->mc = $mc;
19
+ }
20
+
21
+ public function set($type, $val)
22
+ {
23
+ $data = $this->mc->get($this->key);
24
+ $data[$type] = $val;
25
+ $this->mc->set($this->key, $data);
26
+ }
27
+
28
+ public function get($type)
29
+ {
30
+ $data = $this->mc->get($this->key);
31
+ return isset($data[$type]) ? $data[$type] : false;
32
+ }
33
+
34
+ public function getData()
35
+ {
36
+ return $this->mc->get($this->key);
37
+ }
38
+
39
+ public function setData($data)
40
+ {
41
+ $this->mc->set($this->key, $data);
42
+ }
43
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/persist/ExifPersister.php ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * User: simon
4
+ * Date: 19.08.2016
5
+ * Time: 18:05
6
+ */
7
+ namespace ShortPixel\persist;
8
+
9
+ use \ShortPixel\Persister;
10
+
11
+ class ExifPersister implements Persister {
12
+
13
+
14
+ function __construct($options)
15
+ {
16
+ // nothing to do, the ExifPersister doesn't need any configuration
17
+ }
18
+
19
+ public static function IGNORED_BY_DEFAULT() {
20
+ return array();
21
+ }
22
+
23
+ function isOptimized($path)
24
+ {
25
+ switch(exif_imagetype($path)) {
26
+ case IMAGETYPE_JPEG:
27
+ case IMAGETYPE_JPEG2000:
28
+ case IMAGETYPE_TIFF_II:
29
+ case IMAGETYPE_TIFF_MM:
30
+ $exif = @exif_read_data($path, 0, true);
31
+ if($exif===false) return false;
32
+ foreach ($exif as $key => $section) {
33
+ if($key == "EXIF"){
34
+ foreach ($section as $name => $val) {
35
+ if($name === "UserComment") {
36
+ $code = substr($val, -5);
37
+ if($code === \ShortPixel\ShortPixel::LOSSLESS_EXIF_TAG || $code === \ShortPixel\ShortPixel::LOSSY_EXIF_TAG) {
38
+ return true;
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }
44
+ break;
45
+ case IMAGETYPE_PNG:
46
+ $png = new PNGReader($path);
47
+ $sections = $png->get_sections();
48
+ $png = PNGMetadataExtractor::getMetadata($path);
49
+ if(isset($png["text"])) {
50
+ if(is_array($png["text"])){
51
+ foreach($png["text"] as $key => $item) {
52
+ if($key == "APP1_Profile" && isset($item["x-default"])) {
53
+ $lines = explode("\n", $item["x-default"]);
54
+ if(isset($lines[7])){
55
+ $val = trim(substr(hex2bin($lines[7]), -6));
56
+ if($val === \ShortPixel\ShortPixel::LOSSLESS_EXIF_TAG || $val === \ShortPixel\ShortPixel::LOSSY_EXIF_TAG)
57
+ return true;
58
+ }
59
+ }
60
+ }
61
+ } else {
62
+
63
+ }
64
+ }
65
+ /*if(isset($sections["COMMENT"]) && $sections["COMMENT"] == "SPXLL") {
66
+ return true;
67
+ }*/
68
+ }
69
+ return false;
70
+ }
71
+
72
+ function info($path, $recurse = true, $fileList = false, $exclude = array(), $persistPath = false) {
73
+ throw new Exception("Not implemented");
74
+ }
75
+
76
+ function getTodo($path, $count, $exclude = array(), $persistFolder = false, $maxTotalFileSize = false, $recurseDepth = PHP_INT_MAX)
77
+ {
78
+ $results = array();
79
+ $this->getTodoRecursive($path, $count, array_values(array_merge($exclude, array('.','..'))), $results, $recurseDepth);
80
+ return $results;
81
+ }
82
+
83
+ private function getTodoRecursive($path, &$count, $ignore, &$results, $recurseDepth) {
84
+ if($count <= 0) return;
85
+ $files = scandir($path);
86
+ foreach($files as $t) {
87
+ if($count <= 0) return;
88
+ if(in_array($t, $ignore)) continue;
89
+ $tpath = rtrim($path, '/') . '/' . $t;
90
+ if (is_dir($tpath)) {
91
+ if($recurseDepth <= 0) continue;
92
+ self::getTodoRecursive($tpath, $count, $ignore, $results, $recurseDepth -1);
93
+ } elseif( \ShortPixel\ShortPixel::isProcessable($t)
94
+ && !$this->isOptimized($tpath)) {
95
+ $results[] = $tpath;
96
+ $count--;
97
+ }
98
+ }
99
+ }
100
+
101
+ function getOptimizationData($path)
102
+ {
103
+ // TODO: Implement getOptimizationData() method.
104
+ }
105
+
106
+ function getNextTodo($path, $count)
107
+ {
108
+ // TODO: Implement getNextTodo() method.
109
+ }
110
+
111
+ function doneGet()
112
+ {
113
+ // TODO: Implement doneGet() method.
114
+ }
115
+
116
+ function setPending($path, $optData)
117
+ {
118
+ // TODO: Implement setPending() method.
119
+ }
120
+
121
+ function setOptimized($path, $optData)
122
+ {
123
+ // TODO: Implement setOptimized() method.
124
+ }
125
+
126
+ function setFailed($path, $optData)
127
+ {
128
+ // TODO: Implement setFailed() method.
129
+ }
130
+
131
+ function setSkipped($path, $optData)
132
+ {
133
+ // TODO: Implement setSkipped() method.
134
+ }
135
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/persist/PNGMetadataExtractor.php ADDED
@@ -0,0 +1,389 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ShortPixel\persist;
4
+
5
+ class PNGMetadataExtractor {
6
+ private static $pngSig;
7
+
8
+ private static $crcSize;
9
+
10
+ private static $textChunks;
11
+
12
+ const VERSION = 1;
13
+ const MAX_CHUNK_SIZE = 3145728; // 3 megabytes
14
+
15
+ static function getMetadata( $filename ) {
16
+ self::$pngSig = pack( "C8", 137, 80, 78, 71, 13, 10, 26, 10 );
17
+ self::$crcSize = 4;
18
+ /* based on list at http://owl.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html#TextualData
19
+ * and http://www.w3.org/TR/PNG/#11keywords
20
+ */
21
+ self::$textChunks = array(
22
+ 'xml:com.adobe.xmp' => 'xmp',
23
+ # Artist is unofficial. Author is the recommended
24
+ # keyword in the PNG spec. However some people output
25
+ # Artist so support both.
26
+ 'artist' => 'Artist',
27
+ 'model' => 'Model',
28
+ 'make' => 'Make',
29
+ 'author' => 'Artist',
30
+ 'comment' => 'PNGFileComment',
31
+ 'description' => 'ImageDescription',
32
+ 'title' => 'ObjectName',
33
+ 'copyright' => 'Copyright',
34
+ # Source as in original device used to make image
35
+ # not as in who gave you the image
36
+ 'source' => 'Model',
37
+ 'software' => 'Software',
38
+ 'disclaimer' => 'Disclaimer',
39
+ 'warning' => 'ContentWarning',
40
+ 'url' => 'Identifier', # Not sure if this is best mapping. Maybe WebStatement.
41
+ 'label' => 'Label',
42
+ 'creation time' => 'DateTimeDigitized',
43
+ 'raw profile type app1' => 'APP1_Profile',
44
+ /* Other potentially useful things - Document */
45
+ );
46
+
47
+ $frameCount = 0;
48
+ $loopCount = 1;
49
+ $text = array();
50
+ $duration = 0.0;
51
+ $bitDepth = 0;
52
+ $colorType = 'unknown';
53
+
54
+ if ( !$filename ) {
55
+ throw new Exception( __METHOD__ . ": No file name specified" );
56
+ } elseif ( !file_exists( $filename ) || is_dir( $filename ) ) {
57
+ throw new Exception( __METHOD__ . ": File $filename does not exist" );
58
+ }
59
+
60
+ $fh = fopen( $filename, 'rb' );
61
+
62
+ if ( !$fh ) {
63
+ throw new Exception( __METHOD__ . ": Unable to open file $filename" );
64
+ }
65
+
66
+ // Check for the PNG header
67
+ $buf = fread( $fh, 8 );
68
+ if ( $buf != self::$pngSig ) {
69
+ throw new Exception( __METHOD__ . ": Not a valid PNG file; header: $buf" );
70
+ }
71
+
72
+ // Read chunks
73
+ while ( !feof( $fh ) ) {
74
+ $buf = fread( $fh, 4 );
75
+ if ( !$buf || strlen( $buf ) < 4 ) {
76
+ throw new Exception( __METHOD__ . ": Read error" );
77
+ }
78
+ $chunk = unpack( "N", $buf );
79
+ $chunk_size = $chunk[1];
80
+
81
+ if ( $chunk_size < 0 ) {
82
+ throw new Exception( __METHOD__ . ": Chunk size too big for unpack" );
83
+ }
84
+
85
+ $chunk_type = fread( $fh, 4 );
86
+ if ( !$chunk_type || strlen( $chunk_type ) < 4 ) {
87
+ throw new Exception( __METHOD__ . ": Read error" );
88
+ }
89
+
90
+ if ( $chunk_type == "IHDR" ) {
91
+ $buf = self::read( $fh, $chunk_size );
92
+ if ( !$buf || strlen( $buf ) < $chunk_size ) {
93
+ throw new Exception( __METHOD__ . ": Read error" );
94
+ }
95
+ $bitDepth = ord( substr( $buf, 8, 1 ) );
96
+ // Detect the color type in British English as per the spec
97
+ // http://www.w3.org/TR/PNG/#11IHDR
98
+ switch ( ord( substr( $buf, 9, 1 ) ) ) {
99
+ case 0:
100
+ $colorType = 'greyscale';
101
+ break;
102
+ case 2:
103
+ $colorType = 'truecolour';
104
+ break;
105
+ case 3:
106
+ $colorType = 'index-coloured';
107
+ break;
108
+ case 4:
109
+ $colorType = 'greyscale-alpha';
110
+ break;
111
+ case 6:
112
+ $colorType = 'truecolour-alpha';
113
+ break;
114
+ default:
115
+ $colorType = 'unknown';
116
+ break;
117
+ }
118
+ } elseif ( $chunk_type == "acTL" ) {
119
+ $buf = fread( $fh, $chunk_size );
120
+ if ( !$buf || strlen( $buf ) < $chunk_size || $chunk_size < 4 ) {
121
+ throw new Exception( __METHOD__ . ": Read error" );
122
+ }
123
+
124
+ $actl = unpack( "Nframes/Nplays", $buf );
125
+ $frameCount = $actl['frames'];
126
+ $loopCount = $actl['plays'];
127
+ } elseif ( $chunk_type == "fcTL" ) {
128
+ $buf = self::read( $fh, $chunk_size );
129
+ if ( !$buf || strlen( $buf ) < $chunk_size ) {
130
+ throw new Exception( __METHOD__ . ": Read error" );
131
+ }
132
+ $buf = substr( $buf, 20 );
133
+ if ( strlen( $buf ) < 4 ) {
134
+ throw new Exception( __METHOD__ . ": Read error" );
135
+ }
136
+
137
+ $fctldur = unpack( "ndelay_num/ndelay_den", $buf );
138
+ if ( $fctldur['delay_den'] == 0 ) {
139
+ $fctldur['delay_den'] = 100;
140
+ }
141
+ if ( $fctldur['delay_num'] ) {
142
+ $duration += $fctldur['delay_num'] / $fctldur['delay_den'];
143
+ }
144
+ } elseif ( $chunk_type == "iTXt" ) {
145
+ // Extracts iTXt chunks, uncompressing if necessary.
146
+ $buf = self::read( $fh, $chunk_size );
147
+ $items = array();
148
+ if ( preg_match(
149
+ '/^([^\x00]{1,79})\x00(\x00|\x01)\x00([^\x00]*)(.)[^\x00]*\x00(.*)$/Ds',
150
+ $buf, $items )
151
+ ) {
152
+ /* $items[1] = text chunk name, $items[2] = compressed flag,
153
+ * $items[3] = lang code (or ""), $items[4]= compression type.
154
+ * $items[5] = content
155
+ */
156
+
157
+ // Theoretically should be case-sensitive, but in practise...
158
+ $items[1] = strtolower( $items[1] );
159
+ if ( !isset( self::$textChunks[$items[1]] ) ) {
160
+ // Only extract textual chunks on our list.
161
+ fseek( $fh, self::$crcSize, SEEK_CUR );
162
+ continue;
163
+ }
164
+
165
+ $items[3] = strtolower( $items[3] );
166
+ if ( $items[3] == '' ) {
167
+ // if no lang specified use x-default like in xmp.
168
+ $items[3] = 'x-default';
169
+ }
170
+
171
+ // if compressed
172
+ if ( $items[2] == "\x01" ) {
173
+ if ( function_exists( 'gzuncompress' ) && $items[4] === "\x00" ) {
174
+ $errLevel = error_reporting(E_ERROR | E_PARSE);
175
+ $items[5] = gzuncompress( $items[5] );
176
+ error_reporting($errLevel);
177
+
178
+ if ( $items[5] === false ) {
179
+ // decompression failed
180
+ wfDebug( __METHOD__ . ' Error decompressing iTxt chunk - ' . $items[1] . "\n" );
181
+ fseek( $fh, self::$crcSize, SEEK_CUR );
182
+ continue;
183
+ }
184
+ } else {
185
+ wfDebug( __METHOD__ . ' Skipping compressed png iTXt chunk due to lack of zlib,'
186
+ . " or potentially invalid compression method\n" );
187
+ fseek( $fh, self::$crcSize, SEEK_CUR );
188
+ continue;
189
+ }
190
+ }
191
+ $finalKeyword = self::$textChunks[$items[1]];
192
+ $text[$finalKeyword][$items[3]] = $items[5];
193
+ $text[$finalKeyword]['_type'] = 'lang';
194
+ } else {
195
+ // Error reading iTXt chunk
196
+ throw new Exception( __METHOD__ . ": Read error on iTXt chunk" );
197
+ }
198
+ } elseif ( $chunk_type == 'tEXt' ) {
199
+ $buf = self::read( $fh, $chunk_size );
200
+
201
+ // In case there is no \x00 which will make explode fail.
202
+ if ( strpos( $buf, "\x00" ) === false ) {
203
+ throw new Exception( __METHOD__ . ": Read error on tEXt chunk" );
204
+ }
205
+
206
+ list( $keyword, $content ) = explode( "\x00", $buf, 2 );
207
+ if ( $keyword === '' || $content === '' ) {
208
+ throw new Exception( __METHOD__ . ": Read error on tEXt chunk" );
209
+ }
210
+
211
+ // Theoretically should be case-sensitive, but in practise...
212
+ $keyword = strtolower( $keyword );
213
+ if ( !isset( self::$textChunks[$keyword] ) ) {
214
+ // Don't recognize chunk, so skip.
215
+ fseek( $fh, self::$crcSize, SEEK_CUR );
216
+ continue;
217
+ }
218
+ $errLevel = error_reporting(E_ERROR | E_PARSE);
219
+ $content = iconv( 'ISO-8859-1', 'UTF-8', $content );
220
+ error_reporting($errLevel);
221
+
222
+ if ( $content === false ) {
223
+ throw new Exception( __METHOD__ . ": Read error (error with iconv)" );
224
+ }
225
+
226
+ $finalKeyword = self::$textChunks[$keyword];
227
+ $text[$finalKeyword]['x-default'] = $content;
228
+ $text[$finalKeyword]['_type'] = 'lang';
229
+ } elseif ( $chunk_type == 'zTXt' ) {
230
+ if ( function_exists( 'gzuncompress' ) ) {
231
+ $buf = self::read( $fh, $chunk_size );
232
+
233
+ // In case there is no \x00 which will make explode fail.
234
+ if ( strpos( $buf, "\x00" ) === false ) {
235
+ throw new Exception( __METHOD__ . ": Read error on zTXt chunk" );
236
+ }
237
+
238
+ list( $keyword, $postKeyword ) = explode( "\x00", $buf, 2 );
239
+ if ( $keyword === '' || $postKeyword === '' ) {
240
+ throw new Exception( __METHOD__ . ": Read error on zTXt chunk" );
241
+ }
242
+ // Theoretically should be case-sensitive, but in practise...
243
+ $keyword = strtolower( $keyword );
244
+
245
+ if ( !isset( self::$textChunks[$keyword] ) ) {
246
+ // Don't recognize chunk, so skip.
247
+ fseek( $fh, self::$crcSize, SEEK_CUR );
248
+ continue;
249
+ }
250
+ $compression = substr( $postKeyword, 0, 1 );
251
+ $content = substr( $postKeyword, 1 );
252
+ if ( $compression !== "\x00" ) {
253
+ wfDebug( __METHOD__ . " Unrecognized compression method in zTXt ($keyword). Skipping.\n" );
254
+ fseek( $fh, self::$crcSize, SEEK_CUR );
255
+ continue;
256
+ }
257
+
258
+ $errLevel = error_reporting(E_ERROR | E_PARSE);
259
+ $content = gzuncompress( $content );
260
+ error_reporting($errLevel);
261
+
262
+ if ( $content === false ) {
263
+ // decompression failed
264
+ wfDebug( __METHOD__ . ' Error decompressing zTXt chunk - ' . $keyword . "\n" );
265
+ fseek( $fh, self::$crcSize, SEEK_CUR );
266
+ continue;
267
+ }
268
+
269
+ $errLevel = error_reporting(E_ERROR | E_PARSE);
270
+ $content = iconv( 'ISO-8859-1', 'UTF-8', $content );
271
+ error_reporting($errLevel);
272
+
273
+ if ( $content === false ) {
274
+ throw new Exception( __METHOD__ . ": Read error (error with iconv)" );
275
+ }
276
+
277
+ $finalKeyword = self::$textChunks[$keyword];
278
+ $text[$finalKeyword]['x-default'] = $content;
279
+ $text[$finalKeyword]['_type'] = 'lang';
280
+ } else {
281
+ wfDebug( __METHOD__ . " Cannot decompress zTXt chunk due to lack of zlib. Skipping.\n" );
282
+ fseek( $fh, $chunk_size, SEEK_CUR );
283
+ }
284
+ } elseif ( $chunk_type == 'tIME' ) {
285
+ // last mod timestamp.
286
+ if ( $chunk_size !== 7 ) {
287
+ throw new Exception( __METHOD__ . ": tIME wrong size" );
288
+ }
289
+ $buf = self::read( $fh, $chunk_size );
290
+ if ( !$buf || strlen( $buf ) < $chunk_size ) {
291
+ throw new Exception( __METHOD__ . ": Read error" );
292
+ }
293
+
294
+ // Note: spec says this should be UTC.
295
+ $t = unpack( "ny/Cm/Cd/Ch/Cmin/Cs", $buf );
296
+ $strTime = sprintf( "%04d%02d%02d%02d%02d%02d",
297
+ $t['y'], $t['m'], $t['d'], $t['h'],
298
+ $t['min'], $t['s'] );
299
+
300
+ $exifTime = wfTimestamp( TS_EXIF, $strTime );
301
+
302
+ if ( $exifTime ) {
303
+ $text['DateTime'] = $exifTime;
304
+ }
305
+ } elseif ( $chunk_type == 'pHYs' ) {
306
+ // how big pixels are (dots per meter).
307
+ if ( $chunk_size !== 9 ) {
308
+ throw new Exception( __METHOD__ . ": pHYs wrong size" );
309
+ }
310
+
311
+ $buf = self::read( $fh, $chunk_size );
312
+ if ( !$buf || strlen( $buf ) < $chunk_size ) {
313
+ throw new Exception( __METHOD__ . ": Read error" );
314
+ }
315
+
316
+ $dim = unpack( "Nwidth/Nheight/Cunit", $buf );
317
+ if ( $dim['unit'] == 1 ) {
318
+ // Need to check for negative because php
319
+ // doesn't deal with super-large unsigned 32-bit ints well
320
+ if ( $dim['width'] > 0 && $dim['height'] > 0 ) {
321
+ // unit is meters
322
+ // (as opposed to 0 = undefined )
323
+ $text['XResolution'] = $dim['width']
324
+ . '/100';
325
+ $text['YResolution'] = $dim['height']
326
+ . '/100';
327
+ $text['ResolutionUnit'] = 3;
328
+ // 3 = dots per cm (from Exif).
329
+ }
330
+ }
331
+ } elseif ( $chunk_type == "IEND" ) {
332
+ break;
333
+ } else {
334
+ fseek( $fh, $chunk_size, SEEK_CUR );
335
+ }
336
+ fseek( $fh, self::$crcSize, SEEK_CUR );
337
+ }
338
+ fclose( $fh );
339
+
340
+ if ( $loopCount > 1 ) {
341
+ $duration *= $loopCount;
342
+ }
343
+
344
+ if ( isset( $text['DateTimeDigitized'] ) ) {
345
+ // Convert date format from rfc2822 to exif.
346
+ foreach ( $text['DateTimeDigitized'] as $name => &$value ) {
347
+ if ( $name === '_type' ) {
348
+ continue;
349
+ }
350
+
351
+ // @todo FIXME: Currently timezones are ignored.
352
+ // possibly should be wfTimestamp's
353
+ // responsibility. (at least for numeric TZ)
354
+ $formatted = wfTimestamp( TS_EXIF, $value );
355
+ if ( $formatted ) {
356
+ // Only change if we could convert the
357
+ // date.
358
+ // The png standard says it should be
359
+ // in rfc2822 format, but not required.
360
+ // In general for the exif stuff we
361
+ // prettify the date if we can, but we
362
+ // display as-is if we cannot or if
363
+ // it is invalid.
364
+ // So do the same here.
365
+
366
+ $value = $formatted;
367
+ }
368
+ }
369
+ }
370
+
371
+ return array(
372
+ 'frameCount' => $frameCount,
373
+ 'loopCount' => $loopCount,
374
+ 'duration' => $duration,
375
+ 'text' => $text,
376
+ 'bitDepth' => $bitDepth,
377
+ 'colorType' => $colorType,
378
+ );
379
+ }
380
+
381
+ private static function read( $fh, $size ) {
382
+ if ( $size > self::MAX_CHUNK_SIZE ) {
383
+ throw new Exception( __METHOD__ . ': Chunk size of ' . $size .
384
+ ' too big. Max size is: ' . self::MAX_CHUNK_SIZE );
385
+ }
386
+
387
+ return fread( $fh, $size );
388
+ }
389
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/persist/PNGReader.php ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ShortPixel\persist;
4
+
5
+ class PNGReader
6
+ {
7
+ private $_chunks;
8
+ private $_fp;
9
+
10
+ function __construct($file) {
11
+ if (!file_exists($file)) {
12
+ throw new Exception('File does not exist');
13
+ }
14
+
15
+ $this->_chunks = array ();
16
+
17
+ // Open the file
18
+ $this->_fp = fopen($file, 'r');
19
+
20
+ if (!$this->_fp)
21
+ throw new Exception('Unable to open file');
22
+
23
+ // Read the magic bytes and verify
24
+ $header = fread($this->_fp, 8);
25
+
26
+ if ($header != "\x89PNG\x0d\x0a\x1a\x0a")
27
+ throw new Exception('Is not a valid PNG image');
28
+
29
+ // Loop through the chunks. Byte 0-3 is length, Byte 4-7 is type
30
+ $chunkHeader = fread($this->_fp, 8);
31
+
32
+ while ($chunkHeader) {
33
+ // Extract length and type from binary data
34
+ $chunk = @unpack('Nsize/a4type', $chunkHeader);
35
+
36
+ // Store position into internal array
37
+ if (!isset($this->_chunks[$chunk['type']]) || $this->_chunks[$chunk['type']] === null)
38
+ $this->_chunks[$chunk['type']] = array ();
39
+ $this->_chunks[$chunk['type']][] = array (
40
+ 'offset' => ftell($this->_fp),
41
+ 'size' => $chunk['size']
42
+ );
43
+
44
+ // Skip to next chunk (over body and CRC)
45
+ fseek($this->_fp, $chunk['size'] + 4, SEEK_CUR);
46
+
47
+ // Read next chunk header
48
+ $chunkHeader = fread($this->_fp, 8);
49
+ }
50
+ }
51
+
52
+ function __destruct() { fclose($this->_fp); }
53
+
54
+ // Returns all chunks of said type
55
+ public function get_chunks($type) {
56
+ if (!isset($this->_chunks[$type]) || $this->_chunks[$type] === null)
57
+ return null;
58
+
59
+ $chunks = array ();
60
+
61
+ foreach ($this->_chunks[$type] as $chunk) {
62
+ if ($chunk['size'] > 0) {
63
+ fseek($this->_fp, $chunk['offset'], SEEK_SET);
64
+ $chunks[] = fread($this->_fp, $chunk['size']);
65
+ } else {
66
+ $chunks[] = '';
67
+ }
68
+ }
69
+
70
+ return $chunks;
71
+ }
72
+
73
+ public function get_sections() {
74
+ $rawTextData = $this->get_chunks('tEXt');
75
+ if($rawTextData === null) {
76
+ //$rawTextData = gzinflate($this->get_chunks('zTXt'));
77
+ }
78
+
79
+ $metadata = array();
80
+
81
+ if(!is_array($rawTextData)) return $metadata;
82
+
83
+ foreach($rawTextData as $data) {
84
+ $sections = explode("\0", $data);
85
+
86
+ if($sections > 1) {
87
+ $key = array_shift($sections);
88
+ $metadata[$key] = implode("\0", $sections);
89
+ } else {
90
+ $metadata[] = $data;
91
+ }
92
+ }
93
+ return $metadata;
94
+ }
95
+ }
vendor/shortpixel/shortpixel-php/lib/ShortPixel/persist/TextPersister.php ADDED
@@ -0,0 +1,644 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * User: simon
4
+ * Date: 19.08.2016
5
+ * Time: 18:05
6
+ */
7
+ namespace ShortPixel\persist;
8
+
9
+ use ShortPixel\ClientException;
10
+ use ShortPixel\Lock;
11
+ use ShortPixel\notify\ProgressNotifierFileQ;
12
+ use \ShortPixel\Persister;
13
+ use ShortPixel\Settings;
14
+ use \ShortPixel\ShortPixel;
15
+ use \ShortPixel\Client;
16
+ use \ShortPixel\SPCache;
17
+ use ShortPixel\SPLog;
18
+
19
+ /**
20
+ * Class TextPersister - save the optimization information in .shortpixel files in the current folder of the images
21
+ * @package ShortPixel\persist
22
+ */
23
+ class TextPersister implements Persister {
24
+
25
+ private $fp;
26
+ private $options;
27
+ private $logger;
28
+ private $cache;
29
+ private STATIC $ALLOWED_STATUSES = array('pending', 'success', 'skip', 'deleted');
30
+ private STATIC $ALLOWED_TYPES = array('I', 'D');
31
+
32
+ const FLAG_WEBP = 2;//second bit set like in "10"
33
+ const FLAG_AVIF = 32;//6th bit set like in "100000"
34
+
35
+ const LINE_LENGTH = 465; //including the \r\n at the end
36
+ const LINE_LENGTH_V2 = 610; //including the \r\n at the end - NOT YET USED
37
+
38
+ function __construct($options)
39
+ {
40
+ $this->options = $options;
41
+ $this->fp = array();
42
+ $this->logger = SPLog::Get(SPLog::PRODUCER_PERSISTER);
43
+ $this->cache = SPCache::Get();
44
+ }
45
+
46
+ public static function IGNORED_BY_DEFAULT() {
47
+ return array('.','..',ShortPixel::opt('persist_name'),Settings::FOLDER_INI_NAME,Lock::FOLDER_LOCK_FILE,ProgressNotifierFileQ::PROGRESS_FILE_NAME,'ShortPixelBackups');
48
+ }
49
+
50
+ function isOptimized($path)
51
+ {
52
+ if(!file_exists($path)) {
53
+ return false;
54
+ }
55
+ $fp = $this->openMetaFile(dirname($path), 'read');
56
+ if(!$fp) {
57
+ return false;
58
+ }
59
+
60
+ while (($line = fgets($fp)) !== FALSE) {
61
+ $data = $this->parse($line);
62
+ if($data->file === \ShortPixel\MB_basename($path) && $data->status == 'success' ) {
63
+ return true;
64
+ }
65
+ }
66
+ fclose($fp);
67
+
68
+ return false;
69
+ }
70
+
71
+ protected function ignored($exclude) {
72
+ return array_values(array_merge(self::IGNORED_BY_DEFAULT(), is_array($exclude) ? $exclude : array()));
73
+ }
74
+
75
+ /**
76
+ * @param $path - the file path on the local drive
77
+ * @param bool $recurse - boolean - go into subfolders or not
78
+ * @param bool $fileList - return the list of files with optimization status (only current folder, not subfolders)
79
+ * @param array $exclude - array of folder names that you want to exclude from the optimization
80
+ * @param bool $persistPath - the path where to look for the metadata, if different from the $path
81
+ * @param int $recurseDepth - how many subfolders deep to go. Defaults to PHP_INT_MAX
82
+ * @param bool $retrySkipped - if true, all skipped files will be reset to pending with retries = 0
83
+ * @return object|void (object)array('status', 'total', 'succeeded', 'pending', 'same', 'failed')
84
+ * @throws PersistException
85
+ */
86
+ function info($path, $recurse = true, $fileList = false, $exclude = array(), $persistPath = false, $recurseDepth = PHP_INT_MAX, $retrySkipped = false) {
87
+ if($persistPath === false) {
88
+ $persistPath = $path;
89
+ }
90
+ $toClose = false; $persistFolder = false;
91
+ $info = array('status' => 'error', 'message' => "Unknown error, please contact support.", 'code' => -999);
92
+
93
+ try {
94
+ if(is_dir($path)) {
95
+ try {
96
+ $persistFolder = $persistPath;
97
+ $toClose = $this->openMetaFileIfNeeded($persistFolder);
98
+ $fp = $this->getMetaFile($persistPath);
99
+ $dataArr = $this->readMetaFile($fp);
100
+ } catch(ClientException $e) {
101
+ if(is_dir($persistPath) && file_exists($persistPath . '/' . ShortPixel::opt("persist_name"))) {
102
+ throw $e; //rethrow, there's a problem with the meta file.
103
+ }
104
+ $dataArr = array(); //there's no problem if the metadata file is missing and cannot be created, for the info call
105
+ }
106
+
107
+ $info = (object)array('status' => 'pending', 'total' => 0, 'succeeded' => 0, 'pending' => 0, 'same' => 0, 'failed' => 0, 'totalSize'=> 0, 'totalOptimizedSize'=> 0, 'todo' => null);
108
+ $files = scandir($path);
109
+ $ignore = $this->ignored($exclude);
110
+
111
+ foreach($files as $file) {
112
+ $filePath = $path . '/' . $file;
113
+ $targetFilePath = $persistPath . '/' . $file;
114
+ if (in_array($file, $ignore)
115
+ || (!ShortPixel::isProcessable($file) && !is_dir($filePath))
116
+ || isset($dataArr[$file]) && $dataArr[$file]->status == 'deleted'
117
+ ) {
118
+ continue;
119
+ }
120
+ if (is_dir($filePath)) {
121
+ if(!$recurse || $recurseDepth <= 0) continue;
122
+ $subInfo = $this->info($filePath, $recurse, $fileList, $exclude, $targetFilePath, $recurseDepth - 1);
123
+ if($subInfo->status == 'error') {
124
+ $info = $subInfo;
125
+ break;
126
+ }
127
+ $info->total += $subInfo->total;
128
+ $info->succeeded += $subInfo->succeeded;
129
+ $info->pending += $subInfo->pending;
130
+ $info->same += $subInfo->same;
131
+ $info->failed += $subInfo->failed;
132
+ $info->totalSize += $subInfo->totalSize;
133
+ $info->totalOptimizedSize += $subInfo->totalOptimizedSize;
134
+ }
135
+ else {
136
+ $info->total++;
137
+ if(!isset($dataArr[$file]) || $dataArr[$file]->status == 'pending') {
138
+ $info->pending++;
139
+ $this->logger->log(SPLog::PRODUCER_PERSISTER, "TextPersister->info - PENDING STATUS: $path/$file");
140
+ }
141
+ elseif( $dataArr[$file]->status == 'success' && $this->isChanged($dataArr[$file], $file, $persistPath, $path)
142
+ // || ($dataArr[$file]->status == 'skip' && ($dataArr[$file]->retries <= ShortPixel::MAX_RETRIES || $retrySkipped))) {
143
+ || ($dataArr[$file]->status == 'skip' && $retrySkipped)) {
144
+ //file changed since last optimized, mark it as pending
145
+ $dataArr[$file]->status = 'pending';
146
+ if($dataArr[$file]->status == 'skip' && $retrySkipped) {
147
+ $dataArr[$file]->retries = 0;
148
+ } else {
149
+ $this->logger->log(SPLog::PRODUCER_PERSISTER, "TextPersister->info - CHANGED - REVERT TO PENDING: $path/$file");
150
+ }
151
+ $this->updateMeta($dataArr[$file], $fp);
152
+ $info->pending++;
153
+ }
154
+ elseif($dataArr[$file]->status == 'success') {
155
+ if($dataArr[$file]->percent > 0) {
156
+ $info->succeeded++;
157
+ $info->totalOptimizedSize += $dataArr[$file]->optimizedSize;
158
+ $info->totalSize += round(100.0 * $dataArr[$file]->optimizedSize / (100.0 - $dataArr[$file]->percent));
159
+ } else {
160
+ $info->same++;
161
+ }
162
+ }
163
+ elseif($dataArr[$file]->status == 'skip'){
164
+ $info->failed++;
165
+ }
166
+ }
167
+ if($fileList) $info->fileList = $dataArr;
168
+ }
169
+
170
+ if(isset($info->pending) && $info->pending == 0 && $info->status !== 'error') {
171
+ $info->status = 'success';
172
+ }
173
+ if($info->status !== 'error') {
174
+ $info->todo = $this->getTodoInternal($files, $dataArr, $fp, $path, 1, $exclude, $persistPath, ShortPixel::CLIENT_MAX_BODY_SIZE, $recurseDepth);
175
+ }
176
+ }
177
+ else {
178
+ if(!file_exists($persistPath)) {
179
+ throw new ClientException("File not found: $persistPath", -15);
180
+ }
181
+ $persistFolder = dirname($persistPath);
182
+ $meta = $toClose = false;
183
+ try {
184
+ $toClose = $this->openMetaFileIfNeeded($persistFolder);
185
+ $meta = $this->findMeta($persistPath);
186
+ } catch(ClientException $e) {
187
+ if(is_dir($persistFolder) && file_exists($persistFolder . '/' . ShortPixel::opt("persist_name"))) {
188
+ throw $e;
189
+ }
190
+ }
191
+
192
+ if(!$meta) {
193
+ $info = (object)array('status' => 'pending');
194
+ } else {
195
+ $info = (object)array('status' => $meta->getStatus());
196
+ }
197
+
198
+ }
199
+ }
200
+ catch(ClientException $e) {
201
+ $info = (object)array('status' => 'error', 'message' => $e->getMessage(), 'code' => $e->getCode());
202
+ }
203
+ catch(\Exception $e) { //that should've been a finally but we need to be PHP5.4 compatible...
204
+ if($toClose) {
205
+ $this->closeMetaFile($persistFolder);
206
+ }
207
+ throw $e;
208
+ }
209
+ if($toClose) {
210
+ $this->closeMetaFile($persistFolder);
211
+ }
212
+ return $info;
213
+ }
214
+
215
+ function getTodo($path, $count, $exclude = array(), $persistPath = false, $maxTotalFileSizeMb = ShortPixel::CLIENT_MAX_BODY_SIZE, $recurseDepth = PHP_INT_MAX)
216
+ {
217
+ if(!file_exists($path) || !is_dir($path)) {
218
+ $this->logger->log(SPLog::PRODUCER_PERSISTER, "TextPersister->getTodo - file not found or not a directory: $path");
219
+ return array();
220
+ }
221
+ if(!$persistPath) {$persistPath = $path;}
222
+
223
+ $toClose = $this->openMetaFileIfNeeded($persistPath);
224
+ $fp = $this->getMetaFile($persistPath);
225
+
226
+ $files = scandir($path);
227
+ $dataArr = $this->readMetaFile($fp);
228
+
229
+ $ret = $this->getTodoInternal($files, $dataArr, $fp, $path, $count, $exclude, $persistPath, $maxTotalFileSizeMb, $recurseDepth);
230
+
231
+ if($toClose) { $this->closeMetaFile($persistPath); }
232
+
233
+ if(count($ret->files) + count($ret->filesPending) + $ret->filesWaiting == 0) {
234
+ $this->logger->logFirst($path, SPLog::PRODUCER_PERSISTER, "TextPersister->getTodo - FOR $path RETURN NONE");
235
+ } else {
236
+ $this->logger->clearLogged(SPLog::PRODUCER_PERSISTER, $path);
237
+ $this->logger->log(SPLog::PRODUCER_PERSISTER, "TextPersister->getTodo - FOR $path RETURN", $ret);
238
+ }
239
+ return $ret;
240
+ }
241
+
242
+
243
+ protected function getTodoInternal(&$files, &$dataArr, $fp, $path, $count, $exclude, $persistPath, $maxTotalFileSizeMb, $recurseDepth)
244
+ {
245
+ $results = array();
246
+ $pendingURLs = array();
247
+ $ignore = $this->ignored($exclude);
248
+ $remain = $count;
249
+ $maxTotalFileSize = $maxTotalFileSizeMb * pow(1024, 2);
250
+ $totalFileSize = 0;
251
+ $filesWaiting = 0;
252
+ foreach($files as $file) {
253
+ $filePath = $path . '/' . $file;
254
+ $targetPath = $persistPath . '/' . $file;
255
+ if(in_array($file, $ignore)) {
256
+ continue; //and do not log
257
+ }
258
+ if(!file_exists($filePath)) {
259
+ continue; // strange but found this for a client..., on windows: HS ID 711715228
260
+ }
261
+ if( (!ShortPixel::isProcessable($file) && !is_dir($filePath))
262
+ || isset($dataArr[$file]) && $dataArr[$file]->status == 'deleted'
263
+ || isset($dataArr[$file])
264
+ && ( $dataArr[$file]->status == 'success' && !$this->isChanged($dataArr[$file], $file, $persistPath, $path)
265
+ || $dataArr[$file]->status == 'skip') ) {
266
+ if(!isset($dataArr[$file]) || $dataArr[$file]->status !== 'success')
267
+ $this->logger->logFirst($filePath, SPLog::PRODUCER_PERSISTER, "TextPersister->getTodo - SKIPPING $path/$file - status " . (isset($dataArr[$file]) ? $dataArr[$file]->status : "not processable"));
268
+ continue;
269
+ }
270
+ //if retried too many times recently {
271
+ if(isset($dataArr[$file]) && $dataArr[$file]->status == 'pending') {
272
+ $retries = $dataArr[$file]->retries;
273
+ //over 3 retries wait a minute for each, over 5 retries 2 min. for each, over 10 retries 5 min for each, over 10 retries, 10 min. for each.
274
+ $delta = max(0, $retries - 2) * 60 + max(0, $retries - 5) * 60 + max(0, $retries - 10) * 180 + max(0, $retries - 20) * 450;
275
+ if($dataArr[$file]->changeDate > time() - $delta) {
276
+ $filesWaiting++;
277
+ $this->logger->logFirst($filePath, SPLog::PRODUCER_PERSISTER, "TextPersister->getTodo - TOO MANY RETRIES for $file");
278
+ continue;
279
+ }
280
+ }
281
+ if(is_dir($filePath)) {
282
+ if($recurseDepth <= 0) continue;
283
+ if(!isset($dataArr[$file])) {
284
+ $dataArr[$file] = $this->newMeta($filePath);
285
+ $dataArr[$file]->filePos = $this->appendMeta($dataArr[$file], $fp);
286
+ }
287
+ $resultsSubfolder = $this->cache->fetch($filePath);
288
+ if(!$resultsSubfolder) {
289
+ $resultsSubfolder = $this->getTodo($filePath, $count, $exclude, $targetPath, $maxTotalFileSizeMb, $recurseDepth - 1);
290
+ if(!count($resultsSubfolder->files)) {
291
+ //cache the folders with nothing to do.
292
+ $this->logger->log(SPLog::PRODUCER_PERSISTER, "TextPersister->getTodo - Nothing to do for: $filePath. Caching.");
293
+ $this->cache->store($filePath, $resultsSubfolder);
294
+ }
295
+ } else {
296
+ $this->logger->log(SPLog::PRODUCER_PERSISTER, "TextPersister->getTodo - Cache says nothing to do for: $filePath");
297
+ }
298
+ if(count($resultsSubfolder->files)) {
299
+ return $resultsSubfolder;
300
+ } elseif($dataArr[$file]->status != 'success' && !$resultsSubfolder->filesWaiting) {//otherwise ignore the folder but mark it as succeeded;
301
+ $dataArr[$file]->status = 'success';
302
+ $this->updateMeta($dataArr[$file], $fp);
303
+ }
304
+ } else {
305
+ $toUpdate = false; //will defer updating the record only if we finally add the image (if the image is too large for this set will not add it in the end
306
+ clearstatcache(true, $targetPath);
307
+ if(isset($dataArr[$file])) {
308
+ if( ($dataArr[$file]->status == 'success')
309
+ && (filesize($targetPath) !== $dataArr[$file]->optimizedSize)) {
310
+ // a file with the wrong size
311
+ $dataArr[$file]->status = 'pending';
312
+ $dataArr[$file]->optimizedSize = 0;
313
+ $dataArr[$file]->changeDate = time();
314
+ $toUpdate = true;
315
+ if(time() - strtotime($dataArr[$file]->changeDate) < 1800) { //need to refresh the file processing on the server
316
+ $this->updateMeta($dataArr[$file], $fp);
317
+ return (object)array('files' => array($filePath), 'filesPending' => array(), 'filesWaiting' => 0, 'refresh' => true);
318
+ }
319
+ }
320
+ elseif($dataArr[$file]->status == 'error') {
321
+ if($dataArr[$file]->retries >= ShortPixel::MAX_RETRIES) {
322
+ $dataArr[$file]->status = 'skip';
323
+ $this->updateMeta($dataArr[$file], $fp);
324
+ continue;
325
+ } else {
326
+ $dataArr[$file]->retries += 1;
327
+ $toUpdate = true;
328
+ }
329
+ }
330
+
331
+ elseif($dataArr[$file]->status == 'pending' && preg_match("/http[s]{0,1}:\/\/" . Client::API_DOMAIN() . "/", $dataArr[$file]->message)) {
332
+ //elseif($dataArr[$file]->status == 'pending' && strpos($dataArr[$file]->message, str_replace("https://", "http://",\ShortPixel\Client::API_URL())) === 0) {
333
+ //the file is already uploaded and the call should be made with the existent URL on the optimization server
334
+ $apiURL = $dataArr[$file]->message;
335
+ $pendingURLs[$apiURL] = $filePath;
336
+ }
337
+ }
338
+ elseif(!isset($dataArr[$file])) {
339
+ $dataArr[$file] = $this->newMeta($filePath);
340
+ $dataArr[$file]->filePos = $this->appendMeta($dataArr[$file], $fp);
341
+ }
342
+
343
+ clearstatcache(true, $filePath);
344
+ if(filesize($filePath) + $totalFileSize > $maxTotalFileSize){
345
+ if(filesize($filePath) > $maxTotalFileSize) { //skip this as it won't ever be selected with current settings
346
+ $dataArr[$file]->status = 'skip';
347
+ if(filesize($filePath) > ShortPixel::CLIENT_MAX_BODY_SIZE * pow(1024, 2)) {
348
+ $dataArr[$file]->retries = 99;
349
+ }
350
+ $dataArr[$file]->message = 'File larger than the set limit of ' . $maxTotalFileSizeMb . 'MBytes';
351
+ $this->updateMeta($dataArr[$file], $fp); //this one is too big, we skipped it, just continue with next.
352
+ }
353
+ continue; //the total file size would exceed the limit so leave this image out for now. If it's not too large by itself, will take it in the next pass.
354
+ }
355
+ if($toUpdate) {
356
+ $this->updateMeta($dataArr[$file], $fp);
357
+ }
358
+ $results[] = $filePath;
359
+ $totalFileSize += filesize($filePath);
360
+ $remain--;
361
+
362
+ if($remain <= 0) {
363
+ break;
364
+ }
365
+ }
366
+ }
367
+
368
+ return (object)array('files' => $results, 'filesPending' => $pendingURLs, 'filesWaiting' => $filesWaiting, 'refresh' => false);
369
+ }
370
+ /**
371
+ * @param $data - the .shortpixel metadata
372
+ * @param $file - the file basename
373
+ * @param $persistPath - the target path for the optimized files and for the .shortpixel metadata
374
+ * @param $sourcePath - the path of the original images
375
+ * @return bool true if the image is optimized but needs to be reoptimized because it changed
376
+ */
377
+ protected function isChanged($data, $file, $persistPath, $sourcePath ) {
378
+ clearstatcache(true, $sourcePath);
379
+ return $persistPath === $sourcePath && filesize($sourcePath . '/' . $file) != $data->optimizedSize
380
+ || $persistPath !== $sourcePath && $data->originalSize > 0 && filesize($sourcePath . '/' . $file) != $data->originalSize;
381
+ }
382
+
383
+ function getNextTodo($path, $count)
384
+ {
385
+ // TODO: Implement getNextTodo() method.
386
+ }
387
+
388
+ function doneGet()
389
+ {
390
+ // TODO: Implement doneGet() method.
391
+ }
392
+
393
+ function getOptimizationData($path)
394
+ {
395
+ // TODO: Implement getOptimizationData() method.
396
+ }
397
+
398
+ function setPending($path, $optData) {
399
+ return $this->setStatus($path, $optData, 'pending');
400
+ }
401
+
402
+ function setOptimized($path, $optData = array()) {
403
+ return $this->setStatus($path, $optData, 'success');
404
+ }
405
+
406
+ function setFailed($path, $optData) {
407
+ return $this->setStatus($path, $optData, 'error');
408
+ }
409
+
410
+ function setSkipped($path, $optData) {
411
+ return $this->setStatus($path, $optData, 'skip');
412
+ }
413
+
414
+ protected function setStatus($path, $optData, $status) {
415
+ $toClose = $this->openMetaFileIfNeeded(dirname($path));
416
+ $fp = $this->getMetaFile(dirname($path));
417
+
418
+ $meta = $this->findMeta($path);
419
+ if($meta) {
420
+ $meta->retries++;
421
+ $meta->changeDate = time();
422
+ } else {
423
+ $meta = $this->newMeta($path);
424
+ }
425
+ $meta->status = $status == 'error' ? $meta->retries > ShortPixel::MAX_RETRIES ? 'skip' : 'pending' : $status;
426
+ $metaArr = array_merge((array)$meta, $optData);
427
+ if(isset($meta->filePos)) {
428
+ $this->updateMeta((object)$metaArr, $fp, false);
429
+ } else {
430
+ $this->appendMeta((object)$metaArr, $fp, false);
431
+ }
432
+
433
+ if($toClose) {
434
+ $this->closeMetaFile(dirname($path));
435
+ }
436
+ return $meta->status;
437
+ }
438
+
439
+ protected function openMetaFileIfNeeded($path) {
440
+ if(isset($this->fp[$path])) {
441
+ fseek($this->fp[$path], 0);
442
+ return false;
443
+ }
444
+ $fp = $this->openMetaFile($path);
445
+ if(!$fp) {
446
+ throw new \Exception("Could not open meta file in folder " . $path . ". Please check permissions.", -14);
447
+ }
448
+ $this->fp[$path] = $fp;
449
+ return true;
450
+ }
451
+
452
+ protected function getMetaFile($path) {
453
+ return $this->fp[$path];
454
+ }
455
+
456
+ protected function closeMetaFile($path) {
457
+ if(isset($this->fp[$path])) {
458
+ $fp = $this->fp[$path];
459
+ unset($this->fp[$path]);
460
+ fclose($fp);
461
+ }
462
+ }
463
+
464
+ protected function readMetaFile($fp) {
465
+ $dataArr = array(); $err = false;
466
+ for ($i = 0; ($line = fgets($fp)) !== FALSE; $i++) {
467
+ $data = $this->parse($line);
468
+ if($data) {
469
+ $data->filePos = $i;
470
+ if(isset($dataArr[$data->file])) {
471
+ $err = true; //found situations where a line was duplicated, will rewrite but take only the first
472
+ } else {
473
+ $dataArr[$data->file] = $data;
474
+ }
475
+ } else {
476
+ $err = true;
477
+ }
478
+ }
479
+ if($err) { //at least one error found in the .shortpixel file, rewrite it
480
+ fseek($fp, 0);
481
+ ftruncate($fp, 0);
482
+ foreach($dataArr as $meta) {
483
+ fwrite($fp, $this->assemble($meta));
484
+ fwrite($fp, $line . "\r\n");
485
+ }
486
+ }
487
+ return $dataArr;
488
+ }
489
+
490
+ protected function openMetaFile($path, $type = 'update') {
491
+ $metaFile = $path . '/' . ShortPixel::opt("persist_name");
492
+ if(!is_dir($path) && !@mkdir($path, 0777, true)) { //create the folder
493
+ throw new ClientException("The metadata destination path cannot be found. Please check rights", -17);
494
+ }
495
+ $fp = @fopen($metaFile, $type == 'update' ? 'c+' : 'r');
496
+ if(!$fp) {
497
+ if(is_dir($metaFile)) { //saw this for a client
498
+ throw new ClientException("Could not open persistence file $metaFile. There's already a directory with this name.", -16);
499
+ } else {
500
+ throw new ClientException("Could not open persistence file $metaFile. Please check rights.", -16);
501
+ }
502
+ }
503
+ return $fp;
504
+ }
505
+
506
+ protected function findMeta($path) {
507
+ $fp = $this->openMetaFile(dirname($path));
508
+ fseek($fp, 0);
509
+ for ($i = 0; ($line = fgets($fp)) !== FALSE; $i++) {
510
+ $data = $this->parse($line);
511
+ if(!property_exists($data, 'file')) {
512
+ die(var_dump($line));
513
+ }
514
+ if($data->file === \ShortPixel\MB_basename($path)) {
515
+ $data->filePos = $i;
516
+ return $data;
517
+ }
518
+ }
519
+ return false;
520
+ }
521
+
522
+ /**
523
+ * @param $meta
524
+ * @param bool|false $returnPointer - set this to true if need to have the file pointer back afterwards, such as when updating while reading the file line by line
525
+ */
526
+ protected function updateMeta($meta, $fp, $returnPointer = false) {
527
+ if($returnPointer) {
528
+ $crt = ftell($fp);
529
+ }
530
+ fseek($fp, self::LINE_LENGTH * $meta->filePos); // +2 for the \r\n
531
+ fwrite($fp, $this->assemble($meta));
532
+ fflush($fp);
533
+ if($returnPointer) {
534
+ fseek($fp, $crt);
535
+ }
536
+ }
537
+
538
+ /**
539
+ * @param $meta
540
+ * @param bool|false $returnPointer - set this to true if need to have the file pointer back afterwards, such as when updating while reading the file line by line
541
+ */
542
+ protected function appendMeta($meta, $fp, $returnPointer = false) {
543
+ if($returnPointer) {
544
+ $crt = ftell($fp);
545
+ }
546
+ $fstat = fstat($fp);
547
+ fseek($fp, 0, SEEK_END);
548
+ $line = $this->assemble($meta);
549
+ //$ob = $this->parse($line);
550
+ fwrite($fp, $line . "\r\n");
551
+ fflush($fp);
552
+ if($returnPointer) {
553
+ fseek($fp, $crt);
554
+ }
555
+ return $fstat['size'] / self::LINE_LENGTH;
556
+ }
557
+
558
+ protected function newMeta($file) {
559
+ //$this->logger->log(SPLog::PRODUCER_PERSISTER, "newMeta: file $file exists? " . (file_exists($file) ? "Yes" : "No"));
560
+ return (object) array(
561
+ "type" => is_dir($file) ? 'D' : 'I',
562
+ "status" => 'pending',
563
+ "retries" => 0,
564
+ "compressionType" => $this->options['lossy'] == 1 ? 'lossy' : ($this->options['lossy'] == 2 ? 'glossy' : 'lossless'),
565
+ "keepExif" => $this->options['keep_exif'],
566
+ "cmyk2rgb" => $this->options['cmyk2rgb'],
567
+ "resize" => $this->options['resize_width'] ? 1 : 0,
568
+ "resizeWidth" => 0 + $this->options['resize_width'],
569
+ "resizeHeight" => 0 + $this->options['resize_height'],
570
+ "convertto" => $this->options['convertto'],
571
+ "percent" => null,
572
+ "optimizedSize" => null,
573
+ "changeDate" => time(),
574
+ "file" => \ShortPixel\MB_basename($file),
575
+ "message" => '',
576
+ //file does not exist if source is a WebFolder and the optimized images are saved to a different target
577
+ "originalSize" => is_dir($file) || !file_exists($file) ? 0 : filesize($file));
578
+ }
579
+
580
+ protected function parse($line) {
581
+ if(strlen(rtrim($line, "\r\n")) != (self::LINE_LENGTH - 2)) return false;
582
+ $percent = trim(substr($line, 52, 6));
583
+ $optimizedSize = trim(substr($line, 58, 9));
584
+ $originalSize = trim(substr($line, 454, 9));
585
+
586
+ $convertto = trim(substr($line, 42, 10));
587
+ if(is_numeric($convertto)) {
588
+ //convert to string representation
589
+ $conv = [];
590
+ if($convertto | self::FLAG_WEBP) $conv[] = '+webp';
591
+ if($convertto | self::FLAG_AVIF) $conv[] = '+avif';
592
+ $convertto = implode('|', $conv);
593
+ $this->logger->log(SPLog::PRODUCER_PERSISTER, "Convertto $convertto");
594
+ }
595
+
596
+ $ret = (object) array(
597
+ "type" => trim(substr($line, 0, 2)),
598
+ "status" => trim(substr($line, 2, 11)),
599
+ "retries" => trim(substr($line, 13, 2)),
600
+ "compressionType" => trim(substr($line, 15, 9)),
601
+ "keepExif" => trim(substr($line, 24, 2)),
602
+ "cmyk2rgb" => trim(substr($line, 26, 2)),
603
+ "resize" => trim(substr($line, 28, 2)),
604
+ "resizeWidth" => trim(substr($line, 30, 6)),
605
+ "resizeHeight" => trim(substr($line, 36, 6)),
606
+ "convertto" => $convertto,
607
+ "percent" => is_numeric($percent) ? floatval($percent) : 0.0,
608
+ "optimizedSize" => is_numeric($optimizedSize) ? intval($optimizedSize) : 0,
609
+ "changeDate" => strtotime(trim(substr($line, 67, 20))),
610
+ "file" => rtrim(substr($line, 87, 256)), //rtrim because there could be file names starting with a blank!! (had that)
611
+ "message" => trim(substr($line, 343, 111)),
612
+ "originalSize" => is_numeric($originalSize) ? intval($originalSize) : 0,
613
+ );
614
+ if(!in_array($ret->status, self::$ALLOWED_STATUSES) || !$ret->changeDate) {
615
+ return false;
616
+ }
617
+ return $ret;
618
+ }
619
+
620
+ protected function assemble($data) {
621
+ $convertto = 1;
622
+ if(strpos($data->convertto, '+webp') !== false) $convertto |= self::FLAG_WEBP;
623
+ if(strpos($data->convertto, '+avif') !== false) $convertto |= self::FLAG_AVIF;
624
+
625
+ return sprintf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
626
+ str_pad($data->type, 2),
627
+ str_pad($data->status, 11),
628
+ str_pad($data->retries % 100, 2), // for folders, retries can be > 100 so do a sanity check here - we're not actually interested in folder retries
629
+ str_pad($data->compressionType, 9),
630
+ str_pad($data->keepExif, 2),
631
+ str_pad($data->cmyk2rgb, 2),
632
+ str_pad($data->resize, 2),
633
+ str_pad(substr($data->resizeWidth, 0 , 5), 6),
634
+ str_pad(substr($data->resizeHeight, 0 , 5), 6),
635
+ str_pad($convertto, 10),
636
+ str_pad(substr(number_format($data->percent, 2, ".",""),0 , 5), 6),
637
+ str_pad(substr(number_format($data->optimizedSize, 0, ".", ""),0 , 8), 9),
638
+ str_pad(date("Y-m-d H:i:s", $data->changeDate), 20),
639
+ str_pad(substr($data->file, 0, 255), 256),
640
+ str_pad(substr($data->message, 0, 110), 111),
641
+ str_pad(substr(number_format($data->originalSize, 0, ".", ""),0 , 8), 9)
642
+ );
643
+ }
644
+ }
vendor/shortpixel/shortpixel-php/lib/cmdShortpixelOptimize.php ADDED
@@ -0,0 +1,327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Created by: simon
4
+ * Date: 15.11.2016
5
+ * Time: 14:59
6
+ * Usage: cmdShortpixelOptimize.php --apiKey=<your-api-key-here> --folder=/full/path/to/your/images
7
+ * - add --compression=x : 1 for lossy, 2 for glossy and 0 for lossless
8
+ * - add --resize=800x600/[type] where type can be 1 for outer resize (default) and 3 for inner resize
9
+ * - add --backupBase=/full/path/to/your/backup/basedir
10
+ * - add --targetFolder to specify a different destination for the optimized files.
11
+ * - add --webPath=http://yoursites.address/img/folder/ to map the folder to a web URL and have our servers download the images instead of posting them (less heavy on memory for large files)
12
+ * - add --keepExif to keep the EXIF data
13
+ * - add --speeed=x x between 1 and 10 - default is 10 but if you have large images it will eat up a lot of memory when creating the post messages so sometimes you might need to lower it. Not needed when using the webPath mapping.
14
+ * - add --verbose parameter for more info during optimization
15
+ * - add --clearLock to clear a lock that's already placed on the folder. BE SURE you know what you're doing, files might get corrupted if the previous script is still running. The locks expire in 6 min. anyway.
16
+ * - add --logLevel for different areas of logging - bitwise flags: 4 for metadata handling, 8 for server comm (add them up to log more areas)
17
+ * - add --cacheTime=[seconds] to cache the folders which have no new image to process. Useful for large folders for which checking at each pass is slowing down the optimization.
18
+ * - add --quiet for no output - TBD
19
+ * - the backup path will be used as parent directory to the backup folder which, if the backup path is outside the optimized folder, will be the basename of the folder, otherwise will be ShortPixelBackup
20
+ * The script will read the .sp-options configuration file and will honour the parameters set there, but the command line parameters take priority
21
+ */
22
+
23
+ ini_set('memory_limit','256M');
24
+ //error_reporting(E_ALL);
25
+ //ini_set('display_errors', 1);
26
+
27
+ require_once("shortpixel-php-req.php");
28
+
29
+ use \ShortPixel\SPLog;
30
+
31
+ $processId = uniqid("CLI");
32
+
33
+ $options = getopt("", array("apiKey::", "folder::", "targetFolder::", "webPath::", "compression::", "resize::", "createWebP", "createAVIF", "keepExif", "speed::", "backupBase::", "verbose", "clearLock", "retrySkipped",
34
+ "exclude::", "recurseDepth::", "logLevel::", "cacheTime::"));
35
+
36
+ $verbose = isset($options["verbose"]) ? (isset($options["logLevel"]) ? $options["logLevel"] : 0) | SPLog::PRODUCER_CMD_VERBOSE : 0;
37
+ $logger = SPLog::Init($processId, $verbose | SPLog::PRODUCER_CMD, SPLog::TARGET_CONSOLE, false, ($verbose ? SPLog::FLAG_MEMORY : SPLog::FLAG_NONE));
38
+ $logger->log(SPLog::PRODUCER_CMD_VERBOSE, "ShortPixel CLI version " . \ShortPixel\ShortPixel::VERSION);
39
+
40
+ $logger->log(SPLog::PRODUCER_CMD_VERBOSE, "ShortPixel Logging VERBOSE" . ($verbose & SPLog::PRODUCER_PERSISTER ? ", PERSISTER" : "") . ($verbose & SPLog::PRODUCER_CLIENT ? ", CLIENT" : ""));
41
+
42
+ $apiKey = isset($options["apiKey"]) ? $options["apiKey"] : false;
43
+ $folder = isset($options["folder"]) ? verifyFolder($options["folder"]) : false;
44
+ $targetFolder = isset($options["targetFolder"]) ? verifyFolder($options["targetFolder"], true) : $folder;
45
+ $webPath = isset($options["webPath"]) ? filter_var($options["webPath"], FILTER_VALIDATE_URL) : false;
46
+ $compression = isset($options["compression"]) ? intval($options["compression"]) : false;
47
+ $resizeRaw = isset($options["resize"]) ? $options["resize"] : false;
48
+ $createWebP = isset($options["createWebP"]);
49
+ $createAVIF = isset($options["createAVIF"]);
50
+ $keepExif = isset($options["keepExif"]);
51
+ $speed = isset($options["speed"]) ? intval($options["speed"]) : false;
52
+ $bkBase = isset($options["backupBase"]) ? verifyFolder($options["backupBase"]) : false;
53
+ $clearLock = isset($options["clearLock"]);
54
+ $retrySkipped = isset($options["retrySkipped"]);
55
+ $exclude = isset($options["exclude"]) ? explode(",", $options["exclude"]) : array();
56
+ $recurseDepth = isset($options["recurseDepth"]) && is_numeric($options["recurseDepth"]) && $options["recurseDepth"] >= 0 ? $options["recurseDepth"] : PHP_INT_MAX;
57
+ $cacheTime = isset($options["cacheTime"]) && is_numeric($options["cacheTime"]) && $options["cacheTime"] >= 0 ? $options["cacheTime"] : 0;
58
+
59
+ if(!function_exists('curl_version')) {
60
+ $logger->bye(SPLog::PRODUCER_CMD, "cURL is not enabled. ShortPixel needs Curl to send the images to optimization and retrieve the results. Please enable cURL and retry.");
61
+ } elseif($verbose) {
62
+ $ver = curl_version();
63
+ $logger->log(SPLog::PRODUCER_CMD_VERBOSE, "cURL version: " . $ver['version']);
64
+ }
65
+
66
+ if($webPath === false && isset($options["webPath"])) {
67
+ $logger->bye(SPLog::PRODUCER_CMD, "The Web Path specified is invalid - " . $options["webPath"]);
68
+ }
69
+
70
+ $bkFolder = $bkFolderRel = false;
71
+ if($bkBase) {
72
+ if(is_dir($bkBase)) {
73
+ $bkBase = trailingslashit($bkBase);
74
+ $bkFolder = $bkBase . (strpos($bkBase, trailingslashit($folder)) === 0 ? 'ShortPixelBackups' : basename($folder) . (strpos($bkBase, trailingslashit(dirname($folder))) === 0 ? "_SP_BKP" : "" ));
75
+ $bkFolderRel = \ShortPixel\Settings::pathToRelative($bkFolder, $targetFolder);
76
+ } else {
77
+ $logger->bye(SPLog::PRODUCER_CMD, "Backup path does not exist ($bkFolder)");
78
+ }
79
+ }
80
+
81
+ //handle the ctrl+C
82
+ if (function_exists('pcntl_signal')) {
83
+ declare(ticks=1); // PHP internal, make signal handling work
84
+ pcntl_signal(SIGINT, 'spCmdSignalHandler');
85
+ }
86
+
87
+ //sanity checks
88
+ if(!$apiKey || strlen($apiKey) != 20 || !ctype_alnum($apiKey)) {
89
+ $logger->bye(SPLog::PRODUCER_CMD, "Please provide a valid API Key");
90
+ }
91
+
92
+ if(!$folder || strlen($folder) == 0) {
93
+ $logger->bye(SPLog::PRODUCER_CMD, "Please specify a folder to optimize");
94
+ }
95
+
96
+ if($targetFolder != $folder) {
97
+ if(strpos($targetFolder, trailingslashit($folder)) === 0) {
98
+ $logger->bye(SPLog::PRODUCER_CMD, "Target folder cannot be a subfolder of the source folder. ( $targetFolder $folder)");
99
+ } elseif (strpos($folder, trailingslashit($targetFolder)) === 0) {
100
+ $logger->bye(SPLog::PRODUCER_CMD, "Target folder cannot be a parent folder of the source folder.");
101
+ } else {
102
+ @mkdir($targetFolder, 0777, true);
103
+ }
104
+ }
105
+
106
+ $notifier = \ShortPixel\notify\ProgressNotifier::constructNotifier($folder);
107
+ $logger->log(SPLog::PRODUCER_CMD_VERBOSE, "Using notifier: " . get_class($notifier));
108
+
109
+ try {
110
+ //check if the folder is not locked by another ShortPixel process
111
+ $splock = new \ShortPixel\Lock($processId, $targetFolder, $clearLock);
112
+ try {
113
+ $splock->lock();
114
+ } catch(\Exception $ex) {
115
+ $logger->log(SPLog::PRODUCER_CMD_VERBOSE, "Waiting for lock...");
116
+ $splock->requestLock("CLI");
117
+ $logger->log(SPLog::PRODUCER_CMD_VERBOSE, "Lock aquired");
118
+ }
119
+
120
+ $logger->log(SPLog::PRODUCER_CMD, "Starting to optimize folder $folder using API Key $apiKey ...");
121
+
122
+ ShortPixel\setKey($apiKey);
123
+
124
+ //try to get optimization options from the folder .sp-options
125
+ $optionsHandler = new \ShortPixel\Settings();
126
+ $sourceOptions = $optionsHandler->readOptions($folder);
127
+ $targetOptions = $optionsHandler->readOptions($targetFolder);
128
+ $folderOptions = array_merge(is_array($sourceOptions) ? $sourceOptions : [], is_array($targetOptions) ? $targetOptions : []);
129
+ if(count($folderOptions)) {
130
+ $logger->log(SPLog::PRODUCER_CMD_VERBOSE, "Options from .sp-options file: ", $folderOptions);
131
+ }
132
+
133
+ if((!isset($webPath) || !$webPath) && isset($folderOptions["base_url"]) && strlen($folderOptions["base_url"])) {
134
+ $webPath = $folderOptions["base_url"];
135
+ $logger->log(SPLog::PRODUCER_CMD_VERBOSE, "Using Web Path from settings: $webPath");
136
+ }
137
+
138
+ // ********************* OPTIMIZATION OPTIONS FROM COMMAND LINE TAKE PRECEDENCE *********************
139
+ $overrides = array();
140
+ if($compression !== false) {
141
+ $overrides['lossy'] = $compression;
142
+ }
143
+ if($resizeRaw !== false) {
144
+ $tmp = explode("/", $resizeRaw);
145
+ $resizeType = (count($tmp) == 2) && ($tmp[1] == 3) ? 3 : 1;
146
+ $sizes = explode("x", $tmp[0]);
147
+ if(count($sizes) == 2 and is_numeric($sizes[0]) && is_numeric($sizes[1])) {
148
+ $overrides['resize'] = $resizeType;
149
+ $overrides['resize_width'] = $sizes[0];
150
+ $overrides['resize_height'] = $sizes[1];
151
+ $logger->log(SPLog::PRODUCER_CMD_VERBOSE, "Resize type: " . ($resizeType == 3 ? "inner" : "outer") . ", width: {$overrides['resize_width']}, height: {$overrides['resize_height']}");
152
+ } else {
153
+ $splock->unlock();
154
+ $logger->bye(SPLog::PRODUCER_CMD, "Malformed parameter --resize, should be --resize=[width]x[height]/[type] type being 1 for outer and 3 for inner");
155
+ }
156
+ }
157
+ if($createWebP !== false) {
158
+ $overrides['convertto'] = '+webp';
159
+ }
160
+ if($createAVIF !== false) {
161
+ $overrides['convertto'] = (strlen($overrides['convertto']) ? $overrides['convertto'] . '|' : '') . '+avif';
162
+ }
163
+ if($keepExif !== false) {
164
+ $overrides['keep_exif'] = 1;
165
+ }
166
+
167
+ if($bkFolderRel) {
168
+ $overrides['backup_path'] = $bkFolderRel;
169
+ }
170
+ if(!count($exclude) && isset($folderOptions["exclude"]) && strlen($folderOptions["exclude"])) {
171
+ $exclude = $folderOptions["exclude"];
172
+ }
173
+ $optimizationOptions = array_merge($folderOptions, $overrides, array("persist_type" => "text", "notify_progress" => true, "cache_time" => $cacheTime));
174
+ $logger->log(SPLog::PRODUCER_CMD_VERBOSE, "Using OPTIONS: ", $optimizationOptions);
175
+ \ShortPixel\ShortPixel::setOptions($optimizationOptions);
176
+
177
+ $imageCount = $failedImageCount = $sameImageCount = 0;
178
+ $tries = 0;
179
+ $consecutiveExceptions = 0;
180
+ $folderOptimized = false;
181
+ $targetFolderParam = ($targetFolder !== $folder ? $targetFolder : false);
182
+
183
+ $splock->setTimeout(7200);
184
+ $splock->lock();
185
+ $info = \ShortPixel\folderInfo($folder, true, false, $exclude, $targetFolderParam, $recurseDepth, $retrySkipped);
186
+ $splock->setTimeout(360);
187
+ $splock->lock();
188
+ $notifier->recordProgress($info, true);
189
+
190
+ if($info->status == 'error') {
191
+ $splock->unlock();
192
+ $logger->bye(SPLog::PRODUCER_CMD, "Error: " . $info->message . " (Code: " . $info->code . ")");
193
+ }
194
+
195
+ $logger->log(SPLog::PRODUCER_CMD, "Folder has " . $info->total . " files, " . $info->succeeded . " optimized, " . $info->pending . " pending, " . $info->same . " don't need optimization, " . $info->failed . " failed.");
196
+
197
+ if($info->status == "success") {
198
+ $logger->log(SPLog::PRODUCER_CMD, "Congratulations, the folder is optimized.");
199
+ }
200
+ else {
201
+ $lockTimeout = 360;
202
+ while ($tries < 100000) {
203
+ $crtImageCount = 0;
204
+ $tempus = time();
205
+ try {
206
+ if ($webPath) {
207
+ $result = \ShortPixel\fromWebFolder($folder, $webPath, $exclude, $targetFolderParam, $recurseDepth)->wait(300)->toFiles($targetFolder);
208
+ } else {
209
+ $speed = ($speed ? $speed : \ShortPixel\ShortPixel::MAX_ALLOWED_FILES_PER_CALL);
210
+ $result = \ShortPixel\fromFolder($folder, $speed, $exclude, $targetFolderParam, \ShortPixel\ShortPixel::CLIENT_MAX_BODY_SIZE, $recurseDepth)->wait(300)->toFiles($targetFolder);
211
+ }
212
+ if(time() - $tempus > $lockTimeout - 100) {
213
+ //increase the timeout of the lock file if a pass takes too long (for large folders)
214
+ $lockTimeout += time() - $tempus;
215
+ $logger->log(SPLog::PRODUCER_CMD_VERBOSE, "Increasing lock timeout to: $lockTimeout");
216
+ $splock->setTimeout($lockTimeout);
217
+ }
218
+ } catch (\ShortPixel\ClientException $ex) {
219
+ if ($ex->getCode() == \ShortPixel\ClientException::NO_FILE_FOUND || $ex->getCode() == 2) {
220
+ break;
221
+ } else {
222
+ $logger->log(SPLog::PRODUCER_CMD, "ClientException: " . $ex->getMessage() . " (CODE: " . $ex->getCode() . ")");
223
+ $tries++;
224
+ if(++$consecutiveExceptions > \ShortPixel\ShortPixel::MAX_RETRIES) {
225
+ $logger->log(SPLog::PRODUCER_CMD, "Too many exceptions. Exiting.");
226
+ break;
227
+ }
228
+ $splock->lock();
229
+ continue;
230
+ }
231
+ }
232
+ $tries++;
233
+ $consecutiveExceptions = 0;
234
+
235
+ if (count($result->succeeded) > 0) {
236
+ $crtImageCount += count($result->succeeded);
237
+ $imageCount += $crtImageCount;
238
+ } elseif (count($result->failed)) {
239
+ $crtImageCount += count($result->failed);
240
+ $failedImageCount += count($result->failed);
241
+ } elseif (count($result->same)) {
242
+ $crtImageCount += count($result->same);
243
+ $sameImageCount += count($result->same);
244
+ } elseif (count($result->pending)) {
245
+ $crtImageCount += count($result->pending);
246
+ }
247
+ if ($verbose) {
248
+ $msg = "\n" . date("Y-m-d H:i:s") . " PASS $tries : " . count($result->succeeded) . " succeeded, " . count($result->pending) . " pending, " . count($result->same) . " don't need optimization, " . count($result->failed) . " failed\n";
249
+ foreach ($result->succeeded as $item) {
250
+ $msg .= " - " . $item->SavedFile . " " . $item->Status->Message . " ("
251
+ . ($item->PercentImprovement > 0 ? "Reduced by " . $item->PercentImprovement . "%" : "") . ($item->PercentImprovement < 5 ? " - Bonus processing" : ""). ")\n";
252
+ }
253
+ foreach ($result->pending as $item) {
254
+ $msg .= " - " . $item->SavedFile . " " . $item->Status->Message . "\n";
255
+ }
256
+ foreach ($result->same as $item) {
257
+ $msg .= " - " . $item->SavedFile . " " . $item->Status->Message . " (Bonus processing)\n";
258
+ }
259
+ foreach ($result->failed as $item) {
260
+ $msg .= " - " . $item->SavedFile . " " . $item->Status->Message . "\n";
261
+ }
262
+ $logger->logRaw($msg . "\n");
263
+ } else {
264
+ $logger->logRaw(str_pad("", $crtImageCount, "#"));
265
+ }
266
+ //if no files were processed in this pass, the folder is done
267
+ if ($crtImageCount == 0) {
268
+ $folderOptimized = (!isset($item) || $item->Status->Code == 2);
269
+ break;
270
+ }
271
+ //check & refresh the lock file
272
+ $splock->lock();
273
+ }
274
+
275
+ $logger->log(SPLog::PRODUCER_CMD, "This pass: $imageCount images optimized, $sameImageCount don't need optimization, $failedImageCount failed to optimize." . ($folderOptimized ? " Congratulations, the folder is optimized.":""));
276
+ if ($crtImageCount > 0) $logger->log(SPLog::PRODUCER_CMD, "Images still pending, please relaunch the script to continue.");
277
+ echo("\n");
278
+ }
279
+ } catch(\Exception $e) {
280
+ //record progress only if it's not a lock exception.
281
+ if($e->getCode() != -19) {
282
+ $notifier->recordProgress((object)array("status" => (object)array("code" => $e->getCode(), "message" => $e->getMessage())), true);
283
+ }
284
+ $logger->log(SPLog::PRODUCER_CMD, "\n" . $e->getMessage() . "( code: " . $e->getCode() . " type: " . get_class($e) . " )" . "\n");
285
+ }
286
+
287
+ //cleanup the lock file
288
+ $splock->unlock();
289
+
290
+ function verifyFolder($folder, $create = false)
291
+ {
292
+ global $logger;
293
+ $folder = rtrim($folder, '/');
294
+ $suffix = '';
295
+ if($create) {
296
+ $suffix = '/' . basename($folder);
297
+ $folder = dirname($folder);
298
+ }
299
+ $folder = (realpath($folder) ? realpath($folder) : $folder);
300
+ if (!is_dir($folder)) {
301
+ if (substr($folder, 0, 2) == "./") {
302
+ $folder = str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . "/" . substr($folder, 2);
303
+ }
304
+ if (!is_dir($folder)) {
305
+ if ((strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && preg_match('/^[a-zA-Z]:\//', $folder) === 0) //it's Windows and no drive letter X - relative path?
306
+ || (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' && substr($folder, 0, 1) !== '/')
307
+ ) { //linux and no / - relative path?
308
+ $folder = str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . "/" . $folder;
309
+ }
310
+ }
311
+ if (!is_dir($folder)) {
312
+ $logger->log(SPLog::PRODUCER_CMD, "The folder $folder does not exist.");
313
+ }
314
+ }
315
+ return str_replace(DIRECTORY_SEPARATOR, '/', $folder . $suffix);
316
+ }
317
+
318
+ function trailingslashit($path) {
319
+ return rtrim($path, '/') . '/';
320
+ }
321
+
322
+ function spCmdSignalHandler($signo)
323
+ {
324
+ global $splock, $logger;
325
+ $splock->unlock();
326
+ $logger->bye(SPLog::PRODUCER_CMD, "Caught interrupt signal, exiting.");
327
+ }
vendor/shortpixel/shortpixel-php/lib/data/shortpixel.crt ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIE/DCCA+SgAwIBAgIQTOYGEQsUbdJjOrgYq+G/QjANBgkqhkiG9w0BAQsFADB1
3
+ MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEpMCcGA1UECxMg
4
+ U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIzAhBgNVBAMTGlN0YXJ0
5
+ Q29tIENsYXNzIDEgQ2xpZW50IENBMB4XDTE2MDQwNzEwNTkzOVoXDTE3MDQwNzEw
6
+ NTkzOVowRDEdMBsGA1UEAwwUc2ltb25Ac2hvcnRwaXhlbC5jb20xIzAhBgkqhkiG
7
+ 9w0BCQEWFHNpbW9uQHNob3J0cGl4ZWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC
8
+ AQ8AMIIBCgKCAQEA6VymIV3QNQ67On0AgvkuntO2u20e7UIkk/WrG+jbf6mfQoyo
9
+ vd18t8VHfGnXAHMLB1SDzu/JDCAe+xgk4Y2uoMnNTA0NkPmqHg1rUKehVhXaAuMb
10
+ d4cKJhkmD3FtlUuAWZB//578nL+VZ2ufj3RON+vufNbePB8coexdkE0mMQPq9paK
11
+ 399o8vQjULFuyRJPXXcVIkfiaS6hhfR2qbtG/0nivVIUi7GTjnqinBbsGUcOO53m
12
+ dSvPvreL5yqDPXORFX/bGa8ssVKxetFnpwyfv4e9+52kZqwWULbCW341uuYq0PfU
13
+ wZQ0HbXFUi08LExULq+5ZetWglcEdQSqSWFCEwIDAQABo4IBtzCCAbMwDgYDVR0P
14
+ AQH/BAQDAgSwMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDAJBgNVHRME
15
+ AjAAMB0GA1UdDgQWBBRWf5A1tltRHE/XkPbfyvqRQSUYLTAfBgNVHSMEGDAWgBQk
16
+ gWw5Yb5JD4+3G0YrySi1J0htaDBvBggrBgEFBQcBAQRjMGEwJAYIKwYBBQUHMAGG
17
+ GGh0dHA6Ly9vY3NwLnN0YXJ0c3NsLmNvbTA5BggrBgEFBQcwAoYtaHR0cDovL2Fp
18
+ YS5zdGFydHNzbC5jb20vY2VydHMvc2NhLmNsaWVudDEuY3J0MDgGA1UdHwQxMC8w
19
+ LaAroCmGJ2h0dHA6Ly9jcmwuc3RhcnRzc2wuY29tL3NjYS1jbGllbnQxLmNybDAf
20
+ BgNVHREEGDAWgRRzaW1vbkBzaG9ydHBpeGVsLmNvbTAjBgNVHRIEHDAahhhodHRw
21
+ Oi8vd3d3LnN0YXJ0c3NsLmNvbS8wRgYDVR0gBD8wPTA7BgsrBgEEAYG1NwECBTAs
22
+ MCoGCCsGAQUFBwIBFh5odHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS9wb2xpY3kwDQYJ
23
+ KoZIhvcNAQELBQADggEBAFbFDY2edKRLAG0sDjOiiGob9F11ImhhIDQLy5/M2djn
24
+ 1D3x9z1sc+U5kTfS7DNdK4K8XDY+TndAE7h+mEB8hKh0+UYkX00VqhdnBWtDopHM
25
+ 2C/3PCyKeCr9YSUIvvSKrOnNsmm7tuiybmd63iQKTe8SWIMJOSZ9i0E8jm03RmKw
26
+ QYW+KzOXsq0hRy/CdOBuVxHvu97TeMqyeH5/S0ChxCPZozr8xxT8+sHgkFy4r11P
27
+ +YADBHflgqrtbfmbcEoQwx3ucRmiBOtSm8IVQhfiPeYcsOIgPfRFfpu55Qa4kvpk
28
+ lP6nlHlkln7XDJsg9i7jHIbyrh9aGGPSMU4mSiEd2Q4=
29
+ -----END CERTIFICATE-----
vendor/shortpixel/shortpixel-php/lib/no-composer.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once("../lib/shortpixel-php-req.php");
3
+
4
+ ShortPixel\setKey("<YOUR API KEY HERE>");
5
+ $tmpFolder = tempnam(sys_get_temp_dir(), "shortpixel-php");
6
+ echo("Temp folder: " . $tmpFolder);
7
+ if(file_exists($tmpFolder)) unlink($tmpFolder);
8
+ mkdir($tmpFolder);
9
+ \ShortPixel\fromUrls("https://shortpixel.com/img/tests/wrapper/shortpixel.png")->refresh()->wait(300)->toFiles($tmpFolder);
10
+ echo("\nSuccessfully saved the optimized image from URL to temp folder.\n");
11
+ \ShortPixel\fromFile(__DIR__ . "/data/cc.jpg")->refresh()->wait(300)->toFiles($tmpFolder);
12
+ echo("\nSuccessfully saved the optimized image from path to temp folder.\n\n");
vendor/shortpixel/shortpixel-php/lib/shortpixel-php-req.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ function __autoload($class_name) {
4
+ require_once("./$class_name.php");
5
+ }
6
+
7
+ use ShortPixel\Persister;
8
+ use ShortPixel\Commander;
9
+ use ShortPixel\Client;
10
+ use ShortPixel\Exception;
11
+ use ShortPixel\Source;
12
+ use ShortPixel\persist\TextPersister;
13
+ use ShortPixel\persist\ExifPersister;
14
+ use ShortPixel\persist\PNGMetadataExtractor;
15
+ use ShortPixel\persist\PNGReader;
16
+ use ShortPixel\Result;
17
+ use ShortPixel;
18
+ */
19
+
20
+ require_once("ShortPixel/Settings.php");
21
+ require_once("ShortPixel/Lock.php");
22
+ require_once("ShortPixel/SPLog.php");
23
+ require_once("ShortPixel/SPCache.php");
24
+
25
+ require_once("ShortPixel/Persister.php");
26
+ require_once("ShortPixel/persist/TextPersister.php");
27
+ require_once("ShortPixel/persist/ExifPersister.php");
28
+ require_once("ShortPixel/persist/PNGMetadataExtractor.php");
29
+ require_once("ShortPixel/persist/PNGReader.php");
30
+
31
+ require_once("ShortPixel/notify/ProgressNotifier.php");
32
+ require_once("ShortPixel/notify/ProgressNotifierMemcache.php");
33
+ require_once("ShortPixel/notify/ProgressNotifierFileQ.php");
34
+
35
+ require_once("ShortPixel/Commander.php");
36
+ require_once("ShortPixel/Client.php");
37
+ require_once("ShortPixel/Exception.php");
38
+ require_once("ShortPixel/Source.php");
39
+ require_once("ShortPixel/Result.php");
40
+ require_once("ShortPixel.php");
41
+
vendor/shortpixel/shortpixel-php/phpunit.xml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!--<phpunit bootstrap="test/helper.php" colors="true">-->
2
+ <phpunit colors="true">
3
+ <testsuites>
4
+ <testsuite name="ShortPixel PHP Test Suite">
5
+ <directory suffix="Test.php">test</directory>
6
+ </testsuite>
7
+ </testsuites>
8
+ <filter>
9
+ <whitelist>
10
+ <directory>lib</directory>
11
+ </whitelist>
12
+ </filter>
13
+ </phpunit>
vendor/symfony/deprecation-contracts/LICENSE ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2020-2021 Fabien Potencier
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is furnished
8
+ to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
vendor/symfony/deprecation-contracts/function.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ if (!function_exists('trigger_deprecation')) {
13
+ /**
14
+ * Triggers a silenced deprecation notice.
15
+ *
16
+ * @param string $package The name of the Composer package that is triggering the deprecation
17
+ * @param string $version The version of the package that introduced the deprecation
18
+ * @param string $message The message of the deprecation
19
+ * @param mixed ...$args Values to insert in the message using printf() formatting
20
+ *
21
+ * @author Nicolas Grekas <p@tchwork.com>
22
+ */
23
+ function trigger_deprecation(string $package, string $version, string $message, ...$args): void
24
+ {
25
+ @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
26
+ }
27
+ }
vendor/symfony/dotenv/.gitattributes ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ /Tests export-ignore
2
+ /phpunit.xml.dist export-ignore
3
+ /.gitattributes export-ignore
4
+ /.gitignore export-ignore
vendor/symfony/dotenv/Dotenv.php ADDED
@@ -0,0 +1,571 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\Dotenv;
13
+
14
+ use Symfony\Component\Dotenv\Exception\FormatException;
15
+ use Symfony\Component\Dotenv\Exception\FormatExceptionContext;
16
+ use Symfony\Component\Dotenv\Exception\PathException;
17
+ use Symfony\Component\Process\Exception\ExceptionInterface as ProcessException;
18
+ use Symfony\Component\Process\Process;
19
+
20
+ /**
21
+ * Manages .env files.
22
+ *
23
+ * @author Fabien Potencier <fabien@symfony.com>
24
+ * @author Kévin Dunglas <dunglas@gmail.com>
25
+ */
26
+ final class Dotenv
27
+ {
28
+ public const VARNAME_REGEX = '(?i:[A-Z][A-Z0-9_]*+)';
29
+ public const STATE_VARNAME = 0;
30
+ public const STATE_VALUE = 1;
31
+
32
+ private $path;
33
+ private $cursor;
34
+ private $lineno;
35
+ private $data;
36
+ private $end;
37
+ private $values;
38
+ private $envKey;
39
+ private $debugKey;
40
+ private $prodEnvs = ['prod'];
41
+ private $usePutenv = false;
42
+
43
+ /**
44
+ * @param string $envKey
45
+ */
46
+ public function __construct($envKey = 'APP_ENV', string $debugKey = 'APP_DEBUG')
47
+ {
48
+ if (\in_array($envKey = (string) $envKey, ['1', ''], true)) {
49
+ trigger_deprecation('symfony/dotenv', '5.1', 'Passing a boolean to the constructor of "%s" is deprecated, use "Dotenv::usePutenv()".', __CLASS__);
50
+ $this->usePutenv = (bool) $envKey;
51
+ $envKey = 'APP_ENV';
52
+ }
53
+
54
+ $this->envKey = $envKey;
55
+ $this->debugKey = $debugKey;
56
+ }
57
+
58
+ /**
59
+ * @return $this
60
+ */
61
+ public function setProdEnvs(array $prodEnvs): self
62
+ {
63
+ $this->prodEnvs = $prodEnvs;
64
+
65
+ return $this;
66
+ }
67
+
68
+ /**
69
+ * @param bool $usePutenv If `putenv()` should be used to define environment variables or not.
70
+ * Beware that `putenv()` is not thread safe, that's why this setting defaults to false
71
+ *
72
+ * @return $this
73
+ */
74
+ public function usePutenv($usePutenv = true): self
75
+ {
76
+ $this->usePutenv = $usePutenv;
77
+
78
+ return $this;
79
+ }
80
+
81
+ /**
82
+ * Loads one or several .env files.
83
+ *
84
+ * @param string $path A file to load
85
+ * @param ...string $extraPaths A list of additional files to load
86
+ *
87
+ * @throws FormatException when a file has a syntax error
88
+ * @throws PathException when a file does not exist or is not readable
89
+ */
90
+ public function load(string $path, string ...$extraPaths): void
91
+ {
92
+ $this->doLoad(false, \func_get_args());
93
+ }
94
+
95
+ /**
96
+ * Loads a .env file and the corresponding .env.local, .env.$env and .env.$env.local files if they exist.
97
+ *
98
+ * .env.local is always ignored in test env because tests should produce the same results for everyone.
99
+ * .env.dist is loaded when it exists and .env is not found.
100
+ *
101
+ * @param string $path A file to load
102
+ * @param string $envKey|null The name of the env vars that defines the app env
103
+ * @param string $defaultEnv The app env to use when none is defined
104
+ * @param array $testEnvs A list of app envs for which .env.local should be ignored
105
+ *
106
+ * @throws FormatException when a file has a syntax error
107
+ * @throws PathException when a file does not exist or is not readable
108
+ */
109
+ public function loadEnv(string $path, string $envKey = null, string $defaultEnv = 'dev', array $testEnvs = ['test']): void
110
+ {
111
+ $k = $envKey ?? $this->envKey;
112
+
113
+ if (is_file($path) || !is_file($p = "$path.dist")) {
114
+ $this->load($path);
115
+ } else {
116
+ $this->load($p);
117
+ }
118
+
119
+ if (null === $env = $_SERVER[$k] ?? $_ENV[$k] ?? null) {
120
+ $this->populate([$k => $env = $defaultEnv]);
121
+ }
122
+
123
+ if (!\in_array($env, $testEnvs, true) && is_file($p = "$path.local")) {
124
+ $this->load($p);
125
+ $env = $_SERVER[$k] ?? $_ENV[$k] ?? $env;
126
+ }
127
+
128
+ if ('local' === $env) {
129
+ return;
130
+ }
131
+
132
+ if (is_file($p = "$path.$env")) {
133
+ $this->load($p);
134
+ }
135
+
136
+ if (is_file($p = "$path.$env.local")) {
137
+ $this->load($p);
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Loads env vars from .env.local.php if the file exists or from the other .env files otherwise.
143
+ *
144
+ * This method also configures the APP_DEBUG env var according to the current APP_ENV.
145
+ *
146
+ * See method loadEnv() for rules related to .env files.
147
+ */
148
+ public function bootEnv(string $path, string $defaultEnv = 'dev', array $testEnvs = ['test']): void
149
+ {
150
+ $p = $path.'.local.php';
151
+ $env = is_file($p) ? include $p : null;
152
+ $k = $this->envKey;
153
+
154
+ if (\is_array($env) && (!isset($env[$k]) || ($_SERVER[$k] ?? $_ENV[$k] ?? $env[$k]) === $env[$k])) {
155
+ $this->populate($env);
156
+ } else {
157
+ $this->loadEnv($path, $k, $defaultEnv, $testEnvs);
158
+ }
159
+
160
+ $_SERVER += $_ENV;
161
+
162
+ $k = $this->debugKey;
163
+ $debug = $_SERVER[$k] ?? !\in_array($_SERVER[$this->envKey], $this->prodEnvs, true);
164
+ $_SERVER[$k] = $_ENV[$k] = (int) $debug || (!\is_bool($debug) && filter_var($debug, \FILTER_VALIDATE_BOOLEAN)) ? '1' : '0';
165
+ }
166
+
167
+ /**
168
+ * Loads one or several .env files and enables override existing vars.
169
+ *
170
+ * @param string $path A file to load
171
+ * @param ...string $extraPaths A list of additional files to load
172
+ *
173
+ * @throws FormatException when a file has a syntax error
174
+ * @throws PathException when a file does not exist or is not readable
175
+ */
176
+ public function overload(string $path, string ...$extraPaths): void
177
+ {
178
+ $this->doLoad(true, \func_get_args());
179
+ }
180
+
181
+ /**
182
+ * Sets values as environment variables (via putenv, $_ENV, and $_SERVER).
183
+ *
184
+ * @param array $values An array of env variables
185
+ * @param bool $overrideExistingVars true when existing environment variables must be overridden
186
+ */
187
+ public function populate(array $values, bool $overrideExistingVars = false): void
188
+ {
189
+ $updateLoadedVars = false;
190
+ $loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? ''));
191
+
192
+ foreach ($values as $name => $value) {
193
+ $notHttpName = 0 !== strpos($name, 'HTTP_');
194
+ // don't check existence with getenv() because of thread safety issues
195
+ if (!isset($loadedVars[$name]) && (!$overrideExistingVars && (isset($_ENV[$name]) || (isset($_SERVER[$name]) && $notHttpName)))) {
196
+ continue;
197
+ }
198
+
199
+ if ($this->usePutenv) {
200
+ putenv("$name=$value");
201
+ }
202
+
203
+ $_ENV[$name] = $value;
204
+ if ($notHttpName) {
205
+ $_SERVER[$name] = $value;
206
+ }
207
+
208
+ if (!isset($loadedVars[$name])) {
209
+ $loadedVars[$name] = $updateLoadedVars = true;
210
+ }
211
+ }
212
+
213
+ if ($updateLoadedVars) {
214
+ unset($loadedVars['']);
215
+ $loadedVars = implode(',', array_keys($loadedVars));
216
+ $_ENV['SYMFONY_DOTENV_VARS'] = $_SERVER['SYMFONY_DOTENV_VARS'] = $loadedVars;
217
+
218
+ if ($this->usePutenv) {
219
+ putenv('SYMFONY_DOTENV_VARS='.$loadedVars);
220
+ }
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Parses the contents of an .env file.
226
+ *
227
+ * @param string $data The data to be parsed
228
+ * @param string $path The original file name where data where stored (used for more meaningful error messages)
229
+ *
230
+ * @return array An array of env variables
231
+ *
232
+ * @throws FormatException when a file has a syntax error
233
+ */
234
+ public function parse(string $data, string $path = '.env'): array
235
+ {
236
+ $this->path = $path;
237
+ $this->data = str_replace(["\r\n", "\r"], "\n", $data);
238
+ $this->lineno = 1;
239
+ $this->cursor = 0;
240
+ $this->end = \strlen($this->data);
241
+ $state = self::STATE_VARNAME;
242
+ $this->values = [];
243
+ $name = '';
244
+
245
+ $this->skipEmptyLines();
246
+
247
+ while ($this->cursor < $this->end) {
248
+ switch ($state) {
249
+ case self::STATE_VARNAME:
250
+ $name = $this->lexVarname();
251
+ $state = self::STATE_VALUE;
252
+ break;
253
+
254
+ case self::STATE_VALUE:
255
+ $this->values[$name] = $this->lexValue();
256
+ $state = self::STATE_VARNAME;
257
+ break;
258
+ }
259
+ }
260
+
261
+ if (self::STATE_VALUE === $state) {
262
+ $this->values[$name] = '';
263
+ }
264
+
265
+ try {
266
+ return $this->values;
267
+ } finally {
268
+ $this->values = [];
269
+ $this->data = null;
270
+ $this->path = null;
271
+ }
272
+ }
273
+
274
+ private function lexVarname(): string
275
+ {
276
+ // var name + optional export
277
+ if (!preg_match('/(export[ \t]++)?('.self::VARNAME_REGEX.')/A', $this->data, $matches, 0, $this->cursor)) {
278
+ throw $this->createFormatException('Invalid character in variable name');
279
+ }
280
+ $this->moveCursor($matches[0]);
281
+
282
+ if ($this->cursor === $this->end || "\n" === $this->data[$this->cursor] || '#' === $this->data[$this->cursor]) {
283
+ if ($matches[1]) {
284
+ throw $this->createFormatException('Unable to unset an environment variable');
285
+ }
286
+
287
+ throw $this->createFormatException('Missing = in the environment variable declaration');
288
+ }
289
+
290
+ if (' ' === $this->data[$this->cursor] || "\t" === $this->data[$this->cursor]) {
291
+ throw $this->createFormatException('Whitespace characters are not supported after the variable name');
292
+ }
293
+
294
+ if ('=' !== $this->data[$this->cursor]) {
295
+ throw $this->createFormatException('Missing = in the environment variable declaration');
296
+ }
297
+ ++$this->cursor;
298
+
299
+ return $matches[2];
300
+ }
301
+
302
+ private function lexValue(): string
303
+ {
304
+ if (preg_match('/[ \t]*+(?:#.*)?$/Am', $this->data, $matches, 0, $this->cursor)) {
305
+ $this->moveCursor($matches[0]);
306
+ $this->skipEmptyLines();
307
+
308
+ return '';
309
+ }
310
+
311
+ if (' ' === $this->data[$this->cursor] || "\t" === $this->data[$this->cursor]) {
312
+ throw $this->createFormatException('Whitespace are not supported before the value');
313
+ }
314
+
315
+ $loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? ($_ENV['SYMFONY_DOTENV_VARS'] ?? '')));
316
+ unset($loadedVars['']);
317
+ $v = '';
318
+
319
+ do {
320
+ if ("'" === $this->data[$this->cursor]) {
321
+ $len = 0;
322
+
323
+ do {
324
+ if ($this->cursor + ++$len === $this->end) {
325
+ $this->cursor += $len;
326
+
327
+ throw $this->createFormatException('Missing quote to end the value');
328
+ }
329
+ } while ("'" !== $this->data[$this->cursor + $len]);
330
+
331
+ $v .= substr($this->data, 1 + $this->cursor, $len - 1);
332
+ $this->cursor += 1 + $len;
333
+ } elseif ('"' === $this->data[$this->cursor]) {
334
+ $value = '';
335
+
336
+ if (++$this->cursor === $this->end) {
337
+ throw $this->createFormatException('Missing quote to end the value');
338
+ }
339
+
340
+ while ('"' !== $this->data[$this->cursor] || ('\\' === $this->data[$this->cursor - 1] && '\\' !== $this->data[$this->cursor - 2])) {
341
+ $value .= $this->data[$this->cursor];
342
+ ++$this->cursor;
343
+
344
+ if ($this->cursor === $this->end) {
345
+ throw $this->createFormatException('Missing quote to end the value');
346
+ }
347
+ }
348
+ ++$this->cursor;
349
+ $value = str_replace(['\\"', '\r', '\n'], ['"', "\r", "\n"], $value);
350
+ $resolvedValue = $value;
351
+ $resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars);
352
+ $resolvedValue = $this->resolveCommands($resolvedValue, $loadedVars);
353
+ $resolvedValue = str_replace('\\\\', '\\', $resolvedValue);
354
+ $v .= $resolvedValue;
355
+ } else {
356
+ $value = '';
357
+ $prevChr = $this->data[$this->cursor - 1];
358
+ while ($this->cursor < $this->end && !\in_array($this->data[$this->cursor], ["\n", '"', "'"], true) && !((' ' === $prevChr || "\t" === $prevChr) && '#' === $this->data[$this->cursor])) {
359
+ if ('\\' === $this->data[$this->cursor] && isset($this->data[$this->cursor + 1]) && ('"' === $this->data[$this->cursor + 1] || "'" === $this->data[$this->cursor + 1])) {
360
+ ++$this->cursor;
361
+ }
362
+
363
+ $value .= $prevChr = $this->data[$this->cursor];
364
+
365
+ if ('$' === $this->data[$this->cursor] && isset($this->data[$this->cursor + 1]) && '(' === $this->data[$this->cursor + 1]) {
366
+ ++$this->cursor;
367
+ $value .= '('.$this->lexNestedExpression().')';
368
+ }
369
+
370
+ ++$this->cursor;
371
+ }
372
+ $value = rtrim($value);
373
+ $resolvedValue = $value;
374
+ $resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars);
375
+ $resolvedValue = $this->resolveCommands($resolvedValue, $loadedVars);
376
+ $resolvedValue = str_replace('\\\\', '\\', $resolvedValue);
377
+
378
+ if ($resolvedValue === $value && preg_match('/\s+/', $value)) {
379
+ throw $this->createFormatException('A value containing spaces must be surrounded by quotes');
380
+ }
381
+
382
+ $v .= $resolvedValue;
383
+
384
+ if ($this->cursor < $this->end && '#' === $this->data[$this->cursor]) {
385
+ break;
386
+ }
387
+ }
388
+ } while ($this->cursor < $this->end && "\n" !== $this->data[$this->cursor]);
389
+
390
+ $this->skipEmptyLines();
391
+
392
+ return $v;
393
+ }
394
+
395
+ private function lexNestedExpression(): string
396
+ {
397
+ ++$this->cursor;
398
+ $value = '';
399
+
400
+ while ("\n" !== $this->data[$this->cursor] && ')' !== $this->data[$this->cursor]) {
401
+ $value .= $this->data[$this->cursor];
402
+
403
+ if ('(' === $this->data[$this->cursor]) {
404
+ $value .= $this->lexNestedExpression().')';
405
+ }
406
+
407
+ ++$this->cursor;
408
+
409
+ if ($this->cursor === $this->end) {
410
+ throw $this->createFormatException('Missing closing parenthesis.');
411
+ }
412
+ }
413
+
414
+ if ("\n" === $this->data[$this->cursor]) {
415
+ throw $this->createFormatException('Missing closing parenthesis.');
416
+ }
417
+
418
+ return $value;
419
+ }
420
+
421
+ private function skipEmptyLines()
422
+ {
423
+ if (preg_match('/(?:\s*+(?:#[^\n]*+)?+)++/A', $this->data, $match, 0, $this->cursor)) {
424
+ $this->moveCursor($match[0]);
425
+ }
426
+ }
427
+
428
+ private function resolveCommands(string $value, array $loadedVars): string
429
+ {
430
+ if (false === strpos($value, '$')) {
431
+ return $value;
432
+ }
433
+
434
+ $regex = '/
435
+ (\\\\)? # escaped with a backslash?
436
+ \$
437
+ (?<cmd>
438
+ \( # require opening parenthesis
439
+ ([^()]|\g<cmd>)+ # allow any number of non-parens, or balanced parens (by nesting the <cmd> expression recursively)
440
+ \) # require closing paren
441
+ )
442
+ /x';
443
+
444
+ return preg_replace_callback($regex, function ($matches) use ($loadedVars) {
445
+ if ('\\' === $matches[1]) {
446
+ return substr($matches[0], 1);
447
+ }
448
+
449
+ if ('\\' === \DIRECTORY_SEPARATOR) {
450
+ throw new \LogicException('Resolving commands is not supported on Windows.');
451
+ }
452
+
453
+ if (!class_exists(Process::class)) {
454
+ throw new \LogicException('Resolving commands requires the Symfony Process component.');
455
+ }
456
+
457
+ $process = method_exists(Process::class, 'fromShellCommandline') ? Process::fromShellCommandline('echo '.$matches[0]) : new Process('echo '.$matches[0]);
458
+
459
+ if (!method_exists(Process::class, 'fromShellCommandline') && method_exists(Process::class, 'inheritEnvironmentVariables')) {
460
+ // Symfony 3.4 does not inherit env vars by default:
461
+ $process->inheritEnvironmentVariables();
462
+ }
463
+
464
+ $env = [];
465
+ foreach ($this->values as $name => $value) {
466
+ if (isset($loadedVars[$name]) || (!isset($_ENV[$name]) && !(isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')))) {
467
+ $env[$name] = $value;
468
+ }
469
+ }
470
+ $process->setEnv($env);
471
+
472
+ try {
473
+ $process->mustRun();
474
+ } catch (ProcessException $e) {
475
+ throw $this->createFormatException(sprintf('Issue expanding a command (%s)', $process->getErrorOutput()));
476
+ }
477
+
478
+ return preg_replace('/[\r\n]+$/', '', $process->getOutput());
479
+ }, $value);
480
+ }
481
+
482
+ private function resolveVariables(string $value, array $loadedVars): string
483
+ {
484
+ if (false === strpos($value, '$')) {
485
+ return $value;
486
+ }
487
+
488
+ $regex = '/
489
+ (?<!\\\\)
490
+ (?P<backslashes>\\\\*) # escaped with a backslash?
491
+ \$
492
+ (?!\() # no opening parenthesis
493
+ (?P<opening_brace>\{)? # optional brace
494
+ (?P<name>'.self::VARNAME_REGEX.')? # var name
495
+ (?P<default_value>:[-=][^\}]++)? # optional default value
496
+ (?P<closing_brace>\})? # optional closing brace
497
+ /x';
498
+
499
+ $value = preg_replace_callback($regex, function ($matches) use ($loadedVars) {
500
+ // odd number of backslashes means the $ character is escaped
501
+ if (1 === \strlen($matches['backslashes']) % 2) {
502
+ return substr($matches[0], 1);
503
+ }
504
+
505
+ // unescaped $ not followed by variable name
506
+ if (!isset($matches['name'])) {
507
+ return $matches[0];
508
+ }
509
+
510
+ if ('{' === $matches['opening_brace'] && !isset($matches['closing_brace'])) {
511
+ throw $this->createFormatException('Unclosed braces on variable expansion');
512
+ }
513
+
514
+ $name = $matches['name'];
515
+ if (isset($loadedVars[$name]) && isset($this->values[$name])) {
516
+ $value = $this->values[$name];
517
+ } elseif (isset($_ENV[$name])) {
518
+ $value = $_ENV[$name];
519
+ } elseif (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) {
520
+ $value = $_SERVER[$name];
521
+ } elseif (isset($this->values[$name])) {
522
+ $value = $this->values[$name];
523
+ } else {
524
+ $value = (string) getenv($name);
525
+ }
526
+
527
+ if ('' === $value && isset($matches['default_value']) && '' !== $matches['default_value']) {
528
+ $unsupportedChars = strpbrk($matches['default_value'], '\'"{$');
529
+ if (false !== $unsupportedChars) {
530
+ throw $this->createFormatException(sprintf('Unsupported character "%s" found in the default value of variable "$%s".', $unsupportedChars[0], $name));
531
+ }
532
+
533
+ $value = substr($matches['default_value'], 2);
534
+
535
+ if ('=' === $matches['default_value'][1]) {
536
+ $this->values[$name] = $value;
537
+ }
538
+ }
539
+
540
+ if (!$matches['opening_brace'] && isset($matches['closing_brace'])) {
541
+ $value .= '}';
542
+ }
543
+
544
+ return $matches['backslashes'].$value;
545
+ }, $value);
546
+
547
+ return $value;
548
+ }
549
+
550
+ private function moveCursor(string $text)
551
+ {
552
+ $this->cursor += \strlen($text);
553
+ $this->lineno += substr_count($text, "\n");
554
+ }
555
+
556
+ private function createFormatException(string $message): FormatException
557
+ {
558
+ return new FormatException($message, new FormatExceptionContext($this->data, $this->path, $this->lineno, $this->cursor));
559
+ }
560
+
561
+ private function doLoad(bool $overrideExistingVars, array $paths): void
562
+ {
563
+ foreach ($paths as $path) {
564
+ if (!is_readable($path) || is_dir($path)) {
565
+ throw new PathException($path);
566
+ }
567
+
568
+ $this->populate($this->parse(file_get_contents($path), $path), $overrideExistingVars);
569
+ }
570
+ }
571
+ }
vendor/symfony/dotenv/Exception/ExceptionInterface.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\Dotenv\Exception;
13
+
14
+ /**
15
+ * Interface for exceptions.
16
+ *
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ interface ExceptionInterface extends \Throwable
20
+ {
21
+ }
vendor/symfony/dotenv/Exception/FormatException.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\Dotenv\Exception;
13
+
14
+ /**
15
+ * Thrown when a file has a syntax error.
16
+ *
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ final class FormatException extends \LogicException implements ExceptionInterface
20
+ {
21
+ private $context;
22
+
23
+ public function __construct(string $message, FormatExceptionContext $context, int $code = 0, \Throwable $previous = null)
24
+ {
25
+ $this->context = $context;
26
+
27
+ parent::__construct(sprintf("%s in \"%s\" at line %d.\n%s", $message, $context->getPath(), $context->getLineno(), $context->getDetails()), $code, $previous);
28
+ }
29
+
30
+ public function getContext(): FormatExceptionContext
31
+ {
32
+ return $this->context;
33
+ }
34
+ }
vendor/symfony/dotenv/Exception/FormatExceptionContext.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\Dotenv\Exception;
13
+
14
+ /**
15
+ * @author Fabien Potencier <fabien@symfony.com>
16
+ */
17
+ final class FormatExceptionContext
18
+ {
19
+ private $data;
20
+ private $path;
21
+ private $lineno;
22
+ private $cursor;
23
+
24
+ public function __construct(string $data, string $path, int $lineno, int $cursor)
25
+ {
26
+ $this->data = $data;
27
+ $this->path = $path;
28
+ $this->lineno = $lineno;
29
+ $this->cursor = $cursor;
30
+ }
31
+
32
+ public function getPath(): string
33
+ {
34
+ return $this->path;
35
+ }
36
+
37
+ public function getLineno(): int
38
+ {
39
+ return $this->lineno;
40
+ }
41
+
42
+ public function getDetails(): string
43
+ {
44
+ $before = str_replace("\n", '\n', substr($this->data, max(0, $this->cursor - 20), min(20, $this->cursor)));
45
+ $after = str_replace("\n", '\n', substr($this->data, $this->cursor, 20));
46
+
47
+ return '...'.$before.$after."...\n".str_repeat(' ', \strlen($before) + 2).'^ line '.$this->lineno.' offset '.$this->cursor;
48
+ }
49
+ }
vendor/symfony/dotenv/Exception/PathException.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\Dotenv\Exception;
13
+
14
+ /**
15
+ * Thrown when a file does not exist or is not readable.
16
+ *
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ final class PathException extends \RuntimeException implements ExceptionInterface
20
+ {
21
+ public function __construct(string $path, int $code = 0, \Throwable $previous = null)
22
+ {
23
+ parent::__construct(sprintf('Unable to read the "%s" environment file.', $path), $code, $previous);
24
+ }
25
+ }
vendor/symfony/dotenv/LICENSE ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2016-2021 Fabien Potencier
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is furnished
8
+ to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
vendor/symfony/dotenv/Tests/DotenvTest.php ADDED
@@ -0,0 +1,499 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\Dotenv\Tests;
13
+
14
+ use PHPUnit\Framework\TestCase;
15
+ use Symfony\Component\Dotenv\Dotenv;
16
+ use Symfony\Component\Dotenv\Exception\FormatException;
17
+
18
+ class DotenvTest extends TestCase
19
+ {
20
+ /**
21
+ * @dataProvider getEnvDataWithFormatErrors
22
+ */
23
+ public function testParseWithFormatError($data, $error)
24
+ {
25
+ $dotenv = new Dotenv();
26
+
27
+ try {
28
+ $dotenv->parse($data);
29
+ $this->fail('Should throw a FormatException');
30
+ } catch (FormatException $e) {
31
+ $this->assertStringMatchesFormat($error, $e->getMessage());
32
+ }
33
+ }
34
+
35
+ public function getEnvDataWithFormatErrors()
36
+ {
37
+ $tests = [
38
+ ['FOO=BAR BAZ', "A value containing spaces must be surrounded by quotes in \".env\" at line 1.\n...FOO=BAR BAZ...\n ^ line 1 offset 11"],
39
+ ['FOO BAR=BAR', "Whitespace characters are not supported after the variable name in \".env\" at line 1.\n...FOO BAR=BAR...\n ^ line 1 offset 3"],
40
+ ['FOO', "Missing = in the environment variable declaration in \".env\" at line 1.\n...FOO...\n ^ line 1 offset 3"],
41
+ ['FOO="foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO=\"foo...\n ^ line 1 offset 8"],
42
+ ['FOO=\'foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO='foo...\n ^ line 1 offset 8"],
43
+ ["FOO=\"foo\nBAR=\"bar\"", "Missing quote to end the value in \".env\" at line 1.\n...FOO=\"foo\\nBAR=\"bar\"...\n ^ line 1 offset 18"],
44
+ ['FOO=\'foo'."\n", "Missing quote to end the value in \".env\" at line 1.\n...FOO='foo\\n...\n ^ line 1 offset 9"],
45
+ ['export FOO', "Unable to unset an environment variable in \".env\" at line 1.\n...export FOO...\n ^ line 1 offset 10"],
46
+ ['FOO=${FOO', "Unclosed braces on variable expansion in \".env\" at line 1.\n...FOO=\${FOO...\n ^ line 1 offset 9"],
47
+ ['FOO= BAR', "Whitespace are not supported before the value in \".env\" at line 1.\n...FOO= BAR...\n ^ line 1 offset 4"],
48
+ ['Стасян', "Invalid character in variable name in \".env\" at line 1.\n...Стасян...\n ^ line 1 offset 0"],
49
+ ['FOO!', "Missing = in the environment variable declaration in \".env\" at line 1.\n...FOO!...\n ^ line 1 offset 3"],
50
+ ['FOO=$(echo foo', "Missing closing parenthesis. in \".env\" at line 1.\n...FOO=$(echo foo...\n ^ line 1 offset 14"],
51
+ ['FOO=$(echo foo'."\n", "Missing closing parenthesis. in \".env\" at line 1.\n...FOO=$(echo foo\\n...\n ^ line 1 offset 14"],
52
+ ["FOO=\nBAR=\${FOO:-\'a{a}a}", "Unsupported character \"'\" found in the default value of variable \"\$FOO\". in \".env\" at line 2.\n...\\nBAR=\${FOO:-\'a{a}a}...\n ^ line 2 offset 24"],
53
+ ["FOO=\nBAR=\${FOO:-a\$a}", "Unsupported character \"\$\" found in the default value of variable \"\$FOO\". in \".env\" at line 2.\n...FOO=\\nBAR=\${FOO:-a\$a}...\n ^ line 2 offset 20"],
54
+ ["FOO=\nBAR=\${FOO:-a\"a}", "Unclosed braces on variable expansion in \".env\" at line 2.\n...FOO=\\nBAR=\${FOO:-a\"a}...\n ^ line 2 offset 17"],
55
+ ];
56
+
57
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
58
+ $tests[] = ['FOO=$((1dd2))', "Issue expanding a command (%s\n) in \".env\" at line 1.\n...FOO=$((1dd2))...\n ^ line 1 offset 13"];
59
+ }
60
+
61
+ return $tests;
62
+ }
63
+
64
+ /**
65
+ * @dataProvider getEnvData
66
+ */
67
+ public function testParse($data, $expected)
68
+ {
69
+ $dotenv = new Dotenv();
70
+ $this->assertSame($expected, $dotenv->parse($data));
71
+ }
72
+
73
+ public function getEnvData()
74
+ {
75
+ putenv('LOCAL=local');
76
+ $_ENV['LOCAL'] = 'local';
77
+ $_ENV['REMOTE'] = 'remote';
78
+ $_SERVER['SERVERVAR'] = 'servervar';
79
+
80
+ $tests = [
81
+ // backslashes
82
+ ['FOO=foo\\\\bar', ['FOO' => 'foo\\bar']],
83
+ ["FOO='foo\\\\bar'", ['FOO' => 'foo\\\\bar']],
84
+ ['FOO="foo\\\\bar"', ['FOO' => 'foo\\bar']],
85
+
86
+ // escaped backslash in front of variable
87
+ ["BAR=bar\nFOO=foo\\\\\$BAR", ['BAR' => 'bar', 'FOO' => 'foo\\bar']],
88
+ ["BAR=bar\nFOO='foo\\\\\$BAR'", ['BAR' => 'bar', 'FOO' => 'foo\\\\$BAR']],
89
+ ["BAR=bar\nFOO=\"foo\\\\\$BAR\"", ['BAR' => 'bar', 'FOO' => 'foo\\bar']],
90
+
91
+ ['FOO=foo\\\\\\$BAR', ['FOO' => 'foo\\$BAR']],
92
+ ['FOO=\'foo\\\\\\$BAR\'', ['FOO' => 'foo\\\\\\$BAR']],
93
+ ['FOO="foo\\\\\\$BAR"', ['FOO' => 'foo\\$BAR']],
94
+
95
+ // spaces
96
+ ['FOO=bar', ['FOO' => 'bar']],
97
+ [' FOO=bar ', ['FOO' => 'bar']],
98
+ ['FOO=', ['FOO' => '']],
99
+ ["FOO=\n\n\nBAR=bar", ['FOO' => '', 'BAR' => 'bar']],
100
+ ['FOO= ', ['FOO' => '']],
101
+ ["FOO=\nBAR=bar", ['FOO' => '', 'BAR' => 'bar']],
102
+
103
+ // newlines
104
+ ["\n\nFOO=bar\r\n\n", ['FOO' => 'bar']],
105
+ ["FOO=bar\r\nBAR=foo", ['FOO' => 'bar', 'BAR' => 'foo']],
106
+ ["FOO=bar\rBAR=foo", ['FOO' => 'bar', 'BAR' => 'foo']],
107
+ ["FOO=bar\nBAR=foo", ['FOO' => 'bar', 'BAR' => 'foo']],
108
+
109
+ // quotes
110
+ ["FOO=\"bar\"\n", ['FOO' => 'bar']],
111
+ ["FOO=\"bar'foo\"\n", ['FOO' => 'bar\'foo']],
112
+ ["FOO='bar'\n", ['FOO' => 'bar']],
113
+ ["FOO='bar\"foo'\n", ['FOO' => 'bar"foo']],
114
+ ["FOO=\"bar\\\"foo\"\n", ['FOO' => 'bar"foo']],
115
+ ['FOO="bar\nfoo"', ['FOO' => "bar\nfoo"]],
116
+ ['FOO="bar\rfoo"', ['FOO' => "bar\rfoo"]],
117
+ ['FOO=\'bar\nfoo\'', ['FOO' => 'bar\nfoo']],
118
+ ['FOO=\'bar\rfoo\'', ['FOO' => 'bar\rfoo']],
119
+ ["FOO='bar\nfoo'", ['FOO' => "bar\nfoo"]],
120
+ ['FOO=" FOO "', ['FOO' => ' FOO ']],
121
+ ['FOO=" "', ['FOO' => ' ']],
122
+ ['PATH="c:\\\\"', ['PATH' => 'c:\\']],
123
+ ["FOO=\"bar\nfoo\"", ['FOO' => "bar\nfoo"]],
124
+ ['FOO=BAR\\"', ['FOO' => 'BAR"']],
125
+ ["FOO=BAR\\'BAZ", ['FOO' => "BAR'BAZ"]],
126
+ ['FOO=\\"BAR', ['FOO' => '"BAR']],
127
+
128
+ // concatenated values
129
+ ["FOO='bar''foo'\n", ['FOO' => 'barfoo']],
130
+ ["FOO='bar '' baz'", ['FOO' => 'bar baz']],
131
+ ["FOO=bar\nBAR='baz'\"\$FOO\"", ['FOO' => 'bar', 'BAR' => 'bazbar']],
132
+ ["FOO='bar '\\'' baz'", ['FOO' => "bar ' baz"]],
133
+
134
+ // comments
135
+ ["#FOO=bar\nBAR=foo", ['BAR' => 'foo']],
136
+ ["#FOO=bar # Comment\nBAR=foo", ['BAR' => 'foo']],
137
+ ["FOO='bar foo' # Comment", ['FOO' => 'bar foo']],
138
+ ["FOO='bar#foo' # Comment", ['FOO' => 'bar#foo']],
139
+ ["# Comment\r\nFOO=bar\n# Comment\nBAR=foo", ['FOO' => 'bar', 'BAR' => 'foo']],
140
+ ["FOO=bar # Another comment\nBAR=foo", ['FOO' => 'bar', 'BAR' => 'foo']],
141
+ ["FOO=\n\n# comment\nBAR=bar", ['FOO' => '', 'BAR' => 'bar']],
142
+ ['FOO=NOT#COMMENT', ['FOO' => 'NOT#COMMENT']],
143
+ ['FOO= # Comment', ['FOO' => '']],
144
+
145
+ // edge cases (no conversions, only strings as values)
146
+ ['FOO=0', ['FOO' => '0']],
147
+ ['FOO=false', ['FOO' => 'false']],
148
+ ['FOO=null', ['FOO' => 'null']],
149
+
150
+ // export
151
+ ['export FOO=bar', ['FOO' => 'bar']],
152
+ [' export FOO=bar', ['FOO' => 'bar']],
153
+
154
+ // variable expansion
155
+ ["FOO=BAR\nBAR=\$FOO", ['FOO' => 'BAR', 'BAR' => 'BAR']],
156
+ ["FOO=BAR\nBAR=\"\$FOO\"", ['FOO' => 'BAR', 'BAR' => 'BAR']],
157
+ ["FOO=BAR\nBAR='\$FOO'", ['FOO' => 'BAR', 'BAR' => '$FOO']],
158
+ ["FOO_BAR9=BAR\nBAR=\$FOO_BAR9", ['FOO_BAR9' => 'BAR', 'BAR' => 'BAR']],
159
+ ["FOO=BAR\nBAR=\${FOO}Z", ['FOO' => 'BAR', 'BAR' => 'BARZ']],
160
+ ["FOO=BAR\nBAR=\$FOO}", ['FOO' => 'BAR', 'BAR' => 'BAR}']],
161
+ ["FOO=BAR\nBAR=\\\$FOO", ['FOO' => 'BAR', 'BAR' => '$FOO']],
162
+ ['FOO=" \\$ "', ['FOO' => ' $ ']],
163
+ ['FOO=" $ "', ['FOO' => ' $ ']],
164
+ ['BAR=$LOCAL', ['BAR' => 'local']],
165
+ ['BAR=$REMOTE', ['BAR' => 'remote']],
166
+ ['BAR=$SERVERVAR', ['BAR' => 'servervar']],
167
+ ['FOO=$NOTDEFINED', ['FOO' => '']],
168
+ ["FOO=BAR\nBAR=\${FOO:-TEST}", ['FOO' => 'BAR', 'BAR' => 'BAR']],
169
+ ["FOO=BAR\nBAR=\${NOTDEFINED:-TEST}", ['FOO' => 'BAR', 'BAR' => 'TEST']],
170
+ ["FOO=\nBAR=\${FOO:-TEST}", ['FOO' => '', 'BAR' => 'TEST']],
171
+ ["FOO=\nBAR=\$FOO:-TEST}", ['FOO' => '', 'BAR' => 'TEST}']],
172
+ ["FOO=BAR\nBAR=\${FOO:=TEST}", ['FOO' => 'BAR', 'BAR' => 'BAR']],
173
+ ["FOO=BAR\nBAR=\${NOTDEFINED:=TEST}", ['FOO' => 'BAR', 'NOTDEFINED' => 'TEST', 'BAR' => 'TEST']],
174
+ ["FOO=\nBAR=\${FOO:=TEST}", ['FOO' => 'TEST', 'BAR' => 'TEST']],
175
+ ["FOO=\nBAR=\$FOO:=TEST}", ['FOO' => 'TEST', 'BAR' => 'TEST}']],
176
+ ["FOO=foo\nFOOBAR=\${FOO}\${BAR}", ['FOO' => 'foo', 'FOOBAR' => 'foo']],
177
+ ];
178
+
179
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
180
+ $tests = array_merge($tests, [
181
+ // command expansion
182
+ ['FOO=$(echo foo)', ['FOO' => 'foo']],
183
+ ['FOO=$((1+2))', ['FOO' => '3']],
184
+ ['FOO=FOO$((1+2))BAR', ['FOO' => 'FOO3BAR']],
185
+ ['FOO=$(echo "$(echo "$(echo "$(echo foo)")")")', ['FOO' => 'foo']],
186
+ ["FOO=$(echo \"Quotes won't be a problem\")", ['FOO' => 'Quotes won\'t be a problem']],
187
+ ["FOO=bar\nBAR=$(echo \"FOO is \$FOO\")", ['FOO' => 'bar', 'BAR' => 'FOO is bar']],
188
+ ]);
189
+ }
190
+
191
+ return $tests;
192
+ }
193
+
194
+ public function testLoad()
195
+ {
196
+ unset($_ENV['FOO']);
197
+ unset($_ENV['BAR']);
198
+ unset($_SERVER['FOO']);
199
+ unset($_SERVER['BAR']);
200
+ putenv('FOO');
201
+ putenv('BAR');
202
+
203
+ @mkdir($tmpdir = sys_get_temp_dir().'/dotenv');
204
+
205
+ $path1 = tempnam($tmpdir, 'sf-');
206
+ $path2 = tempnam($tmpdir, 'sf-');
207
+
208
+ file_put_contents($path1, 'FOO=BAR');
209
+ file_put_contents($path2, 'BAR=BAZ');
210
+
211
+ (new Dotenv())->usePutenv()->load($path1, $path2);
212
+
213
+ $foo = getenv('FOO');
214
+ $bar = getenv('BAR');
215
+
216
+ putenv('FOO');
217
+ putenv('BAR');
218
+ unlink($path1);
219
+ unlink($path2);
220
+ rmdir($tmpdir);
221
+
222
+ $this->assertSame('BAR', $foo);
223
+ $this->assertSame('BAZ', $bar);
224
+ }
225
+
226
+ public function testLoadEnv()
227
+ {
228
+ unset($_ENV['FOO']);
229
+ unset($_ENV['BAR']);
230
+ unset($_SERVER['FOO']);
231
+ unset($_SERVER['BAR']);
232
+ putenv('FOO');
233
+ putenv('BAR');
234
+
235
+ @mkdir($tmpdir = sys_get_temp_dir().'/dotenv');
236
+
237
+ $path = tempnam($tmpdir, 'sf-');
238
+
239
+ // .env
240
+
241
+ file_put_contents($path, 'FOO=BAR');
242
+ (new Dotenv())->usePutenv()->loadEnv($path, 'TEST_APP_ENV');
243
+ $this->assertSame('BAR', getenv('FOO'));
244
+ $this->assertSame('dev', getenv('TEST_APP_ENV'));
245
+
246
+ // .env.local
247
+
248
+ $_SERVER['TEST_APP_ENV'] = 'local';
249
+ file_put_contents("$path.local", 'FOO=localBAR');
250
+ (new Dotenv())->usePutenv()->loadEnv($path, 'TEST_APP_ENV');
251
+ $this->assertSame('localBAR', getenv('FOO'));
252
+
253
+ // special case for test
254
+
255
+ $_SERVER['TEST_APP_ENV'] = 'test';
256
+ (new Dotenv())->usePutenv()->loadEnv($path, 'TEST_APP_ENV');
257
+ $this->assertSame('BAR', getenv('FOO'));
258
+
259
+ // .env.dev
260
+
261
+ unset($_SERVER['TEST_APP_ENV']);
262
+ file_put_contents("$path.dev", 'FOO=devBAR');
263
+ (new Dotenv())->usePutenv()->loadEnv($path, 'TEST_APP_ENV');
264
+ $this->assertSame('devBAR', getenv('FOO'));
265
+
266
+ // .env.dev.local
267
+
268
+ file_put_contents("$path.dev.local", 'FOO=devlocalBAR');
269
+ (new Dotenv())->usePutenv()->loadEnv($path, 'TEST_APP_ENV');
270
+ $this->assertSame('devlocalBAR', getenv('FOO'));
271
+
272
+ // .env.dist
273
+
274
+ unlink($path);
275
+ file_put_contents("$path.dist", 'BAR=distBAR');
276
+ (new Dotenv())->usePutenv()->loadEnv($path, 'TEST_APP_ENV');
277
+ $this->assertSame('distBAR', getenv('BAR'));
278
+
279
+ putenv('FOO');
280
+ putenv('BAR');
281
+ unlink("$path.dist");
282
+ unlink("$path.local");
283
+ unlink("$path.dev");
284
+ unlink("$path.dev.local");
285
+ rmdir($tmpdir);
286
+ }
287
+
288
+ public function testOverload()
289
+ {
290
+ unset($_ENV['FOO']);
291
+ unset($_ENV['BAR']);
292
+ unset($_SERVER['FOO']);
293
+ unset($_SERVER['BAR']);
294
+
295
+ putenv('FOO=initial_foo_value');
296
+ putenv('BAR=initial_bar_value');
297
+ $_ENV['FOO'] = 'initial_foo_value';
298
+ $_ENV['BAR'] = 'initial_bar_value';
299
+
300
+ @mkdir($tmpdir = sys_get_temp_dir().'/dotenv');
301
+
302
+ $path1 = tempnam($tmpdir, 'sf-');
303
+ $path2 = tempnam($tmpdir, 'sf-');
304
+
305
+ file_put_contents($path1, 'FOO=BAR');
306
+ file_put_contents($path2, 'BAR=BAZ');
307
+
308
+ (new Dotenv())->usePutenv()->overload($path1, $path2);
309
+
310
+ $foo = getenv('FOO');
311
+ $bar = getenv('BAR');
312
+
313
+ putenv('FOO');
314
+ putenv('BAR');
315
+ unlink($path1);
316
+ unlink($path2);
317
+ rmdir($tmpdir);
318
+
319
+ $this->assertSame('BAR', $foo);
320
+ $this->assertSame('BAZ', $bar);
321
+ }
322
+
323
+ public function testLoadDirectory()
324
+ {
325
+ $this->expectException(\Symfony\Component\Dotenv\Exception\PathException::class);
326
+ $dotenv = new Dotenv();
327
+ $dotenv->load(__DIR__);
328
+ }
329
+
330
+ public function testServerSuperglobalIsNotOverridden()
331
+ {
332
+ $originalValue = $_SERVER['argc'];
333
+
334
+ $dotenv = new Dotenv();
335
+ $dotenv->populate(['argc' => 'new_value']);
336
+
337
+ $this->assertSame($originalValue, $_SERVER['argc']);
338
+ }
339
+
340
+ public function testEnvVarIsNotOverridden()
341
+ {
342
+ putenv('TEST_ENV_VAR=original_value');
343
+ $_SERVER['TEST_ENV_VAR'] = 'original_value';
344
+
345
+ $dotenv = (new Dotenv())->usePutenv();
346
+ $dotenv->populate(['TEST_ENV_VAR' => 'new_value']);
347
+
348
+ $this->assertSame('original_value', getenv('TEST_ENV_VAR'));
349
+ }
350
+
351
+ public function testHttpVarIsPartiallyOverridden()
352
+ {
353
+ $_SERVER['HTTP_TEST_ENV_VAR'] = 'http_value';
354
+
355
+ $dotenv = (new Dotenv())->usePutenv();
356
+ $dotenv->populate(['HTTP_TEST_ENV_VAR' => 'env_value']);
357
+
358
+ $this->assertSame('env_value', getenv('HTTP_TEST_ENV_VAR'));
359
+ $this->assertSame('env_value', $_ENV['HTTP_TEST_ENV_VAR']);
360
+ $this->assertSame('http_value', $_SERVER['HTTP_TEST_ENV_VAR']);
361
+ }
362
+
363
+ public function testEnvVarIsOverriden()
364
+ {
365
+ putenv('TEST_ENV_VAR_OVERRIDEN=original_value');
366
+
367
+ $dotenv = (new Dotenv())->usePutenv();
368
+ $dotenv->populate(['TEST_ENV_VAR_OVERRIDEN' => 'new_value'], true);
369
+
370
+ $this->assertSame('new_value', getenv('TEST_ENV_VAR_OVERRIDEN'));
371
+ $this->assertSame('new_value', $_ENV['TEST_ENV_VAR_OVERRIDEN']);
372
+ $this->assertSame('new_value', $_SERVER['TEST_ENV_VAR_OVERRIDEN']);
373
+ }
374
+
375
+ public function testMemorizingLoadedVarsNamesInSpecialVar()
376
+ {
377
+ // Special variable not exists
378
+ unset($_ENV['SYMFONY_DOTENV_VARS']);
379
+ unset($_SERVER['SYMFONY_DOTENV_VARS']);
380
+ putenv('SYMFONY_DOTENV_VARS');
381
+
382
+ unset($_ENV['APP_DEBUG']);
383
+ unset($_SERVER['APP_DEBUG']);
384
+ putenv('APP_DEBUG');
385
+ unset($_ENV['DATABASE_URL']);
386
+ unset($_SERVER['DATABASE_URL']);
387
+ putenv('DATABASE_URL');
388
+
389
+ $dotenv = (new Dotenv())->usePutenv();
390
+ $dotenv->populate(['APP_DEBUG' => '1', 'DATABASE_URL' => 'mysql://root@localhost/db']);
391
+
392
+ $this->assertSame('APP_DEBUG,DATABASE_URL', getenv('SYMFONY_DOTENV_VARS'));
393
+
394
+ // Special variable has a value
395
+ $_ENV['SYMFONY_DOTENV_VARS'] = 'APP_ENV';
396
+ $_SERVER['SYMFONY_DOTENV_VARS'] = 'APP_ENV';
397
+ putenv('SYMFONY_DOTENV_VARS=APP_ENV');
398
+
399
+ $_ENV['APP_DEBUG'] = '1';
400
+ $_SERVER['APP_DEBUG'] = '1';
401
+ putenv('APP_DEBUG=1');
402
+ unset($_ENV['DATABASE_URL']);
403
+ unset($_SERVER['DATABASE_URL']);
404
+ putenv('DATABASE_URL');
405
+
406
+ $dotenv = (new Dotenv())->usePutenv();
407
+ $dotenv->populate(['APP_DEBUG' => '0', 'DATABASE_URL' => 'mysql://root@localhost/db']);
408
+ $dotenv->populate(['DATABASE_URL' => 'sqlite:///somedb.sqlite']);
409
+
410
+ $this->assertSame('APP_ENV,DATABASE_URL', getenv('SYMFONY_DOTENV_VARS'));
411
+ }
412
+
413
+ public function testOverridingEnvVarsWithNamesMemorizedInSpecialVar()
414
+ {
415
+ putenv('SYMFONY_DOTENV_VARS='.$_SERVER['SYMFONY_DOTENV_VARS'] = 'FOO,BAR,BAZ');
416
+
417
+ putenv('FOO=foo');
418
+ putenv('BAR=bar');
419
+ putenv('BAZ=baz');
420
+ putenv('DOCUMENT_ROOT=/var/www');
421
+
422
+ $dotenv = (new Dotenv())->usePutenv();
423
+ $dotenv->populate(['FOO' => 'foo1', 'BAR' => 'bar1', 'BAZ' => 'baz1', 'DOCUMENT_ROOT' => '/boot']);
424
+
425
+ $this->assertSame('foo1', getenv('FOO'));
426
+ $this->assertSame('bar1', getenv('BAR'));
427
+ $this->assertSame('baz1', getenv('BAZ'));
428
+ $this->assertSame('/var/www', getenv('DOCUMENT_ROOT'));
429
+ }
430
+
431
+ public function testGetVariablesValueFromEnvFirst()
432
+ {
433
+ $_ENV['APP_ENV'] = 'prod';
434
+ $dotenv = new Dotenv();
435
+
436
+ $test = "APP_ENV=dev\nTEST1=foo1_\${APP_ENV}";
437
+ $values = $dotenv->parse($test);
438
+ $this->assertSame('foo1_prod', $values['TEST1']);
439
+
440
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
441
+ $test = "APP_ENV=dev\nTEST2=foo2_\$(php -r 'echo \$_SERVER[\"APP_ENV\"];')";
442
+ $values = $dotenv->parse($test);
443
+ $this->assertSame('foo2_prod', $values['TEST2']);
444
+ }
445
+ }
446
+
447
+ public function testGetVariablesValueFromGetenv()
448
+ {
449
+ putenv('Foo=Bar');
450
+
451
+ $dotenv = new Dotenv();
452
+
453
+ try {
454
+ $values = $dotenv->parse('Foo=${Foo}');
455
+ $this->assertSame('Bar', $values['Foo']);
456
+ } finally {
457
+ putenv('Foo');
458
+ }
459
+ }
460
+
461
+ public function testNoDeprecationWarning()
462
+ {
463
+ $dotenv = new Dotenv();
464
+ $this->assertInstanceOf(Dotenv::class, $dotenv);
465
+ }
466
+
467
+ public function testDoNotUsePutenv()
468
+ {
469
+ $dotenv = new Dotenv();
470
+ $dotenv->populate(['TEST_USE_PUTENV' => 'no']);
471
+
472
+ $this->assertSame('no', $_SERVER['TEST_USE_PUTENV']);
473
+ $this->assertSame('no', $_ENV['TEST_USE_PUTENV']);
474
+ $this->assertFalse(getenv('TEST_USE_PUTENV'));
475
+ }
476
+
477
+ public function testBootEnv()
478
+ {
479
+ @mkdir($tmpdir = sys_get_temp_dir().'/dotenv');
480
+ $path = tempnam($tmpdir, 'sf-');
481
+
482
+ file_put_contents($path, 'FOO=BAR');
483
+ (new Dotenv('TEST_APP_ENV', 'TEST_APP_DEBUG'))->bootEnv($path);
484
+
485
+ $this->assertSame('BAR', $_SERVER['FOO']);
486
+
487
+ unset($_SERVER['FOO'], $_ENV['FOO']);
488
+ unlink($path);
489
+
490
+ file_put_contents($path.'.local.php', '<?php return ["TEST_APP_ENV" => "dev", "FOO" => "BAR"];');
491
+ (new Dotenv('TEST_APP_ENV', 'TEST_APP_DEBUG'))->bootEnv($path);
492
+ $this->assertSame('BAR', $_SERVER['FOO']);
493
+ $this->assertSame('1', $_SERVER['TEST_APP_DEBUG']);
494
+
495
+ unset($_SERVER['FOO'], $_ENV['FOO']);
496
+ unlink($path.'.local.php');
497
+ rmdir($tmpdir);
498
+ }
499
+ }
vendor/symfony/polyfill-ctype/Ctype.php ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Polyfill\Ctype;
13
+
14
+ /**
15
+ * Ctype implementation through regex.
16
+ *
17
+ * @internal
18
+ *
19
+ * @author Gert de Pagter <BackEndTea@gmail.com>
20
+ */
21
+ final class Ctype
22
+ {
23
+ /**
24
+ * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise.
25
+ *
26
+ * @see https://php.net/ctype-alnum
27
+ *
28
+ * @param string|int $text
29
+ *
30
+ * @return bool
31
+ */
32
+ public static function ctype_alnum($text)
33
+ {
34
+ $text = self::convert_int_to_char_for_ctype($text);
35
+
36
+ return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text);
37
+ }
38
+
39
+ /**
40
+ * Returns TRUE if every character in text is a letter, FALSE otherwise.
41
+ *
42
+ * @see https://php.net/ctype-alpha
43
+ *
44
+ * @param string|int $text
45
+ *
46
+ * @return bool
47
+ */
48
+ public static function ctype_alpha($text)
49
+ {
50
+ $text = self::convert_int_to_char_for_ctype($text);
51
+
52
+ return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text);
53
+ }
54
+
55
+ /**
56
+ * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise.
57
+ *
58
+ * @see https://php.net/ctype-cntrl
59
+ *
60
+ * @param string|int $text
61
+ *
62
+ * @return bool
63
+ */
64
+ public static function ctype_cntrl($text)
65
+ {
66
+ $text = self::convert_int_to_char_for_ctype($text);
67
+
68
+ return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text);
69
+ }
70
+
71
+ /**
72
+ * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
73
+ *
74
+ * @see https://php.net/ctype-digit
75
+ *
76
+ * @param string|int $text
77
+ *
78
+ * @return bool
79
+ */
80
+ public static function ctype_digit($text)
81
+ {
82
+ $text = self::convert_int_to_char_for_ctype($text);
83
+
84
+ return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text);
85
+ }
86
+
87
+ /**
88
+ * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise.
89
+ *
90
+ * @see https://php.net/ctype-graph
91
+ *
92
+ * @param string|int $text
93
+ *
94
+ * @return bool
95
+ */
96
+ public static function ctype_graph($text)
97
+ {
98
+ $text = self::convert_int_to_char_for_ctype($text);
99
+
100
+ return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text);
101
+ }
102
+
103
+ /**
104
+ * Returns TRUE if every character in text is a lowercase letter.
105
+ *
106
+ * @see https://php.net/ctype-lower
107
+ *
108
+ * @param string|int $text
109
+ *
110
+ * @return bool
111
+ */
112
+ public static function ctype_lower($text)
113
+ {
114
+ $text = self::convert_int_to_char_for_ctype($text);
115
+
116
+ return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text);
117
+ }
118
+
119
+ /**
120
+ * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all.
121
+ *
122
+ * @see https://php.net/ctype-print
123
+ *
124
+ * @param string|int $text
125
+ *
126
+ * @return bool
127
+ */
128
+ public static function ctype_print($text)
129
+ {
130
+ $text = self::convert_int_to_char_for_ctype($text);
131
+
132
+ return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text);
133
+ }
134
+
135
+ /**
136
+ * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise.
137
+ *
138
+ * @see https://php.net/ctype-punct
139
+ *
140
+ * @param string|int $text
141
+ *
142
+ * @return bool
143
+ */
144
+ public static function ctype_punct($text)
145
+ {
146
+ $text = self::convert_int_to_char_for_ctype($text);
147
+
148
+ return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text);
149
+ }
150
+
151
+ /**
152
+ * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters.
153
+ *
154
+ * @see https://php.net/ctype-space
155
+ *
156
+ * @param string|int $text
157
+ *
158
+ * @return bool
159
+ */
160
+ public static function ctype_space($text)
161
+ {
162
+ $text = self::convert_int_to_char_for_ctype($text);
163
+
164
+ return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text);
165
+ }
166
+
167
+ /**
168
+ * Returns TRUE if every character in text is an uppercase letter.
169
+ *
170
+ * @see https://php.net/ctype-upper
171
+ *
172
+ * @param string|int $text
173
+ *
174
+ * @return bool
175
+ */
176
+ public static function ctype_upper($text)
177
+ {
178
+ $text = self::convert_int_to_char_for_ctype($text);
179
+
180
+ return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text);
181
+ }
182
+
183
+ /**
184
+ * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise.
185
+ *
186
+ * @see https://php.net/ctype-xdigit
187
+ *
188
+ * @param string|int $text
189
+ *
190
+ * @return bool
191
+ */
192
+ public static function ctype_xdigit($text)
193
+ {
194
+ $text = self::convert_int_to_char_for_ctype($text);
195
+
196
+ return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text);
197
+ }
198
+
199
+ /**
200
+ * Converts integers to their char versions according to normal ctype behaviour, if needed.
201
+ *
202
+ * If an integer between -128 and 255 inclusive is provided,
203
+ * it is interpreted as the ASCII value of a single character
204
+ * (negative values have 256 added in order to allow characters in the Extended ASCII range).
205
+ * Any other integer is interpreted as a string containing the decimal digits of the integer.
206
+ *
207
+ * @param string|int $int
208
+ *
209
+ * @return mixed
210
+ */
211
+ private static function convert_int_to_char_for_ctype($int)
212
+ {
213
+ if (!\is_int($int)) {
214
+ return $int;
215
+ }
216
+
217
+ if ($int < -128 || $int > 255) {
218
+ return (string) $int;
219
+ }
220
+
221
+ if ($int < 0) {
222
+ $int += 256;
223
+ }
224
+
225
+ return \chr($int);
226
+ }
227
+ }
vendor/symfony/polyfill-ctype/LICENSE ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2018-2019 Fabien Potencier
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is furnished
8
+ to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
vendor/symfony/polyfill-ctype/bootstrap.php ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ use Symfony\Polyfill\Ctype as p;
13
+
14
+ if (\PHP_VERSION_ID >= 80000) {
15
+ return require __DIR__.'/bootstrap80.php';
16
+ }
17
+
18
+ if (!function_exists('ctype_alnum')) {
19
+ function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); }
20
+ }
21
+ if (!function_exists('ctype_alpha')) {
22
+ function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); }
23
+ }
24
+ if (!function_exists('ctype_cntrl')) {
25
+ function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); }
26
+ }
27
+ if (!function_exists('ctype_digit')) {
28
+ function ctype_digit($text) { return p\Ctype::ctype_digit($text); }
29
+ }
30
+ if (!function_exists('ctype_graph')) {
31
+ function ctype_graph($text) { return p\Ctype::ctype_graph($text); }
32
+ }
33
+ if (!function_exists('ctype_lower')) {
34
+ function ctype_lower($text) { return p\Ctype::ctype_lower($text); }
35
+ }
36
+ if (!function_exists('ctype_print')) {
37
+ function ctype_print($text) { return p\Ctype::ctype_print($text); }
38
+ }
39
+ if (!function_exists('ctype_punct')) {
40
+ function ctype_punct($text) { return p\Ctype::ctype_punct($text); }
41
+ }
42
+ if (!function_exists('ctype_space')) {
43
+ function ctype_space($text) { return p\Ctype::ctype_space($text); }
44
+ }
45
+ if (!function_exists('ctype_upper')) {
46
+ function ctype_upper($text) { return p\Ctype::ctype_upper($text); }
47
+ }
48
+ if (!function_exists('ctype_xdigit')) {
49
+ function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); }
50
+ }
vendor/symfony/polyfill-ctype/bootstrap80.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ use Symfony\Polyfill\Ctype as p;
13
+
14
+ if (!function_exists('ctype_alnum')) {
15
+ function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); }
16
+ }
17
+ if (!function_exists('ctype_alpha')) {
18
+ function ctype_alpha(mixed $text): bool { return p\Ctype::ctype_alpha($text); }
19
+ }
20
+ if (!function_exists('ctype_cntrl')) {
21
+ function ctype_cntrl(mixed $text): bool { return p\Ctype::ctype_cntrl($text); }
22
+ }
23
+ if (!function_exists('ctype_digit')) {
24
+ function ctype_digit(mixed $text): bool { return p\Ctype::ctype_digit($text); }
25
+ }
26
+ if (!function_exists('ctype_graph')) {
27
+ function ctype_graph(mixed $text): bool { return p\Ctype::ctype_graph($text); }
28
+ }
29
+ if (!function_exists('ctype_lower')) {
30
+ function ctype_lower(mixed $text): bool { return p\Ctype::ctype_lower($text); }
31
+ }
32
+ if (!function_exists('ctype_print')) {
33
+ function ctype_print(mixed $text): bool { return p\Ctype::ctype_print($text); }
34
+ }
35
+ if (!function_exists('ctype_punct')) {
36
+ function ctype_punct(mixed $text): bool { return p\Ctype::ctype_punct($text); }
37
+ }
38
+ if (!function_exists('ctype_space')) {
39
+ function ctype_space(mixed $text): bool { return p\Ctype::ctype_space($text); }
40
+ }
41
+ if (!function_exists('ctype_upper')) {
42
+ function ctype_upper(mixed $text): bool { return p\Ctype::ctype_upper($text); }
43
+ }
44
+ if (!function_exists('ctype_xdigit')) {
45
+ function ctype_xdigit(mixed $text): bool { return p\Ctype::ctype_xdigit($text); }
46
+ }