WebP Express - Version 0.20.0

Version Description

(released: 17 Jun 2021) * Added WP CLI support. Add "wp webp-express convert" to crontab for nightly conversions of new images! Thanks to Isuru Sampath Ratnayake from Sri Lanka for initializing this. * Added "sharp-yuv" (not as option, but as always on). Better YUV->RGB color conversion at almost no price. Read more here. Supported by cwebp, vips, gmagick, graphicsmagick, imagick and imagemagick * Bumped cwebp binaries to 1.2.0 * cwebp now only validates hash of supplied precompiled binaries when necessary. This cuts down conversion time. * Convert on upload now defaults to false, as it may impact upload experience in themes with many formats.

For more info, see the closed issues on the 0.20.0 milestone on the github repository

Download this release

Release Info

Developer rosell.dk
Plugin Icon 128x128 WebP Express
Version 0.20.0
Comparing to
See all releases

Code changes from version 0.19.1 to 0.20.0

Files changed (105) hide show
  1. README.md +38 -8
  2. README.txt +59 -486
  3. changelog.txt +10 -0
  4. composer.lock +586 -0
  5. lib/classes/AlterHtmlHelper.php +4 -2
  6. lib/classes/BulkConvert.php +38 -25
  7. lib/classes/CLI.php +272 -0
  8. lib/classes/Config.php +1 -1
  9. lib/classes/Convert.php +1 -1
  10. lib/classes/ConvertHelperIndependent.php +1 -1
  11. lib/classes/Paths.php +57 -1
  12. lib/classes/WCFMApi.php +205 -7
  13. lib/classes/WCFMPage.php +3 -3
  14. lib/options/enqueue_scripts.php +1 -1
  15. lib/options/options/conversion-options/convert-on-upload.inc +4 -2
  16. lib/options/options/conversion-options/metadata.inc +1 -1
  17. lib/options/submit.php +1 -1
  18. lib/wcfm/style.css +1 -1
  19. lib/wcfm/wcfm-options.js +4 -1
  20. lib/wcfm/wcfm.js +1 -1
  21. vendor/composer/ClassLoader.php +40 -4
  22. vendor/composer/InstalledVersions.php +373 -0
  23. vendor/composer/autoload_classmap.php +6 -0
  24. vendor/composer/autoload_real.php +7 -2
  25. vendor/composer/autoload_static.php +6 -0
  26. vendor/composer/installed.json +564 -516
  27. vendor/composer/installed.php +112 -0
  28. vendor/composer/installers/.github/workflows/continuous-integration.yml +70 -0
  29. vendor/composer/installers/.github/workflows/lint.yml +30 -0
  30. vendor/composer/installers/.github/workflows/phpstan.yml +51 -0
  31. vendor/composer/installers/composer.json +16 -7
  32. vendor/composer/installers/phpstan.neon.dist +10 -0
  33. vendor/composer/installers/src/Composer/Installers/BaseInstaller.php +6 -6
  34. vendor/composer/installers/src/Composer/Installers/CakePHPInstaller.php +2 -9
  35. vendor/composer/installers/src/Composer/Installers/CockpitInstaller.php +1 -3
  36. vendor/composer/installers/src/Composer/Installers/Installer.php +27 -10
  37. vendor/composer/installers/src/Composer/Installers/MauticInstaller.php +29 -6
  38. vendor/composer/installers/src/Composer/Installers/MiaoxingInstaller.php +10 -0
  39. vendor/composer/installers/src/Composer/Installers/MoodleInstaller.php +1 -0
  40. vendor/composer/installers/src/Composer/Installers/OctoberInstaller.php +2 -1
  41. vendor/composer/installers/src/Composer/Installers/OxidInstaller.php +1 -1
  42. vendor/composer/installers/src/Composer/Installers/ProcessWireInstaller.php +22 -0
  43. vendor/composer/installers/src/Composer/Installers/StarbugInstaller.php +12 -0
  44. vendor/composer/installers/src/Composer/Installers/SyDESInstaller.php +1 -3
  45. vendor/composer/installers/src/Composer/Installers/TaoInstaller.php +18 -0
  46. vendor/composer/installers/src/Composer/Installers/TastyIgniterInstaller.php +32 -0
  47. vendor/composer/installers/src/Composer/Installers/WinterInstaller.php +58 -0
  48. vendor/composer/platform_check.php +26 -0
  49. vendor/rosell-dk/webp-convert/README.md +14 -1
  50. vendor/rosell-dk/webp-convert/composer-php56.json +75 -0
  51. vendor/rosell-dk/webp-convert/composer-php72.json +75 -0
  52. vendor/rosell-dk/webp-convert/composer.json +5 -3
  53. vendor/rosell-dk/webp-convert/docs/development.md +0 -74
  54. vendor/rosell-dk/webp-convert/docs/v1.3/converting/convert-options.md +0 -322
  55. vendor/rosell-dk/webp-convert/docs/v1.3/converting/convert.md +0 -96
  56. vendor/rosell-dk/webp-convert/docs/v1.3/converting/converters.md +0 -322
  57. vendor/rosell-dk/webp-convert/docs/v1.3/serving/convert-and-serve.md +0 -167
  58. vendor/rosell-dk/webp-convert/docs/v1.3/webp-on-demand/tweaks.md +0 -167
  59. vendor/rosell-dk/webp-convert/docs/v1.3/webp-on-demand/webp-on-demand.md +0 -133
  60. vendor/rosell-dk/webp-convert/docs/v1.3/webp-on-demand/without-composer.md +0 -45
  61. vendor/rosell-dk/webp-convert/docs/v2.0/converting/architecture-q50-w600.jpg +0 -0
  62. vendor/rosell-dk/webp-convert/docs/v2.0/converting/converters/stack.md +0 -248
  63. vendor/rosell-dk/webp-convert/docs/v2.0/converting/dice.png +0 -0
  64. vendor/rosell-dk/webp-convert/docs/v2.0/converting/introduction-for-converting.md +0 -218
  65. vendor/rosell-dk/webp-convert/docs/v2.0/converting/mouse-q100.jpg +0 -0
  66. vendor/rosell-dk/webp-convert/docs/v2.0/converting/options.md +0 -346
  67. vendor/rosell-dk/webp-convert/docs/v2.0/migrating-to-2.0.md +0 -73
  68. vendor/rosell-dk/webp-convert/docs/v2.0/serving/introduction-for-serving.md +0 -157
  69. vendor/rosell-dk/webp-convert/docs/v2.0/serving/laravel-nginx-serving.md +0 -116
  70. vendor/rosell-dk/webp-convert/docs/v2.0/webp-on-demand/tweaks.md +0 -181
  71. vendor/rosell-dk/webp-convert/docs/v2.0/webp-on-demand/webp-on-demand.md +0 -145
  72. vendor/rosell-dk/webp-convert/docs/v2.0/webp-on-demand/without-composer.md +0 -58
  73. vendor/rosell-dk/webp-convert/phpunit-41.xml.dist +39 -0
  74. vendor/rosell-dk/webp-convert/src/Convert/Converters/AbstractConverter.php +5 -2
  75. vendor/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/AutoQualityTrait.php +49 -11
  76. vendor/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/OptionsTrait.php +63 -15
  77. vendor/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/WarningLoggerTrait.php +6 -2
  78. vendor/rosell-dk/webp-convert/src/Convert/Converters/Binaries/cwebp-120-linux-x86-64 +0 -0
  79. vendor/rosell-dk/webp-convert/src/Convert/Converters/Binaries/cwebp-120-windows-x64.exe +0 -0
  80. vendor/rosell-dk/webp-convert/src/Convert/Converters/ConverterTraits/CurlTrait.php +2 -2
  81. vendor/rosell-dk/webp-convert/src/Convert/Converters/Cwebp.php +235 -79
  82. vendor/rosell-dk/webp-convert/src/Convert/Converters/Ewww.php +9 -10
  83. vendor/rosell-dk/webp-convert/src/Convert/Converters/FFMpeg.php +1 -0
  84. vendor/rosell-dk/webp-convert/src/Convert/Converters/Gd.php +8 -7
  85. vendor/rosell-dk/webp-convert/src/Convert/Converters/Gmagick.php +24 -1
  86. vendor/rosell-dk/webp-convert/src/Convert/Converters/GraphicsMagick.php +37 -8
  87. vendor/rosell-dk/webp-convert/src/Convert/Converters/ImageMagick.php +32 -8
  88. vendor/rosell-dk/webp-convert/src/Convert/Converters/Imagick.php +21 -1
  89. vendor/rosell-dk/webp-convert/src/Convert/Converters/Stack.php +4 -5
  90. vendor/rosell-dk/webp-convert/src/Convert/Converters/Vips.php +87 -17
  91. vendor/rosell-dk/webp-convert/src/Convert/Converters/Wpc.php +12 -14
  92. vendor/rosell-dk/webp-convert/src/Helpers/BinaryDiscovery.php +2 -1
  93. vendor/rosell-dk/webp-convert/src/Helpers/SanityCheck.txt +0 -255
  94. vendor/rosell-dk/webp-convert/src/Options/ArrayOption.php +10 -0
  95. vendor/rosell-dk/webp-convert/src/Options/BooleanOption.php +3 -0
  96. vendor/rosell-dk/webp-convert/src/Options/GhostOption.php +2 -0
  97. vendor/rosell-dk/webp-convert/src/Options/IntegerOption.php +2 -1
  98. vendor/rosell-dk/webp-convert/src/Options/IntegerOrNullOption.php +1 -0
  99. vendor/rosell-dk/webp-convert/src/Options/MetadataOption.php +3 -0
  100. vendor/rosell-dk/webp-convert/src/Options/Option.php +43 -2
  101. vendor/rosell-dk/webp-convert/src/Options/Options.php +42 -3
  102. vendor/rosell-dk/webp-convert/src/Options/QualityOption.php +2 -0
  103. vendor/rosell-dk/webp-convert/src/Options/StringOption.php +3 -1
  104. vendor/rosell-dk/webp-convert/src/WebPConvert.php +43 -0
  105. webp-express.php +5 -1
README.md CHANGED
@@ -1,7 +1,5 @@
1
  # WebP Express
2
 
3
- [![RIPS CodeRisk](https://coderisk.com/wp/plugin/webp-express/badge "RIPS CodeRisk")](https://coderisk.com/wp/plugin/webp-express)
4
-
5
  Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP.
6
 
7
  The plugin is available on the Wordpress codex ([here](https://wordpress.org/plugins/webp-express/)).
@@ -10,7 +8,7 @@ But well, it is developed ([here on github](https://github.com/rosell-dk/webp-ex
10
  **News: I have added the vendor folder to the repo. To install the plugin here from github, you can simply download the zip and unzip it in your plugin folder**
11
 
12
  ## Description
13
- More than 4 out of 5 mobile users are using a browser that is able to display webp images. Yet, on most websites, they are served jpeg images, which are typically double the size of webp images for a given quality. What a waste of bandwidth! This plugin was created to help remedy that situation. With little effort, Wordpress admins can have their site serving autogenerated webp images to browsers that supports it, while still serving jpeg and png files to browsers that does not support webp.
14
 
15
  ### The image converter
16
  The plugin uses the [WebP Convert](https://github.com/rosell-dk/webp-convert) library to convert images to webp. *WebP Convert* is able to convert images using multiple methods. There are the "local" conversion methods: `imagick`, `cwebp`, `vips`, `gd`. If none of these works on your host, there are the cloud alternatives: `ewww` (paid) or connecting to a Wordpress site where you got WebP Express installed and you enabled the "web service" functionality.
@@ -40,15 +38,12 @@ The plugin implements the "WebP On Demand" solution described [here](https://git
40
  - Currently ~94% of all traffic, and ~96% of mobile browsing traffic are done with browsers supporting webp. Check current numbers on [caniuse.com](https://caniuse.com/webp).
41
  - It's great for the environment too! Reducing network traffic reduces electricity consumption which reduces CO2 emissions.
42
 
43
- ### Recent news
44
- Feb 2019: Multisite is now supported (0.12.0)
45
- Jan 2019: Plugin can now alter HTML (0.11.0)
46
-
47
  ## Installation
48
  1. Upload the plugin files to the `/wp-content/plugins/webp-express` directory, or install the plugin through the WordPress plugins screen directly.
49
  2. Activate the plugin through the 'Plugins' screen in WordPress
50
  3. Configure it (the plugin doesn't do anything until configured)
51
  4. Verify that it works
 
52
 
53
  ### Configuring
54
  You configure the plugin in *Settings > WebP Express*.
@@ -119,6 +114,41 @@ The redirect rules created in *.htaccess* are pointing to a PHP script. If you h
119
  *Note:*
120
  Do not simply remove the plugin without deactivating it first. Deactivation takes care of removing the rules in the *.htaccess* file. With the rules there, but converter gone, your Google Chrome visitors will not see any jpeg images.
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
  ## Limitations
124
 
@@ -673,7 +703,7 @@ The 0.17.0 release contained binaries with dots in their filenames, which caused
673
  ### When is feature X coming? / Roadmap
674
  No schedule. I move forward as time allows. I currently spend a lot of time answering questions in the support forum. If someone would be nice and help out answering questions here, it would allow me to spend that time developing. Also, donations would allow me to turn down some of the more boring requests from my customers, and speed things up here.
675
 
676
- Here are my current plans ahead: 0.20 will probably be a file manager-like interface for converting / bulk converting / viewing conversion logs / comparing original vs webp visually - kind of a merge of current "test converter" and "bulk conversion" interfaces, and with an addition of a file explorer. 0.21 might allow excluding certain files and folders. 0.22 could be supporting Save-Data header in Varied Image Responses mode (send extra compressed images to clients who wants to use as little bandwidth as possible). 0.21 might be displaying rules for NGINX. 0.23 might be an effort to allow webp for all browsers using [this javascript library](http://libwebpjs.hohenlimburg.org/v0.6.0/). Unfortunately, the javascript library does not (currently) support srcset attributes, which is why I moved this item down the priority list. We need srcset to be supported for the feature to be useful. 0.21 might be WAMP support. The current milestones, their subtasks and their progress can be viewed here: https://github.com/rosell-dk/webp-express/milestones
677
 
678
  If you wish to affect priorities, it is certainly possible. You can try to argue your case in the forum or you can simply let the money do the talking. By donating as little as a cup of coffee on [ko-fi.com/rosell](https://ko-fi.com/rosell), you can leave a wish. I shall take these wishes into account when prioritizing between new features.
679
 
1
  # WebP Express
2
 
 
 
3
  Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP.
4
 
5
  The plugin is available on the Wordpress codex ([here](https://wordpress.org/plugins/webp-express/)).
8
  **News: I have added the vendor folder to the repo. To install the plugin here from github, you can simply download the zip and unzip it in your plugin folder**
9
 
10
  ## Description
11
+ More than 9 out of 10 users are using a browser that is able to display webp images. Yet, on most websites, they are served jpeg images, which are typically double the size of webp images for a given quality. What a waste of bandwidth! This plugin was created to help remedy that situation. With little effort, Wordpress admins can have their site serving autogenerated webp images to browsers that supports it, while still serving jpeg and png files to browsers that does not support webp.
12
 
13
  ### The image converter
14
  The plugin uses the [WebP Convert](https://github.com/rosell-dk/webp-convert) library to convert images to webp. *WebP Convert* is able to convert images using multiple methods. There are the "local" conversion methods: `imagick`, `cwebp`, `vips`, `gd`. If none of these works on your host, there are the cloud alternatives: `ewww` (paid) or connecting to a Wordpress site where you got WebP Express installed and you enabled the "web service" functionality.
38
  - Currently ~94% of all traffic, and ~96% of mobile browsing traffic are done with browsers supporting webp. Check current numbers on [caniuse.com](https://caniuse.com/webp).
39
  - It's great for the environment too! Reducing network traffic reduces electricity consumption which reduces CO2 emissions.
40
 
 
 
 
 
41
  ## Installation
42
  1. Upload the plugin files to the `/wp-content/plugins/webp-express` directory, or install the plugin through the WordPress plugins screen directly.
43
  2. Activate the plugin through the 'Plugins' screen in WordPress
44
  3. Configure it (the plugin doesn't do anything until configured)
45
  4. Verify that it works
46
+ 5. (Optional) Bulk convert all images, either in the admin ui or using WP CLI (command: "webp-express")
47
 
48
  ### Configuring
49
  You configure the plugin in *Settings > WebP Express*.
114
  *Note:*
115
  Do not simply remove the plugin without deactivating it first. Deactivation takes care of removing the rules in the *.htaccess* file. With the rules there, but converter gone, your Google Chrome visitors will not see any jpeg images.
116
 
117
+ ### Bulk convert
118
+ You can start a bulk conversion two ways:
119
+ 1. In the admin UI. On the settings screen, there is a "Bulk Convert" button
120
+ 2. By using WP CLI (command: "webp-express").
121
+
122
+ I'm currently working on a file manager interface, which will become a third way.
123
+
124
+ ### Making sure new images becomes converted
125
+ There are several ways:
126
+ 1. Enable redirection to converter in the *.htaccess rules* section.
127
+ 2. Enable "Convert on upload". Note that this may impact upload experience in themes which defines many formats.
128
+ 3. Set up a cron job, which executes `wp webp-express convert` regularily
129
+
130
+ ### WP CLI command
131
+ WebP Express currently supports commands for converting and flushing webp images throug the CLI. You can use the --help option to learn about the options:
132
+ `wp webp-express --help`. Displays the available commands
133
+ `wp webp-express convert --help`. Displays the available options for the "convert" command.
134
+
135
+ A few examples:
136
+ `wp webp-express convert`: Creates webp images for all unconverted images
137
+ `wp webp-express convert --reconvert`: Also convert images that are already converted
138
+ `wp webp-express convert themes`: Only images in the themes folder
139
+ `wp webp-express convert uploads/2021`: Only images in the "2021" folder inside the uploads folder
140
+ `wp webp-express convert --only-png`: Only the PNG images
141
+ `wp webp-express convert --quality=50`: Use quality 50 (instead of what was entered in settings screen)
142
+ `wp webp-express convert --converter=cwebp`: Specifically use cwebp converter.
143
+
144
+ `wp webp-express flushwebp`: Remove all webp images
145
+ `wp webp-express flushwebp --only-png`: Remove all webp images that are conversions of PNG images
146
+
147
+ Synopsises:
148
+ `wp webp-express convert [<location>] [--reconvert] [--only-png] [--only-jpeg] [--quality=<number>] [--near-lossless=<number>] [--alpha-quality=<number>] [--encoding=<auto|lossy|lossless>] [--converter=<converter>]`
149
+ `wp webp-express flushwebp [--only-png]`
150
+
151
+ I'm considering adding commands for viewing status, viewing conversion stats, generating the .htaccess files and modifying the settings. Please let me know if you need any of these or perhaps something else.
152
 
153
  ## Limitations
154
 
703
  ### When is feature X coming? / Roadmap
704
  No schedule. I move forward as time allows. I currently spend a lot of time answering questions in the support forum. If someone would be nice and help out answering questions here, it would allow me to spend that time developing. Also, donations would allow me to turn down some of the more boring requests from my customers, and speed things up here.
705
 
706
+ Here are my current plans ahead: 0.21 will probably be a file manager-like interface for converting / bulk converting / viewing conversion logs / comparing original vs webp visually - kind of a merge of current "test converter" and "bulk conversion" interfaces, and with an addition of a file explorer. 0.22 might allow excluding certain files and folders. 0.23 could be supporting Save-Data header in Varied Image Responses mode (send extra compressed images to clients who wants to use as little bandwidth as possible). 0.21 might be displaying rules for NGINX. 0.24 might be an effort to allow webp for all browsers using [this javascript library](http://libwebpjs.hohenlimburg.org/v0.6.0/). Unfortunately, the javascript library does not (currently) support srcset attributes, which is why I moved this item down the priority list. We need srcset to be supported for the feature to be useful. 0.26 might be WAMP support. The current milestones, their subtasks and their progress can be viewed here: https://github.com/rosell-dk/webp-express/milestones
707
 
708
  If you wish to affect priorities, it is certainly possible. You can try to argue your case in the forum or you can simply let the money do the talking. By donating as little as a cup of coffee on [ko-fi.com/rosell](https://ko-fi.com/rosell), you can leave a wish. I shall take these wishes into account when prioritizing between new features.
709
 
README.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: https://ko-fi.com/rosell
4
  Tags: webp, images, performance
5
  Requires at least: 4.0
6
  Tested up to: 5.7
7
- Stable tag: 0.19.1
8
  Requires PHP: 5.6
9
  License: GPLv3
10
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -13,7 +13,7 @@ Serve autogenerated WebP images instead of jpeg/png to browsers that supports We
13
 
14
  == Description ==
15
 
16
- More than 4 out of 5 mobile users are using a browser that is able to display webp images. Yet, on most websites, they are served jpeg images, which are typically double the size of webp images for a given quality. What a waste of bandwidth! This plugin was created to help remedy that situation. With little effort, Wordpress admins can have their site serving autogenerated webp images to browsers that supports it, while still serving jpeg and png files to browsers that does not support webp.
17
 
18
  ### The image converter
19
  The plugin uses the [WebP Convert](https://github.com/rosell-dk/webp-convert) library to convert images to webp. *WebP Convert* is able to convert images using multiple methods. There are the "local" conversion methods: `imagick`, `cwebp`, `vips`, `gd`. If none of these works on your host, there are the cloud alternatives: `ewww` (paid) or connecting to a Wordpress site where you got WebP Express installed and you enabled the "web service" functionality.
@@ -49,6 +49,7 @@ The plugin implements the "WebP On Demand" solution described [here](https://git
49
  2. Activate the plugin through the 'Plugins' screen in WordPress
50
  3. Configure it (the plugin doesn't do anything until configured)
51
  4. Verify that it works
 
52
 
53
  ### Configuring
54
  You configure the plugin in *Settings > WebP Express*.
@@ -118,6 +119,42 @@ The redirect rules created in *.htaccess* are pointing to a PHP script. If you h
118
  *Note:*
119
  Do not simply remove the plugin without deactivating it first. Deactivation takes care of removing the rules in the *.htaccess* file. With the rules there, but converter gone, your Google Chrome visitors will not see any jpeg images.
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  == Limitations ==
122
 
123
  * The plugin [should now work on Microsoft IIS server](https://github.com/rosell-dk/webp-express/pull/213), but it has not been tested thoroughly.
@@ -136,31 +173,20 @@ Bread on the table don't come for free, even though this plugin does, and always
136
  * [Mathieu Gollain-Dupont](https://www.linkedin.com/in/mathieu-gollain-dupont-9938a4a/)
137
  * Ruben Solvang
138
 
139
- **Persons who contributed with [ko-fi](https://ko-fi.com/rosell) since last plugin update - Thanks!**
140
-
141
- * 3 May: Anonymous
142
- * 2 May: Anonymous
143
- * 13 Apr: Beachmat
144
- * 30 Mar: Chris Gibbs
145
- * 20 Mar: Mick Wall
146
- * 28 Feb: Jim Shelton
147
- * 3 Feb: Bjarne Olddrup
148
- * 14 Jan: Maggie
149
- * 6 Jan: Tim Schön
150
- * 24 Dec: Enno Lenze
151
- * 19 Dec: Anonymous
152
- * 22 Nov: Flavioweb
153
- * 16 Nov: WiredGorilla
154
 
155
  **Persons who contributed with extra generously amounts of coffee / lifetime backing (>30$) - thanks!:**
156
 
157
- * Max Kreminsky ($110)
158
  * Justin - BigScoots ($105)
159
  * Bill Vallance ($102)
160
  * Sebastian ($99)
161
  * Tammy Lee ($90)
162
  * Steven Sullivan ($51)
163
- * Mathieu Gollain-Dupont ($45)
164
 
165
  == Frequently Asked Questions ==
166
 
@@ -729,7 +755,7 @@ If you are wondering why Alter HTML are missing some images, it can be due to on
729
  = When is feature X coming? / Roadmap =
730
  No schedule. I move forward as time allows. I currently spend a lot of time answering questions in the support forum. If someone would be nice and help out answering questions here, it would allow me to spend that time developing. Also, donations would allow me to turn down some of the more boring requests from my customers, and speed things up here.
731
 
732
- Here are my current plans ahead: 0.20 will probably be a file manager-like interface for converting / bulk converting / viewing conversion logs / comparing original vs webp visually - kind of a merge of current "test converter" and "bulk conversion" interfaces, and with an addition of a file explorer. 0.21 might allow excluding certain files and folders. 0.22 could be supporting Save-Data header in Varied Image Responses mode (send extra compressed images to clients who wants to use as little bandwidth as possible). 0.21 might be displaying rules for NGINX. 0.23 might be an effort to allow webp for all browsers using [this javascript library](http://libwebpjs.hohenlimburg.org/v0.6.0/). Unfortunately, the javascript library does not (currently) support srcset attributes, which is why I moved this item down the priority list. We need srcset to be supported for the feature to be useful. 0.21 might be WAMP support. The current milestones, their subtasks and their progress can be viewed here: https://github.com/rosell-dk/webp-express/milestones
733
 
734
  If you wish to affect priorities, it is certainly possible. You can try to argue your case in the forum or you can simply let the money do the talking. By donating as little as a cup of coffee on [ko-fi.com/rosell](https://ko-fi.com/rosell), you can leave a wish. I shall take these wishes into account when prioritizing between new features.
735
 
@@ -745,480 +771,27 @@ If you want to make sure that my coffee supplies don't run dry, you can even buy
745
 
746
  == Changelog ==
747
 
 
 
 
 
 
 
 
 
 
 
748
  = 0.19.1 =
749
  *(released: 03 May 2021)*
750
  * Bugfix for PHP 8.0 - fread() does not permit second argument to be 0. Thanks to @trition for reporting and fixing this bug.
751
 
752
- = 0.19.0 =
753
- *(released: 13 Nov 2020)*
754
- * New convertion method: ffmpeg
755
- * Fixed problem in Bulk Convert when files had special characters in their filename
756
- * Prevented problems if the plugin gets included twice (can anybody enlighten me on how this might happen?)
757
-
758
- For more info, see the closed issues on the [0.19.0 milestone on the github repository](https://github.com/rosell-dk/webp-express/milestone/36?closed=1)
759
-
760
- = 0.18.3 =
761
- *(released: 5 Nov 2020)*
762
- * Bugfix: WebP Express uses live tests to determine the capabilities of the server in respect to .htaccess files (using the htaccess-capability-tester library). The results are used for warnings and also for optimizing the rules in the .htaccess files. However, HTTP Requests can fail due to other reasons than the feature not working (ie timeout). Such failures should lead to an indeterminate result, but it was interpreted as if the feature was not working.
763
- * The Live test now displays a bit more information if the HTTP request failed.
764
- * Changed default value for "destination structure" to "Image roots", as "Document root" doesn't work on hosts that have defined DOCUMENT_ROOT in an unusual way.
765
- * Added possibility to change "%{DOCUMENT_ROOT}" part of RewriteCond by adding a line to wp-config.php. THIS IS A BETA FEATURE AND MIGHT BE REVOKED IF NOBODY ACTUALLY NEEDS IT.
766
- * Got rid of PHP notice Constant WEBPEXPRESS_MIGRATION_VERSION already defined
767
- * Fixed donation link. It now points to https://ko-fi.com/rosell again
768
-
769
- For more info, see the closed issues on the [0.18.3 milestone on the github repository](https://github.com/rosell-dk/webp-express/milestone/34?closed=1)
770
-
771
- = 0.18.2 =
772
- *(released: 28 Sep 2020)*
773
- * Bugfix: Fixed error on the settings page on a handful of setups.
774
-
775
- = 0.18.1 =
776
- *(released: 24 Sep 2020)*
777
- * Bugfix: Bulk Convert failed to show list on systems that did not have the [utf8-encode()](https://www.php.net/manual/en/function.utf8-encode.php) function.
778
-
779
- = 0.18.0 =
780
- *(released: 24 Sep 2020)*
781
- * You can now set cache control header in CDN friendly mode too
782
- * The code for testing what actually works in .htaccess files on the server setup has been moved to a new library: [htaccess-capability-tester](https://github.com/rosell-dk/htaccess-capability-tester). It has been strengthened in the process.
783
- * Improved diagnosing in the "Live test" buttons
784
- * Simplified the logic for adding "Vary header" in the .htaccess residing in the cache dir. The logic no longer depends on the Apache module "mod_envif" being installed. mod_envif has Apache "Base" status, which means it is very rarely missing, so I decided not to trigger automatically updating of the .htaccess rules. To apply the change, you must click the button that forces .htaccess regeneration
785
- * The plugin has a folder called "wod" which contains php scripts for converting an image to webp. This is used for the function that rely on redirect magic to trigger conversion ("Enable redirection to converter?" and "Create webp files upon request?"). The .htaccess file in the "wod" folder in the plugin dir contains directives for modifying access (in order to counterfight rules placed by security plugins for disallows scripts to be run directly). However if these directives has been disallowed in the server setup, any request to a file in the folder will result in a 500 internal server error. To circumvent this, a "wod2" folder has been added, which contains the same scripts, but without the .htaccess. Upon saving, WebP Express now automatically checks which one works, and points to that in the .htaccess rules.
786
- * Bugfix: webp mime type was not registred in .htaccess in "CDN friendly" mode. This is a minor fix so I decided not to update the .htaccess automatically. To apply it, you must click the button that forces .htaccess regeneration.
787
- * Bugfix: Bulk convert failed to load the list when there were filenames containing non-unicode characters
788
- * Added a new way to support me. I'm on [GitHub Sponsors](https://github.com/sponsors/rosell-dk)!
789
-
790
- For more info, see the closed issues on the 0.18.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/33?closed=1
791
-
792
- = 0.17.5 =
793
- *(released: 11 Aug 2020)*
794
- * Fixed "Path is outside resolved document root" in a certain symlinked configuration. Thanks to @spiderPan on github for providing the fix.
795
- * Added content filtering hooks for several third party plugins including ACF and WooCommerce Product Images. With this change, the "Use content filtering hooks" in Alter HTML works in more scenarios, which means there are fewer scenarios where you have to resort to the slower "The complete page" option. Thanks to alextuan for providing the contribution
796
- * Fixed problems with Alter HTML when migrating: Absolute paths were cached in the database and the cache was only updated upon saving settings. The paths are not cached anymore (recalculating these on each page load is not a performance problem)
797
-
798
- For more info, see the closed issues on the 0.17.5 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/30?closed=1
799
-
800
- = 0.17.4 =
801
- *(released: 26 Jun 2020)*
802
- * Fixed bug: Configuration was repeatedly resetting for some users
803
- * Fixed "Path is outside resolved document root" on file conversion attempts in Windows. Thanks to @Ruzgfpegk from Japan for providing the fix.
804
- * Fix errors not caught in the selftest. Thanks to Benji Bilheimer from Germany providing the fix.
805
- * Fix errors not caught in the selftest with unverified certificates. Thanks to Rikesh Ramlochund from Mauritius for providing the fix.
806
- * Fixed errors with filenames containing encoded symbols. Thanks to Eddie Zhou from Canada for the fix.
807
-
808
- For more info, see the closed issues on the 0.17.3 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/32?closed=1
809
-
810
- = 0.17.3 =
811
- *(released: 3 Feb 2020)*
812
-
813
- * Fixed critical bug: Fatal error after updating plugin (if one had been postponing updating WebP Express for a while and then updated Wordpress to 5.2 and THEN updated WebP Express)
814
- * A critical bug was fixed in the webp-convert library (PHP 7.4 related)
815
- * A critical bug was fixed in dom-util-for-webp library (PHP 7.4 related)
816
- * Alter HTML now processes the "poster" attribute in Video tags. Thanks to @MikhailRoot from Russia for the PR on github.
817
- * On some Litespeed hosts, WebP Express reported that mod_headers was not available even though it was. Thanks to @lubieowoce from Poland for the PR on github)
818
-
819
- For more info, see the closed issues on the 0.17.3 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/31?closed=1
820
-
821
- = 0.17.2 =
822
- *(released: 5 Oct 2019)*
823
-
824
- * Fixed bug: Updating plugin failed on a few hosts (in the unzip phase). Problem was introduced in 0.17.0 with the updated binaries.
825
- * Fixed bug: Alter HTML used the protocol (http/https) for the site for generated links (rather than keeping the protocol for the link). Thanks to Jacob Gullberg from Sweden for discovering this bug.
826
-
827
- If you experienced update problems due to the update bug, you will probably be left with an incomplete installation. Some of the plugin files are there, but not all. Especially, the main plugin file (webp-express.php) is missing, which means that Wordpress don't "see" the plugin (it is missing from the list). Trying to install WebP Express again will probably not work, because the "webp-express" folder is already there. You will then have to remove the "webp-express" folder in "plugins" manually (via ftp or a plugin, such as File Manager).
828
-
829
- For more info, see the closed issues on the 0.17.2 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/29?closed=1
830
-
831
- = 0.17.1 =
832
- *(released: 3 Oct 2019)*
833
-
834
- * Fixed NGINX rules in FAQ (added xdestination for the create webp upon request functionality)
835
- * Fixed issue with Alter HTML. Thanks to @jonathanernst for discovering issue and supplying the patch.
836
- * WebP Express now works on WP Engine. Check out the new "I am on WP Engine" section in the FAQ
837
- * Miscellaneous bug fixes
838
-
839
- For more info, see the closed issues on the 0.17.1 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/27?closed=1
840
-
841
- = 0.17.0 =
842
- *(released: 27 sep 2019)*
843
-
844
- * Cwebp conversion method runs on more systems (not subject to open_basedir restrictions and also tries "pure" cwebp command). Thanks to cng11 for reaching out so I spotted this.
845
- * Ewww conversion method no longer does a remote api-key check for each conversion - so it is faster. If an ewww conversions fails due to a non-functional key, the key will not be tried again (until next time the options are saved)
846
- * Updated cwebp binaries to version 1.0.3
847
-
848
- = 0.16.0 =
849
- *(released: 24 sep 2019)*
850
-
851
- * Added option to specify CDN urls in Alter HTML. Thanks to Gunnar Peipman from Estonia for suggesting this.
852
- * Direct Nginx users to Nginx FAQ section on welcome page
853
- * Fixed Bulk Conversion halting due to nonce expiry
854
- * Fixed unexpected output upon reactivation
855
- * Added affiliate link to [Optimole](https://optimole.pxf.io/20b0M) in the "Don't despair - You have options!" message
856
-
857
- = 0.15.3 =
858
- *(released: 19 sep 2019)*
859
-
860
- * Fixed fatal error upon activation for systems which cannot use document root for structuring (rare)
861
-
862
- = 0.15.2 =
863
- *(released: 17 sep 2019)*
864
-
865
- * Fixed the bug when File extension was set to "Set to .webp". It was buggy when file extension contained uppercase letters.
866
-
867
- = 0.15.1 =
868
- *(released: 17 sep 2019)*
869
-
870
- * Bug alert: Added alert about a bug when destination folder is set to "mingled" and File extension is set to "Set to .webp"
871
- * Bugfix: Plugin URL pointed to webpexpress - it should point to parent. This gave trouble with images located in plugins. Thanks to Guillaume Meyer from Switzerland for discovering and reporting.
872
- * Bugfix: Images with uppercase chars in extension did not get Vary:Accept
873
- * Bugfix: There were issues with "All content" and destination:document-root when webp-realizer is activated
874
-
875
- For more info, see the closed issues on the 0.15.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/23?closed=1
876
-
877
- = 0.15.0 =
878
- *(released: 17 sep 2019)*
879
-
880
- * Provided test-buttons for checking if the redirects works.
881
- * You can now choose which folders WebP Express is active in. Ie "Uploads and Themes".
882
- * You can now choose an alternative file structure for the webps which does not rely on DOCUMENT_ROOT being available.
883
- * WebP Express can now handle when wp-content is symlinked.
884
- * The .htaccess rules are now divided across folders. Some rules are needed where the source files are located, some where the webp files are located.
885
- * Added option to convert only PNG files
886
- * And a couple of bugfixes.
887
-
888
- For more info, see the closed issues on the 0.15.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/22?closed=1
889
-
890
- = 0.14.22 =
891
- *(released: 4 aug 2019)*
892
-
893
- * Fixed bug in Nginx rules in the FAQ (they did not take into account that the webp images outside upload folder are never stored "mingled")
894
- * Fixed bug: The extension setting was not respected - it was always appending ".webp", never setting. Thanks to Florian from Germany and Derrick Hammer from USA for reporting.
895
- * Fixed bug: It turns out that Imagick gives up quality detection for some images and returns 0. This resulted in very poor quality webps when the quality was set to same as jpeg. The 0 is now treated as a failure and the max-quality will be used for such images. Thanks to @sanjayojha303 from India for reporting that there were quality problems on some images.
896
- * Fixed bug-like behavior: The conversion scripts no longer requires that the respective setting is on for Nginx. Thanks to Mike from Russia for reporting this.
897
- * Fixed bug: The error handler in webp-convert for handling warnings could in some cases result in endless recursion. For some the result was that they could no longer upload images. Thanks to Tobias Keller from Germany for reporting this bug.
898
- * Fixed minor bug: Attempt to call private method in a rare scenario (when accessing one of the php scripts in the "wod" folder directly - which is not allowed). Thanks to Giacomo Lawrance from the U.K. for providing input that led to this discovery.
899
- * Fixed minor bug: It was not tested whether a corresponding webp existed before trying to deleting it when an image was deleted. This produced warnings in debug.log.
900
- * Security related: Added sanitizing of paths to avoid false positives on coderisk.com (there where no risk because already test the paths for sanity - but this is not detected by coderisk, as the variable is not modified). This was simply done in order get rid of the warnings at coderisk.
901
- * Security fix: Paths were not sanitized on Windows.
902
-
903
- = 0.14.21 =
904
- *(released: 30 jun 2019)*
905
-
906
- * Hopefully fixed WebP Express Error: "png" option is Object
907
-
908
- = 0.14.20 =
909
- *(released: 29 jun 2019)*
910
-
911
- * Fixed bug: Ewww api-key was forgot upon saving options
912
-
913
- = 0.14.19 =
914
- *(released: 28 jun 2019)** Provided test-buttons for checking if the redirects works.
915
- * You can now choose which folders WebP Express is active in. Ie "Uploads and Themes".
916
- * You can now choose an alternative file structure for the webps which does not rely on DOCUMENT_ROOT being available.
917
- * WebP Express can now handle when wp-content is symlinked.
918
- * The .htaccess rules are now divided across folders. Some rules are needed where the source files are located, some where the webp files are located.
919
- * And a couple of bugfixes.
920
-
921
- For more info, see the closed issues on the 0.15.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/22?closed=1
922
-
923
- * Removed a line that might course Sanity Check to fail ("path not within document root")
924
-
925
- = 0.14.18 =
926
- *(released: 28 jun 2019)*
927
-
928
- * Fixed Sanity Error: Path is outside allowed path on systems using symlinked folders
929
- * Updated cache breaking token for javascript in order for the last fix for changing password with Remote WebP Express to take effect
930
- * Fixed undefined variable error in image_make_intermediate_size hook, which prevented webps thumbnails to be generated upon upload
931
- * Minor bug fix in cwebp converter (updated to webp-convert v.2.1.4)
932
-
933
- = 0.14.17 =
934
- *(released: 28 jun 2019)*
935
-
936
- * Relaxed abspath sanity check on Windows
937
- * Fixed updating password for Remote WebP Express
938
-
939
- = 0.14.16 =
940
- *(released: 26 jun 2019)*
941
-
942
- * Fixed conversion errors using Bulk convert or Test convert on systems with symlinked folders
943
-
944
- = 0.14.15 =
945
- *(released: 26 jun 2019)*
946
-
947
- * Fixed errors with "redirect to conversion script" on systems with symlinked folders
948
- * Fixed errors with "redirect to conversion script" on systems where the filename cannot be passed through an environment variable
949
-
950
- = 0.14.14 =
951
- *(released: 26 jun 2019)*
952
-
953
- * Fixed errors on systems with symlinked folders
954
-
955
- = 0.14.13 =
956
- *(released: 26 jun 2019)*
957
-
958
- * Fixed errors in conversion scripts
959
-
960
- = 0.14.12 =
961
- *(released: 26 jun 2019)*
962
-
963
- * Fixed critical bug
964
-
965
- = 0.14.11 =
966
- *(released: 24 jun 2019)*
967
-
968
- The following security fixes has been applied in 0.14.0 - 0.14.11:
969
- It is urged that you upgrade all of you WebP Express installations!
970
-
971
- – Security fix: Closed a security hole that could be used to view the content of any file on the server (provided that the full path is known or guessed). This is a very serious flaw, which unfortunately has been around for quite a while.
972
- – Security fix: Added capability checks to options page.
973
- – Security fix: Sanitized user input.
974
- – Security fix: Added checks for file paths and directories.
975
- – Security fix: Nonces and capability checks for AJAX calls.
976
-
977
- = 0.14.10 =
978
- *(released: 24 jun 2019)*
979
-
980
- * Security related
981
-
982
- = 0.14.9 =
983
- *(released: 22 jun 2019)*
984
-
985
- * Security related
986
-
987
- = 0.14.8 =
988
- *(released: 21 jun 2019)*
989
-
990
- * Security related
991
-
992
- = 0.14.7 =
993
- *(released: 20 jun 2019)*
994
-
995
- * Security related: Removed unneccesary files from webp-convert library
996
-
997
- = 0.14.6 =
998
- *(released: 20 jun 2019)*
999
-
1000
- * Security related
1001
-
1002
- = 0.14.5 =
1003
- *(released: 20 jun 2019)*
1004
-
1005
- * Security related
1006
-
1007
- = 0.14.4 =
1008
- *(released: 18 jun 2019)*
1009
-
1010
- * Now bundles with multiple cwebp binaries for linux for systems where 1.0.2 fails.
1011
-
1012
- = 0.14.3 =
1013
- *(released: 18 jun 2019)*
1014
-
1015
- * Fixed filename of supplied cwebp for linux (bug was introduced in 0.14.2)
1016
-
1017
- = 0.14.2 =
1018
- *(released: 17 jun 2019)*
1019
-
1020
- * Fixed problem with older versions of cwebp
1021
- * Fixed that images was not deleted
1022
- * Fixed cache problem on options page on systems that disables cache busting (it resulted in "SyntaxError: JSON.parse")
1023
-
1024
- = 0.14.1 =
1025
- *(released: 15 jun 2019)*
1026
-
1027
- * Security related
1028
-
1029
- = 0.14.0 =
1030
- *(released: 15 jun 2019)*
1031
-
1032
- * Security fix: Closed a security hole that could be used to view the content of any file on the server (provided that the full path is known or guessed). This is a very serious flaw, which has been around for quite a while. I urge you to upgrade to 0.14.0.
1033
- * Added new "encoding" option, which can be set to auto. This can in some cases dramatically reduce the size of the webp. It is supported by all converters except ewww and gd.
1034
- * Added new "near-lossless" option (only for cwebp and vips). Using this is a good idea for reducing size of lossless webps with an acceptable loss of quality
1035
- * Added new "alpha-quality" option (all converters, except ewww and gd). Using this is a good idea when images with transparency are converted to lossy webp - it has the potential to reduce the size up to 50% (depending on the source material) while keeping an acceptable level of quality
1036
- * Added new conversion methods: Vips and GraphicsMagick
1037
- * Imagick conversion method now supports webp options (finally cracked it!)
1038
- * Using MimeType detection instead of relying on file extensions
1039
- * In "test" converter you now change options and also test PNG files
1040
- * Added conversion logs
1041
- * PNGs are now enabled by default (with the new conversion features especially PNGs are compressed much better)
1042
-
1043
- For more info, see the closed issues on the 0.14.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/9?closed=1
1044
-
1045
- = 0.13.2 =
1046
- *(released: 16 may 2019)*
1047
-
1048
- * Fixed Fatal error on image upload in combination with the [Enable Media Replace](https://de.wordpress.org/plugins/enable-media-replace/) plugin. Thanks to Alexander Graef from Germany for reporting.
1049
- * It seems we finally nailed the blank settings page bug. Thanks to all involved, especially Richard Spenceley from the UK
1050
-
1051
- = 0.13.1 =
1052
- *(released: 10 may 2019)*
1053
-
1054
- * Fixed critical bug which could result in update failures in the Gutenberg editor. Thanks to Andrei Glingeanu from Moldova for his part in solving this.
1055
- * Fixed bug that caused the Bulk convert to start over. Thanks to Bas van Dijk, presumably from the Netherlands, for finding the root cause.
1056
- * On Nginx (in some configurations), the script that triggered conversion were exiting prematurely. Thanks to Sam Benson from the UK for fixing this.
1057
- * Improved Bulk conversion client, which was slowing down when many images where converted.
1058
- * A class had existential problems on HHVM. Thanks to @jaumerrr for posting the PHP error message.
1059
- * When selecting "custom" in the Cache-Control dropdown, the default text was invalid syntax. Thanks to Kevin Batdorf from Thailand for discovering this.
1060
- * When an image was deleted, the corresponding webp image was not automatically deleted. Thanks to Tobias Keller from Germany for pointing out that this could potentially lead to old webp images being shown.
1061
-
1062
- For more info, see the closed issues on the 0.13.1 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/17?closed=1
1063
-
1064
- = 0.13.0 =
1065
- *(released: 21 mar 2019)*
1066
-
1067
- * Bulk Conversion
1068
- * Fixed problems with Gd converter and PNG
1069
- * Optinally auto convert upon media upload
1070
- * Windows fix (thanks, lwxbr!)
1071
-
1072
- For more info, see the closed issues on the 0.13.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/16?closed=1
1073
-
1074
- = 0.12.2 =
1075
- *(released 8 mar 2019)*
1076
-
1077
- * Fixed bug: On some nginx configurations, the newly added protection against directly calling the converter scripts were triggering also when it should not.
1078
-
1079
- = 0.12.1 =
1080
- *(released 7 mar 2019)*
1081
-
1082
- * Fixed bug: Alter HTML crashed when HTML was larger than 600kb and "image urls" where selected
1083
-
1084
- = 0.12.0 =
1085
- *(released 5 mar 2019)*
1086
-
1087
- * Multisite support (!)
1088
- * A new operation mode: "No conversion", if you do not want to use WebP Express for converting. Replaces the old "Just redirect" mode
1089
- * Added capability testing of .htaccess. The .htaccess rules are now tailored to the capabilities on the system. For example, on some platforms the filename of a requested image is passed to the converter script through the query string, but on platforms that supports passing it through an environment variable, that method is used instead
1090
- * Picturefill.js is now optional (alter html, picture tag)
1091
- * A great bunch more!
1092
-
1093
- For more info, see the closed issues on the 0.12.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/12?closed=1
1094
-
1095
- = 0.11.3 =
1096
- *(released 18 feb 2019)*
1097
-
1098
- * Fixed bug: Alter HTML caused media library not to display images on some systems. Alter HTML is now disabled in admin mode.
1099
- * Alter HTML (picture tags) could produce the source tags with "src" attribute. But source tags inside picture tags must use "srcset" attribute. Fixed.
1100
- * Alter HTML (image urls): srcsets containing "x" descriptors wasn't handled (ie, srcset="image.jpg 1x")
1101
- * Fixed rewrite rules when placed in root so they are confined to wp-content and uploads. In particular, they no longer apply in wp-admin area, which might have caused problems, ie with media library.
1102
- * Added warning when rules are placed in root and "Convert non-existing webp-files upon request" feature is enabled and WebP Express rules are to be placed below Wordpress rules
1103
- * Fixed bug: The code that determined if WebP Express had placed rules in a .htaccess failed in "CDN friendly" mode. The effect was that these rules was not cleaned up upon plugin deactivation
1104
-
1105
- = 0.11.2 =
1106
- *(released 14 feb 2019)*
1107
-
1108
- * Fixed bug which caused Alter HTML to fail miserably on some setups
1109
- * AlterHTML now also looks for lazy load attributes in DIV and LI tags.
1110
-
1111
- = 0.11.1 =
1112
- *(released 6 feb 2019)*
1113
-
1114
- * Fixed bug which caused the new "Convert non-existing webp-files upon request" not to work on all setups
1115
-
1116
- = 0.11.0 =
1117
- *(released 6 feb 2019)*
1118
- * Alter HTML to point to webp files (choose between picture tags or simply altering all image urls)
1119
- * Convert non-existing webp-files upon request (means you can reference the converted webp files before they are actually converted!)
1120
-
1121
- For more info, see the closed issues on the 0.11.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/14?closed=1
1122
-
1123
- = 0.10.0 =
1124
- *(released 7 jan 2019)*
1125
-
1126
- * Introduced "Operation modes" in order to keep setting screens simple but still allow tweaking
1127
- * WebP Express can now be used in conjunction with Cache Enabler and ShortPixel
1128
- * Cache-Control header is now added in *.htaccess*, when redirecting directly to existing webp
1129
-
1130
- For more info, see the closed issues on the 0.10.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/7?closed=1
1131
-
1132
- = 0.9.1 =
1133
- *(released 28 dec 2018)*
1134
-
1135
- * Fixed critical bug causing blank page on options page
1136
-
1137
- = 0.9.0 =
1138
- *(released 27 dec 2018)*
1139
- * Optionally make .htaccess redirect directly to existing webp (improves performance)
1140
- * Optionally do not send filename from *.htaccess* to the PHP in Querystring, but use other means (improves security and reduces risks of problems due to firewall rules)
1141
- * Fixed some bugs
1142
-
1143
- For more info, see the closed issues on the 0.9.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/issues?q=is%3Aclosed+milestone%3A0.9.0
1144
-
1145
- = 0.8.1 =
1146
- *(released 11 dec 2018)*
1147
-
1148
- * Fixed javascript bug
1149
-
1150
- = 0.8.0 =
1151
- *(released 11 dec 2018)*
1152
-
1153
- * New conversion method, which calls imagick binary directly. This will make WebP express work out of the box on more systems
1154
- * Made sure not to trigger LFI warning i Wordfence (to activate, click the force .htaccess button)
1155
- * Imagick can now be configured to set quality to auto on systems where the auto option isn't generally available
1156
- * Added Last-Modified header to images. This makes image caching work better
1157
- * On some systems, converted files where stored in ie *..doc-rootwp-content..* rather than *..doc-root/wp-content..*. This is fixed, a clean-up script corrects the file structure upon upgrade.
1158
- * Added condition in .htaccess that checks that source file exists before handing over to converter
1159
-
1160
- For more info, see the closed issues on the 0.8.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/issues?q=is%3Aclosed+milestone%3A0.8.0
1161
-
1162
- = 0.7.2 =
1163
- *(released 21 nov 2018)*
1164
- Fixed a critical bug which generated an error message which caused corrupt images. It was not the bug itself, but the error message it generated, that caused the images to be corrupted. It only happened when debugging was enabled in php.ini
1165
-
1166
- = 0.7.1 =
1167
- *(released 9 nov 2018)*
1168
- Fixed minor "bug". The Api version combobox in Remote WebP Express converter was showing on new sites, but I only want it to show when old api is being used.
1169
-
1170
- = 0.7.0 =
1171
- *(released 9 nov 2018)*
1172
- This version added option to provide conversion service to other sites!
1173
-
1174
- For more info, see the closed issues on the 0.7.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/issues?q=is%3Aclosed+milestone%3A0.7.0
1175
-
1176
- = 0.6.0 =
1177
- *(released 4 okt 2018)*
1178
- This version added option for setting caching header, fixed a serious issue with *Imagick*, added a new converter, *Gmagick*, added a great deal of options to *Cwebp* and generally improved the interface.
1179
-
1180
- * Added option for caching
1181
- * Fixed long standing and serious issue with Imagick converter. It no longer generates webp images in poor quality
1182
- * Added gmagick as a new conversion method
1183
- * WebPExpress now runs on newly released WebP-Convert 1.2.0
1184
- * Added many new options for *cwebp*
1185
- * You can now quickly see converter status by hovering over a converter
1186
- * You can now choose between having quality auto-detected or not (if the server supports detecting quality).
1187
- * If the server does not support detecting quality, the WPC converter will display a quality "auto" option
1188
- * Added special intro message for those who has no working conversion methods
1189
- * Added help texts for options
1190
- * Settings are now saved, when changing converter options. Too many times, I found myself forgetting to save...
1191
-
1192
- For more info, see the closed issues on the 0.6.0 milestone on our github repository: https://github.com/rosell-dk/webp-express/issues?q=is%3Aclosed+milestone%3A0.6.0
1193
-
1194
- = 0.5.0 =
1195
- *(released 14 sep 2018)*
1196
- This version works on many more setups than the previous. Also uses less resources and handles when images are changed.
1197
-
1198
- * Configuration is now stored in a separate configuration file instead of storing directly in the *.htaccess* file and passing it on via query string. When updating, these settings are migrated automatically.
1199
- * Handles setups where Wordpress has been given its own directory (both methods mentioned [here](https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory))
1200
- * Handles setups where *wp-content* has been moved, even out of Wordpress root.
1201
- * Handles setups where Uploads folder has been moved, even out of *wp-content*.
1202
- * Handles setups where Plugins folder has been moved, even out of *wp-content* or out of Wordpress root
1203
- * Is not as likely to be subject to firewalls blocking requests (in 0.4.0, we passed all options in a querystring, and that could trigger firewalls under some circumstances)
1204
- * Is not as likely to be subject to rewrite rules from other plugins interfering. WebP Express now stores the .htaccess in the wp-content folder (if you allow it). As this is deeper than the root folder, the rules in here takes precedence over rules in the main *.htaccess*
1205
- * The *.htaccess* now passes the complete absulute path to the source file instead of a relative path. This is a less error-prone method.
1206
- * Reconverts the webp, if source image has changed
1207
- * Now runs on version 1.0.0 of [WebP On Demand](https://github.com/rosell-dk/webp-on-demand). Previously ran on 0.3.0
1208
- * Now takes care of only loading the PHP classes when needed in order not to slow down your Wordpress. The frontend will only need to process four lines of code. The backend footprint is also quite small now (80 lines of code of hooks)
1209
- * Now works in Wordpress 4.0 - 4.6.
1210
- * Added cache-breaking tokens to image test links
1211
- * Denies deactivation if rewrite rules could not be removed
1212
- * Refactored thoroughly
1213
- * More helpful texts.
1214
- * Extensive testing. Tested on Wordpress 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8 and 4.9. Tested with PHP 5.6, PHP 7.0 and PHP 7.1. Tested on Apache and LiteSpeed. Tested when missing various write permissions. Tested migration. Tested when installed in root, in subfolder, when Wordpress has its own directory (both methods), when wp-content is moved out of Wordpress directory, when plugins is moved out of Wordpress directory, when both of them are moved and when uploads have been moved.
1215
-
1216
- For more info, see the closed issues on the 0.5.0 milestone on our github repository: https://github.com/rosell-dk/webp-express/milestone/2?closed=1
1217
-
1218
  For older releases, check out changelog.txt
1219
 
1220
  == Upgrade Notice ==
1221
 
 
 
 
1222
  = 0.19.1 =
1223
  * Bugfix for PHP 8
1224
 
4
  Tags: webp, images, performance
5
  Requires at least: 4.0
6
  Tested up to: 5.7
7
+ Stable tag: 0.20.1
8
  Requires PHP: 5.6
9
  License: GPLv3
10
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
13
 
14
  == Description ==
15
 
16
+ More than 9 out of 10 users are using a browser that is able to display webp images. Yet, on most websites, they are served jpeg images, which are typically double the size of webp images for a given quality. What a waste of bandwidth! This plugin was created to help remedy that situation. With little effort, Wordpress admins can have their site serving autogenerated webp images to browsers that supports it, while still serving jpeg and png files to browsers that does not support webp.
17
 
18
  ### The image converter
19
  The plugin uses the [WebP Convert](https://github.com/rosell-dk/webp-convert) library to convert images to webp. *WebP Convert* is able to convert images using multiple methods. There are the "local" conversion methods: `imagick`, `cwebp`, `vips`, `gd`. If none of these works on your host, there are the cloud alternatives: `ewww` (paid) or connecting to a Wordpress site where you got WebP Express installed and you enabled the "web service" functionality.
49
  2. Activate the plugin through the 'Plugins' screen in WordPress
50
  3. Configure it (the plugin doesn't do anything until configured)
51
  4. Verify that it works
52
+ 5. (Optional) Bulk convert all images, either in the admin ui or using WP CLI (command: "webp-express")
53
 
54
  ### Configuring
55
  You configure the plugin in *Settings > WebP Express*.
119
  *Note:*
120
  Do not simply remove the plugin without deactivating it first. Deactivation takes care of removing the rules in the *.htaccess* file. With the rules there, but converter gone, your Google Chrome visitors will not see any jpeg images.
121
 
122
+ ### Bulk convert
123
+ You can start a bulk conversion two ways:
124
+ 1. In the admin UI. On the settings screen, there is a "Bulk Convert" button
125
+ 2. By using WP CLI (command: "webp-express").
126
+
127
+ I'm currently working on a file manager interface, which will become a third way.
128
+
129
+ ### Making sure new images becomes converted
130
+ There are several ways:
131
+ 1. Enable redirection to converter in the *.htaccess rules* section.
132
+ 2. Enable "Convert on upload". Note that this may impact upload experience in themes which defines many formats.
133
+ 3. Set up a cron job, which executes `wp webp-express convert` regularily
134
+
135
+ ### WP CLI command
136
+ WebP Express currently supports commands for converting and flushing webp images throug the CLI. You can use the --help option to learn about the options:
137
+ `wp webp-express --help`. Displays the available commands
138
+ `wp webp-express convert --help`. Displays the available options for the "convert" command.
139
+
140
+ A few examples:
141
+ `wp webp-express convert`: Creates webp images for all unconverted images
142
+ `wp webp-express convert --reconvert`: Also convert images that are already converted
143
+ `wp webp-express convert themes`: Only images in the themes folder
144
+ `wp webp-express convert uploads/2021`: Only images in the "2021" folder inside the uploads folder
145
+ `wp webp-express convert --only-png`: Only the PNG images
146
+ `wp webp-express convert --quality=50`: Use quality 50 (instead of what was entered in settings screen)
147
+ `wp webp-express convert --converter=cwebp`: Specifically use cwebp converter.
148
+
149
+ `wp webp-express flushwebp`: Remove all webp images
150
+ `wp webp-express flushwebp --only-png`: Remove all webp images that are conversions of PNG images
151
+
152
+ Synopsises:
153
+ `wp webp-express convert [<location>] [--reconvert] [--only-png] [--only-jpeg] [--quality=<number>] [--near-lossless=<number>] [--alpha-quality=<number>] [--encoding=<auto|lossy|lossless>] [--converter=<converter>]`
154
+ `wp webp-express flushwebp [--only-png]`
155
+
156
+ I'm considering adding commands for viewing status, viewing conversion stats, generating the .htaccess files and modifying the settings. Please let me know if you need any of these or perhaps something else.
157
+
158
  == Limitations ==
159
 
160
  * The plugin [should now work on Microsoft IIS server](https://github.com/rosell-dk/webp-express/pull/213), but it has not been tested thoroughly.
173
  * [Mathieu Gollain-Dupont](https://www.linkedin.com/in/mathieu-gollain-dupont-9938a4a/)
174
  * Ruben Solvang
175
 
176
+ **Persons who recently contributed with [ko-fi](https://ko-fi.com/rosell) - Thanks!**
177
+ * 19 May: Gary Wong
178
+ * 12 May: nicoletta
179
+ * 6 May: Hanneke
 
 
 
 
 
 
 
 
 
 
 
180
 
181
  **Persons who contributed with extra generously amounts of coffee / lifetime backing (>30$) - thanks!:**
182
 
183
+ * Max Kreminsky ($115)
184
  * Justin - BigScoots ($105)
185
  * Bill Vallance ($102)
186
  * Sebastian ($99)
187
  * Tammy Lee ($90)
188
  * Steven Sullivan ($51)
189
+ * Mathieu Gollain-Dupont ($50)
190
 
191
  == Frequently Asked Questions ==
192
 
755
  = When is feature X coming? / Roadmap =
756
  No schedule. I move forward as time allows. I currently spend a lot of time answering questions in the support forum. If someone would be nice and help out answering questions here, it would allow me to spend that time developing. Also, donations would allow me to turn down some of the more boring requests from my customers, and speed things up here.
757
 
758
+ Here are my current plans ahead: 0.21 will probably be a file manager-like interface for converting / bulk converting / viewing conversion logs / comparing original vs webp visually - kind of a merge of current "test converter" and "bulk conversion" interfaces, and with an addition of a file explorer. 0.22 might allow excluding certain files and folders. 0.23 could be supporting Save-Data header in Varied Image Responses mode (send extra compressed images to clients who wants to use as little bandwidth as possible). 0.21 might be displaying rules for NGINX. 0.24 might be an effort to allow webp for all browsers using [this javascript library](http://libwebpjs.hohenlimburg.org/v0.6.0/). Unfortunately, the javascript library does not (currently) support srcset attributes, which is why I moved this item down the priority list. We need srcset to be supported for the feature to be useful. 0.26 might be WAMP support. The current milestones, their subtasks and their progress can be viewed here: https://github.com/rosell-dk/webp-express/milestones
759
 
760
  If you wish to affect priorities, it is certainly possible. You can try to argue your case in the forum or you can simply let the money do the talking. By donating as little as a cup of coffee on [ko-fi.com/rosell](https://ko-fi.com/rosell), you can leave a wish. I shall take these wishes into account when prioritizing between new features.
761
 
771
 
772
  == Changelog ==
773
 
774
+ = 0.20.0 =
775
+ *(released: 17 Jun 2021)*
776
+ * Added WP CLI support. Add "wp webp-express convert" to crontab for nightly conversions of new images! Thanks to Isuru Sampath Ratnayake from Sri Lanka for initializing this.
777
+ * Added "sharp-yuv" (not as option, but as always on). Better YUV->RGB color conversion at almost no price. [Read more here](https://www.ctrl.blog/entry/webp-sharp-yuv.html). Supported by cwebp, vips, gmagick, graphicsmagick, imagick and imagemagick
778
+ * Bumped cwebp binaries to 1.2.0
779
+ * cwebp now only validates hash of supplied precompiled binaries when necessary. This cuts down conversion time.
780
+ * Convert on upload now defaults to false, as it may impact upload experience in themes with many formats.
781
+
782
+ For more info, see the closed issues on the [0.20.0 milestone on the github repository](https://github.com/rosell-dk/webp-express/milestone/38?closed=1)
783
+
784
  = 0.19.1 =
785
  *(released: 03 May 2021)*
786
  * Bugfix for PHP 8.0 - fread() does not permit second argument to be 0. Thanks to @trition for reporting and fixing this bug.
787
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
788
  For older releases, check out changelog.txt
789
 
790
  == Upgrade Notice ==
791
 
792
+ = 0.20.0 =
793
+ * Added WP CLI support. Add "wp webp-express convert" to crontab for nightly conversions of new images.
794
+
795
  = 0.19.1 =
796
  * Bugfix for PHP 8
797
 
changelog.txt CHANGED
@@ -1,3 +1,13 @@
 
 
 
 
 
 
 
 
 
 
1
  = 0.19.1 =
2
  *(released: 03 May 2021)*
3
  * Bugfix for PHP 8.0 - fread() does not permit second argument to be 0. Thanks to @trition for reporting and fixing this bug.
1
+ = 0.20.0 =
2
+ *(released: 17 Jun 2021)*
3
+ * Added WP CLI support. Add "wp webp-express convert" to crontab for nightly conversions of new images! Thanks to Isuru Sampath Ratnayake from Sri Lanka for initializing this.
4
+ * Added "sharp-yuv" (not as option, but as always on). Better YUV->RGB color conversion at almost no price. [Read more here](https://www.ctrl.blog/entry/webp-sharp-yuv.html). Supported by cwebp, vips, gmagick, graphicsmagick, imagick and imagemagick
5
+ * Bumped cwebp binaries to 1.2.0
6
+ * cwebp now only validates hash of supplied precompiled binaries when necessary. This cuts down conversion time.
7
+ * Convert on upload now defaults to false, as it may impact upload experience in themes with many formats.
8
+ * bugfix: Alpha quality was saved incorrectly for PNG. Thanks to Chriss Gibbs from the UK for finding and fixing this.
9
+ * bugfix: wp-debug log could be flooded with "Undefined index: HTTP_ACCEPT". Thanks to @markusreis for finding and fixing this.
10
+
11
  = 0.19.1 =
12
  *(released: 03 May 2021)*
13
  * Bugfix for PHP 8.0 - fread() does not permit second argument to be 0. Thanks to @trition for reporting and fixing this bug.
composer.lock ADDED
@@ -0,0 +1,586 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_readme": [
3
+ "This file locks the dependencies of your project to a known state",
4
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5
+ "This file is @generated automatically"
6
+ ],
7
+ "content-hash": "8d87b7a5fbf478fb9601a085868d82b5",
8
+ "packages": [
9
+ {
10
+ "name": "composer/installers",
11
+ "version": "v1.11.0",
12
+ "source": {
13
+ "type": "git",
14
+ "url": "https://github.com/composer/installers.git",
15
+ "reference": "ae03311f45dfe194412081526be2e003960df74b"
16
+ },
17
+ "dist": {
18
+ "type": "zip",
19
+ "url": "https://api.github.com/repos/composer/installers/zipball/ae03311f45dfe194412081526be2e003960df74b",
20
+ "reference": "ae03311f45dfe194412081526be2e003960df74b",
21
+ "shasum": ""
22
+ },
23
+ "require": {
24
+ "composer-plugin-api": "^1.0 || ^2.0"
25
+ },
26
+ "replace": {
27
+ "roundcube/plugin-installer": "*",
28
+ "shama/baton": "*"
29
+ },
30
+ "require-dev": {
31
+ "composer/composer": "1.6.* || ^2.0",
32
+ "composer/semver": "^1 || ^3",
33
+ "phpstan/phpstan": "^0.12.55",
34
+ "phpstan/phpstan-phpunit": "^0.12.16",
35
+ "symfony/phpunit-bridge": "^4.2 || ^5",
36
+ "symfony/process": "^2.3"
37
+ },
38
+ "type": "composer-plugin",
39
+ "extra": {
40
+ "class": "Composer\\Installers\\Plugin",
41
+ "branch-alias": {
42
+ "dev-main": "1.x-dev"
43
+ }
44
+ },
45
+ "autoload": {
46
+ "psr-4": {
47
+ "Composer\\Installers\\": "src/Composer/Installers"
48
+ }
49
+ },
50
+ "notification-url": "https://packagist.org/downloads/",
51
+ "license": [
52
+ "MIT"
53
+ ],
54
+ "authors": [
55
+ {
56
+ "name": "Kyle Robinson Young",
57
+ "email": "kyle@dontkry.com",
58
+ "homepage": "https://github.com/shama"
59
+ }
60
+ ],
61
+ "description": "A multi-framework Composer library installer",
62
+ "homepage": "https://composer.github.io/installers/",
63
+ "keywords": [
64
+ "Craft",
65
+ "Dolibarr",
66
+ "Eliasis",
67
+ "Hurad",
68
+ "ImageCMS",
69
+ "Kanboard",
70
+ "Lan Management System",
71
+ "MODX Evo",
72
+ "MantisBT",
73
+ "Mautic",
74
+ "Maya",
75
+ "OXID",
76
+ "Plentymarkets",
77
+ "Porto",
78
+ "RadPHP",
79
+ "SMF",
80
+ "Starbug",
81
+ "Thelia",
82
+ "Whmcs",
83
+ "WolfCMS",
84
+ "agl",
85
+ "aimeos",
86
+ "annotatecms",
87
+ "attogram",
88
+ "bitrix",
89
+ "cakephp",
90
+ "chef",
91
+ "cockpit",
92
+ "codeigniter",
93
+ "concrete5",
94
+ "croogo",
95
+ "dokuwiki",
96
+ "drupal",
97
+ "eZ Platform",
98
+ "elgg",
99
+ "expressionengine",
100
+ "fuelphp",
101
+ "grav",
102
+ "installer",
103
+ "itop",
104
+ "joomla",
105
+ "known",
106
+ "kohana",
107
+ "laravel",
108
+ "lavalite",
109
+ "lithium",
110
+ "magento",
111
+ "majima",
112
+ "mako",
113
+ "mediawiki",
114
+ "miaoxing",
115
+ "modulework",
116
+ "modx",
117
+ "moodle",
118
+ "osclass",
119
+ "phpbb",
120
+ "piwik",
121
+ "ppi",
122
+ "processwire",
123
+ "puppet",
124
+ "pxcms",
125
+ "reindex",
126
+ "roundcube",
127
+ "shopware",
128
+ "silverstripe",
129
+ "sydes",
130
+ "sylius",
131
+ "symfony",
132
+ "tastyigniter",
133
+ "typo3",
134
+ "wordpress",
135
+ "yawik",
136
+ "zend",
137
+ "zikula"
138
+ ],
139
+ "support": {
140
+ "issues": "https://github.com/composer/installers/issues",
141
+ "source": "https://github.com/composer/installers/tree/v1.11.0"
142
+ },
143
+ "funding": [
144
+ {
145
+ "url": "https://packagist.com",
146
+ "type": "custom"
147
+ },
148
+ {
149
+ "url": "https://github.com/composer",
150
+ "type": "github"
151
+ },
152
+ {
153
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
154
+ "type": "tidelift"
155
+ }
156
+ ],
157
+ "time": "2021-04-28T06:42:17+00:00"
158
+ },
159
+ {
160
+ "name": "onnov/detect-encoding",
161
+ "version": "v1.2.0",
162
+ "source": {
163
+ "type": "git",
164
+ "url": "https://github.com/onnov/detect-encoding.git",
165
+ "reference": "c88cea4f0c83d7f7e0675f2801e0995f328de1ce"
166
+ },
167
+ "dist": {
168
+ "type": "zip",
169
+ "url": "https://api.github.com/repos/onnov/detect-encoding/zipball/c88cea4f0c83d7f7e0675f2801e0995f328de1ce",
170
+ "reference": "c88cea4f0c83d7f7e0675f2801e0995f328de1ce",
171
+ "shasum": ""
172
+ },
173
+ "require": {
174
+ "ext-iconv": "*",
175
+ "php": ">=7.2"
176
+ },
177
+ "require-dev": {
178
+ "infection/infection": "*",
179
+ "phpbench/phpbench": "*",
180
+ "phpcompatibility/php-compatibility": "*",
181
+ "phpmd/phpmd": "*",
182
+ "phpstan/phpstan": "*",
183
+ "phpstan/phpstan-strict-rules": "*",
184
+ "phpunit/phpunit": "*",
185
+ "roave/backward-compatibility-check": "*",
186
+ "squizlabs/php_codesniffer": "*"
187
+ },
188
+ "type": "library",
189
+ "autoload": {
190
+ "psr-4": {
191
+ "Onnov\\DetectEncoding\\": "src/"
192
+ }
193
+ },
194
+ "notification-url": "https://packagist.org/downloads/",
195
+ "license": [
196
+ "MIT"
197
+ ],
198
+ "authors": [
199
+ {
200
+ "name": "onnov",
201
+ "email": "oblnn@yandex.ru"
202
+ }
203
+ ],
204
+ "description": "Text encoding definition class instead of mb_detect_encoding. Defines: utf-8, windows-1251, koi8-r, iso-8859-5, ibm866, .....",
205
+ "homepage": "https://github.com/onnov/detect-encoding",
206
+ "keywords": [
207
+ "cyrillic",
208
+ "encoding",
209
+ "ibm866",
210
+ "iconv",
211
+ "iso-8859-5",
212
+ "koi8-r",
213
+ "mb_detect_encoding",
214
+ "utf-8",
215
+ "windows-1251"
216
+ ],
217
+ "support": {
218
+ "issues": "https://github.com/onnov/detect-encoding/issues",
219
+ "source": "https://github.com/onnov/detect-encoding/tree/master"
220
+ },
221
+ "time": "2019-09-20T16:11:46+00:00"
222
+ },
223
+ {
224
+ "name": "rosell-dk/dom-util-for-webp",
225
+ "version": "0.4.0",
226
+ "source": {
227
+ "type": "git",
228
+ "url": "https://github.com/rosell-dk/dom-util-for-webp.git",
229
+ "reference": "5928aecf64d59124b341dce23ce8ecf48a6eded6"
230
+ },
231
+ "dist": {
232
+ "type": "zip",
233
+ "url": "https://api.github.com/repos/rosell-dk/dom-util-for-webp/zipball/5928aecf64d59124b341dce23ce8ecf48a6eded6",
234
+ "reference": "5928aecf64d59124b341dce23ce8ecf48a6eded6",
235
+ "shasum": ""
236
+ },
237
+ "require-dev": {
238
+ "friendsofphp/php-cs-fixer": "^2.11",
239
+ "phpunit/phpunit": "5.7.27",
240
+ "squizlabs/php_codesniffer": "3.*"
241
+ },
242
+ "suggest": {
243
+ "phpstan/phpstan": "Suggested for dev, in order to analyse code before committing"
244
+ },
245
+ "type": "library",
246
+ "extra": {
247
+ "scripts-descriptions": {
248
+ "ci": "Run tests before CI",
249
+ "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
250
+ "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
251
+ "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
252
+ "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
253
+ "test": "Launches the preconfigured PHPUnit"
254
+ }
255
+ },
256
+ "autoload": {
257
+ "psr-4": {
258
+ "DOMUtilForWebP\\": "src/"
259
+ }
260
+ },
261
+ "notification-url": "https://packagist.org/downloads/",
262
+ "license": [
263
+ "MIT"
264
+ ],
265
+ "authors": [
266
+ {
267
+ "name": "Bjørn Rosell",
268
+ "homepage": "https://www.bitwise-it.dk/contact",
269
+ "role": "Project Author"
270
+ }
271
+ ],
272
+ "description": "Replace image URLs found in HTML",
273
+ "keywords": [
274
+ "Webp",
275
+ "html",
276
+ "images",
277
+ "replace"
278
+ ],
279
+ "support": {
280
+ "issues": "https://github.com/rosell-dk/dom-util-for-webp/issues",
281
+ "source": "https://github.com/rosell-dk/dom-util-for-webp/tree/0.4.0"
282
+ },
283
+ "time": "2020-02-02T11:16:27+00:00"
284
+ },
285
+ {
286
+ "name": "rosell-dk/htaccess-capability-tester",
287
+ "version": "0.9",
288
+ "source": {
289
+ "type": "git",
290
+ "url": "https://github.com/rosell-dk/htaccess-capability-tester.git",
291
+ "reference": "2eb2cf38a9f42fc3aa647d6e9c896e124bfebf4c"
292
+ },
293
+ "dist": {
294
+ "type": "zip",
295
+ "url": "https://api.github.com/repos/rosell-dk/htaccess-capability-tester/zipball/2eb2cf38a9f42fc3aa647d6e9c896e124bfebf4c",
296
+ "reference": "2eb2cf38a9f42fc3aa647d6e9c896e124bfebf4c",
297
+ "shasum": ""
298
+ },
299
+ "require": {
300
+ "php": "^5.6 | ^7.0"
301
+ },
302
+ "require-dev": {
303
+ "phpunit/php-code-coverage": "dev-4.0-dev as 4.0.4",
304
+ "phpunit/phpunit": "^5.7",
305
+ "squizlabs/php_codesniffer": "3.*"
306
+ },
307
+ "suggest": {
308
+ "php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
309
+ },
310
+ "type": "library",
311
+ "extra": {
312
+ "scripts-descriptions": {
313
+ "ci": "Run tests before CI",
314
+ "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
315
+ "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
316
+ "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
317
+ "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
318
+ "test": "Launches the preconfigured PHPUnit"
319
+ }
320
+ },
321
+ "autoload": {
322
+ "psr-4": {
323
+ "HtaccessCapabilityTester\\": "src/"
324
+ }
325
+ },
326
+ "notification-url": "https://packagist.org/downloads/",
327
+ "license": [
328
+ "MIT"
329
+ ],
330
+ "authors": [
331
+ {
332
+ "name": "Bjørn Rosell",
333
+ "homepage": "https://www.bitwise-it.dk/contact",
334
+ "role": "Project Author"
335
+ }
336
+ ],
337
+ "description": "Test the capabilities of .htaccess files on the server using live tests",
338
+ "keywords": [
339
+ ".htaccess",
340
+ "apache",
341
+ "litespeed"
342
+ ],
343
+ "support": {
344
+ "issues": "https://github.com/rosell-dk/htaccess-capability-tester/issues",
345
+ "source": "https://github.com/rosell-dk/htaccess-capability-tester/tree/0.9"
346
+ },
347
+ "funding": [
348
+ {
349
+ "url": "https://github.com/rosell-dk",
350
+ "type": "github"
351
+ },
352
+ {
353
+ "url": "https://ko-fi.com/rosell",
354
+ "type": "ko_fi"
355
+ }
356
+ ],
357
+ "time": "2020-11-04T10:29:38+00:00"
358
+ },
359
+ {
360
+ "name": "rosell-dk/image-mime-type-guesser",
361
+ "version": "0.3",
362
+ "source": {
363
+ "type": "git",
364
+ "url": "https://github.com/rosell-dk/image-mime-type-guesser.git",
365
+ "reference": "204fd61ca81e3b0ba46c6165dab8f74816b1fe99"
366
+ },
367
+ "dist": {
368
+ "type": "zip",
369
+ "url": "https://api.github.com/repos/rosell-dk/image-mime-type-guesser/zipball/204fd61ca81e3b0ba46c6165dab8f74816b1fe99",
370
+ "reference": "204fd61ca81e3b0ba46c6165dab8f74816b1fe99",
371
+ "shasum": ""
372
+ },
373
+ "require-dev": {
374
+ "friendsofphp/php-cs-fixer": "^2.11",
375
+ "phpunit/phpunit": "^5.7.27",
376
+ "squizlabs/php_codesniffer": "3.*"
377
+ },
378
+ "type": "library",
379
+ "extra": {
380
+ "scripts-descriptions": {
381
+ "ci": "Run tests before CI",
382
+ "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
383
+ "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
384
+ "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
385
+ "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
386
+ "test": "Launches the preconfigured PHPUnit"
387
+ }
388
+ },
389
+ "autoload": {
390
+ "psr-4": {
391
+ "ImageMimeTypeGuesser\\": "src/"
392
+ }
393
+ },
394
+ "notification-url": "https://packagist.org/downloads/",
395
+ "license": [
396
+ "MIT"
397
+ ],
398
+ "authors": [
399
+ {
400
+ "name": "Bjørn Rosell",
401
+ "homepage": "https://www.bitwise-it.dk/contact",
402
+ "role": "Project Author"
403
+ }
404
+ ],
405
+ "description": "Guess mime type of images",
406
+ "keywords": [
407
+ "image",
408
+ "images",
409
+ "mime",
410
+ "mime type"
411
+ ],
412
+ "support": {
413
+ "issues": "https://github.com/rosell-dk/image-mime-type-guesser/issues",
414
+ "source": "https://github.com/rosell-dk/image-mime-type-guesser/tree/0.3"
415
+ },
416
+ "time": "2019-03-29T09:33:28+00:00"
417
+ },
418
+ {
419
+ "name": "rosell-dk/webp-convert",
420
+ "version": "2.6.0",
421
+ "source": {
422
+ "type": "git",
423
+ "url": "https://github.com/rosell-dk/webp-convert.git",
424
+ "reference": "ed230afe56d3157dc402c33585e3ab7f15c7ac80"
425
+ },
426
+ "dist": {
427
+ "type": "zip",
428
+ "url": "https://api.github.com/repos/rosell-dk/webp-convert/zipball/ed230afe56d3157dc402c33585e3ab7f15c7ac80",
429
+ "reference": "ed230afe56d3157dc402c33585e3ab7f15c7ac80",
430
+ "shasum": ""
431
+ },
432
+ "require": {
433
+ "php": "^5.6 | ^7.0 | ^8.0",
434
+ "rosell-dk/image-mime-type-guesser": "^0.3"
435
+ },
436
+ "require-dev": {
437
+ "friendsofphp/php-cs-fixer": "^2.11",
438
+ "phpunit/phpunit": "^9.3",
439
+ "squizlabs/php_codesniffer": "3.*"
440
+ },
441
+ "suggest": {
442
+ "ext-gd": "to use GD extension for converting. Note: Gd must be compiled with webp support",
443
+ "ext-imagick": "to use Imagick extension for converting. Note: Gd must be compiled with webp support",
444
+ "ext-vips": "to use Vips extension for converting.",
445
+ "php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
446
+ },
447
+ "type": "library",
448
+ "extra": {
449
+ "scripts-descriptions": {
450
+ "ci": "Run tests before CI",
451
+ "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
452
+ "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
453
+ "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
454
+ "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
455
+ "test": "Launches the preconfigured PHPUnit"
456
+ }
457
+ },
458
+ "autoload": {
459
+ "psr-4": {
460
+ "WebPConvert\\": "src/"
461
+ }
462
+ },
463
+ "notification-url": "https://packagist.org/downloads/",
464
+ "license": [
465
+ "MIT"
466
+ ],
467
+ "authors": [
468
+ {
469
+ "name": "Bjørn Rosell",
470
+ "homepage": "https://www.bitwise-it.dk/contact",
471
+ "role": "Project Author"
472
+ },
473
+ {
474
+ "name": "Martin Folkers",
475
+ "homepage": "https://twobrain.io",
476
+ "role": "Collaborator"
477
+ }
478
+ ],
479
+ "description": "Convert JPEG & PNG to WebP with PHP",
480
+ "keywords": [
481
+ "Webp",
482
+ "cwebp",
483
+ "gd",
484
+ "image conversion",
485
+ "images",
486
+ "imagick",
487
+ "jpg",
488
+ "jpg2webp",
489
+ "png",
490
+ "png2webp"
491
+ ],
492
+ "support": {
493
+ "issues": "https://github.com/rosell-dk/webp-convert/issues",
494
+ "source": "https://github.com/rosell-dk/webp-convert/tree/2.6.0"
495
+ },
496
+ "funding": [
497
+ {
498
+ "url": "https://github.com/rosell-dk",
499
+ "type": "github"
500
+ },
501
+ {
502
+ "url": "https://ko-fi.com/rosell",
503
+ "type": "ko_fi"
504
+ }
505
+ ],
506
+ "time": "2021-05-20T10:56:47+00:00"
507
+ },
508
+ {
509
+ "name": "rosell-dk/webp-convert-cloud-service",
510
+ "version": "2.0.1",
511
+ "source": {
512
+ "type": "git",
513
+ "url": "https://github.com/rosell-dk/webp-convert-cloud-service.git",
514
+ "reference": "703c2f1c76d30468ee3977170bfa3da138d8c4ad"
515
+ },
516
+ "dist": {
517
+ "type": "zip",
518
+ "url": "https://api.github.com/repos/rosell-dk/webp-convert-cloud-service/zipball/703c2f1c76d30468ee3977170bfa3da138d8c4ad",
519
+ "reference": "703c2f1c76d30468ee3977170bfa3da138d8c4ad",
520
+ "shasum": ""
521
+ },
522
+ "require": {
523
+ "rosell-dk/webp-convert": "^2.0.0"
524
+ },
525
+ "require-dev": {
526
+ "friendsofphp/php-cs-fixer": "^2.11",
527
+ "phpunit/phpunit": "5.7.27",
528
+ "squizlabs/php_codesniffer": "3.*"
529
+ },
530
+ "type": "library",
531
+ "extra": {
532
+ "scripts-descriptions": {
533
+ "ci": "Run tests before CI",
534
+ "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
535
+ "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
536
+ "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
537
+ "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
538
+ "test": "Launches the preconfigured PHPUnit"
539
+ }
540
+ },
541
+ "autoload": {
542
+ "psr-4": {
543
+ "WebPConvertCloudService\\": "src/"
544
+ }
545
+ },
546
+ "notification-url": "https://packagist.org/downloads/",
547
+ "license": [
548
+ "MIT"
549
+ ],
550
+ "authors": [
551
+ {
552
+ "name": "Bjørn Rosell",
553
+ "homepage": "https://www.bitwise-it.dk/contact",
554
+ "role": "Project Author"
555
+ }
556
+ ],
557
+ "description": "Cloud service for converting JPEG & PNG to WebP",
558
+ "keywords": [
559
+ "Webp",
560
+ "cwebp",
561
+ "gd",
562
+ "image conversion",
563
+ "images",
564
+ "imagick",
565
+ "jpg",
566
+ "jpg2webp",
567
+ "png",
568
+ "png2webp"
569
+ ],
570
+ "support": {
571
+ "issues": "https://github.com/rosell-dk/webp-convert-cloud-service/issues",
572
+ "source": "https://github.com/rosell-dk/webp-convert-cloud-service/tree/master"
573
+ },
574
+ "time": "2019-06-30T08:28:35+00:00"
575
+ }
576
+ ],
577
+ "packages-dev": [],
578
+ "aliases": [],
579
+ "minimum-stability": "stable",
580
+ "stability-flags": [],
581
+ "prefer-stable": false,
582
+ "prefer-lowest": false,
583
+ "platform": [],
584
+ "platform-dev": [],
585
+ "plugin-api-version": "2.1.0"
586
+ }
lib/classes/AlterHtmlHelper.php CHANGED
@@ -238,8 +238,10 @@ class AlterHtmlHelper
238
  self::getOptions();
239
 
240
  // Fail for webp-disabled browsers (when "only-for-webp-enabled-browsers" is set)
241
- if ((self::$options['only-for-webp-enabled-browsers']) && (strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') === false)) {
242
- return $returnValueOnFail;
 
 
243
  }
244
 
245
  // Fail for relative urls. Wordpress doesn't use such very much anyway
238
  self::getOptions();
239
 
240
  // Fail for webp-disabled browsers (when "only-for-webp-enabled-browsers" is set)
241
+ if (self::$options['only-for-webp-enabled-browsers']) {
242
+ if (!isset($_SERVER['HTTP_ACCEPT']) || (strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') === false)) {
243
+ return $returnValueOnFail;
244
+ }
245
  }
246
 
247
  // Fail for relative urls. Wordpress doesn't use such very much anyway
lib/classes/BulkConvert.php CHANGED
@@ -7,17 +7,9 @@ use \Onnov\DetectEncoding\EncodingDetector;
7
  class BulkConvert
8
  {
9
 
10
- public static function getList($config)
11
  {
12
-
13
- /*
14
- isUploadDirMovedOutOfWPContentDir
15
- isUploadDirMovedOutOfAbsPath
16
- isPluginDirMovedOutOfAbsPath
17
- isPluginDirMovedOutOfWpContent
18
- isWPContentDirMovedOutOfAbsPath */
19
-
20
- $listOptions = [
21
  //'root' => Paths::getUploadDirAbs(),
22
  'ext' => $config['destination-extension'],
23
  'destination-folder' => $config['destination-folder'], /* hm, "destination-folder" is a bad name... */
@@ -32,6 +24,25 @@ class BulkConvert
32
  ],
33
  'flattenList' => true,
34
  ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  $rootIds = Paths::filterOutSubRoots($config['scope']);
37
 
@@ -114,18 +125,19 @@ class BulkConvert
114
  if (preg_match($filter['_regexPattern'], $filename)) {
115
  $addThis = true;
116
 
 
 
 
 
 
 
 
 
 
 
 
117
  if (($filter['only-converted']) || ($filter['only-unconverted'])) {
118
  //$cacheDir = $listOptions['image-root'] . '/' . $relDir;
119
- $destination = ConvertHelperIndependent::getDestination(
120
- $dir . "/" . $filename,
121
- $listOptions['destination-folder'],
122
- $listOptions['ext'],
123
- $listOptions['webExpressContentDirAbs'],
124
- $listOptions['uploadDirAbs'],
125
- $listOptions['useDocRootForStructuringCacheDir'],
126
- $listOptions['imageRoots']
127
- );
128
- $webpExists = @file_exists($destination);
129
 
130
  // Check if corresponding webp exists
131
  /*
@@ -189,10 +201,10 @@ class BulkConvert
189
  if (!$encodedToUTF8) {
190
  if (function_exists('mb_convert_encoding')) {
191
  $encoding = mb_detect_encoding($path, mb_detect_order(), true);
192
- if ($encoding) {
193
- $path = mb_convert_encoding($path, 'UTF-8', $encoding);
194
- $encodedToUTF8 = true;
195
- }
196
  }
197
  }
198
 
@@ -230,7 +242,8 @@ class BulkConvert
230
  $results[] = $path;
231
  } else {
232
  $results[] = [
233
- 'name' => basename($path)
 
234
  ];
235
  }
236
  }
7
  class BulkConvert
8
  {
9
 
10
+ public static function defaultListOptions($config)
11
  {
12
+ return [
 
 
 
 
 
 
 
 
13
  //'root' => Paths::getUploadDirAbs(),
14
  'ext' => $config['destination-extension'],
15
  'destination-folder' => $config['destination-folder'], /* hm, "destination-folder" is a bad name... */
24
  ],
25
  'flattenList' => true,
26
  ];
27
+ }
28
+
29
+ /**
30
+ * Get grouped list of files. They are grouped by image roots.
31
+ *
32
+ */
33
+ public static function getList($config, $listOptions = null)
34
+ {
35
+
36
+ /*
37
+ isUploadDirMovedOutOfWPContentDir
38
+ isUploadDirMovedOutOfAbsPath
39
+ isPluginDirMovedOutOfAbsPath
40
+ isPluginDirMovedOutOfWpContent
41
+ isWPContentDirMovedOutOfAbsPath */
42
+
43
+ if (is_null($listOptions)) {
44
+ $listOptions = self::defaultListOptions($config);
45
+ }
46
 
47
  $rootIds = Paths::filterOutSubRoots($config['scope']);
48
 
125
  if (preg_match($filter['_regexPattern'], $filename)) {
126
  $addThis = true;
127
 
128
+ $destination = ConvertHelperIndependent::getDestination(
129
+ $dir . "/" . $filename,
130
+ $listOptions['destination-folder'],
131
+ $listOptions['ext'],
132
+ $listOptions['webExpressContentDirAbs'],
133
+ $listOptions['uploadDirAbs'],
134
+ $listOptions['useDocRootForStructuringCacheDir'],
135
+ $listOptions['imageRoots']
136
+ );
137
+ $webpExists = @file_exists($destination);
138
+
139
  if (($filter['only-converted']) || ($filter['only-unconverted'])) {
140
  //$cacheDir = $listOptions['image-root'] . '/' . $relDir;
 
 
 
 
 
 
 
 
 
 
141
 
142
  // Check if corresponding webp exists
143
  /*
201
  if (!$encodedToUTF8) {
202
  if (function_exists('mb_convert_encoding')) {
203
  $encoding = mb_detect_encoding($path, mb_detect_order(), true);
204
+ if ($encoding) {
205
+ $path = mb_convert_encoding($path, 'UTF-8', $encoding);
206
+ $encodedToUTF8 = true;
207
+ }
208
  }
209
  }
210
 
242
  $results[] = $path;
243
  } else {
244
  $results[] = [
245
+ 'name' => basename($path),
246
+ 'isConverted' => $webpExists
247
  ];
248
  }
249
  }
lib/classes/CLI.php ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ class CLI extends \WP_CLI_Command
6
+ {
7
+
8
+ private static function printableSize($bytes) {
9
+ return ($bytes < 10000) ? $bytes . " bytes" : round($bytes / 1024) . ' kb';
10
+ }
11
+
12
+ /**
13
+ * Convert images to webp
14
+ *
15
+ * ## OPTIONS
16
+ * [<location>]
17
+ * : Limit which folders to process to a single location. Ie "uploads/2021". The first part is the
18
+ * "image root", which must be "uploads", "themes", "plugins", "wp-content" or "index"
19
+ *
20
+ * [--reconvert]
21
+ * : Even convert images that are already converted (new conversions replaces the old conversions)
22
+ *
23
+ * [--only-png]
24
+ * : Only convert PNG images
25
+ *
26
+ * [--only-jpeg]
27
+ * : Only convert jpeg images
28
+ *
29
+ * [--quality=]
30
+ * : Override quality with specified (0-100)
31
+ *
32
+ * [--near-lossless]
33
+ * : Override near-lossless quality with specified (0-100)
34
+ *
35
+ * [--alpha-quality]
36
+ * : Override alpha-quality quality with specified (0-100)
37
+ *
38
+ * [--encoding]
39
+ * : Override encoding quality with specified ("auto", "lossy" or "lossless")
40
+ *
41
+ * [--converter=<converter>]
42
+ * : Specify the converter to use (default is to use the stack). Valid options: cwebp | vips | ewww | imagemagick | imagick | gmagick | graphicsmagick | ffmpeg | gd | wpc | ewww
43
+ */
44
+ public function convert($args, $assoc_args)
45
+ {
46
+ $config = Config::loadConfigAndFix();
47
+ $override = [];
48
+
49
+ if (isset($assoc_args['quality'])) {
50
+ $override['max-quality'] = intval($assoc_args['quality']);
51
+ $override['png-quality'] = intval($assoc_args['quality']);
52
+ }
53
+ if (isset($assoc_args['near-lossless'])) {
54
+ $override['png-near-lossless'] = intval($assoc_args['near-lossless']);
55
+ $override['jpeg-near-lossless'] = intval($assoc_args['near-lossless']);
56
+ }
57
+ if (isset($assoc_args['alpha-quality'])) {
58
+ $override['alpha-quality'] = intval($assoc_args['alpha-quality']);
59
+ }
60
+ if (isset($assoc_args['encoding'])) {
61
+ if (!in_array($assoc_args['encoding'], ['auto', 'lossy', 'lossless'])) {
62
+ \WP_CLI::error('encoding must be auto, lossy or lossless');
63
+ }
64
+ $override['png-encoding'] = $assoc_args['encoding'];
65
+ $override['jpeg-encoding'] = $assoc_args['encoding'];
66
+ }
67
+ if (isset($assoc_args['converter'])) {
68
+ if (!in_array($assoc_args['converter'], ConvertersHelper::getDefaultConverterNames())) {
69
+ \WP_CLI::error(
70
+ '"' . $assoc_args['converter'] . '" is not a valid converter id. ' .
71
+ 'Valid converters are: ' . implode(', ', ConvertersHelper::getDefaultConverterNames())
72
+ );
73
+ }
74
+ }
75
+
76
+ $config = array_merge($config, $override);
77
+
78
+ \WP_CLI::log('Converting with the following settings:');
79
+ \WP_CLI::log('- Lossless quality: ' . $config['png-quality'] . ' for PNG, ' . $config['max-quality'] . " for jpeg");
80
+ \WP_CLI::log(
81
+ '- Near lossless: ' .
82
+ ($config['png-enable-near-lossless'] ? $config['png-near-lossless'] : 'disabled') . ' for PNG, ' .
83
+ ($config['jpeg-enable-near-lossless'] ? $config['jpeg-near-lossless'] : 'disabled') . ' for jpeg, '
84
+ );
85
+ \WP_CLI::log('- Alpha quality: ' . $config['alpha-quality']);
86
+ \WP_CLI::log('- Encoding: ' . $config['png-encoding'] . ' for PNG, ' . $config['jpeg-encoding'] . " for jpeg");
87
+
88
+ if (count($override) == 0) {
89
+ \WP_CLI::log('Note that you can override these with --quality=<quality>, etc');
90
+ }
91
+ \WP_CLI::log('');
92
+
93
+
94
+ $listOptions = BulkConvert::defaultListOptions($config);
95
+ if (isset($assoc_args['reconvert'])) {
96
+ $listOptions['filter']['only-unconverted'] = false;
97
+ }
98
+ if (isset($assoc_args['only-png'])) {
99
+ $listOptions['filter']['image-types'] = 2;
100
+ }
101
+ if (isset($assoc_args['only-jpeg'])) {
102
+ $listOptions['filter']['image-types'] = 1;
103
+ }
104
+
105
+ if (!isset($args[0])) {
106
+ $groups = BulkConvert::getList($config, $listOptions);
107
+ foreach($groups as $group){
108
+ \WP_CLI::log($group['groupName'] . ' contains ' . count($group['files']) . ' ' .
109
+ (isset($assoc_args['reconvert']) ? '' : 'unconverted ') .
110
+ 'files');
111
+ }
112
+ \WP_CLI::log('');
113
+ } else {
114
+ $location = $args[0];
115
+ if (strpos($location, '/') === 0) {
116
+ $location = substr($location, 1);
117
+ }
118
+ if (strpos($location, '/') === false) {
119
+ $rootId = $location;
120
+ $path = '.';
121
+ } else {
122
+ list($rootId, $path) = explode('/', $location, 2);
123
+ }
124
+
125
+ if (!in_array($rootId, Paths::getImageRootIds())) {
126
+ \WP_CLI::error(
127
+ '"' . $args[0] . '" is not a valid image root. ' .
128
+ 'Valid roots are: ' . implode(', ', Paths::getImageRootIds())
129
+ );
130
+ }
131
+
132
+ $root = Paths::getAbsDirById($rootId) . '/' . $path;
133
+ if (!file_exists($root)) {
134
+ \WP_CLI::error(
135
+ '"' . $args[0] . '" does not exist. '
136
+ );
137
+ }
138
+ $listOptions['root'] = $root;
139
+ $groups = [
140
+ [
141
+ 'groupName' => $args[0],
142
+ 'root' => $root,
143
+ 'files' => BulkConvert::getListRecursively('.', $listOptions)
144
+ ]
145
+ ];
146
+ if (count($groups[0]['files']) == 0) {
147
+ \WP_CLI::log('Nothing to convert in ' . $args[0]);
148
+ }
149
+ }
150
+
151
+ $orgTotalFilesize = 0;
152
+ $webpTotalFilesize = 0;
153
+
154
+ $converter = null;
155
+ $convertOptions = null;
156
+
157
+ if (isset($assoc_args['converter'])) {
158
+
159
+ $converter = $assoc_args['converter'];
160
+ $convertOptions = Config::generateWodOptionsFromConfigObj($config)['webp-convert']['convert'];
161
+
162
+ // find the converter
163
+ $optionsForThisConverter = null;
164
+ foreach ($convertOptions['converters'] as $c) {
165
+ if ($c['converter'] == $converter) {
166
+ $optionsForThisConverter = (isset($c['options']) ? $c['options'] : []);
167
+ break;
168
+ }
169
+ }
170
+ if (!is_array($optionsForThisConverter)) {
171
+ \WP_CLI::error('Failed handling options');
172
+ }
173
+
174
+ $convertOptions = array_merge($convertOptions, $optionsForThisConverter);
175
+ unset($convertOptions['converters']);
176
+ }
177
+
178
+ foreach($groups as $group){
179
+ if (count($group['files']) == 0) continue;
180
+
181
+ \WP_CLI::log('Converting ' . count($group['files']) . ' files in ' . $group['groupName']);
182
+ \WP_CLI::log('------------------------------');
183
+ $root = $group['root'];
184
+
185
+ $files = array_reverse($group['files']);
186
+ //echo count($group["files"]);
187
+ foreach($files as $key => $file)
188
+ {
189
+ $path = trailingslashit($group['root']) . $file;
190
+ \WP_CLI::log('Converting: ' . $file);
191
+
192
+ $result = Convert::convertFile($path, $config, $convertOptions, $converter);
193
+
194
+ if ($result['success']) {
195
+ $orgSize = $result['filesize-original'];
196
+ $webpSize = $result['filesize-webp'];
197
+
198
+ $orgTotalFilesize += $orgSize;
199
+ $webpTotalFilesize += $webpSize;
200
+
201
+ //$percentage = round(($orgSize - $webpSize)/$orgSize * 100);
202
+ $percentage = ($orgSize == 0 ? 100 : round(($webpSize/$orgSize) * 100));
203
+
204
+ \WP_CLI::log(
205
+ \WP_CLI::colorize(
206
+ "%GOK%n. " .
207
+ "Size: " .
208
+ ($percentage<90 ? "%G" : ($percentage<100 ? "%Y" : "%R")) .
209
+ $percentage .
210
+ "% %nof original" .
211
+ " (" . self::printableSize($orgSize) . ' => ' . self::printableSize($webpSize) .
212
+ ") "
213
+ )
214
+ );
215
+ //print_r($result);
216
+ } else {
217
+ \WP_CLI::log(
218
+ \WP_CLI::colorize("%RConversion failed. " . $result['msg'] . "%n")
219
+ );
220
+ }
221
+ }
222
+ }
223
+
224
+ if ($orgTotalFilesize > 0) {
225
+ $percentage = ($orgTotalFilesize == 0 ? 100 : round(($webpTotalFilesize/$orgTotalFilesize) * 100));
226
+ \WP_CLI::log(
227
+ \WP_CLI::colorize(
228
+ "Done. " .
229
+ "Size of webps: " .
230
+ ($percentage<90 ? "%G" : ($percentage<100 ? "%Y" : "%R")) .
231
+ $percentage .
232
+ "% %nof original" .
233
+ " (" . self::printableSize($orgTotalFilesize) . ' => ' . self::printableSize($webpTotalFilesize) .
234
+ ") "
235
+ )
236
+ );
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Flush webps
242
+ *
243
+ * ## OPTIONS
244
+ * [--only-png]
245
+ * : Only flush webps that are conversions of a PNG)
246
+ */
247
+ public function flushwebp($args, $assoc_args)
248
+ {
249
+ $config = Config::loadConfigAndFix();
250
+
251
+ $onlyPng = isset($assoc_args['only-png']);
252
+
253
+ if ($onlyPng) {
254
+ \WP_CLI::log('Flushing webp files that are conversions of PNG images');
255
+ } else {
256
+ \WP_CLI::log('Flushing all webp files');
257
+ }
258
+
259
+ $result = CachePurge::purge($config, $onlyPng);
260
+
261
+ \WP_CLI::log(
262
+ \WP_CLI::colorize("%GFlushed " . $result['delete-count'] . " webp files%n")
263
+ );
264
+ if ($result['fail-count'] > 0) {
265
+ \WP_CLI::log(
266
+ \WP_CLI::colorize("%RFailed deleting " . $result['fail-count'] . " webp files%n")
267
+ );
268
+ }
269
+ }
270
+
271
+
272
+ }
lib/classes/Config.php CHANGED
@@ -63,7 +63,7 @@ class Config
63
  'converters' => [],
64
  'metadata' => 'none',
65
  //'log-call-arguments' => true,
66
- 'convert-on-upload' => true,
67
 
68
  // serve options
69
  'fail' => 'original',
63
  'converters' => [],
64
  'metadata' => 'none',
65
  //'log-call-arguments' => true,
66
+ 'convert-on-upload' => false,
67
 
68
  // serve options
69
  'fail' => 'original',
lib/classes/Convert.php CHANGED
@@ -56,7 +56,7 @@ class Convert
56
  $config = Config::loadConfigAndFix(); // ps: if this fails to load, default config is returned.
57
  }
58
  if (!is_array($config)) {
59
- throw new SanityException('file is corrupt');
60
  }
61
 
62
  // Check convert options
56
  $config = Config::loadConfigAndFix(); // ps: if this fails to load, default config is returned.
57
  }
58
  if (!is_array($config)) {
59
+ throw new SanityException('configuration file is corrupt');
60
  }
61
 
62
  // Check convert options
lib/classes/ConvertHelperIndependent.php CHANGED
@@ -571,7 +571,7 @@ APACHE
571
  $text = preg_replace('#' . preg_quote($_SERVER["DOCUMENT_ROOT"]) . '#', '[doc-root]', $text);
572
 
573
  // TODO: Put version number somewhere else. Ie \WebPExpress\VersionNumber::version
574
- $text = 'WebP Express 0.19.1. ' . $msgTop . ', ' . date("Y-m-d H:i:s") . "\n\r\n\r" . $text;
575
 
576
  $logFile = self::getLogFilename($source, $logDir);
577
 
571
  $text = preg_replace('#' . preg_quote($_SERVER["DOCUMENT_ROOT"]) . '#', '[doc-root]', $text);
572
 
573
  // TODO: Put version number somewhere else. Ie \WebPExpress\VersionNumber::version
574
+ $text = 'WebP Express 0.20.0. ' . $msgTop . ', ' . date("Y-m-d H:i:s") . "\n\r\n\r" . $text;
575
 
576
  $logFile = self::getLogFilename($source, $logDir);
577
 
lib/classes/Paths.php CHANGED
@@ -566,7 +566,7 @@ APACHE
566
  * Get destination root url and path, provided rootId and some configuration options
567
  *
568
  * This method kind of establishes the overall structure of the cache dir.
569
- * (but not quite, as the logic is also in ConverterHelper::getDestination).
570
  *
571
  * @param string $rootId
572
  * @param string $destinationFolder ("mingled" or "separate")
@@ -604,6 +604,62 @@ APACHE
604
  }
605
  }
606
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
607
  public static function getUrlPathById($dirId) {
608
  return self::getUrlPathFromUrl(self::getUrlById($dirId));
609
  }
566
  * Get destination root url and path, provided rootId and some configuration options
567
  *
568
  * This method kind of establishes the overall structure of the cache dir.
569
+ * (but not quite, as the logic is also in ConverterHelperIndependent::getDestination).
570
  *
571
  * @param string $rootId
572
  * @param string $destinationFolder ("mingled" or "separate")
604
  }
605
  }
606
 
607
+ /**
608
+ * Append ".webp" to path or replace extension with "webp", depending on what is appropriate.
609
+ *
610
+ * If destination-folder is set to mingled and destination-extension is set to "set" and
611
+ * the path is inside upload folder, the appropriate thing is to SET the extension.
612
+ * Otherwise, it is to APPEND.
613
+ *
614
+ * @param string $path
615
+ * @param string $destinationFolder
616
+ * @param string $destinationExt
617
+ * @param boolean $inUploadFolder
618
+ */
619
+ public static function appendOrSetExtension($path, $destinationFolder, $destinationExt, $inUploadFolder)
620
+ {
621
+ if (($destinationFolder == 'mingled') && ($destinationExt == 'set') && $inUploadFolder) {
622
+ return preg_replace('/\\.(jpe?g|png)$/i', '', $path) . '.webp';
623
+ } else {
624
+ return $path . '.webp';
625
+ }
626
+ }
627
+
628
+ /**
629
+ * Get destination root url and path, provided rootId and some configuration options
630
+ *
631
+ * This method kind of establishes the overall structure of the cache dir.
632
+ * (but not quite, as the logic is also in ConverterHelperIndependent::getDestination).
633
+ *
634
+ * @param string $rootId
635
+ * @param string $relPath
636
+ * @param string $destinationFolder ("mingled" or "separate")
637
+ * @param string $destinationExt ('append' or 'set')
638
+ * @param string $destinationStructure ("doc-root" or "image-roots")
639
+ *
640
+ * @return array url and abs-path of destination
641
+ */
642
+ public static function destinationPath($rootId, $relPath, $destinationFolder, $destinationExtension, $destinationStructure) {
643
+ $root = self::destinationRoot($rootId, $destinationFolder, $destinationStructure);
644
+ $inUploadFolder = ($rootId == 'upload');
645
+ $relPath = ConvertHelperIndependent::appendOrSetExtension($relPath, $destinationFolder, $destinationExt, $inUploadFolder);
646
+
647
+ return [
648
+ 'abs-path' => $root['abs-path'] . '/' . $relPath,
649
+ 'url' => $root['url'] . '/' . $relPath,
650
+ ];
651
+ }
652
+
653
+ public static function destinationPathConvenience($rootId, $relPath, $config) {
654
+ return self::destinationPath(
655
+ $rootId,
656
+ $relPath,
657
+ $config['destination-folder'],
658
+ $config['destination-extension'],
659
+ $config['destination-structure']
660
+ );
661
+ }
662
+
663
  public static function getUrlPathById($dirId) {
664
  return self::getUrlPathFromUrl(self::getUrlById($dirId));
665
  }
lib/classes/WCFMApi.php CHANGED
@@ -2,32 +2,230 @@
2
 
3
  namespace WebPExpress;
4
 
 
 
5
  /**
6
  *
7
  */
8
 
9
  class WCFMApi
10
  {
11
-
12
- public static function processRequest() {
13
  if (!check_ajax_referer('webpexpress-wcfm-nonce', 'nonce', false)) {
14
- wp_send_json_error('The security nonce has expired. You need to reload (press F5) and try again)');
15
- wp_die();
16
  }
 
 
17
 
18
- $result = self::processGetTree();
 
 
 
 
 
 
 
 
 
 
19
 
20
  $json = wp_json_encode($result, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
21
  if ($json === false) {
22
  // TODO: We can do better error handling than this!
23
- echo '';
24
  } else {
25
  echo $json;
26
  }
27
-
28
  wp_die();
29
  }
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  public static function processGetTree() {
32
  $config = Config::loadConfigAndFix();
33
  $rootIds = Paths::filterOutSubRoots($config['scope']);
2
 
3
  namespace WebPExpress;
4
 
5
+ use \WebPConvert\Convert\Converters\Stack;
6
+
7
  /**
8
  *
9
  */
10
 
11
  class WCFMApi
12
  {
13
+ private static function doProcessRequest() {
 
14
  if (!check_ajax_referer('webpexpress-wcfm-nonce', 'nonce', false)) {
15
+ throw new \Exception('The security nonce has expired. You need to reload (press F5) and try again)');
 
16
  }
17
+ Validate::postHasKey('command');
18
+ $command = sanitize_text_field(stripslashes($_POST['command']));
19
 
20
+ switch ($command) {
21
+ case 'get-tree':
22
+ $result = self::processGetTree();
23
+ break;
24
+ case 'conversion-settings':
25
+ $result = self::processConversionSettings();
26
+ break;
27
+ case 'info':
28
+ $result = self::processInfo();
29
+ break;
30
+ }
31
 
32
  $json = wp_json_encode($result, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
33
  if ($json === false) {
34
  // TODO: We can do better error handling than this!
35
+ throw new \Exception('Failed encoding result to JSON');
36
  } else {
37
  echo $json;
38
  }
 
39
  wp_die();
40
  }
41
 
42
+ public static function processRequest() {
43
+ try {
44
+ self::doProcessRequest();
45
+ }
46
+ catch (\Exception $e) {
47
+ wp_send_json_error($e->getMessage());
48
+ wp_die();
49
+ }
50
+ }
51
+ /*
52
+ {
53
+ "converters": [
54
+ {
55
+ "converter": "cwebp",
56
+ "options": {
57
+ "use-nice": true,
58
+ "try-common-system-paths": true,
59
+ "try-supplied-binary-for-os": true,
60
+ "method": 6,
61
+ "low-memory": true,
62
+ "command-line-options": ""
63
+ },
64
+ "working": true
65
+ },
66
+ {
67
+ "converter": "vips",
68
+ "options": {
69
+ "smart-subsample": false,
70
+ "preset": "none"
71
+ },
72
+ "working": false
73
+ },
74
+ {
75
+ "converter": "imagemagick",
76
+ "options": {
77
+ "use-nice": true
78
+ },
79
+ "working": true,
80
+ "deactivated": true
81
+ },
82
+ {
83
+ "converter": "graphicsmagick",
84
+ "options": {
85
+ "use-nice": true
86
+ },
87
+ "working": false
88
+ },
89
+ {
90
+ "converter": "ffmpeg",
91
+ "options": {
92
+ "use-nice": true,
93
+ "method": 4
94
+ },
95
+ "working": false
96
+ },
97
+ {
98
+ "converter": "wpc",
99
+ "working": false,
100
+ "options": {
101
+ "api-key": ""
102
+ }
103
+ },
104
+ {
105
+ "converter": "ewww",
106
+ "working": false
107
+ },
108
+ {
109
+ "converter": "imagick",
110
+ "working": false
111
+ },
112
+ {
113
+ "converter": "gmagick",
114
+ "working": false
115
+ },
116
+ {
117
+ "converter": "gd",
118
+ "options": {
119
+ "skip-pngs": false
120
+ },
121
+ "working": false
122
+ }
123
+ ]
124
+ }*/
125
+ public static function processConversionSettings() {
126
+ require_once __DIR__ . "/../../vendor/autoload.php";
127
+ $availableConverters = Stack::getAvailableConverters();
128
+
129
+ $converters = [];
130
+ //$supportsEncoding = [];
131
+ foreach ($availableConverters as $converter) {
132
+ $converters[] = [
133
+ 'id' => $converter,
134
+ 'name' => $converter
135
+ ];
136
+ /*if () {
137
+ $supportsEncoding[] = $converter;
138
+ }*/
139
+ }
140
+ $systemStatus = [
141
+ 'converterRequirements' => [
142
+ 'gd' => [
143
+ 'extensionLoaded' => extension_loaded('gd'),
144
+ 'compiledWithWebP' => function_exists('imagewebp'),
145
+ ]
146
+ // TODO: Add more!
147
+ ]
148
+ ];
149
+
150
+ //getUnsupportedDefaultOptions
151
+ //supportedStandardOptions: {
152
+
153
+ return [
154
+ 'converters' => $converters,
155
+ 'systemStatus' => $systemStatus
156
+ ];
157
+
158
+ /*
159
+ $config = Config::loadConfigAndFix();
160
+ // 'working', 'deactivated'
161
+ $foundFirstWorkingAndActive = false;
162
+ foreach ($config['converters'] as $converter) {
163
+ $converters[] = [
164
+ 'id' => $converter['converter'],
165
+ 'name' => $converter['converter']
166
+ ];
167
+ if ($converter['working']) {
168
+ if
169
+ }
170
+ if (!$foundFirstWorkingAndActive) {
171
+
172
+ }
173
+ }*/
174
+
175
+ return [
176
+ 'converters' => $converters
177
+ ];
178
+ }
179
+
180
+
181
+ public static function processInfo() {
182
+
183
+ Validate::postHasKey('args');
184
+
185
+ //$args = json_decode(sanitize_text_field(stripslashes($_POST['args'])), true);
186
+
187
+ $args = $_POST['args'];
188
+ if (!array_key_exists('path', $args)) {
189
+ throw new \Exception('"path" argument missing for command');
190
+ }
191
+
192
+ $path = SanityCheck::pathWithoutDirectoryTraversal($args['path']);
193
+ $path = ltrim($path, '/');
194
+ $pathTokens = explode('/', $path);
195
+
196
+ $rootId = array_shift($pathTokens); // Shift off the first item, which is the scope
197
+ $relPath = implode('/', $pathTokens);
198
+ $config = Config::loadConfigAndFix();
199
+ $rootIds = Paths::filterOutSubRoots($config['scope']);
200
+ if (!in_array($rootId, $rootIds)) {
201
+ throw new \Exception('Invalid scope');
202
+ }
203
+
204
+ $absPath = Paths::getAbsDirById($rootId) . '/' . $relPath;
205
+ //absPathExistsAndIsFile
206
+ SanityCheck::absPathExists($absPath);
207
+
208
+ // TODO: What if it is a dir?
209
+
210
+ $destination = Paths::destinationPathConvenience($rootId, $relPath, $config);
211
+
212
+ $absPathDest = $destination['abs-path'] . '/' . $relPath;
213
+
214
+ return [
215
+ 'original' => [
216
+ 'name' => $absPath,
217
+ 'size' => filesize($absPath),
218
+ 'url' => '',
219
+ ],
220
+ 'converted' => [
221
+ 'name' => $destination['abs-path'],
222
+ 'size' => 70,
223
+ 'url' => ''
224
+ ],
225
+ 'log' => 'blah blah blah'
226
+ ];
227
+ }
228
+
229
  public static function processGetTree() {
230
  $config = Config::loadConfigAndFix();
231
  $rootIds = Paths::filterOutSubRoots($config['scope']);
lib/classes/WCFMPage.php CHANGED
@@ -31,10 +31,10 @@ class WCFMPage
31
  $wcfmNonce = wp_create_nonce('webpexpress-wcfm-nonce');
32
  echo '<scr' . 'ipt>window.webpExpressWCFMNonce = "' . $wcfmNonce . '";</scr' . 'ipt>';
33
 
34
- echo '<scr' . 'ipt src="' . $baseUrl . '/wcfm-options.js?2"></scr' . 'ipt>';
35
- echo '<scr' . 'ipt type="module" src="' . $baseUrl . '/wcfm.js"></scr' . 'ipt>';
36
 
37
- echo '<link rel="stylesheet" href="' . $baseUrl . '/style.css">';
38
  }
39
 
40
  }
31
  $wcfmNonce = wp_create_nonce('webpexpress-wcfm-nonce');
32
  echo '<scr' . 'ipt>window.webpExpressWCFMNonce = "' . $wcfmNonce . '";</scr' . 'ipt>';
33
 
34
+ echo '<scr' . 'ipt src="' . $baseUrl . '/wcfm-options.js?7"></scr' . 'ipt>';
35
+ echo '<scr' . 'ipt type="module" src="' . $baseUrl . '/wcfm.js?3"></scr' . 'ipt>';
36
 
37
+ echo '<link rel="stylesheet" href="' . $baseUrl . '/style.css?1">';
38
  }
39
 
40
  }
lib/options/enqueue_scripts.php CHANGED
@@ -5,7 +5,7 @@ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
5
  use \WebPExpress\Paths;
6
  use \WebPExpress\Config;
7
 
8
- $ver = '6'; // note: Minimum 1
9
  $jsDir = 'js/0.19.0'; // We change dir when it is critical that no-one gets the cached version (there is a plugin that strips version strings out there...)
10
 
11
  if (!function_exists('webp_express_add_inline_script')) {
5
  use \WebPExpress\Paths;
6
  use \WebPExpress\Config;
7
 
8
+ $ver = '7'; // note: Minimum 1
9
  $jsDir = 'js/0.19.0'; // We change dir when it is critical that no-one gets the cached version (there is a plugin that strips version strings out there...)
10
 
11
  if (!function_exists('webp_express_add_inline_script')) {
lib/options/options/conversion-options/convert-on-upload.inc CHANGED
@@ -3,8 +3,10 @@
3
  Convert on upload
4
  <?php
5
  echo helpIcon(
6
- '<p>Convert images at the moment they have been uploaded through the media library. ' .
7
- 'Of course, the "Image types to work on" setting is respected.</p>' .
 
 
8
  '<p>Technically, we are hooking into the <i>handle_upload</i> filter to trigger conversion of the image ' .
9
  'and the <i>image_make_intermediate_size</i> filter for the thumbnails.</p>'
10
  );
3
  Convert on upload
4
  <?php
5
  echo helpIcon(
6
+ '<p>Convert images at the moment they have been uploaded through the media library ' .
7
+ '(of course, the "Image types to work on" setting is respected). ' .
8
+ 'Be aware that this may slow the down the experience of uploading in the media library, ' .
9
+ 'especially if your theme creates many thumbnails.</p>' .
10
  '<p>Technically, we are hooking into the <i>handle_upload</i> filter to trigger conversion of the image ' .
11
  'and the <i>image_make_intermediate_size</i> filter for the thumbnails.</p>'
12
  );
lib/options/options/conversion-options/metadata.inc CHANGED
@@ -6,7 +6,7 @@ $metadata = $config['metadata'];
6
  echo '<tr><th scope="row">Metadata';
7
  echo helpIcon(
8
  'Decide what to do with image metadata, such as Exif. Note that this setting is not supported by the "Gd" conversion method, ' .
9
- 'as it is not possible to copy the metadata with the Gd extension. Imagickbinary also currently lacks support'
10
  );
11
  echo '</th><td>';
12
 
6
  echo '<tr><th scope="row">Metadata';
7
  echo helpIcon(
8
  'Decide what to do with image metadata, such as Exif. Note that this setting is not supported by the "Gd" conversion method, ' .
9
+ 'as it is not possible to copy the metadata with the Gd extension.'
10
  );
11
  echo '</th><td>';
12
 
lib/options/submit.php CHANGED
@@ -425,7 +425,7 @@ $sanitized = [
425
  'lossless',
426
  'auto'
427
  ]),
428
- 'alpha-quality' => webpexpress_getSanitizedQuality('png-quality', 80),
429
  'convert-on-upload' => isset($_POST['convert-on-upload']),
430
  'converters' => webpexpress_getSanitizedConverters(),
431
 
425
  'lossless',
426
  'auto'
427
  ]),
428
+ 'alpha-quality' => webpexpress_getSanitizedQuality('alpha-quality', 80),
429
  'convert-on-upload' => isset($_POST['convert-on-upload']),
430
  'converters' => webpexpress_getSanitizedConverters(),
431
 
lib/wcfm/style.css CHANGED
@@ -1 +1 @@
1
- .fileitem[data-v-1d130b20]{vertical-align:middle;white-space:nowrap}.fileitem p[data-v-1d130b20]:hover{background:#eee}.fileitem p[data-v-1d130b20]{user-select:none;cursor:default;margin:0;padding:3px;line-height:25px;border-bottom:1px solid #f2f2f2}.fileitem p .foldUnfold[data-v-1d130b20]{user-select:none;cursor:pointer}.fileitem p button[data-v-1d130b20]{float:right;margin-top:2px;cursor:pointer;background-color:#fff;margin-right:10px}.fileitem p svg.icon-fold[data-v-1d130b20],.fileitem p svg.icon-unfold[data-v-1d130b20]{width:12px;height:12px;vertical-align:middle;padding:3px;margin-right:3px;display:inline-block;border:0 solid grey}.fileitem p svg.icon-file[data-v-1d130b20],.fileitem p svg.icon-folder[data-v-1d130b20]{width:20px;height:20px;display:inline;vertical-align:middle;padding-top:1px;padding-bottom:2px;padding-right:5px}.fileitem p svg.icon-file[data-v-1d130b20]{margin-left:22px}ul{list-style-type:none;padding:0;margin:0}li{margin:0 0 0 20px}.multipane{display:flex;align-items:stretch}.multipane.layout-h{flex-direction:column}.multipane.layout-v{flex-direction:row}.multipane div.pane{position:relative;z-index:1;padding:15px;overflow:auto;border:1px solid #ccc;background:#fff}@media (max-width:800px){.multipane{flex-direction:column!important}div.pane{width:100%!important;margin-bottom:8px;box-sizing:border-box}.multipane-resizer{display:none!important}}.multipane-resizer{display:block;position:relative;z-index:2}.layout-h>.multipane-resizer{height:10px;margin-top:-10px;top:5px;cursor:row-resize}.layout-v>.multipane-resizer{width:14px;cursor:col-resize}.layout-v>.multipane-resizer:before{display:block;content:"";width:3px;height:40px;position:absolute;top:50%;left:50%;margin-top:-20px;margin-left:-2px;border-left:1px solid #ccc;border-right:1px solid #ccc}.layout-v>.multipane-resizer:hover:before{border-color:#999}#webpconvert-filemanager,.mainpanel{height:100%}.mainpanel{min-height:400px}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
1
+ .selectbox[data-v-a8799ec4]{width:200px;position:relative;display:inline-block}.selectbox .box[data-v-a8799ec4]{border:1px solid #000;padding:5px 10px;width:100%;box-sizing:border-box;cursor:pointer;user-select:none}.selectbox .box[data-v-a8799ec4]::after{position:absolute;content:"";top:14px;right:10px;width:0;height:0;border:6px solid transparent;border-color:#000 transparent transparent}.selectbox .box.is-open[data-v-a8799ec4]::after{border-color:transparent transparent #000;top:6px}.selectbox .dropdown[data-v-a8799ec4]{position:absolute;z-index:100;background-color:#fff;border:1px solid #eee;left:0;right:0}.selectbox .dropdown .option[data-v-a8799ec4]{padding:5px 10px;border-bottom:1px solid #eee}.selectbox .dropdown .option .icon[data-v-a8799ec4]{width:18px;height:18px;float:right}.selectbox .dropdown .option[data-v-a8799ec4]:hover{background-color:#eee;cursor:pointer;user-select:none}.quality-lossy .input-quality[data-v-74dc2e9e]{width:40px;padding:3px 5px}.quality-lossy .auto label[data-v-74dc2e9e]{padding:0 10px 0 20px}.quality-lossless .input-quality[data-v-db8a8a84]{width:40px}.convert-options>div>div[data-v-115f004a]:first-child{min-width:120px}.convert-options>div>div[data-v-115f004a]:last-child{width:300px;box-sizing:border-box}.convert-options>div>div:last-child [data-v-115f004a]{box-sizing:border-box;width:100%}.convert-options>div input.method[data-v-115f004a]{width:40px}.fileitem[data-v-a2787766]{vertical-align:middle;white-space:nowrap}.fileitem p[data-v-a2787766]:hover{background:#eee}.fileitem p[data-v-a2787766]{user-select:none;cursor:pointer;margin:0;padding:3px;line-height:25px;border-bottom:1px solid #f2f2f2}.fileitem p .foldUnfold[data-v-a2787766]{user-select:none;cursor:pointer}.fileitem p .buttons[data-v-a2787766]{float:right;cursor:default}.fileitem p .buttons button[data-v-a2787766]{margin-top:2px;margin-bottom:0;cursor:pointer;background-color:#fff;margin-right:10px}.fileitem p svg.icon-fold[data-v-a2787766],.fileitem p svg.icon-unfold[data-v-a2787766]{width:12px;height:12px;vertical-align:middle;padding:3px;margin-right:3px;display:inline-block;border:0 solid grey}.fileitem p svg.icon-file[data-v-a2787766],.fileitem p svg.icon-folder[data-v-a2787766]{width:20px;height:20px;display:inline;vertical-align:middle;padding-top:1px;padding-bottom:2px;padding-right:5px}.fileitem p svg.icon-file[data-v-a2787766]{margin-left:22px}ul{list-style-type:none;padding:0;margin:0}li{margin:0 0 0 20px}.multipane{display:flex;align-items:stretch}.multipane.layout-h{flex-direction:column}.multipane.layout-v{flex-direction:row}.multipane div.pane{position:relative;z-index:1;padding:15px;overflow:auto;border:1px solid #ccc;background:#fff}@media (max-width:800px){.multipane{flex-direction:column!important}div.pane{width:100%!important;margin-bottom:8px;box-sizing:border-box}.multipane-resizer{display:none!important}}.multipane-resizer{display:block;position:relative;z-index:2}.layout-h>.multipane-resizer{height:10px;margin-top:-10px;top:5px;cursor:row-resize}.layout-v>.multipane-resizer{width:14px;cursor:col-resize}.layout-v>.multipane-resizer:before{display:block;content:"";width:3px;height:40px;position:absolute;top:50%;left:50%;margin-top:-20px;margin-left:-2px;border-left:1px solid #ccc;border-right:1px solid #ccc}.layout-v>.multipane-resizer:hover:before{border-color:#999}#webpconvert-filemanager,.mainpanel{height:100%}.mainpanel{min-height:400px}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}input{padding:3px 5px}.table-table{display:table}.table-table>*{display:table-row}.table-table>*>*{display:table-cell;padding:5px 20px 5px 0}.table-inline-block>div>*{display:inline-block;margin-right:10px;padding:5px 20px 5px 0}.table-inline-block>div>:first-child{width:120px}
lib/wcfm/wcfm-options.js CHANGED
@@ -3,6 +3,9 @@ window["wcfmoptions"] = {};
3
 
4
  window["wcfmoptions"]['poster'] = function(action, options, successCallback, errorCallback) {
5
 
 
 
 
6
  // Call the API
7
  jQuery.ajax({
8
  type: "POST",
@@ -11,7 +14,7 @@ window["wcfmoptions"]['poster'] = function(action, options, successCallback, err
11
  'action': 'webpexpress-wcfm-api',
12
  'nonce' : window.webpExpressWCFMNonce,
13
  'command': action,
14
- 'options': options
15
  },
16
  dataType: 'text',
17
  timeout: 30000,
3
 
4
  window["wcfmoptions"]['poster'] = function(action, options, successCallback, errorCallback) {
5
 
6
+ console.log('wcfmoptions.poster called. Action: ' + action);
7
+ console.log('wcfmoptions.poster called. Options: ' + JSON.stringify(options));
8
+
9
  // Call the API
10
  jQuery.ajax({
11
  type: "POST",
14
  'action': 'webpexpress-wcfm-api',
15
  'nonce' : window.webpExpressWCFMNonce,
16
  'command': action,
17
+ 'args': options
18
  },
19
  dataType: 'text',
20
  timeout: 30000,
lib/wcfm/wcfm.js CHANGED
@@ -1 +1 @@
1
- function e(e,t){const n=Object.create(null),o=e.split(",");for(let e=0;e<o.length;e++)n[o[e]]=!0;return t?e=>!!n[e.toLowerCase()]:e=>!!n[e]}const t=e("Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl"),n=e("itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly");function o(e){if(x(e)){const t={};for(let n=0;n<e.length;n++){const l=e[n],r=o(S(l)?s(l):l);if(r)for(const e in r)t[e]=r[e]}return t}if(O(e))return e}const l=/;(?![^(]*\))/g,r=/:(.+)/;function s(e){const t={};return e.split(l).forEach((e=>{if(e){const n=e.split(r);n.length>1&&(t[n[0].trim()]=n[1].trim())}})),t}function i(e){let t="";if(S(e))t=e;else if(x(e))for(let n=0;n<e.length;n++)t+=i(e[n])+" ";else if(O(e))for(const n in e)e[n]&&(t+=n+" ");return t.trim()}const c=e=>null==e?"":O(e)?JSON.stringify(e,u,2):String(e),u=(e,t)=>w(t)?{[`Map(${t.size})`]:[...t.entries()].reduce(((e,[t,n])=>(e[t+" =>"]=n,e)),{})}:C(t)?{[`Set(${t.size})`]:[...t.values()]}:!O(t)||x(t)||P(t)?t:String(t),a={},f=[],p=()=>{},d=()=>!1,h=/^on[^a-z]/,m=e=>h.test(e),g=e=>e.startsWith("onUpdate:"),v=Object.assign,y=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},_=Object.prototype.hasOwnProperty,b=(e,t)=>_.call(e,t),x=Array.isArray,w=e=>"[object Map]"===R(e),C=e=>"[object Set]"===R(e),k=e=>"function"==typeof e,S=e=>"string"==typeof e,F=e=>"symbol"==typeof e,O=e=>null!==e&&"object"==typeof e,E=e=>O(e)&&k(e.then)&&k(e.catch),M=Object.prototype.toString,R=e=>M.call(e),P=e=>"[object Object]"===R(e),z=e=>S(e)&&"NaN"!==e&&"-"!==e[0]&&""+parseInt(e,10)===e,$=e(",key,ref,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),I=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},T=/-(\w)/g,j=I((e=>e.replace(T,((e,t)=>t?t.toUpperCase():"")))),N=/\B([A-Z])/g,A=I((e=>e.replace(N,"-$1").toLowerCase())),V=I((e=>e.charAt(0).toUpperCase()+e.slice(1))),U=I((e=>e?"on"+V(e):"")),D=(e,t)=>e!==t&&(e==e||t==t),B=(e,t)=>{for(let n=0;n<e.length;n++)e[n](t)},L=(e,t,n)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},H=e=>{const t=parseFloat(e);return isNaN(t)?e:t},W=new WeakMap,K=[];let G;const q=Symbol(""),J=Symbol("");function X(e,t=a){(function(e){return e&&!0===e._isEffect})(e)&&(e=e.raw);const n=function(e,t){const n=function(){if(!n.active)return t.scheduler?void 0:e();if(!K.includes(n)){Q(n);try{return te.push(ee),ee=!0,K.push(n),G=n,e()}finally{K.pop(),oe(),G=K[K.length-1]}}};return n.id=Z++,n.allowRecurse=!!t.allowRecurse,n._isEffect=!0,n.active=!0,n.raw=e,n.deps=[],n.options=t,n}(e,t);return t.lazy||n(),n}function Y(e){e.active&&(Q(e),e.options.onStop&&e.options.onStop(),e.active=!1)}let Z=0;function Q(e){const{deps:t}=e;if(t.length){for(let n=0;n<t.length;n++)t[n].delete(e);t.length=0}}let ee=!0;const te=[];function ne(){te.push(ee),ee=!1}function oe(){const e=te.pop();ee=void 0===e||e}function le(e,t,n){if(!ee||void 0===G)return;let o=W.get(e);o||W.set(e,o=new Map);let l=o.get(n);l||o.set(n,l=new Set),l.has(G)||(l.add(G),G.deps.push(l))}function re(e,t,n,o,l,r){const s=W.get(e);if(!s)return;const i=new Set,c=e=>{e&&e.forEach((e=>{(e!==G||e.allowRecurse)&&i.add(e)}))};if("clear"===t)s.forEach(c);else if("length"===n&&x(e))s.forEach(((e,t)=>{("length"===t||t>=o)&&c(e)}));else switch(void 0!==n&&c(s.get(n)),t){case"add":x(e)?z(n)&&c(s.get("length")):(c(s.get(q)),w(e)&&c(s.get(J)));break;case"delete":x(e)||(c(s.get(q)),w(e)&&c(s.get(J)));break;case"set":w(e)&&c(s.get(q))}i.forEach((e=>{e.options.scheduler?e.options.scheduler(e):e()}))}const se=new Set(Object.getOwnPropertyNames(Symbol).map((e=>Symbol[e])).filter(F)),ie=pe(),ce=pe(!1,!0),ue=pe(!0),ae=pe(!0,!0),fe={};function pe(e=!1,t=!1){return function(n,o,l){if("__v_isReactive"===o)return!e;if("__v_isReadonly"===o)return e;if("__v_raw"===o&&l===(e?Ve:Ae).get(n))return n;const r=x(n);if(r&&b(fe,o))return Reflect.get(fe,o,l);const s=Reflect.get(n,o,l);if(F(o)?se.has(o):"__proto__"===o||"__v_isRef"===o)return s;if(e||le(n,0,o),t)return s;if(qe(s)){return!r||!z(o)?s.value:s}return O(s)?e?Be(s):De(s):s}}["includes","indexOf","lastIndexOf"].forEach((e=>{const t=Array.prototype[e];fe[e]=function(...e){const n=Ge(this);for(let e=0,t=this.length;e<t;e++)le(n,0,e+"");const o=t.apply(n,e);return-1===o||!1===o?t.apply(n,e.map(Ge)):o}})),["push","pop","shift","unshift","splice"].forEach((e=>{const t=Array.prototype[e];fe[e]=function(...e){ne();const n=t.apply(this,e);return oe(),n}}));function de(e=!1){return function(t,n,o,l){const r=t[n];if(!e&&(o=Ge(o),!x(t)&&qe(r)&&!qe(o)))return r.value=o,!0;const s=x(t)&&z(n)?Number(n)<t.length:b(t,n),i=Reflect.set(t,n,o,l);return t===Ge(l)&&(s?D(o,r)&&re(t,"set",n,o):re(t,"add",n,o)),i}}const he={get:ie,set:de(),deleteProperty:function(e,t){const n=b(e,t),o=(e[t],Reflect.deleteProperty(e,t));return o&&n&&re(e,"delete",t,void 0),o},has:function(e,t){const n=Reflect.has(e,t);return F(t)&&se.has(t)||le(e,0,t),n},ownKeys:function(e){return le(e,0,x(e)?"length":q),Reflect.ownKeys(e)}},me={get:ue,set:(e,t)=>!0,deleteProperty:(e,t)=>!0},ge=v({},he,{get:ce,set:de(!0)}),ve=(v({},me,{get:ae}),e=>O(e)?De(e):e),ye=e=>O(e)?Be(e):e,_e=e=>e,be=e=>Reflect.getPrototypeOf(e);function xe(e,t,n=!1,o=!1){const l=Ge(e=e.__v_raw),r=Ge(t);t!==r&&!n&&le(l,0,t),!n&&le(l,0,r);const{has:s}=be(l),i=n?ye:o?_e:ve;return s.call(l,t)?i(e.get(t)):s.call(l,r)?i(e.get(r)):void 0}function we(e,t=!1){const n=this.__v_raw,o=Ge(n),l=Ge(e);return e!==l&&!t&&le(o,0,e),!t&&le(o,0,l),e===l?n.has(e):n.has(e)||n.has(l)}function Ce(e,t=!1){return e=e.__v_raw,!t&&le(Ge(e),0,q),Reflect.get(e,"size",e)}function ke(e){e=Ge(e);const t=Ge(this),n=be(t).has.call(t,e),o=t.add(e);return n||re(t,"add",e,e),o}function Se(e,t){t=Ge(t);const n=Ge(this),{has:o,get:l}=be(n);let r=o.call(n,e);r||(e=Ge(e),r=o.call(n,e));const s=l.call(n,e),i=n.set(e,t);return r?D(t,s)&&re(n,"set",e,t):re(n,"add",e,t),i}function Fe(e){const t=Ge(this),{has:n,get:o}=be(t);let l=n.call(t,e);l||(e=Ge(e),l=n.call(t,e));o&&o.call(t,e);const r=t.delete(e);return l&&re(t,"delete",e,void 0),r}function Oe(){const e=Ge(this),t=0!==e.size,n=e.clear();return t&&re(e,"clear",void 0,void 0),n}function Ee(e,t){return function(n,o){const l=this,r=l.__v_raw,s=Ge(r),i=e?ye:t?_e:ve;return!e&&le(s,0,q),r.forEach(((e,t)=>n.call(o,i(e),i(t),l)))}}function Me(e,t,n){return function(...o){const l=this.__v_raw,r=Ge(l),s=w(r),i="entries"===e||e===Symbol.iterator&&s,c="keys"===e&&s,u=l[e](...o),a=t?ye:n?_e:ve;return!t&&le(r,0,c?J:q),{next(){const{value:e,done:t}=u.next();return t?{value:e,done:t}:{value:i?[a(e[0]),a(e[1])]:a(e),done:t}},[Symbol.iterator](){return this}}}}function Re(e){return function(...t){return"delete"!==e&&this}}const Pe={get(e){return xe(this,e)},get size(){return Ce(this)},has:we,add:ke,set:Se,delete:Fe,clear:Oe,forEach:Ee(!1,!1)},ze={get(e){return xe(this,e,!1,!0)},get size(){return Ce(this)},has:we,add:ke,set:Se,delete:Fe,clear:Oe,forEach:Ee(!1,!0)},$e={get(e){return xe(this,e,!0)},get size(){return Ce(this,!0)},has(e){return we.call(this,e,!0)},add:Re("add"),set:Re("set"),delete:Re("delete"),clear:Re("clear"),forEach:Ee(!0,!1)};function Ie(e,t){const n=t?ze:e?$e:Pe;return(t,o,l)=>"__v_isReactive"===o?!e:"__v_isReadonly"===o?e:"__v_raw"===o?t:Reflect.get(b(n,o)&&o in t?n:t,o,l)}["keys","values","entries",Symbol.iterator].forEach((e=>{Pe[e]=Me(e,!1,!1),$e[e]=Me(e,!0,!1),ze[e]=Me(e,!1,!0)}));const Te={get:Ie(!1,!1)},je={get:Ie(!1,!0)},Ne={get:Ie(!0,!1)},Ae=new WeakMap,Ve=new WeakMap;function Ue(e){return e.__v_skip||!Object.isExtensible(e)?0:function(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}((e=>R(e).slice(8,-1))(e))}function De(e){return e&&e.__v_isReadonly?e:Le(e,!1,he,Te)}function Be(e){return Le(e,!0,me,Ne)}function Le(e,t,n,o){if(!O(e))return e;if(e.__v_raw&&(!t||!e.__v_isReactive))return e;const l=t?Ve:Ae,r=l.get(e);if(r)return r;const s=Ue(e);if(0===s)return e;const i=new Proxy(e,2===s?o:n);return l.set(e,i),i}function He(e){return We(e)?He(e.__v_raw):!(!e||!e.__v_isReactive)}function We(e){return!(!e||!e.__v_isReadonly)}function Ke(e){return He(e)||We(e)}function Ge(e){return e&&Ge(e.__v_raw)||e}function qe(e){return Boolean(e&&!0===e.__v_isRef)}const Je={get:(e,t,n)=>{return qe(o=Reflect.get(e,t,n))?o.value:o;var o},set:(e,t,n,o)=>{const l=e[t];return qe(l)&&!qe(n)?(l.value=n,!0):Reflect.set(e,t,n,o)}};class Xe{constructor(e,t,n){this._setter=t,this._dirty=!0,this.__v_isRef=!0,this.effect=X(e,{lazy:!0,scheduler:()=>{this._dirty||(this._dirty=!0,re(Ge(this),"set","value"))}}),this.__v_isReadonly=n}get value(){return this._dirty&&(this._value=this.effect(),this._dirty=!1),le(Ge(this),0,"value"),this._value}set value(e){this._setter(e)}}function Ye(e,t,n,o){let l;try{l=o?e(...o):e()}catch(e){Qe(e,t,n)}return l}function Ze(e,t,n,o){if(k(e)){const l=Ye(e,t,n,o);return l&&E(l)&&l.catch((e=>{Qe(e,t,n)})),l}const l=[];for(let r=0;r<e.length;r++)l.push(Ze(e[r],t,n,o));return l}function Qe(e,t,n,o=!0){t&&t.vnode;if(t){let o=t.parent;const l=t.proxy,r=n;for(;o;){const t=o.ec;if(t)for(let n=0;n<t.length;n++)if(!1===t[n](e,l,r))return;o=o.parent}const s=t.appContext.config.errorHandler;if(s)return void Ye(s,null,10,[e,l,r])}!function(e,t,n,o=!0){console.error(e)}(e,0,0,o)}let et=!1,tt=!1;const nt=[];let ot=0;const lt=[];let rt=null,st=0;const it=[];let ct=null,ut=0;const at=Promise.resolve();let ft=null,pt=null;function dt(e){const t=ft||at;return e?t.then(this?e.bind(this):e):t}function ht(e){nt.length&&nt.includes(e,et&&e.allowRecurse?ot+1:ot)||e===pt||(nt.push(e),mt())}function mt(){et||tt||(tt=!0,ft=at.then(bt))}function gt(e,t,n,o){x(e)?n.push(...e):t&&t.includes(e,e.allowRecurse?o+1:o)||n.push(e),mt()}function vt(e,t=null){if(lt.length){for(pt=t,rt=[...new Set(lt)],lt.length=0,st=0;st<rt.length;st++)rt[st]();rt=null,st=0,pt=null,vt(e,t)}}function yt(e){if(it.length){const e=[...new Set(it)];if(it.length=0,ct)return void ct.push(...e);for(ct=e,ct.sort(((e,t)=>_t(e)-_t(t))),ut=0;ut<ct.length;ut++)ct[ut]();ct=null,ut=0}}const _t=e=>null==e.id?1/0:e.id;function bt(e){tt=!1,et=!0,vt(e),nt.sort(((e,t)=>_t(e)-_t(t)));try{for(ot=0;ot<nt.length;ot++){const e=nt[ot];e&&Ye(e,null,14)}}finally{ot=0,nt.length=0,yt(),et=!1,ft=null,(nt.length||it.length)&&bt(e)}}function xt(e,t,...n){const o=e.vnode.props||a;let l=n;const r=t.startsWith("update:"),s=r&&t.slice(7);if(s&&s in o){const e=("modelValue"===s?"model":s)+"Modifiers",{number:t,trim:r}=o[e]||a;r?l=n.map((e=>e.trim())):t&&(l=n.map(H))}let i=U(j(t)),c=o[i];!c&&r&&(i=U(A(t)),c=o[i]),c&&Ze(c,e,6,l);const u=o[i+"Once"];if(u){if(e.emitted){if(e.emitted[i])return}else(e.emitted={})[i]=!0;Ze(u,e,6,l)}}function wt(e,t,n=!1){if(!t.deopt&&void 0!==e.__emits)return e.__emits;const o=e.emits;let l={},r=!1;if(!k(e)){const o=e=>{r=!0,v(l,wt(e,t,!0))};!n&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}return o||r?(x(o)?o.forEach((e=>l[e]=null)):v(l,o),e.__emits=l):e.__emits=null}function Ct(e,t){return!(!e||!m(t))&&(t=t.replace(/Once$/,""),b(e,t[2].toLowerCase()+t.slice(3))||b(e,t.slice(2)))}let kt=null;function St(e){kt=e}function Ft(e){const{type:t,vnode:n,proxy:o,withProxy:l,props:r,propsOptions:[s],slots:i,attrs:c,emit:u,render:a,renderCache:f,data:p,setupState:d,ctx:h}=e;let m;kt=e;try{let e;if(4&n.shapeFlag){const t=l||o;m=Yn(a.call(t,t,f,r,d,p,h)),e=c}else{const n=t;0,m=Yn(n.length>1?n(r,{attrs:c,slots:i,emit:u}):n(r,null)),e=t.props?c:Et(c)}let v=m;if(!1!==t.inheritAttrs&&e){const t=Object.keys(e),{shapeFlag:n}=v;t.length&&(1&n||6&n)&&(s&&t.some(g)&&(e=Mt(e,s)),v=qn(v,e))}n.dirs&&(v.dirs=v.dirs?v.dirs.concat(n.dirs):n.dirs),n.transition&&(v.transition=n.transition),m=v}catch(t){Qe(t,e,1),m=Gn(Tn)}return kt=null,m}function Ot(e){const t=e.filter((e=>!(Bn(e)&&e.type===Tn&&"v-if"!==e.children)));return 1===t.length&&Bn(t[0])?t[0]:null}const Et=e=>{let t;for(const n in e)("class"===n||"style"===n||m(n))&&((t||(t={}))[n]=e[n]);return t},Mt=(e,t)=>{const n={};for(const o in e)g(o)&&o.slice(9)in t||(n[o]=e[o]);return n};function Rt(e,t,n){const o=Object.keys(t);if(o.length!==Object.keys(e).length)return!0;for(let l=0;l<o.length;l++){const r=o[l];if(t[r]!==e[r]&&!Ct(n,r))return!0}return!1}function Pt(e){if(k(e)&&(e=e()),x(e)){e=Ot(e)}return Yn(e)}let zt=0;const $t=e=>zt+=e;function It(e,t,n={},o){let l=e[t];zt++;const r=(Vn(),Dn($n,{key:n.key},l?l(n):o?o():[],1===e._?64:-2));return zt--,r}function Tt(e,t=kt){if(!t)return e;const n=(...n)=>{zt||Vn(!0);const o=kt;St(t);const l=e(...n);return St(o),zt||Un(),l};return n._c=!0,n}let jt=null;const Nt=[];function At(e){Nt.push(jt=e)}function Vt(){Nt.pop(),jt=Nt[Nt.length-1]||null}function Ut(e){return t=>Tt((function(){At(e);const n=t.apply(this,arguments);return Vt(),n}))}function Dt(e,t,n,o=!1){const l={},r={};L(r,Hn,1),Bt(e,t,l,r),n?e.props=o?l:Le(l,!1,ge,je):e.type.props?e.props=l:e.props=r,e.attrs=r}function Bt(e,t,n,o){const[l,r]=e.propsOptions;if(t)for(const r in t){const s=t[r];if($(r))continue;let i;l&&b(l,i=j(r))?n[i]=s:Ct(e.emitsOptions,r)||(o[r]=s)}if(r){const t=Ge(n);for(let o=0;o<r.length;o++){const s=r[o];n[s]=Lt(l,t,s,t[s],e)}}}function Lt(e,t,n,o,l){const r=e[n];if(null!=r){const e=b(r,"default");if(e&&void 0===o){const e=r.default;r.type!==Function&&k(e)?(yo(l),o=e(t),yo(null)):o=e}r[0]&&(b(t,n)||e?!r[1]||""!==o&&o!==A(n)||(o=!0):o=!1)}return o}function Ht(e,t,n=!1){if(!t.deopt&&e.__props)return e.__props;const o=e.props,l={},r=[];let s=!1;if(!k(e)){const o=e=>{s=!0;const[n,o]=Ht(e,t,!0);v(l,n),o&&r.push(...o)};!n&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}if(!o&&!s)return e.__props=f;if(x(o))for(let e=0;e<o.length;e++){const t=j(o[e]);Wt(t)&&(l[t]=a)}else if(o)for(const e in o){const t=j(e);if(Wt(t)){const n=o[e],s=l[t]=x(n)||k(n)?{type:n}:n;if(s){const e=qt(Boolean,s.type),n=qt(String,s.type);s[0]=e>-1,s[1]=n<0||e<n,(e>-1||b(s,"default"))&&r.push(t)}}}return e.__props=[l,r]}function Wt(e){return"$"!==e[0]}function Kt(e){const t=e&&e.toString().match(/^\s*function (\w+)/);return t?t[1]:""}function Gt(e,t){return Kt(e)===Kt(t)}function qt(e,t){if(x(t)){for(let n=0,o=t.length;n<o;n++)if(Gt(t[n],e))return n}else if(k(t))return Gt(t,e)?0:-1;return-1}function Jt(e,t,n=vo,o=!1){if(n){const l=n[e]||(n[e]=[]),r=t.__weh||(t.__weh=(...o)=>{if(n.isUnmounted)return;ne(),yo(n);const l=Ze(t,n,e,o);return yo(null),oe(),l});return o?l.unshift(r):l.push(r),r}}const Xt=e=>(t,n=vo)=>!_o&&Jt(e,t,n),Yt=Xt("bm"),Zt=Xt("m"),Qt=Xt("bu"),en=Xt("u"),tn=Xt("bum"),nn=Xt("um"),on=Xt("rtg"),ln=Xt("rtc"),rn={};function sn(e,t,n){return cn(e,t,n)}function cn(e,t,{immediate:n,deep:o,flush:l,onTrack:r,onTrigger:s}=a,i=vo){let c,u,f=!1;if(qe(e)?(c=()=>e.value,f=!!e._shallow):He(e)?(c=()=>e,o=!0):c=x(e)?()=>e.map((e=>qe(e)?e.value:He(e)?an(e):k(e)?Ye(e,i,2):void 0)):k(e)?t?()=>Ye(e,i,2):()=>{if(!i||!i.isUnmounted)return u&&u(),Ye(e,i,3,[d])}:p,t&&o){const e=c;c=()=>an(e())}const d=e=>{u=v.options.onStop=()=>{Ye(e,i,4)}};let h=x(e)?[]:rn;const m=()=>{if(v.active)if(t){const e=v();(o||f||D(e,h))&&(u&&u(),Ze(t,i,3,[e,h===rn?void 0:h,d]),h=e)}else v()};let g;m.allowRecurse=!!t,g="sync"===l?m:"post"===l?()=>kn(m,i&&i.suspense):()=>{!i||i.isMounted?function(e){gt(e,rt,lt,st)}(m):m()};const v=X(c,{lazy:!0,onTrack:r,onTrigger:s,scheduler:g});return wo(v),t?n?m():h=v():"post"===l?kn(v,i&&i.suspense):v(),()=>{Y(v),i&&y(i.effects,v)}}function un(e,t,n){const o=this.proxy;return cn(S(e)?()=>o[e]:e.bind(o),t.bind(o),n,this)}function an(e,t=new Set){if(!O(e)||t.has(e))return e;if(t.add(e),qe(e))an(e.value,t);else if(x(e))for(let n=0;n<e.length;n++)an(e[n],t);else if(C(e)||w(e))e.forEach((e=>{an(e,t)}));else for(const n in e)an(e[n],t);return e}const fn=e=>e.type.__isKeepAlive;function pn(e,t,n=vo){const o=e.__wdc||(e.__wdc=()=>{let t=n;for(;t;){if(t.isDeactivated)return;t=t.parent}e()});if(Jt(t,o,n),n){let e=n.parent;for(;e&&e.parent;)fn(e.parent.vnode)&&dn(o,t,n,e),e=e.parent}}function dn(e,t,n,o){const l=Jt(t,e,o,!0);nn((()=>{y(o[t],l)}),n)}const hn=e=>"_"===e[0]||"$stable"===e,mn=e=>x(e)?e.map(Yn):[Yn(e)],gn=(e,t,n)=>Tt((e=>mn(t(e))),n),vn=(e,t)=>{const n=e._ctx;for(const o in e){if(hn(o))continue;const l=e[o];if(k(l))t[o]=gn(0,l,n);else if(null!=l){const e=mn(l);t[o]=()=>e}}},yn=(e,t)=>{const n=mn(t);e.slots.default=()=>n};function _n(e,t,n,o){const l=e.dirs,r=t&&t.dirs;for(let s=0;s<l.length;s++){const i=l[s];r&&(i.oldValue=r[s].value);const c=i.dir[o];c&&Ze(c,n,8,[e.el,i,e,t])}}function bn(){return{app:null,config:{isNativeTag:d,performance:!1,globalProperties:{},optionMergeStrategies:{},isCustomElement:d,errorHandler:void 0,warnHandler:void 0},mixins:[],components:{},directives:{},provides:Object.create(null)}}let xn=0;function wn(e,t){return function(n,o=null){null==o||O(o)||(o=null);const l=bn(),r=new Set;let s=!1;const i=l.app={_uid:xn++,_component:n,_props:o,_container:null,_context:l,version:So,get config(){return l.config},set config(e){},use:(e,...t)=>(r.has(e)||(e&&k(e.install)?(r.add(e),e.install(i,...t)):k(e)&&(r.add(e),e(i,...t))),i),mixin:e=>(l.mixins.includes(e)||(l.mixins.push(e),(e.props||e.emits)&&(l.deopt=!0)),i),component:(e,t)=>t?(l.components[e]=t,i):l.components[e],directive:(e,t)=>t?(l.directives[e]=t,i):l.directives[e],mount(r,c){if(!s){const u=Gn(n,o);return u.appContext=l,c&&t?t(u,r):e(u,r),s=!0,i._container=r,r.__vue_app__=i,u.component.proxy}},unmount(){s&&e(null,i._container)},provide:(e,t)=>(l.provides[e]=t,i)};return i}}const Cn={scheduler:ht,allowRecurse:!0},kn=function(e,t){t&&t.pendingBranch?x(e)?t.effects.push(...e):t.effects.push(e):gt(e,ct,it,ut)},Sn=(e,t,n,o,l)=>{if(x(e))return void e.forEach(((e,r)=>Sn(e,t&&(x(t)?t[r]:t),n,o,l)));let r;r=l?4&l.shapeFlag?l.component.proxy:l.el:null;const{i:s,r:i}=e,c=t&&t.r,u=s.refs===a?s.refs={}:s.refs,f=s.setupState;if(null!=c&&c!==i&&(S(c)?(u[c]=null,b(f,c)&&(f[c]=null)):qe(c)&&(c.value=null)),S(i)){const e=()=>{u[i]=r,b(f,i)&&(f[i]=r)};r?(e.id=-1,kn(e,o)):e()}else if(qe(i)){const e=()=>{i.value=r};r?(e.id=-1,kn(e,o)):e()}else k(i)&&Ye(i,n,12,[r,u])};function Fn(e){return function(e,t){const{insert:n,remove:o,patchProp:l,forcePatchProp:r,createElement:s,createText:i,createComment:c,setText:u,setElementText:d,parentNode:h,nextSibling:m,setScopeId:g=p,cloneNode:y,insertStaticContent:_}=e,x=(e,t,n,o=null,l=null,r=null,s=!1,i=!1)=>{e&&!Ln(e,t)&&(o=te(e),q(e,l,r,!0),e=null),-2===t.patchFlag&&(i=!1,t.dynamicChildren=null);const{type:c,ref:u,shapeFlag:a}=t;switch(c){case In:w(e,t,n,o);break;case Tn:C(e,t,n,o);break;case jn:null==e&&k(t,n,o,s);break;case $n:I(e,t,n,o,l,r,s,i);break;default:1&a?S(e,t,n,o,l,r,s,i):6&a?T(e,t,n,o,l,r,s,i):(64&a||128&a)&&c.process(e,t,n,o,l,r,s,i,se)}null!=u&&l&&Sn(u,e&&e.ref,l,r,t)},w=(e,t,o,l)=>{if(null==e)n(t.el=i(t.children),o,l);else{const n=t.el=e.el;t.children!==e.children&&u(n,t.children)}},C=(e,t,o,l)=>{null==e?n(t.el=c(t.children||""),o,l):t.el=e.el},k=(e,t,n,o)=>{[e.el,e.anchor]=_(e.children,t,n,o)},S=(e,t,n,o,l,r,s,i)=>{s=s||"svg"===t.type,null==e?F(t,n,o,l,r,s,i):R(e,t,l,r,s,i)},F=(e,t,o,r,i,c,u)=>{let a,f;const{type:p,props:h,shapeFlag:m,transition:g,scopeId:v,patchFlag:_,dirs:b}=e;if(e.el&&void 0!==y&&-1===_)a=e.el=y(e.el);else{if(a=e.el=s(e.type,c,h&&h.is),8&m?d(a,e.children):16&m&&M(e.children,a,null,r,i,c&&"foreignObject"!==p,u||!!e.dynamicChildren),b&&_n(e,null,r,"created"),h){for(const t in h)$(t)||l(a,t,null,h[t],c,e.children,r,i,ee);(f=h.onVnodeBeforeMount)&&On(f,r,e)}O(a,v,e,r)}b&&_n(e,null,r,"beforeMount");const x=(!i||i&&!i.pendingBranch)&&g&&!g.persisted;x&&g.beforeEnter(a),n(a,t,o),((f=h&&h.onVnodeMounted)||x||b)&&kn((()=>{f&&On(f,r,e),x&&g.enter(a),b&&_n(e,null,r,"mounted")}),i)},O=(e,t,n,o)=>{if(t&&g(e,t),o){const l=o.type.__scopeId;l&&l!==t&&g(e,l+"-s"),n===o.subTree&&O(e,o.vnode.scopeId,o.vnode,o.parent)}},M=(e,t,n,o,l,r,s,i=0)=>{for(let c=i;c<e.length;c++){const i=e[c]=s?Zn(e[c]):Yn(e[c]);x(null,i,t,n,o,l,r,s)}},R=(e,t,n,o,s,i)=>{const c=t.el=e.el;let{patchFlag:u,dynamicChildren:f,dirs:p}=t;u|=16&e.patchFlag;const h=e.props||a,m=t.props||a;let g;if((g=m.onVnodeBeforeUpdate)&&On(g,n,t,e),p&&_n(t,e,n,"beforeUpdate"),u>0){if(16&u)z(c,t,h,m,n,o,s);else if(2&u&&h.class!==m.class&&l(c,"class",null,m.class,s),4&u&&l(c,"style",h.style,m.style,s),8&u){const i=t.dynamicProps;for(let t=0;t<i.length;t++){const u=i[t],a=h[u],f=m[u];(f!==a||r&&r(c,u))&&l(c,u,a,f,s,e.children,n,o,ee)}}1&u&&e.children!==t.children&&d(c,t.children)}else i||null!=f||z(c,t,h,m,n,o,s);const v=s&&"foreignObject"!==t.type;f?P(e.dynamicChildren,f,c,n,o,v):i||H(e,t,c,null,n,o,v),((g=m.onVnodeUpdated)||p)&&kn((()=>{g&&On(g,n,t,e),p&&_n(t,e,n,"updated")}),o)},P=(e,t,n,o,l,r)=>{for(let s=0;s<t.length;s++){const i=e[s],c=t[s],u=i.type===$n||!Ln(i,c)||6&i.shapeFlag||64&i.shapeFlag?h(i.el):n;x(i,c,u,null,o,l,r,!0)}},z=(e,t,n,o,s,i,c)=>{if(n!==o){for(const u in o){if($(u))continue;const a=o[u],f=n[u];(a!==f||r&&r(e,u))&&l(e,u,f,a,c,t.children,s,i,ee)}if(n!==a)for(const r in n)$(r)||r in o||l(e,r,n[r],null,c,t.children,s,i,ee)}},I=(e,t,o,l,r,s,c,u)=>{const a=t.el=e?e.el:i(""),f=t.anchor=e?e.anchor:i("");let{patchFlag:p,dynamicChildren:d}=t;p>0&&(u=!0),null==e?(n(a,o,l),n(f,o,l),M(t.children,o,f,r,s,c,u)):p>0&&64&p&&d?(P(e.dynamicChildren,d,o,r,s,c),(null!=t.key||r&&t===r.subTree)&&En(e,t,!0)):H(e,t,o,f,r,s,c,u)},T=(e,t,n,o,l,r,s,i)=>{null==e?512&t.shapeFlag?l.ctx.activate(t,n,o,s,i):N(t,n,o,l,r,s,i):V(e,t,i)},N=(e,t,n,o,l,r,s)=>{const i=e.component=function(e,t,n){const o=e.type,l=(t?t.appContext:e.appContext)||mo,r={uid:go++,vnode:e,type:o,parent:t,appContext:l,root:null,next:null,subTree:null,update:null,render:null,proxy:null,withProxy:null,effects:null,provides:t?t.provides:Object.create(l.provides),accessCache:null,renderCache:[],components:null,directives:null,propsOptions:Ht(o,l),emitsOptions:wt(o,l),emit:null,emitted:null,ctx:a,data:a,props:a,attrs:a,slots:a,refs:a,setupState:a,setupContext:null,suspense:n,suspenseId:n?n.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null};return r.ctx={_:r},r.root=t?t.root:r,r.emit=xt.bind(null,r),r}(e,o,l);if(fn(e)&&(i.ctx.renderer=se),function(e,t=!1){_o=t;const{props:n,children:o,shapeFlag:l}=e.vnode,r=4&l;Dt(e,n,r,t),((e,t)=>{if(32&e.vnode.shapeFlag){const n=t._;n?(e.slots=t,L(t,"_",n)):vn(t,e.slots={})}else e.slots={},t&&yn(e,t);L(e.slots,Hn,1)})(e,o);const s=r?function(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,po);const{setup:o}=n;if(o){const n=e.setupContext=o.length>1?function(e){return{attrs:e.attrs,slots:e.slots,emit:e.emit}}(e):null;vo=e,ne();const l=Ye(o,e,0,[e.props,n]);if(oe(),vo=null,E(l)){if(t)return l.then((t=>{bo(e,t)}));e.asyncDep=l}else bo(e,l)}else xo(e)}(e,t):void 0;_o=!1}(i),i.asyncDep){if(l&&l.registerDep(i,U),!e.el){const e=i.subTree=Gn(Tn);C(null,e,t,n)}}else U(i,e,t,n,l,r,s)},V=(e,t,n)=>{const o=t.component=e.component;if(function(e,t,n){const{props:o,children:l,component:r}=e,{props:s,children:i,patchFlag:c}=t,u=r.emitsOptions;if(t.dirs||t.transition)return!0;if(!(n&&c>0))return!(!l&&!i||i&&i.$stable)||o!==s&&(o?!s||Rt(o,s,u):!!s);if(1024&c)return!0;if(16&c)return o?Rt(o,s,u):!!s;if(8&c){const e=t.dynamicProps;for(let t=0;t<e.length;t++){const n=e[t];if(s[n]!==o[n]&&!Ct(u,n))return!0}}return!1}(e,t,n)){if(o.asyncDep&&!o.asyncResolved)return void D(o,t,n);o.next=t,function(e){const t=nt.indexOf(e);t>-1&&(nt[t]=null)}(o.update),o.update()}else t.component=e.component,t.el=e.el,o.vnode=t},U=(e,t,n,o,l,r,s)=>{e.update=X((function(){if(e.isMounted){let t,{next:n,bu:o,u:i,parent:c,vnode:u}=e,a=n;n?(n.el=u.el,D(e,n,s)):n=u,o&&B(o),(t=n.props&&n.props.onVnodeBeforeUpdate)&&On(t,c,n,u);const f=Ft(e),p=e.subTree;e.subTree=f,x(p,f,h(p.el),te(p),e,l,r),n.el=f.el,null===a&&function({vnode:e,parent:t},n){for(;t&&t.subTree===e;)(e=t.vnode).el=n,t=t.parent}(e,f.el),i&&kn(i,l),(t=n.props&&n.props.onVnodeUpdated)&&kn((()=>{On(t,c,n,u)}),l)}else{let s;const{el:i,props:c}=t,{bm:u,m:a,parent:f}=e;u&&B(u),(s=c&&c.onVnodeBeforeMount)&&On(s,f,t);const p=e.subTree=Ft(e);i&&ce?ce(t.el,p,e,l):(x(null,p,n,o,e,l,r),t.el=p.el),a&&kn(a,l),(s=c&&c.onVnodeMounted)&&kn((()=>{On(s,f,t)}),l);const{a:d}=e;d&&256&t.shapeFlag&&kn(d,l),e.isMounted=!0}}),Cn)},D=(e,t,n)=>{t.component=e;const o=e.vnode.props;e.vnode=t,e.next=null,function(e,t,n,o){const{props:l,attrs:r,vnode:{patchFlag:s}}=e,i=Ge(l),[c]=e.propsOptions;if(!(o||s>0)||16&s){let o;Bt(e,t,l,r);for(const r in i)t&&(b(t,r)||(o=A(r))!==r&&b(t,o))||(c?!n||void 0===n[r]&&void 0===n[o]||(l[r]=Lt(c,t||a,r,void 0,e)):delete l[r]);if(r!==i)for(const e in r)t&&b(t,e)||delete r[e]}else if(8&s){const n=e.vnode.dynamicProps;for(let o=0;o<n.length;o++){const s=n[o],u=t[s];if(c)if(b(r,s))r[s]=u;else{const t=j(s);l[t]=Lt(c,i,t,u,e)}else r[s]=u}}re(e,"set","$attrs")}(e,t.props,o,n),((e,t)=>{const{vnode:n,slots:o}=e;let l=!0,r=a;if(32&n.shapeFlag){const e=t._;e?1===e?l=!1:v(o,t):(l=!t.$stable,vn(t,o)),r=t}else t&&(yn(e,t),r={default:1});if(l)for(const e in o)hn(e)||e in r||delete o[e]})(e,t.children),vt(void 0,e.update)},H=(e,t,n,o,l,r,s,i=!1)=>{const c=e&&e.children,u=e?e.shapeFlag:0,a=t.children,{patchFlag:f,shapeFlag:p}=t;if(f>0){if(128&f)return void K(c,a,n,o,l,r,s,i);if(256&f)return void W(c,a,n,o,l,r,s,i)}8&p?(16&u&&ee(c,l,r),a!==c&&d(n,a)):16&u?16&p?K(c,a,n,o,l,r,s,i):ee(c,l,r,!0):(8&u&&d(n,""),16&p&&M(a,n,o,l,r,s,i))},W=(e,t,n,o,l,r,s,i)=>{t=t||f;const c=(e=e||f).length,u=t.length,a=Math.min(c,u);let p;for(p=0;p<a;p++){const o=t[p]=i?Zn(t[p]):Yn(t[p]);x(e[p],o,n,null,l,r,s,i)}c>u?ee(e,l,r,!0,!1,a):M(t,n,o,l,r,s,i,a)},K=(e,t,n,o,l,r,s,i)=>{let c=0;const u=t.length;let a=e.length-1,p=u-1;for(;c<=a&&c<=p;){const o=e[c],u=t[c]=i?Zn(t[c]):Yn(t[c]);if(!Ln(o,u))break;x(o,u,n,null,l,r,s,i),c++}for(;c<=a&&c<=p;){const o=e[a],c=t[p]=i?Zn(t[p]):Yn(t[p]);if(!Ln(o,c))break;x(o,c,n,null,l,r,s,i),a--,p--}if(c>a){if(c<=p){const e=p+1,a=e<u?t[e].el:o;for(;c<=p;)x(null,t[c]=i?Zn(t[c]):Yn(t[c]),n,a,l,r,s),c++}}else if(c>p)for(;c<=a;)q(e[c],l,r,!0),c++;else{const d=c,h=c,m=new Map;for(c=h;c<=p;c++){const e=t[c]=i?Zn(t[c]):Yn(t[c]);null!=e.key&&m.set(e.key,c)}let g,v=0;const y=p-h+1;let _=!1,b=0;const w=new Array(y);for(c=0;c<y;c++)w[c]=0;for(c=d;c<=a;c++){const o=e[c];if(v>=y){q(o,l,r,!0);continue}let u;if(null!=o.key)u=m.get(o.key);else for(g=h;g<=p;g++)if(0===w[g-h]&&Ln(o,t[g])){u=g;break}void 0===u?q(o,l,r,!0):(w[u-h]=c+1,u>=b?b=u:_=!0,x(o,t[u],n,null,l,r,s,i),v++)}const C=_?function(e){const t=e.slice(),n=[0];let o,l,r,s,i;const c=e.length;for(o=0;o<c;o++){const c=e[o];if(0!==c){if(l=n[n.length-1],e[l]<c){t[o]=l,n.push(o);continue}for(r=0,s=n.length-1;r<s;)i=(r+s)/2|0,e[n[i]]<c?r=i+1:s=i;c<e[n[r]]&&(r>0&&(t[o]=n[r-1]),n[r]=o)}}r=n.length,s=n[r-1];for(;r-- >0;)n[r]=s,s=t[s];return n}(w):f;for(g=C.length-1,c=y-1;c>=0;c--){const e=h+c,i=t[e],a=e+1<u?t[e+1].el:o;0===w[c]?x(null,i,n,a,l,r,s):_&&(g<0||c!==C[g]?G(i,n,a,2):g--)}}},G=(e,t,o,l,r=null)=>{const{el:s,type:i,transition:c,children:u,shapeFlag:a}=e;if(6&a)return void G(e.component.subTree,t,o,l);if(128&a)return void e.suspense.move(t,o,l);if(64&a)return void i.move(e,t,o,se);if(i===$n){n(s,t,o);for(let e=0;e<u.length;e++)G(u[e],t,o,l);return void n(e.anchor,t,o)}if(2!==l&&1&a&&c)if(0===l)c.beforeEnter(s),n(s,t,o),kn((()=>c.enter(s)),r);else{const{leave:e,delayLeave:l,afterLeave:r}=c,i=()=>n(s,t,o),u=()=>{e(s,(()=>{i(),r&&r()}))};l?l(s,i,u):u()}else n(s,t,o)},q=(e,t,n,o=!1,l=!1)=>{const{type:r,props:s,ref:i,children:c,dynamicChildren:u,shapeFlag:a,patchFlag:f,dirs:p}=e;if(null!=i&&t&&Sn(i,null,t,n,null),256&a)return void t.ctx.deactivate(e);const d=1&a&&p;let h;if((h=s&&s.onVnodeBeforeUnmount)&&On(h,t,e),6&a)Q(e.component,n,o);else{if(128&a)return void e.suspense.unmount(n,o);d&&_n(e,null,t,"beforeUnmount"),u&&(r!==$n||f>0&&64&f)?ee(u,t,n,!1,!0):(r===$n&&(128&f||256&f)||!l&&16&a)&&ee(c,t,n),64&a&&(o||!Mn(e.props))&&e.type.remove(e,se),o&&J(e)}((h=s&&s.onVnodeUnmounted)||d)&&kn((()=>{h&&On(h,t,e),d&&_n(e,null,t,"unmounted")}),n)},J=e=>{const{type:t,el:n,anchor:l,transition:r}=e;if(t===$n)return void Z(n,l);const s=()=>{o(n),r&&!r.persisted&&r.afterLeave&&r.afterLeave()};if(1&e.shapeFlag&&r&&!r.persisted){const{leave:t,delayLeave:o}=r,l=()=>t(n,s);o?o(e.el,s,l):l()}else s()},Z=(e,t)=>{let n;for(;e!==t;)n=m(e),o(e),e=n;o(t)},Q=(e,t,n)=>{const{bum:o,effects:l,update:r,subTree:s,um:i}=e;if(o&&B(o),l)for(let e=0;e<l.length;e++)Y(l[e]);r&&(Y(r),q(s,e,t,n)),i&&kn(i,t),kn((()=>{e.isUnmounted=!0}),t),t&&t.pendingBranch&&!t.isUnmounted&&e.asyncDep&&!e.asyncResolved&&e.suspenseId===t.pendingId&&(t.deps--,0===t.deps&&t.resolve())},ee=(e,t,n,o=!1,l=!1,r=0)=>{for(let s=r;s<e.length;s++)q(e[s],t,n,o,l)},te=e=>6&e.shapeFlag?te(e.component.subTree):128&e.shapeFlag?e.suspense.next():m(e.anchor||e.el),le=(e,t)=>{null==e?t._vnode&&q(t._vnode,null,null,!0):x(t._vnode||null,e,t),yt(),t._vnode=e},se={p:x,um:q,m:G,r:J,mt:N,mc:M,pc:H,pbc:P,n:te,o:e};let ie,ce;t&&([ie,ce]=t(se));return{render:le,hydrate:ie,createApp:wn(le,ie)}}(e)}function On(e,t,n,o=null){Ze(e,t,7,[n,o])}function En(e,t,n=!1){const o=e.children,l=t.children;if(x(o)&&x(l))for(let e=0;e<o.length;e++){const t=o[e];let r=l[e];1&r.shapeFlag&&!r.dynamicChildren&&((r.patchFlag<=0||32===r.patchFlag)&&(r=l[e]=Zn(l[e]),r.el=t.el),n||En(t,r))}}const Mn=e=>e&&(e.disabled||""===e.disabled);function Rn(e){return function(e,t,n=!0){const o=kt||vo;if(o){const n=o.type;if("components"===e){const e=n.displayName||n.name;if(e&&(e===t||e===j(t)||e===V(j(t))))return n}return zn(o[e]||n[e],t)||zn(o.appContext[e],t)}}("components",e)||e}const Pn=Symbol();function zn(e,t){return e&&(e[t]||e[j(t)]||e[V(j(t))])}const $n=Symbol(void 0),In=Symbol(void 0),Tn=Symbol(void 0),jn=Symbol(void 0),Nn=[];let An=null;function Vn(e=!1){Nn.push(An=e?null:[])}function Un(){Nn.pop(),An=Nn[Nn.length-1]||null}function Dn(e,t,n,o,l){const r=Gn(e,t,n,o,l,!0);return r.dynamicChildren=An||f,Un(),An&&An.push(r),r}function Bn(e){return!!e&&!0===e.__v_isVNode}function Ln(e,t){return e.type===t.type&&e.key===t.key}const Hn="__vInternal",Wn=({key:e})=>null!=e?e:null,Kn=({ref:e})=>null!=e?x(e)?e:{i:kt,r:e}:null,Gn=function(e,t=null,n=null,l=0,r=null,s=!1){e&&e!==Pn||(e=Tn);if(Bn(e)){const o=qn(e,t,!0);return n&&Qn(o,n),o}c=e,k(c)&&"__vccOpts"in c&&(e=e.__vccOpts);var c;if(t){(Ke(t)||Hn in t)&&(t=v({},t));let{class:e,style:n}=t;e&&!S(e)&&(t.class=i(e)),O(n)&&(Ke(n)&&!x(n)&&(n=v({},n)),t.style=o(n))}const u=S(e)?1:(e=>e.__isSuspense)(e)?128:(e=>e.__isTeleport)(e)?64:O(e)?4:k(e)?2:0,a={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Wn(t),ref:t&&Kn(t),scopeId:jt,children:null,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:u,patchFlag:l,dynamicProps:r,dynamicChildren:null,appContext:null};if(Qn(a,n),128&u){const{content:e,fallback:t}=function(e){const{shapeFlag:t,children:n}=e;let o,l;return 32&t?(o=Pt(n.default),l=Pt(n.fallback)):(o=Pt(n),l=Yn(null)),{content:o,fallback:l}}(a);a.ssContent=e,a.ssFallback=t}!s&&An&&(l>0||6&u)&&32!==l&&An.push(a);return a};function qn(e,t,n=!1){const{props:l,ref:r,patchFlag:s}=e,c=t?function(...e){const t=v({},e[0]);for(let n=1;n<e.length;n++){const l=e[n];for(const e in l)if("class"===e)t.class!==l.class&&(t.class=i([t.class,l.class]));else if("style"===e)t.style=o([t.style,l.style]);else if(m(e)){const n=t[e],o=l[e];n!==o&&(t[e]=n?[].concat(n,l[e]):o)}else""!==e&&(t[e]=l[e])}return t}(l||{},t):l;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:c,key:c&&Wn(c),ref:t&&t.ref?n&&r?x(r)?r.concat(Kn(t)):[r,Kn(t)]:Kn(t):r,scopeId:e.scopeId,children:e.children,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==$n?-1===s?16:16|s:s,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&qn(e.ssContent),ssFallback:e.ssFallback&&qn(e.ssFallback),el:e.el,anchor:e.anchor}}function Jn(e=" ",t=0){return Gn(In,null,e,t)}function Xn(e="",t=!1){return t?(Vn(),Dn(Tn,null,e)):Gn(Tn,null,e)}function Yn(e){return null==e||"boolean"==typeof e?Gn(Tn):x(e)?Gn($n,null,e):"object"==typeof e?null===e.el?e:qn(e):Gn(In,null,String(e))}function Zn(e){return null===e.el?e:qn(e)}function Qn(e,t){let n=0;const{shapeFlag:o}=e;if(null==t)t=null;else if(x(t))n=16;else if("object"==typeof t){if(1&o||64&o){const n=t.default;return void(n&&(n._c&&$t(1),Qn(e,n()),n._c&&$t(-1)))}{n=32;const o=t._;o||Hn in t?3===o&&kt&&(1024&kt.vnode.patchFlag?(t._=2,e.patchFlag|=1024):t._=1):t._ctx=kt}}else k(t)?(t={default:t,_ctx:kt},n=32):(t=String(t),64&o?(n=16,t=[Jn(t)]):n=8);e.children=t,e.shapeFlag|=n}function eo(e,t){if(vo){let n=vo.provides;const o=vo.parent&&vo.parent.provides;o===n&&(n=vo.provides=Object.create(o)),n[e]=t}else;}function to(e,t,n=!1){const o=vo||kt;if(o){const l=null==o.parent?o.vnode.appContext&&o.vnode.appContext.provides:o.parent.provides;if(l&&e in l)return l[e];if(arguments.length>1)return n&&k(t)?t():t}}let no=!1;function oo(e,t,n=[],o=[],l=[],r=!1){const{mixins:s,extends:i,data:c,computed:u,methods:a,watch:f,provide:d,inject:h,components:m,directives:g,beforeMount:y,mounted:_,beforeUpdate:b,updated:w,activated:C,deactivated:S,beforeDestroy:F,beforeUnmount:E,destroyed:M,unmounted:R,render:P,renderTracked:z,renderTriggered:$,errorCaptured:I}=t,T=e.proxy,j=e.ctx,N=e.appContext.mixins;if(r&&P&&e.render===p&&(e.render=P),r||(no=!0,lo("beforeCreate","bc",t,e,N),no=!1,io(e,N,n,o,l)),i&&oo(e,i,n,o,l,!0),s&&io(e,s,n,o,l),h)if(x(h))for(let e=0;e<h.length;e++){const t=h[e];j[t]=to(t)}else for(const e in h){const t=h[e];O(t)?j[e]=to(t.from||e,t.default,!0):j[e]=to(t)}if(a)for(const e in a){const t=a[e];k(t)&&(j[e]=t.bind(T))}if(r?c&&n.push(c):(n.length&&n.forEach((t=>co(e,t,T))),c&&co(e,c,T)),u)for(const e in u){const t=u[e],n=Co({get:k(t)?t.bind(T,T):k(t.get)?t.get.bind(T,T):p,set:!k(t)&&k(t.set)?t.set.bind(T):p});Object.defineProperty(j,e,{enumerable:!0,configurable:!0,get:()=>n.value,set:e=>n.value=e})}var A;f&&o.push(f),!r&&o.length&&o.forEach((e=>{for(const t in e)uo(e[t],j,T,t)})),d&&l.push(d),!r&&l.length&&l.forEach((e=>{const t=k(e)?e.call(T):e;for(const e in t)eo(e,t[e])})),r&&(m&&v(e.components||(e.components=v({},e.type.components)),m),g&&v(e.directives||(e.directives=v({},e.type.directives)),g)),r||lo("created","c",t,e,N),y&&Yt(y.bind(T)),_&&Zt(_.bind(T)),b&&Qt(b.bind(T)),w&&en(w.bind(T)),C&&pn(C.bind(T),"a",A),S&&function(e,t){pn(e,"da",t)}(S.bind(T)),I&&((e,t=vo)=>{Jt("ec",e,t)})(I.bind(T)),z&&ln(z.bind(T)),$&&on($.bind(T)),E&&tn(E.bind(T)),R&&nn(R.bind(T))}function lo(e,t,n,o,l){so(e,t,l,o);const{extends:r,mixins:s}=n;r&&ro(e,t,r,o),s&&so(e,t,s,o);const i=n[e];i&&Ze(i.bind(o.proxy),o,t)}function ro(e,t,n,o){n.extends&&ro(e,t,n.extends,o);const l=n[e];l&&Ze(l.bind(o.proxy),o,t)}function so(e,t,n,o){for(let l=0;l<n.length;l++){const r=n[l].mixins;r&&so(e,t,r,o);const s=n[l][e];s&&Ze(s.bind(o.proxy),o,t)}}function io(e,t,n,o,l){for(let r=0;r<t.length;r++)oo(e,t[r],n,o,l,!0)}function co(e,t,n){const o=t.call(n,n);O(o)&&(e.data===a?e.data=De(o):v(e.data,o))}function uo(e,t,n,o){const l=o.includes(".")?function(e,t){const n=t.split(".");return()=>{let t=e;for(let e=0;e<n.length&&t;e++)t=t[n[e]];return t}}(n,o):()=>n[o];if(S(e)){const n=t[e];k(n)&&sn(l,n)}else if(k(e))sn(l,e.bind(n));else if(O(e))if(x(e))e.forEach((e=>uo(e,t,n,o)));else{const o=k(e.handler)?e.handler.bind(n):t[e.handler];k(o)&&sn(l,o,e)}}function ao(e,t,n){const o=n.appContext.config.optionMergeStrategies,{mixins:l,extends:r}=t;r&&ao(e,r,n),l&&l.forEach((t=>ao(e,t,n)));for(const l in t)o&&b(o,l)?e[l]=o[l](e[l],t[l],n.proxy,l):e[l]=t[l]}const fo=v(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>e.parent&&e.parent.proxy,$root:e=>e.root&&e.root.proxy,$emit:e=>e.emit,$options:e=>function(e){const t=e.type,{__merged:n,mixins:o,extends:l}=t;if(n)return n;const r=e.appContext.mixins;if(!r.length&&!o&&!l)return t;const s={};return r.forEach((t=>ao(s,t,e))),ao(s,t,e),t.__merged=s}(e),$forceUpdate:e=>()=>ht(e.update),$nextTick:e=>dt.bind(e.proxy),$watch:e=>un.bind(e)}),po={get({_:e},t){const{ctx:n,setupState:o,data:l,props:r,accessCache:s,type:i,appContext:c}=e;if("__v_skip"===t)return!0;let u;if("$"!==t[0]){const i=s[t];if(void 0!==i)switch(i){case 0:return o[t];case 1:return l[t];case 3:return n[t];case 2:return r[t]}else{if(o!==a&&b(o,t))return s[t]=0,o[t];if(l!==a&&b(l,t))return s[t]=1,l[t];if((u=e.propsOptions[0])&&b(u,t))return s[t]=2,r[t];if(n!==a&&b(n,t))return s[t]=3,n[t];no||(s[t]=4)}}const f=fo[t];let p,d;return f?("$attrs"===t&&le(e,0,t),f(e)):(p=i.__cssModules)&&(p=p[t])?p:n!==a&&b(n,t)?(s[t]=3,n[t]):(d=c.config.globalProperties,b(d,t)?d[t]:void 0)},set({_:e},t,n){const{data:o,setupState:l,ctx:r}=e;if(l!==a&&b(l,t))l[t]=n;else if(o!==a&&b(o,t))o[t]=n;else if(t in e.props)return!1;return("$"!==t[0]||!(t.slice(1)in e))&&(r[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:o,appContext:l,propsOptions:r}},s){let i;return void 0!==n[s]||e!==a&&b(e,s)||t!==a&&b(t,s)||(i=r[0])&&b(i,s)||b(o,s)||b(fo,s)||b(l.config.globalProperties,s)}},ho=v({},po,{get(e,t){if(t!==Symbol.unscopables)return po.get(e,t,e)},has:(e,n)=>"_"!==n[0]&&!t(n)}),mo=bn();let go=0;let vo=null;const yo=e=>{vo=e};let _o=!1;function bo(e,t,n){var o;k(t)?e.render=t:O(t)&&(e.setupState=He(o=t)?o:new Proxy(o,Je)),xo(e)}function xo(e,t){const n=e.type;e.render||(e.render=n.render||p,e.render._rc&&(e.withProxy=new Proxy(e.ctx,ho))),vo=e,oo(e,n),vo=null}function wo(e){vo&&(vo.effects||(vo.effects=[])).push(e)}function Co(e){const t=function(e){let t,n;return k(e)?(t=e,n=p):(t=e.get,n=e.set),new Xe(t,n,k(e)||!e.set)}(e);return wo(t.effect),t}function ko(e,t){let n;if(x(e)||S(e)){n=new Array(e.length);for(let o=0,l=e.length;o<l;o++)n[o]=t(e[o],o)}else if("number"==typeof e){n=new Array(e);for(let o=0;o<e;o++)n[o]=t(o+1,o)}else if(O(e))if(e[Symbol.iterator])n=Array.from(e,t);else{const o=Object.keys(e);n=new Array(o.length);for(let l=0,r=o.length;l<r;l++){const r=o[l];n[l]=t(e[r],r,l)}}else n=[];return n}const So="3.0.2",Fo="http://www.w3.org/2000/svg",Oo="undefined"!=typeof document?document:null;let Eo,Mo;const Ro={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n)=>t?Oo.createElementNS(Fo,e):Oo.createElement(e,n?{is:n}:void 0),createText:e=>Oo.createTextNode(e),createComment:e=>Oo.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Oo.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},cloneNode:e=>e.cloneNode(!0),insertStaticContent(e,t,n,o){const l=o?Mo||(Mo=Oo.createElementNS(Fo,"svg")):Eo||(Eo=Oo.createElement("div"));l.innerHTML=e;const r=l.firstChild;let s=r,i=s;for(;s;)i=s,Ro.insert(s,t,n),s=l.firstChild;return[r,i]}};const Po=/\s*!important$/;function zo(e,t,n){if(x(n))n.forEach((n=>zo(e,t,n)));else if(t.startsWith("--"))e.setProperty(t,n);else{const o=function(e,t){const n=Io[t];if(n)return n;let o=j(t);if("filter"!==o&&o in e)return Io[t]=o;o=V(o);for(let n=0;n<$o.length;n++){const l=$o[n]+o;if(l in e)return Io[t]=l}return t}(e,t);Po.test(n)?e.setProperty(A(o),n.replace(Po,""),"important"):e[o]=n}}const $o=["Webkit","Moz","ms"],Io={};const To="http://www.w3.org/1999/xlink";let jo=Date.now;"undefined"!=typeof document&&jo()>document.createEvent("Event").timeStamp&&(jo=()=>performance.now());let No=0;const Ao=Promise.resolve(),Vo=()=>{No=0};function Uo(e,t,n,o,l=null){const r=e._vei||(e._vei={}),s=r[t];if(o&&s)s.value=o;else{const[n,i]=function(e){let t;if(Do.test(e)){let n;for(t={};n=e.match(Do);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[e.slice(2).toLowerCase(),t]}(t);if(o){!function(e,t,n,o){e.addEventListener(t,n,o)}(e,n,r[t]=function(e,t){const n=e=>{(e.timeStamp||jo())>=n.attached-1&&Ze(function(e,t){if(x(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map((e=>t=>!t._stopped&&e(t)))}return t}(e,n.value),t,5,[e])};return n.value=e,n.attached=(()=>No||(Ao.then(Vo),No=jo()))(),n}(o,l),i)}else s&&(!function(e,t,n,o){e.removeEventListener(t,n,o)}(e,n,s,i),r[t]=void 0)}}const Do=/(?:Once|Passive|Capture)$/;const Bo=/^on[a-z]/;const Lo=["ctrl","shift","alt","meta"],Ho={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&0!==e.button,middle:e=>"button"in e&&1!==e.button,right:e=>"button"in e&&2!==e.button,exact:(e,t)=>Lo.some((n=>e[n+"Key"]&&!t.includes(n)))},Wo=(e,t)=>(n,...o)=>{for(let e=0;e<t.length;e++){const o=Ho[t[e]];if(o&&o(n,t))return}return e(n,...o)},Ko=v({patchProp:(e,t,o,l,r=!1,s,i,c,u)=>{switch(t){case"class":!function(e,t,n){if(null==t&&(t=""),n)e.setAttribute("class",t);else{const n=e._vtc;n&&(t=(t?[t,...n]:[...n]).join(" ")),e.className=t}}(e,l,r);break;case"style":!function(e,t,n){const o=e.style;if(n)if(S(n))t!==n&&(o.cssText=n);else{for(const e in n)zo(o,e,n[e]);if(t&&!S(t))for(const e in t)null==n[e]&&zo(o,e,"")}else e.removeAttribute("style")}(e,o,l);break;default:m(t)?g(t)||Uo(e,t,0,l,i):function(e,t,n,o){if(o)return"innerHTML"===t||!!(t in e&&Bo.test(t)&&k(n));if("spellcheck"===t||"draggable"===t)return!1;if("form"===t&&"string"==typeof n)return!1;if("list"===t&&"INPUT"===e.tagName)return!1;if(Bo.test(t)&&S(n))return!1;return t in e}(e,t,l,r)?function(e,t,n,o,l,r,s){if("innerHTML"===t||"textContent"===t)return o&&s(o,l,r),void(e[t]=null==n?"":n);if("value"!==t||"PROGRESS"===e.tagName)if(""===n&&"boolean"==typeof e[t])e[t]=!0;else if(null==n&&"string"==typeof e[t])e[t]="",e.removeAttribute(t);else try{e[t]=n}catch(e){}else{e._value=n;const t=null==n?"":n;e.value!==t&&(e.value=t)}}(e,t,l,s,i,c,u):("true-value"===t?e._trueValue=l:"false-value"===t&&(e._falseValue=l),function(e,t,o,l){if(l&&t.startsWith("xlink:"))null==o?e.removeAttributeNS(To,t.slice(6,t.length)):e.setAttributeNS(To,t,o);else{const l=n(t);null==o||l&&!1===o?e.removeAttribute(t):e.setAttribute(t,l?"":o)}}(e,t,l,r))}},forcePatchProp:(e,t)=>"value"===t},Ro);let Go;class qo{static post(e,t,n){var o=this;window.wcfmoptions.poster(e,t,(function(e){n.call(o,e)}),(function(){console.log("failure")}))}}const Jo={style:{position:"absolute",width:"0",height:"0"},width:"0",height:"0",version:"1.1",xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink"},Xo=Gn("defs",null,[Gn("symbol",{id:"icon-folder",viewBox:"0 0 309.267 309.267"},[Gn("g",null,[Gn("path",{style:{fill:"#D0994B"},d:"M260.944,43.491H125.64c0,0-18.324-28.994-28.994-28.994H48.323c-10.67,0-19.329,8.65-19.329,19.329\n \t\tv222.286c0,10.67,8.659,19.329,19.329,19.329h212.621c10.67,0,19.329-8.659,19.329-19.329V62.82\n \t\tC280.273,52.15,271.614,43.491,260.944,43.491z"}),Gn("path",{style:{fill:"#E4E7E7"},d:"M28.994,72.484h251.279v77.317H28.994V72.484z"}),Gn("path",{style:{fill:"#F4B459"},d:"M19.329,91.814h270.609c10.67,0,19.329,8.65,19.329,19.329l-19.329,164.298\n \t\tc0,10.67-8.659,19.329-19.329,19.329H38.658c-10.67,0-19.329-8.659-19.329-19.329L0,111.143C0,100.463,8.659,91.814,19.329,91.814z\n \t\t"})])]),Gn("symbol",{id:"icon-unfold",viewBox:"0 0 32 32"},[Gn("path",{d:"M28,14H18V4c0-1.104-0.896-2-2-2s-2,0.896-2,2v10H4c-1.104,0-2,0.896-2,2s0.896,2,2,2h10v10c0,1.104,0.896,2,2,2 s2-0.896,2-2V18h10c1.104,0,2-0.896,2-2S29.104,14,28,14z"})]),Gn("symbol",{id:"icon-fold",viewBox:"0 0 24 24"},[Gn("g",{fill:"none",stroke:"#000","stroke-width":"3","stroke-linecap":"round","stroke-linejoin":"round"},[Gn("line",{x1:"5",y1:"12",x2:"19",y2:"12"})])]),Gn("symbol",{id:"icon-file",viewBox:"0 0 56 56"},[Gn("g",null,[Gn("path",{style:{fill:"#E9E9E0"},d:"M36.985,0H7.963C7.155,0,6.5,0.655,6.5,1.926V55c0,0.345,0.655,1,1.463,1h40.074\n \t\tc0.808,0,1.463-0.655,1.463-1V12.978c0-0.696-0.093-0.92-0.257-1.085L37.607,0.257C37.442,0.093,37.218,0,36.985,0z"}),Gn("polygon",{style:{fill:"#D9D7CA"},points:"37.5,0.151 37.5,12 49.349,12 \t"}),Gn("circle",{style:{fill:"#F3D55B"},cx:"18.931",cy:"14.431",r:"4.569"}),Gn("polygon",{style:{fill:"#26B99A"},points:"6.5,39 17.5,39 49.5,39 49.5,28 39.5,18.5 29,30 23.517,24.517 \t"}),Gn("path",{style:{fill:"#14A085"},d:"M48.037,56H7.963C7.155,56,6.5,55.345,6.5,54.537V39h43v15.537C49.5,55.345,48.845,56,48.037,56z"}),Gn("g")])])],-1);const Yo={render:function(e,t){return Vn(),Dn("svg",Jo,[Xo])}};var Zo={name:"FileItem",emits:["foldUnfoldClick"],props:{item:Object},data:()=>({hover:!1}),methods:{getWCFM(){var e=this.$parent;for(window.n=e;e;)if("WCFM"==(e=e.$parent).$.type.name)return e;return e},getFullPath(){for(var e=this.$parent,t=[];null!==e&&null!==e.$parent;)e.item&&t.push(e.item.name),e=e.$parent;return t.reverse().join("/")},infoClick(){this.getWCFM().onInfoClick(this.getFullPath())},convertClick(){this.getWCFM().onConvertClick(this.getFullPath())}}};const Qo=Ut("data-v-1d130b20");At("data-v-1d130b20");const el={key:0,class:"icon-fold"},tl=Gn("use",{"xlink:href":"#icon-fold"},null,-1),nl={key:1,class:"icon-unfold"},ol=Gn("use",{"xlink:href":"#icon-unfold"},null,-1),ll=Gn("use",{"xlink:href":"#icon-folder"},null,-1),rl={key:2,class:"icon-file"},sl=Gn("use",{"xlink:href":"#icon-file"},null,-1);Vt();const il=Qo((function(e,t,n,o,l,r){return Vn(),Dn("div",{class:"fileitem",onMouseover:t[5]||(t[5]=e=>l.hover=!0),onMouseleave:t[6]||(t[6]=e=>l.hover=!1)},[Gn("p",null,[n.item.isDir?(Vn(),Dn("span",{key:0,class:"foldUnfold",onClick:t[1]||(t[1]=e=>this.$emit("foldUnfoldClick"))},[n.item.isOpen?(Vn(),Dn("svg",el,[tl])):Xn("",!0),n.item.isOpen?Xn("",!0):(Vn(),Dn("svg",nl,[ol]))])):Xn("",!0),n.item.isDir?(Vn(),Dn("svg",{key:1,class:"icon-folder",onClick:t[2]||(t[2]=e=>this.$emit("foldUnfoldClick"))},[ll])):Xn("",!0),n.item.isDir?Xn("",!0):(Vn(),Dn("svg",rl,[sl])),Jn(" "+c(n.item.name)+" ",1),l.hover?(Vn(),Dn("button",{key:3,onClick:t[3]||(t[3]=Wo((e=>r.convertClick()),["stop"]))},"Convert")):Xn("",!0),l.hover?(Vn(),Dn("button",{key:4,onClick:t[4]||(t[4]=Wo((e=>r.infoClick()),["stop"]))},"Info")):Xn("",!0)])],32)}));Zo.render=il,Zo.__scopeId="data-v-1d130b20";var cl={name:"FileTree",components:{FileItem:Zo},props:{item:Object},methods:{toggle(){this.item.isOpen=!this.item.isOpen}}};const ul={key:0,class:"tree"};cl.render=function(e,t,n,o,l,r){const s=Rn("FileItem"),i=Rn("FileTree");return Vn(),Dn($n,null,[Gn(s,{item:n.item,onDblclick:t[1]||(t[1]=e=>r.toggle()),onClick:t[2]||(t[2]=e=>r.toggle())},null,8,["item"]),void 0!==n.item.children&&n.item.isOpen?(Vn(),Dn("ul",ul,[(Vn(!0),Dn($n,null,ko(n.item.children,(e=>(Vn(),Dn("li",null,[Gn(i,{item:e},null,8,["item"])])))),256))])):Xn("",!0)],64)};var al={name:"InfoPane",components:{},props:{info:Object},methods:{}};const fl={key:0},pl=Gn("h2",null,"Original",-1),dl=Gn("br",null,null,-1),hl=Gn("br",null,null,-1),ml={key:1},gl=Gn("h2",null,"Converted",-1),vl=Gn("br",null,null,-1),yl=Gn("br",null,null,-1),_l={key:2},bl=Gn("h2",null,"Conversion log",-1);al.render=function(e,t,n,o,l,r){return Vn(),Dn($n,null,[n.info.original?(Vn(),Dn("div",fl,[pl,Jn(" Filename: "+c(n.info.original.name),1),dl,Jn(" Size: "+c(n.info.original.size),1),hl])):Xn("",!0),n.info.converted?(Vn(),Dn("div",ml,[gl,Jn(" Filename: "+c(n.info.converted.name),1),vl,Jn(" Size: "+c(n.info.converted.size),1),yl])):Xn("",!0),n.info.log?(Vn(),Dn("div",_l,[bl,Jn(" "+c(n.info.log),1)])):Xn("",!0)],64)};var xl={name:"multipane",props:{layout:{type:String,default:"vertical"}},data:()=>({isResizing:!1}),computed:{classnames(){return["multipane","layout-"+this.layout.slice(0,1),this.isResizing?"is-resizing":""]},cursor(){return this.isResizing?"vertical"==this.layout?"col-resize":"row-resize":""},userSelect(){return this.isResizing?"none":""}},methods:{onMouseDown({target:e,pageX:t,pageY:n}){if("string"==typeof e.className&&e.className.match("multipane-resizer")){let o=this,{$el:l,layout:r}=o,s=e.previousElementSibling,{offsetWidth:i,offsetHeight:c}=s,u=!!(s.style.width+"").match("%");const{addEventListener:a,removeEventListener:f}=window,p=(e,t=0)=>{if(t-=30,"vertical"==r){let n=l.clientWidth,o=e+t;return s.style.width=u?o/n*100+"%":o+"px"}if("horizontal"==r){let n=l.clientHeight,o=e+t;return s.style.height=u?o/n*100+"%":o+"px"}};o.isResizing=!0;let d=p();o.$emit("paneResizeStart",s,e,d);const h=function({pageX:l,pageY:u}){d="vertical"==r?p(i,l-t):p(c,u-n),o.$emit("paneResize",s,e,d)},m=function(){d=p("vertical"==r?s.clientWidth:s.clientHeight),o.isResizing=!1,f("mousemove",h),f("mouseup",m),o.$emit("paneResizeStop",s,e,d)};a("mousemove",h),a("mouseup",m)}}}};xl.render=function(e,t,n,o,l,r){return Vn(),Dn("div",{class:r.classnames,style:{cursor:r.cursor,userSelect:r.userSelect},onMousedown:t[1]||(t[1]=(...e)=>r.onMouseDown(...e))},[It(e.$slots,"default")],38)};const wl={class:"multipane-resizer"},Cl=Gn("div",null,null,-1);const kl={render:function(e,t){return Vn(),Dn("div",wl,[Cl,It(e.$slots,"default")])}};var Sl={name:"WCFM",components:{SVGs:Yo,FileTree:cl,InfoPane:al,Multipane:xl,MultipaneResizer:kl},methods:{onConvertClick(e){alert(e)},onInfoClick(e){var t=this;qo.post("info",{path:e},(function(e){t.selectedInfo=e}))}},mounted(){var e=this;qo.post("get-tree",{folder:""},(function(t){e.item=t}))},data:()=>({selectedItem:null,item:{},selectedInfo:{}})};const Fl={class:"pane",style:{width:"48%"}},Ol={class:"pane",style:{flexGrow:1}};Sl.render=function(e,t,n,o,l,r){const s=Rn("SVGs"),i=Rn("FileTree"),c=Rn("multipane-resizer"),u=Rn("InfoPane"),a=Rn("multipane");return Vn(),Dn($n,null,[Gn(s),Gn(a,{class:"mainpanel",layout:"vertical"},{default:Tt((()=>[Gn("div",Fl,[Gn(i,{item:l.item},null,8,["item"])]),Gn(c),Gn("div",Ol,[Gn(u,{info:l.selectedInfo},null,8,["info"])])])),_:1})],64)};const El=((...e)=>{const t=(Go||(Go=Fn(Ko))).createApp(...e),{mount:n}=t;return t.mount=e=>{const o=function(e){if(S(e)){return document.querySelector(e)}return e}(e);if(!o)return;const l=t._component;k(l)||l.render||l.template||(l.template=o.innerHTML),o.innerHTML="";const r=n(o);return o.removeAttribute("v-cloak"),o.setAttribute("data-v-app",""),r},t})(Sl);window.wcfmoptions||(window.wcfmoptions={},window.wcfmoptions.poster=function(e,t,n,o){switch(e){case"get-tree":var l={name:"root",isDir:!0,isOpen:!0,children:[{name:"folder",isDir:!0,children:[{name:"file",isDir:!1},{name:"subfolder",isDir:!0,children:[{name:"file",isDir:!1}]},{name:"file",isDir:!1}]},{name:"file",isDir:!1},{name:"file2",isDir:!1}]};break;case"info":l={original:{name:"file.png",size:100,url:""},converted:{name:"file.png.webp",size:70,url:""},log:"blah blah blah"};break;default:l="ok"}n(l)}),El.mount("#webpconvert-filemanager");
1
+ function e(e,t){const n=Object.create(null),o=e.split(",");for(let e=0;e<o.length;e++)n[o[e]]=!0;return t?e=>!!n[e.toLowerCase()]:e=>!!n[e]}const t=e("Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl"),n=e("itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly");function o(e){if(k(e)){const t={};for(let n=0;n<e.length;n++){const l=e[n],s=o(E(l)?i(l):l);if(s)for(const e in s)t[e]=s[e]}return t}if(F(e))return e}const l=/;(?![^(]*\))/g,s=/:(.+)/;function i(e){const t={};return e.split(l).forEach((e=>{if(e){const n=e.split(s);n.length>1&&(t[n[0].trim()]=n[1].trim())}})),t}function r(e){let t="";if(E(e))t=e;else if(k(e))for(let n=0;n<e.length;n++)t+=r(e[n])+" ";else if(F(e))for(const n in e)e[n]&&(t+=n+" ");return t.trim()}function c(e,t){if(e===t)return!0;let n=O(e),o=O(t);if(n||o)return!(!n||!o)&&e.getTime()===t.getTime();if(n=k(e),o=k(t),n||o)return!(!n||!o)&&function(e,t){if(e.length!==t.length)return!1;let n=!0;for(let o=0;n&&o<e.length;o++)n=c(e[o],t[o]);return n}(e,t);if(n=F(e),o=F(t),n||o){if(!n||!o)return!1;if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const n in e){const o=e.hasOwnProperty(n),l=t.hasOwnProperty(n);if(o&&!l||!o&&l||!c(e[n],t[n]))return!1}}return String(e)===String(t)}function a(e,t){return e.findIndex((e=>c(e,t)))}const u=e=>null==e?"":F(e)?JSON.stringify(e,d,2):String(e),d=(e,t)=>S(t)?{[`Map(${t.size})`]:[...t.entries()].reduce(((e,[t,n])=>(e[t+" =>"]=n,e)),{})}:C(t)?{[`Set(${t.size})`]:[...t.values()]}:!F(t)||k(t)||P(t)?t:String(t),p={},f=[],h=()=>{},m=()=>!1,v=/^on[^a-z]/,g=e=>v.test(e),y=e=>e.startsWith("onUpdate:"),b=Object.assign,_=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},w=Object.prototype.hasOwnProperty,x=(e,t)=>w.call(e,t),k=Array.isArray,S=e=>"[object Map]"===q(e),C=e=>"[object Set]"===q(e),O=e=>e instanceof Date,V=e=>"function"==typeof e,E=e=>"string"==typeof e,L=e=>"symbol"==typeof e,F=e=>null!==e&&"object"==typeof e,M=e=>F(e)&&V(e.then)&&V(e.catch),R=Object.prototype.toString,q=e=>R.call(e),P=e=>"[object Object]"===q(e),U=e=>E(e)&&"NaN"!==e&&"-"!==e[0]&&""+parseInt(e,10)===e,I=e(",key,ref,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),z=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},D=/-(\w)/g,$=z((e=>e.replace(D,((e,t)=>t?t.toUpperCase():"")))),A=/\B([A-Z])/g,T=z((e=>e.replace(A,"-$1").toLowerCase())),j=z((e=>e.charAt(0).toUpperCase()+e.slice(1))),N=z((e=>e?"on"+j(e):"")),B=(e,t)=>e!==t&&(e==e||t==t),H=(e,t)=>{for(let n=0;n<e.length;n++)e[n](t)},W=(e,t,n)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},K=e=>{const t=parseFloat(e);return isNaN(t)?e:t},Q=new WeakMap,G=[];let Y;const J=Symbol(""),X=Symbol("");function Z(e,t=p){(function(e){return e&&!0===e._isEffect})(e)&&(e=e.raw);const n=function(e,t){const n=function(){if(!n.active)return t.scheduler?void 0:e();if(!G.includes(n)){ne(n);try{return le.push(oe),oe=!0,G.push(n),Y=n,e()}finally{G.pop(),ie(),Y=G[G.length-1]}}};return n.id=te++,n.allowRecurse=!!t.allowRecurse,n._isEffect=!0,n.active=!0,n.raw=e,n.deps=[],n.options=t,n}(e,t);return t.lazy||n(),n}function ee(e){e.active&&(ne(e),e.options.onStop&&e.options.onStop(),e.active=!1)}let te=0;function ne(e){const{deps:t}=e;if(t.length){for(let n=0;n<t.length;n++)t[n].delete(e);t.length=0}}let oe=!0;const le=[];function se(){le.push(oe),oe=!1}function ie(){const e=le.pop();oe=void 0===e||e}function re(e,t,n){if(!oe||void 0===Y)return;let o=Q.get(e);o||Q.set(e,o=new Map);let l=o.get(n);l||o.set(n,l=new Set),l.has(Y)||(l.add(Y),Y.deps.push(l))}function ce(e,t,n,o,l,s){const i=Q.get(e);if(!i)return;const r=new Set,c=e=>{e&&e.forEach((e=>{(e!==Y||e.allowRecurse)&&r.add(e)}))};if("clear"===t)i.forEach(c);else if("length"===n&&k(e))i.forEach(((e,t)=>{("length"===t||t>=o)&&c(e)}));else switch(void 0!==n&&c(i.get(n)),t){case"add":k(e)?U(n)&&c(i.get("length")):(c(i.get(J)),S(e)&&c(i.get(X)));break;case"delete":k(e)||(c(i.get(J)),S(e)&&c(i.get(X)));break;case"set":S(e)&&c(i.get(J))}r.forEach((e=>{e.options.scheduler?e.options.scheduler(e):e()}))}const ae=new Set(Object.getOwnPropertyNames(Symbol).map((e=>Symbol[e])).filter(L)),ue=me(),de=me(!1,!0),pe=me(!0),fe=me(!0,!0),he={};function me(e=!1,t=!1){return function(n,o,l){if("__v_isReactive"===o)return!e;if("__v_isReadonly"===o)return e;if("__v_raw"===o&&l===(e?je:Te).get(n))return n;const s=k(n);if(s&&x(he,o))return Reflect.get(he,o,l);const i=Reflect.get(n,o,l);if(L(o)?ae.has(o):"__proto__"===o||"__v_isRef"===o)return i;if(e||re(n,0,o),t)return i;if(Je(i)){return!s||!U(o)?i.value:i}return F(i)?e?He(i):Be(i):i}}["includes","indexOf","lastIndexOf"].forEach((e=>{const t=Array.prototype[e];he[e]=function(...e){const n=Ye(this);for(let e=0,t=this.length;e<t;e++)re(n,0,e+"");const o=t.apply(n,e);return-1===o||!1===o?t.apply(n,e.map(Ye)):o}})),["push","pop","shift","unshift","splice"].forEach((e=>{const t=Array.prototype[e];he[e]=function(...e){se();const n=t.apply(this,e);return ie(),n}}));function ve(e=!1){return function(t,n,o,l){const s=t[n];if(!e&&(o=Ye(o),!k(t)&&Je(s)&&!Je(o)))return s.value=o,!0;const i=k(t)&&U(n)?Number(n)<t.length:x(t,n),r=Reflect.set(t,n,o,l);return t===Ye(l)&&(i?B(o,s)&&ce(t,"set",n,o):ce(t,"add",n,o)),r}}const ge={get:ue,set:ve(),deleteProperty:function(e,t){const n=x(e,t),o=(e[t],Reflect.deleteProperty(e,t));return o&&n&&ce(e,"delete",t,void 0),o},has:function(e,t){const n=Reflect.has(e,t);return L(t)&&ae.has(t)||re(e,0,t),n},ownKeys:function(e){return re(e,0,k(e)?"length":J),Reflect.ownKeys(e)}},ye={get:pe,set:(e,t)=>!0,deleteProperty:(e,t)=>!0},be=b({},ge,{get:de,set:ve(!0)}),_e=(b({},ye,{get:fe}),e=>F(e)?Be(e):e),we=e=>F(e)?He(e):e,xe=e=>e,ke=e=>Reflect.getPrototypeOf(e);function Se(e,t,n=!1,o=!1){const l=Ye(e=e.__v_raw),s=Ye(t);t!==s&&!n&&re(l,0,t),!n&&re(l,0,s);const{has:i}=ke(l),r=n?we:o?xe:_e;return i.call(l,t)?r(e.get(t)):i.call(l,s)?r(e.get(s)):void 0}function Ce(e,t=!1){const n=this.__v_raw,o=Ye(n),l=Ye(e);return e!==l&&!t&&re(o,0,e),!t&&re(o,0,l),e===l?n.has(e):n.has(e)||n.has(l)}function Oe(e,t=!1){return e=e.__v_raw,!t&&re(Ye(e),0,J),Reflect.get(e,"size",e)}function Ve(e){e=Ye(e);const t=Ye(this),n=ke(t).has.call(t,e),o=t.add(e);return n||ce(t,"add",e,e),o}function Ee(e,t){t=Ye(t);const n=Ye(this),{has:o,get:l}=ke(n);let s=o.call(n,e);s||(e=Ye(e),s=o.call(n,e));const i=l.call(n,e),r=n.set(e,t);return s?B(t,i)&&ce(n,"set",e,t):ce(n,"add",e,t),r}function Le(e){const t=Ye(this),{has:n,get:o}=ke(t);let l=n.call(t,e);l||(e=Ye(e),l=n.call(t,e));o&&o.call(t,e);const s=t.delete(e);return l&&ce(t,"delete",e,void 0),s}function Fe(){const e=Ye(this),t=0!==e.size,n=e.clear();return t&&ce(e,"clear",void 0,void 0),n}function Me(e,t){return function(n,o){const l=this,s=l.__v_raw,i=Ye(s),r=e?we:t?xe:_e;return!e&&re(i,0,J),s.forEach(((e,t)=>n.call(o,r(e),r(t),l)))}}function Re(e,t,n){return function(...o){const l=this.__v_raw,s=Ye(l),i=S(s),r="entries"===e||e===Symbol.iterator&&i,c="keys"===e&&i,a=l[e](...o),u=t?we:n?xe:_e;return!t&&re(s,0,c?X:J),{next(){const{value:e,done:t}=a.next();return t?{value:e,done:t}:{value:r?[u(e[0]),u(e[1])]:u(e),done:t}},[Symbol.iterator](){return this}}}}function qe(e){return function(...t){return"delete"!==e&&this}}const Pe={get(e){return Se(this,e)},get size(){return Oe(this)},has:Ce,add:Ve,set:Ee,delete:Le,clear:Fe,forEach:Me(!1,!1)},Ue={get(e){return Se(this,e,!1,!0)},get size(){return Oe(this)},has:Ce,add:Ve,set:Ee,delete:Le,clear:Fe,forEach:Me(!1,!0)},Ie={get(e){return Se(this,e,!0)},get size(){return Oe(this,!0)},has(e){return Ce.call(this,e,!0)},add:qe("add"),set:qe("set"),delete:qe("delete"),clear:qe("clear"),forEach:Me(!0,!1)};function ze(e,t){const n=t?Ue:e?Ie:Pe;return(t,o,l)=>"__v_isReactive"===o?!e:"__v_isReadonly"===o?e:"__v_raw"===o?t:Reflect.get(x(n,o)&&o in t?n:t,o,l)}["keys","values","entries",Symbol.iterator].forEach((e=>{Pe[e]=Re(e,!1,!1),Ie[e]=Re(e,!0,!1),Ue[e]=Re(e,!1,!0)}));const De={get:ze(!1,!1)},$e={get:ze(!1,!0)},Ae={get:ze(!0,!1)},Te=new WeakMap,je=new WeakMap;function Ne(e){return e.__v_skip||!Object.isExtensible(e)?0:function(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}((e=>q(e).slice(8,-1))(e))}function Be(e){return e&&e.__v_isReadonly?e:We(e,!1,ge,De)}function He(e){return We(e,!0,ye,Ae)}function We(e,t,n,o){if(!F(e))return e;if(e.__v_raw&&(!t||!e.__v_isReactive))return e;const l=t?je:Te,s=l.get(e);if(s)return s;const i=Ne(e);if(0===i)return e;const r=new Proxy(e,2===i?o:n);return l.set(e,r),r}function Ke(e){return Qe(e)?Ke(e.__v_raw):!(!e||!e.__v_isReactive)}function Qe(e){return!(!e||!e.__v_isReadonly)}function Ge(e){return Ke(e)||Qe(e)}function Ye(e){return e&&Ye(e.__v_raw)||e}function Je(e){return Boolean(e&&!0===e.__v_isRef)}const Xe={get:(e,t,n)=>{return Je(o=Reflect.get(e,t,n))?o.value:o;var o},set:(e,t,n,o)=>{const l=e[t];return Je(l)&&!Je(n)?(l.value=n,!0):Reflect.set(e,t,n,o)}};class Ze{constructor(e,t,n){this._setter=t,this._dirty=!0,this.__v_isRef=!0,this.effect=Z(e,{lazy:!0,scheduler:()=>{this._dirty||(this._dirty=!0,ce(Ye(this),"set","value"))}}),this.__v_isReadonly=n}get value(){return this._dirty&&(this._value=this.effect(),this._dirty=!1),re(Ye(this),0,"value"),this._value}set value(e){this._setter(e)}}function et(e,t,n,o){let l;try{l=o?e(...o):e()}catch(e){nt(e,t,n)}return l}function tt(e,t,n,o){if(V(e)){const l=et(e,t,n,o);return l&&M(l)&&l.catch((e=>{nt(e,t,n)})),l}const l=[];for(let s=0;s<e.length;s++)l.push(tt(e[s],t,n,o));return l}function nt(e,t,n,o=!0){t&&t.vnode;if(t){let o=t.parent;const l=t.proxy,s=n;for(;o;){const t=o.ec;if(t)for(let n=0;n<t.length;n++)if(!1===t[n](e,l,s))return;o=o.parent}const i=t.appContext.config.errorHandler;if(i)return void et(i,null,10,[e,l,s])}!function(e,t,n,o=!0){console.error(e)}(e,0,0,o)}let ot=!1,lt=!1;const st=[];let it=0;const rt=[];let ct=null,at=0;const ut=[];let dt=null,pt=0;const ft=Promise.resolve();let ht=null,mt=null;function vt(e){const t=ht||ft;return e?t.then(this?e.bind(this):e):t}function gt(e){st.length&&st.includes(e,ot&&e.allowRecurse?it+1:it)||e===mt||(st.push(e),yt())}function yt(){ot||lt||(lt=!0,ht=ft.then(kt))}function bt(e,t,n,o){k(e)?n.push(...e):t&&t.includes(e,e.allowRecurse?o+1:o)||n.push(e),yt()}function _t(e,t=null){if(rt.length){for(mt=t,ct=[...new Set(rt)],rt.length=0,at=0;at<ct.length;at++)ct[at]();ct=null,at=0,mt=null,_t(e,t)}}function wt(e){if(ut.length){const e=[...new Set(ut)];if(ut.length=0,dt)return void dt.push(...e);for(dt=e,dt.sort(((e,t)=>xt(e)-xt(t))),pt=0;pt<dt.length;pt++)dt[pt]();dt=null,pt=0}}const xt=e=>null==e.id?1/0:e.id;function kt(e){lt=!1,ot=!0,_t(e),st.sort(((e,t)=>xt(e)-xt(t)));try{for(it=0;it<st.length;it++){const e=st[it];e&&et(e,null,14)}}finally{it=0,st.length=0,wt(),ot=!1,ht=null,(st.length||ut.length)&&kt(e)}}function St(e,t,...n){const o=e.vnode.props||p;let l=n;const s=t.startsWith("update:"),i=s&&t.slice(7);if(i&&i in o){const e=("modelValue"===i?"model":i)+"Modifiers",{number:t,trim:s}=o[e]||p;s?l=n.map((e=>e.trim())):t&&(l=n.map(K))}let r=N($(t)),c=o[r];!c&&s&&(r=N(T(t)),c=o[r]),c&&tt(c,e,6,l);const a=o[r+"Once"];if(a){if(e.emitted){if(e.emitted[r])return}else(e.emitted={})[r]=!0;tt(a,e,6,l)}}function Ct(e,t,n=!1){if(!t.deopt&&void 0!==e.__emits)return e.__emits;const o=e.emits;let l={},s=!1;if(!V(e)){const o=e=>{s=!0,b(l,Ct(e,t,!0))};!n&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}return o||s?(k(o)?o.forEach((e=>l[e]=null)):b(l,o),e.__emits=l):e.__emits=null}function Ot(e,t){return!(!e||!g(t))&&(t=t.replace(/Once$/,""),x(e,t[2].toLowerCase()+t.slice(3))||x(e,t.slice(2)))}let Vt=null;function Et(e){Vt=e}function Lt(e){const{type:t,vnode:n,proxy:o,withProxy:l,props:s,propsOptions:[i],slots:r,attrs:c,emit:a,render:u,renderCache:d,data:p,setupState:f,ctx:h}=e;let m;Vt=e;try{let e;if(4&n.shapeFlag){const t=l||o;m=to(u.call(t,t,d,s,f,p,h)),e=c}else{const n=t;0,m=to(n.length>1?n(s,{attrs:c,slots:r,emit:a}):n(s,null)),e=t.props?c:Mt(c)}let v=m;if(!1!==t.inheritAttrs&&e){const t=Object.keys(e),{shapeFlag:n}=v;t.length&&(1&n||6&n)&&(i&&t.some(y)&&(e=Rt(e,i)),v=Xn(v,e))}n.dirs&&(v.dirs=v.dirs?v.dirs.concat(n.dirs):n.dirs),n.transition&&(v.transition=n.transition),m=v}catch(t){nt(t,e,1),m=Jn($n)}return Vt=null,m}function Ft(e){const t=e.filter((e=>!(Wn(e)&&e.type===$n&&"v-if"!==e.children)));return 1===t.length&&Wn(t[0])?t[0]:null}const Mt=e=>{let t;for(const n in e)("class"===n||"style"===n||g(n))&&((t||(t={}))[n]=e[n]);return t},Rt=(e,t)=>{const n={};for(const o in e)y(o)&&o.slice(9)in t||(n[o]=e[o]);return n};function qt(e,t,n){const o=Object.keys(t);if(o.length!==Object.keys(e).length)return!0;for(let l=0;l<o.length;l++){const s=o[l];if(t[s]!==e[s]&&!Ot(n,s))return!0}return!1}function Pt(e){if(V(e)&&(e=e()),k(e)){e=Ft(e)}return to(e)}let Ut=0;const It=e=>Ut+=e;function zt(e,t,n={},o){let l=e[t];Ut++;const s=(Nn(),Hn(zn,{key:n.key},l?l(n):o?o():[],1===e._?64:-2));return Ut--,s}function Dt(e,t=Vt){if(!t)return e;const n=(...n)=>{Ut||Nn(!0);const o=Vt;Et(t);const l=e(...n);return Et(o),Ut||Bn(),l};return n._c=!0,n}let $t=null;const At=[];function Tt(e){At.push($t=e)}function jt(){At.pop(),$t=At[At.length-1]||null}function Nt(e){return t=>Dt((function(){Tt(e);const n=t.apply(this,arguments);return jt(),n}))}function Bt(e,t,n,o=!1){const l={},s={};W(s,Qn,1),Ht(e,t,l,s),n?e.props=o?l:We(l,!1,be,$e):e.type.props?e.props=l:e.props=s,e.attrs=s}function Ht(e,t,n,o){const[l,s]=e.propsOptions;if(t)for(const s in t){const i=t[s];if(I(s))continue;let r;l&&x(l,r=$(s))?n[r]=i:Ot(e.emitsOptions,s)||(o[s]=i)}if(s){const t=Ye(n);for(let o=0;o<s.length;o++){const i=s[o];n[i]=Wt(l,t,i,t[i],e)}}}function Wt(e,t,n,o,l){const s=e[n];if(null!=s){const e=x(s,"default");if(e&&void 0===o){const e=s.default;s.type!==Function&&V(e)?(xo(l),o=e(t),xo(null)):o=e}s[0]&&(x(t,n)||e?!s[1]||""!==o&&o!==T(n)||(o=!0):o=!1)}return o}function Kt(e,t,n=!1){if(!t.deopt&&e.__props)return e.__props;const o=e.props,l={},s=[];let i=!1;if(!V(e)){const o=e=>{i=!0;const[n,o]=Kt(e,t,!0);b(l,n),o&&s.push(...o)};!n&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}if(!o&&!i)return e.__props=f;if(k(o))for(let e=0;e<o.length;e++){const t=$(o[e]);Qt(t)&&(l[t]=p)}else if(o)for(const e in o){const t=$(e);if(Qt(t)){const n=o[e],i=l[t]=k(n)||V(n)?{type:n}:n;if(i){const e=Jt(Boolean,i.type),n=Jt(String,i.type);i[0]=e>-1,i[1]=n<0||e<n,(e>-1||x(i,"default"))&&s.push(t)}}}return e.__props=[l,s]}function Qt(e){return"$"!==e[0]}function Gt(e){const t=e&&e.toString().match(/^\s*function (\w+)/);return t?t[1]:""}function Yt(e,t){return Gt(e)===Gt(t)}function Jt(e,t){if(k(t)){for(let n=0,o=t.length;n<o;n++)if(Yt(t[n],e))return n}else if(V(t))return Yt(t,e)?0:-1;return-1}function Xt(e,t,n=wo,o=!1){if(n){const l=n[e]||(n[e]=[]),s=t.__weh||(t.__weh=(...o)=>{if(n.isUnmounted)return;se(),xo(n);const l=tt(t,n,e,o);return xo(null),ie(),l});return o?l.unshift(s):l.push(s),s}}const Zt=e=>(t,n=wo)=>!ko&&Xt(e,t,n),en=Zt("bm"),tn=Zt("m"),nn=Zt("bu"),on=Zt("u"),ln=Zt("bum"),sn=Zt("um"),rn=Zt("rtg"),cn=Zt("rtc"),an={};function un(e,t,n){return dn(e,t,n)}function dn(e,t,{immediate:n,deep:o,flush:l,onTrack:s,onTrigger:i}=p,r=wo){let c,a,u=!1;if(Je(e)?(c=()=>e.value,u=!!e._shallow):Ke(e)?(c=()=>e,o=!0):c=k(e)?()=>e.map((e=>Je(e)?e.value:Ke(e)?fn(e):V(e)?et(e,r,2):void 0)):V(e)?t?()=>et(e,r,2):()=>{if(!r||!r.isUnmounted)return a&&a(),et(e,r,3,[d])}:h,t&&o){const e=c;c=()=>fn(e())}const d=e=>{a=g.options.onStop=()=>{et(e,r,4)}};let f=k(e)?[]:an;const m=()=>{if(g.active)if(t){const e=g();(o||u||B(e,f))&&(a&&a(),tt(t,r,3,[e,f===an?void 0:f,d]),f=e)}else g()};let v;m.allowRecurse=!!t,v="sync"===l?m:"post"===l?()=>En(m,r&&r.suspense):()=>{!r||r.isMounted?function(e){bt(e,ct,rt,at)}(m):m()};const g=Z(c,{lazy:!0,onTrack:s,onTrigger:i,scheduler:v});return Oo(g),t?n?m():f=g():"post"===l?En(g,r&&r.suspense):g(),()=>{ee(g),r&&_(r.effects,g)}}function pn(e,t,n){const o=this.proxy;return dn(E(e)?()=>o[e]:e.bind(o),t.bind(o),n,this)}function fn(e,t=new Set){if(!F(e)||t.has(e))return e;if(t.add(e),Je(e))fn(e.value,t);else if(k(e))for(let n=0;n<e.length;n++)fn(e[n],t);else if(C(e)||S(e))e.forEach((e=>{fn(e,t)}));else for(const n in e)fn(e[n],t);return e}const hn=e=>e.type.__isKeepAlive;function mn(e,t,n=wo){const o=e.__wdc||(e.__wdc=()=>{let t=n;for(;t;){if(t.isDeactivated)return;t=t.parent}e()});if(Xt(t,o,n),n){let e=n.parent;for(;e&&e.parent;)hn(e.parent.vnode)&&vn(o,t,n,e),e=e.parent}}function vn(e,t,n,o){const l=Xt(t,e,o,!0);sn((()=>{_(o[t],l)}),n)}const gn=e=>"_"===e[0]||"$stable"===e,yn=e=>k(e)?e.map(to):[to(e)],bn=(e,t,n)=>Dt((e=>yn(t(e))),n),_n=(e,t)=>{const n=e._ctx;for(const o in e){if(gn(o))continue;const l=e[o];if(V(l))t[o]=bn(0,l,n);else if(null!=l){const e=yn(l);t[o]=()=>e}}},wn=(e,t)=>{const n=yn(t);e.slots.default=()=>n};function xn(e,t){if(null===Vt)return e;const n=Vt.proxy,o=e.dirs||(e.dirs=[]);for(let e=0;e<t.length;e++){let[l,s,i,r=p]=t[e];V(l)&&(l={mounted:l,updated:l}),o.push({dir:l,instance:n,value:s,oldValue:void 0,arg:i,modifiers:r})}return e}function kn(e,t,n,o){const l=e.dirs,s=t&&t.dirs;for(let i=0;i<l.length;i++){const r=l[i];s&&(r.oldValue=s[i].value);const c=r.dir[o];c&&tt(c,n,8,[e.el,r,e,t])}}function Sn(){return{app:null,config:{isNativeTag:m,performance:!1,globalProperties:{},optionMergeStrategies:{},isCustomElement:m,errorHandler:void 0,warnHandler:void 0},mixins:[],components:{},directives:{},provides:Object.create(null)}}let Cn=0;function On(e,t){return function(n,o=null){null==o||F(o)||(o=null);const l=Sn(),s=new Set;let i=!1;const r=l.app={_uid:Cn++,_component:n,_props:o,_container:null,_context:l,version:Lo,get config(){return l.config},set config(e){},use:(e,...t)=>(s.has(e)||(e&&V(e.install)?(s.add(e),e.install(r,...t)):V(e)&&(s.add(e),e(r,...t))),r),mixin:e=>(l.mixins.includes(e)||(l.mixins.push(e),(e.props||e.emits)&&(l.deopt=!0)),r),component:(e,t)=>t?(l.components[e]=t,r):l.components[e],directive:(e,t)=>t?(l.directives[e]=t,r):l.directives[e],mount(s,c){if(!i){const a=Jn(n,o);return a.appContext=l,c&&t?t(a,s):e(a,s),i=!0,r._container=s,s.__vue_app__=r,a.component.proxy}},unmount(){i&&e(null,r._container)},provide:(e,t)=>(l.provides[e]=t,r)};return r}}const Vn={scheduler:gt,allowRecurse:!0},En=function(e,t){t&&t.pendingBranch?k(e)?t.effects.push(...e):t.effects.push(e):bt(e,dt,ut,pt)},Ln=(e,t,n,o,l)=>{if(k(e))return void e.forEach(((e,s)=>Ln(e,t&&(k(t)?t[s]:t),n,o,l)));let s;s=l?4&l.shapeFlag?l.component.proxy:l.el:null;const{i:i,r:r}=e,c=t&&t.r,a=i.refs===p?i.refs={}:i.refs,u=i.setupState;if(null!=c&&c!==r&&(E(c)?(a[c]=null,x(u,c)&&(u[c]=null)):Je(c)&&(c.value=null)),E(r)){const e=()=>{a[r]=s,x(u,r)&&(u[r]=s)};s?(e.id=-1,En(e,o)):e()}else if(Je(r)){const e=()=>{r.value=s};s?(e.id=-1,En(e,o)):e()}else V(r)&&et(r,n,12,[s,a])};function Fn(e){return function(e,t){const{insert:n,remove:o,patchProp:l,forcePatchProp:s,createElement:i,createText:r,createComment:c,setText:a,setElementText:u,parentNode:d,nextSibling:m,setScopeId:v=h,cloneNode:g,insertStaticContent:y}=e,_=(e,t,n,o=null,l=null,s=null,i=!1,r=!1)=>{e&&!Kn(e,t)&&(o=te(e),Q(e,l,s,!0),e=null),-2===t.patchFlag&&(r=!1,t.dynamicChildren=null);const{type:c,ref:a,shapeFlag:u}=t;switch(c){case Dn:w(e,t,n,o);break;case $n:k(e,t,n,o);break;case An:null==e&&S(t,n,o,i);break;case zn:q(e,t,n,o,l,s,i,r);break;default:1&u?C(e,t,n,o,l,s,i,r):6&u?P(e,t,n,o,l,s,i,r):(64&u||128&u)&&c.process(e,t,n,o,l,s,i,r,oe)}null!=a&&l&&Ln(a,e&&e.ref,l,s,t)},w=(e,t,o,l)=>{if(null==e)n(t.el=r(t.children),o,l);else{const n=t.el=e.el;t.children!==e.children&&a(n,t.children)}},k=(e,t,o,l)=>{null==e?n(t.el=c(t.children||""),o,l):t.el=e.el},S=(e,t,n,o)=>{[e.el,e.anchor]=y(e.children,t,n,o)},C=(e,t,n,o,l,s,i,r)=>{i=i||"svg"===t.type,null==e?O(t,n,o,l,s,i,r):L(e,t,l,s,i,r)},O=(e,t,o,s,r,c,a)=>{let d,p;const{type:f,props:h,shapeFlag:m,transition:v,scopeId:y,patchFlag:b,dirs:_}=e;if(e.el&&void 0!==g&&-1===b)d=e.el=g(e.el);else{if(d=e.el=i(e.type,c,h&&h.is),8&m?u(d,e.children):16&m&&E(e.children,d,null,s,r,c&&"foreignObject"!==f,a||!!e.dynamicChildren),_&&kn(e,null,s,"created"),h){for(const t in h)I(t)||l(d,t,null,h[t],c,e.children,s,r,X);(p=h.onVnodeBeforeMount)&&Mn(p,s,e)}V(d,y,e,s)}_&&kn(e,null,s,"beforeMount");const w=(!r||r&&!r.pendingBranch)&&v&&!v.persisted;w&&v.beforeEnter(d),n(d,t,o),((p=h&&h.onVnodeMounted)||w||_)&&En((()=>{p&&Mn(p,s,e),w&&v.enter(d),_&&kn(e,null,s,"mounted")}),r)},V=(e,t,n,o)=>{if(t&&v(e,t),o){const l=o.type.__scopeId;l&&l!==t&&v(e,l+"-s"),n===o.subTree&&V(e,o.vnode.scopeId,o.vnode,o.parent)}},E=(e,t,n,o,l,s,i,r=0)=>{for(let c=r;c<e.length;c++){const r=e[c]=i?no(e[c]):to(e[c]);_(null,r,t,n,o,l,s,i)}},L=(e,t,n,o,i,r)=>{const c=t.el=e.el;let{patchFlag:a,dynamicChildren:d,dirs:f}=t;a|=16&e.patchFlag;const h=e.props||p,m=t.props||p;let v;if((v=m.onVnodeBeforeUpdate)&&Mn(v,n,t,e),f&&kn(t,e,n,"beforeUpdate"),a>0){if(16&a)R(c,t,h,m,n,o,i);else if(2&a&&h.class!==m.class&&l(c,"class",null,m.class,i),4&a&&l(c,"style",h.style,m.style,i),8&a){const r=t.dynamicProps;for(let t=0;t<r.length;t++){const a=r[t],u=h[a],d=m[a];(d!==u||s&&s(c,a))&&l(c,a,u,d,i,e.children,n,o,X)}}1&a&&e.children!==t.children&&u(c,t.children)}else r||null!=d||R(c,t,h,m,n,o,i);const g=i&&"foreignObject"!==t.type;d?F(e.dynamicChildren,d,c,n,o,g):r||j(e,t,c,null,n,o,g),((v=m.onVnodeUpdated)||f)&&En((()=>{v&&Mn(v,n,t,e),f&&kn(t,e,n,"updated")}),o)},F=(e,t,n,o,l,s)=>{for(let i=0;i<t.length;i++){const r=e[i],c=t[i],a=r.type===zn||!Kn(r,c)||6&r.shapeFlag||64&r.shapeFlag?d(r.el):n;_(r,c,a,null,o,l,s,!0)}},R=(e,t,n,o,i,r,c)=>{if(n!==o){for(const a in o){if(I(a))continue;const u=o[a],d=n[a];(u!==d||s&&s(e,a))&&l(e,a,d,u,c,t.children,i,r,X)}if(n!==p)for(const s in n)I(s)||s in o||l(e,s,n[s],null,c,t.children,i,r,X)}},q=(e,t,o,l,s,i,c,a)=>{const u=t.el=e?e.el:r(""),d=t.anchor=e?e.anchor:r("");let{patchFlag:p,dynamicChildren:f}=t;p>0&&(a=!0),null==e?(n(u,o,l),n(d,o,l),E(t.children,o,d,s,i,c,a)):p>0&&64&p&&f?(F(e.dynamicChildren,f,o,s,i,c),(null!=t.key||s&&t===s.subTree)&&Rn(e,t,!0)):j(e,t,o,d,s,i,c,a)},P=(e,t,n,o,l,s,i,r)=>{null==e?512&t.shapeFlag?l.ctx.activate(t,n,o,i,r):U(t,n,o,l,s,i,r):z(e,t,r)},U=(e,t,n,o,l,s,i)=>{const r=e.component=function(e,t,n){const o=e.type,l=(t?t.appContext:e.appContext)||bo,s={uid:_o++,vnode:e,type:o,parent:t,appContext:l,root:null,next:null,subTree:null,update:null,render:null,proxy:null,withProxy:null,effects:null,provides:t?t.provides:Object.create(l.provides),accessCache:null,renderCache:[],components:null,directives:null,propsOptions:Kt(o,l),emitsOptions:Ct(o,l),emit:null,emitted:null,ctx:p,data:p,props:p,attrs:p,slots:p,refs:p,setupState:p,setupContext:null,suspense:n,suspenseId:n?n.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null};return s.ctx={_:s},s.root=t?t.root:s,s.emit=St.bind(null,s),s}(e,o,l);if(hn(e)&&(r.ctx.renderer=oe),function(e,t=!1){ko=t;const{props:n,children:o,shapeFlag:l}=e.vnode,s=4&l;Bt(e,n,s,t),((e,t)=>{if(32&e.vnode.shapeFlag){const n=t._;n?(e.slots=t,W(t,"_",n)):_n(t,e.slots={})}else e.slots={},t&&wn(e,t);W(e.slots,Qn,1)})(e,o);const i=s?function(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,go);const{setup:o}=n;if(o){const n=e.setupContext=o.length>1?function(e){return{attrs:e.attrs,slots:e.slots,emit:e.emit}}(e):null;wo=e,se();const l=et(o,e,0,[e.props,n]);if(ie(),wo=null,M(l)){if(t)return l.then((t=>{So(e,t)}));e.asyncDep=l}else So(e,l)}else Co(e)}(e,t):void 0;ko=!1}(r),r.asyncDep){if(l&&l.registerDep(r,D),!e.el){const e=r.subTree=Jn($n);k(null,e,t,n)}}else D(r,e,t,n,l,s,i)},z=(e,t,n)=>{const o=t.component=e.component;if(function(e,t,n){const{props:o,children:l,component:s}=e,{props:i,children:r,patchFlag:c}=t,a=s.emitsOptions;if(t.dirs||t.transition)return!0;if(!(n&&c>0))return!(!l&&!r||r&&r.$stable)||o!==i&&(o?!i||qt(o,i,a):!!i);if(1024&c)return!0;if(16&c)return o?qt(o,i,a):!!i;if(8&c){const e=t.dynamicProps;for(let t=0;t<e.length;t++){const n=e[t];if(i[n]!==o[n]&&!Ot(a,n))return!0}}return!1}(e,t,n)){if(o.asyncDep&&!o.asyncResolved)return void A(o,t,n);o.next=t,function(e){const t=st.indexOf(e);t>-1&&(st[t]=null)}(o.update),o.update()}else t.component=e.component,t.el=e.el,o.vnode=t},D=(e,t,n,o,l,s,i)=>{e.update=Z((function(){if(e.isMounted){let t,{next:n,bu:o,u:r,parent:c,vnode:a}=e,u=n;n?(n.el=a.el,A(e,n,i)):n=a,o&&H(o),(t=n.props&&n.props.onVnodeBeforeUpdate)&&Mn(t,c,n,a);const p=Lt(e),f=e.subTree;e.subTree=p,_(f,p,d(f.el),te(f),e,l,s),n.el=p.el,null===u&&function({vnode:e,parent:t},n){for(;t&&t.subTree===e;)(e=t.vnode).el=n,t=t.parent}(e,p.el),r&&En(r,l),(t=n.props&&n.props.onVnodeUpdated)&&En((()=>{Mn(t,c,n,a)}),l)}else{let i;const{el:r,props:c}=t,{bm:a,m:u,parent:d}=e;a&&H(a),(i=c&&c.onVnodeBeforeMount)&&Mn(i,d,t);const p=e.subTree=Lt(e);r&&re?re(t.el,p,e,l):(_(null,p,n,o,e,l,s),t.el=p.el),u&&En(u,l),(i=c&&c.onVnodeMounted)&&En((()=>{Mn(i,d,t)}),l);const{a:f}=e;f&&256&t.shapeFlag&&En(f,l),e.isMounted=!0}}),Vn)},A=(e,t,n)=>{t.component=e;const o=e.vnode.props;e.vnode=t,e.next=null,function(e,t,n,o){const{props:l,attrs:s,vnode:{patchFlag:i}}=e,r=Ye(l),[c]=e.propsOptions;if(!(o||i>0)||16&i){let o;Ht(e,t,l,s);for(const s in r)t&&(x(t,s)||(o=T(s))!==s&&x(t,o))||(c?!n||void 0===n[s]&&void 0===n[o]||(l[s]=Wt(c,t||p,s,void 0,e)):delete l[s]);if(s!==r)for(const e in s)t&&x(t,e)||delete s[e]}else if(8&i){const n=e.vnode.dynamicProps;for(let o=0;o<n.length;o++){const i=n[o],a=t[i];if(c)if(x(s,i))s[i]=a;else{const t=$(i);l[t]=Wt(c,r,t,a,e)}else s[i]=a}}ce(e,"set","$attrs")}(e,t.props,o,n),((e,t)=>{const{vnode:n,slots:o}=e;let l=!0,s=p;if(32&n.shapeFlag){const e=t._;e?1===e?l=!1:b(o,t):(l=!t.$stable,_n(t,o)),s=t}else t&&(wn(e,t),s={default:1});if(l)for(const e in o)gn(e)||e in s||delete o[e]})(e,t.children),_t(void 0,e.update)},j=(e,t,n,o,l,s,i,r=!1)=>{const c=e&&e.children,a=e?e.shapeFlag:0,d=t.children,{patchFlag:p,shapeFlag:f}=t;if(p>0){if(128&p)return void B(c,d,n,o,l,s,i,r);if(256&p)return void N(c,d,n,o,l,s,i,r)}8&f?(16&a&&X(c,l,s),d!==c&&u(n,d)):16&a?16&f?B(c,d,n,o,l,s,i,r):X(c,l,s,!0):(8&a&&u(n,""),16&f&&E(d,n,o,l,s,i,r))},N=(e,t,n,o,l,s,i,r)=>{t=t||f;const c=(e=e||f).length,a=t.length,u=Math.min(c,a);let d;for(d=0;d<u;d++){const o=t[d]=r?no(t[d]):to(t[d]);_(e[d],o,n,null,l,s,i,r)}c>a?X(e,l,s,!0,!1,u):E(t,n,o,l,s,i,r,u)},B=(e,t,n,o,l,s,i,r)=>{let c=0;const a=t.length;let u=e.length-1,d=a-1;for(;c<=u&&c<=d;){const o=e[c],a=t[c]=r?no(t[c]):to(t[c]);if(!Kn(o,a))break;_(o,a,n,null,l,s,i,r),c++}for(;c<=u&&c<=d;){const o=e[u],c=t[d]=r?no(t[d]):to(t[d]);if(!Kn(o,c))break;_(o,c,n,null,l,s,i,r),u--,d--}if(c>u){if(c<=d){const e=d+1,u=e<a?t[e].el:o;for(;c<=d;)_(null,t[c]=r?no(t[c]):to(t[c]),n,u,l,s,i),c++}}else if(c>d)for(;c<=u;)Q(e[c],l,s,!0),c++;else{const p=c,h=c,m=new Map;for(c=h;c<=d;c++){const e=t[c]=r?no(t[c]):to(t[c]);null!=e.key&&m.set(e.key,c)}let v,g=0;const y=d-h+1;let b=!1,w=0;const x=new Array(y);for(c=0;c<y;c++)x[c]=0;for(c=p;c<=u;c++){const o=e[c];if(g>=y){Q(o,l,s,!0);continue}let a;if(null!=o.key)a=m.get(o.key);else for(v=h;v<=d;v++)if(0===x[v-h]&&Kn(o,t[v])){a=v;break}void 0===a?Q(o,l,s,!0):(x[a-h]=c+1,a>=w?w=a:b=!0,_(o,t[a],n,null,l,s,i,r),g++)}const k=b?function(e){const t=e.slice(),n=[0];let o,l,s,i,r;const c=e.length;for(o=0;o<c;o++){const c=e[o];if(0!==c){if(l=n[n.length-1],e[l]<c){t[o]=l,n.push(o);continue}for(s=0,i=n.length-1;s<i;)r=(s+i)/2|0,e[n[r]]<c?s=r+1:i=r;c<e[n[s]]&&(s>0&&(t[o]=n[s-1]),n[s]=o)}}s=n.length,i=n[s-1];for(;s-- >0;)n[s]=i,i=t[i];return n}(x):f;for(v=k.length-1,c=y-1;c>=0;c--){const e=h+c,r=t[e],u=e+1<a?t[e+1].el:o;0===x[c]?_(null,r,n,u,l,s,i):b&&(v<0||c!==k[v]?K(r,n,u,2):v--)}}},K=(e,t,o,l,s=null)=>{const{el:i,type:r,transition:c,children:a,shapeFlag:u}=e;if(6&u)return void K(e.component.subTree,t,o,l);if(128&u)return void e.suspense.move(t,o,l);if(64&u)return void r.move(e,t,o,oe);if(r===zn){n(i,t,o);for(let e=0;e<a.length;e++)K(a[e],t,o,l);return void n(e.anchor,t,o)}if(2!==l&&1&u&&c)if(0===l)c.beforeEnter(i),n(i,t,o),En((()=>c.enter(i)),s);else{const{leave:e,delayLeave:l,afterLeave:s}=c,r=()=>n(i,t,o),a=()=>{e(i,(()=>{r(),s&&s()}))};l?l(i,r,a):a()}else n(i,t,o)},Q=(e,t,n,o=!1,l=!1)=>{const{type:s,props:i,ref:r,children:c,dynamicChildren:a,shapeFlag:u,patchFlag:d,dirs:p}=e;if(null!=r&&t&&Ln(r,null,t,n,null),256&u)return void t.ctx.deactivate(e);const f=1&u&&p;let h;if((h=i&&i.onVnodeBeforeUnmount)&&Mn(h,t,e),6&u)J(e.component,n,o);else{if(128&u)return void e.suspense.unmount(n,o);f&&kn(e,null,t,"beforeUnmount"),a&&(s!==zn||d>0&&64&d)?X(a,t,n,!1,!0):(s===zn&&(128&d||256&d)||!l&&16&u)&&X(c,t,n),64&u&&(o||!qn(e.props))&&e.type.remove(e,oe),o&&G(e)}((h=i&&i.onVnodeUnmounted)||f)&&En((()=>{h&&Mn(h,t,e),f&&kn(e,null,t,"unmounted")}),n)},G=e=>{const{type:t,el:n,anchor:l,transition:s}=e;if(t===zn)return void Y(n,l);const i=()=>{o(n),s&&!s.persisted&&s.afterLeave&&s.afterLeave()};if(1&e.shapeFlag&&s&&!s.persisted){const{leave:t,delayLeave:o}=s,l=()=>t(n,i);o?o(e.el,i,l):l()}else i()},Y=(e,t)=>{let n;for(;e!==t;)n=m(e),o(e),e=n;o(t)},J=(e,t,n)=>{const{bum:o,effects:l,update:s,subTree:i,um:r}=e;if(o&&H(o),l)for(let e=0;e<l.length;e++)ee(l[e]);s&&(ee(s),Q(i,e,t,n)),r&&En(r,t),En((()=>{e.isUnmounted=!0}),t),t&&t.pendingBranch&&!t.isUnmounted&&e.asyncDep&&!e.asyncResolved&&e.suspenseId===t.pendingId&&(t.deps--,0===t.deps&&t.resolve())},X=(e,t,n,o=!1,l=!1,s=0)=>{for(let i=s;i<e.length;i++)Q(e[i],t,n,o,l)},te=e=>6&e.shapeFlag?te(e.component.subTree):128&e.shapeFlag?e.suspense.next():m(e.anchor||e.el),ne=(e,t)=>{null==e?t._vnode&&Q(t._vnode,null,null,!0):_(t._vnode||null,e,t),wt(),t._vnode=e},oe={p:_,um:Q,m:K,r:G,mt:U,mc:E,pc:j,pbc:F,n:te,o:e};let le,re;t&&([le,re]=t(oe));return{render:ne,hydrate:le,createApp:On(ne,le)}}(e)}function Mn(e,t,n,o=null){tt(e,t,7,[n,o])}function Rn(e,t,n=!1){const o=e.children,l=t.children;if(k(o)&&k(l))for(let e=0;e<o.length;e++){const t=o[e];let s=l[e];1&s.shapeFlag&&!s.dynamicChildren&&((s.patchFlag<=0||32===s.patchFlag)&&(s=l[e]=no(l[e]),s.el=t.el),n||Rn(t,s))}}const qn=e=>e&&(e.disabled||""===e.disabled);function Pn(e){return function(e,t,n=!0){const o=Vt||wo;if(o){const n=o.type;if("components"===e){const e=n.displayName||n.name;if(e&&(e===t||e===$(t)||e===j($(t))))return n}return In(o[e]||n[e],t)||In(o.appContext[e],t)}}("components",e)||e}const Un=Symbol();function In(e,t){return e&&(e[t]||e[$(t)]||e[j($(t))])}const zn=Symbol(void 0),Dn=Symbol(void 0),$n=Symbol(void 0),An=Symbol(void 0),Tn=[];let jn=null;function Nn(e=!1){Tn.push(jn=e?null:[])}function Bn(){Tn.pop(),jn=Tn[Tn.length-1]||null}function Hn(e,t,n,o,l){const s=Jn(e,t,n,o,l,!0);return s.dynamicChildren=jn||f,Bn(),jn&&jn.push(s),s}function Wn(e){return!!e&&!0===e.__v_isVNode}function Kn(e,t){return e.type===t.type&&e.key===t.key}const Qn="__vInternal",Gn=({key:e})=>null!=e?e:null,Yn=({ref:e})=>null!=e?k(e)?e:{i:Vt,r:e}:null,Jn=function(e,t=null,n=null,l=0,s=null,i=!1){e&&e!==Un||(e=$n);if(Wn(e)){const o=Xn(e,t,!0);return n&&oo(o,n),o}c=e,V(c)&&"__vccOpts"in c&&(e=e.__vccOpts);var c;if(t){(Ge(t)||Qn in t)&&(t=b({},t));let{class:e,style:n}=t;e&&!E(e)&&(t.class=r(e)),F(n)&&(Ge(n)&&!k(n)&&(n=b({},n)),t.style=o(n))}const a=E(e)?1:(e=>e.__isSuspense)(e)?128:(e=>e.__isTeleport)(e)?64:F(e)?4:V(e)?2:0,u={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Gn(t),ref:t&&Yn(t),scopeId:$t,children:null,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:a,patchFlag:l,dynamicProps:s,dynamicChildren:null,appContext:null};if(oo(u,n),128&a){const{content:e,fallback:t}=function(e){const{shapeFlag:t,children:n}=e;let o,l;return 32&t?(o=Pt(n.default),l=Pt(n.fallback)):(o=Pt(n),l=to(null)),{content:o,fallback:l}}(u);u.ssContent=e,u.ssFallback=t}!i&&jn&&(l>0||6&a)&&32!==l&&jn.push(u);return u};function Xn(e,t,n=!1){const{props:l,ref:s,patchFlag:i}=e,c=t?function(...e){const t=b({},e[0]);for(let n=1;n<e.length;n++){const l=e[n];for(const e in l)if("class"===e)t.class!==l.class&&(t.class=r([t.class,l.class]));else if("style"===e)t.style=o([t.style,l.style]);else if(g(e)){const n=t[e],o=l[e];n!==o&&(t[e]=n?[].concat(n,l[e]):o)}else""!==e&&(t[e]=l[e])}return t}(l||{},t):l;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:c,key:c&&Gn(c),ref:t&&t.ref?n&&s?k(s)?s.concat(Yn(t)):[s,Yn(t)]:Yn(t):s,scopeId:e.scopeId,children:e.children,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==zn?-1===i?16:16|i:i,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Xn(e.ssContent),ssFallback:e.ssFallback&&Xn(e.ssFallback),el:e.el,anchor:e.anchor}}function Zn(e=" ",t=0){return Jn(Dn,null,e,t)}function eo(e="",t=!1){return t?(Nn(),Hn($n,null,e)):Jn($n,null,e)}function to(e){return null==e||"boolean"==typeof e?Jn($n):k(e)?Jn(zn,null,e):"object"==typeof e?null===e.el?e:Xn(e):Jn(Dn,null,String(e))}function no(e){return null===e.el?e:Xn(e)}function oo(e,t){let n=0;const{shapeFlag:o}=e;if(null==t)t=null;else if(k(t))n=16;else if("object"==typeof t){if(1&o||64&o){const n=t.default;return void(n&&(n._c&&It(1),oo(e,n()),n._c&&It(-1)))}{n=32;const o=t._;o||Qn in t?3===o&&Vt&&(1024&Vt.vnode.patchFlag?(t._=2,e.patchFlag|=1024):t._=1):t._ctx=Vt}}else V(t)?(t={default:t,_ctx:Vt},n=32):(t=String(t),64&o?(n=16,t=[Zn(t)]):n=8);e.children=t,e.shapeFlag|=n}function lo(e,t){if(wo){let n=wo.provides;const o=wo.parent&&wo.parent.provides;o===n&&(n=wo.provides=Object.create(o)),n[e]=t}else;}function so(e,t,n=!1){const o=wo||Vt;if(o){const l=null==o.parent?o.vnode.appContext&&o.vnode.appContext.provides:o.parent.provides;if(l&&e in l)return l[e];if(arguments.length>1)return n&&V(t)?t():t}}let io=!1;function ro(e,t,n=[],o=[],l=[],s=!1){const{mixins:i,extends:r,data:c,computed:a,methods:u,watch:d,provide:p,inject:f,components:m,directives:v,beforeMount:g,mounted:y,beforeUpdate:_,updated:w,activated:x,deactivated:S,beforeDestroy:C,beforeUnmount:O,destroyed:E,unmounted:L,render:M,renderTracked:R,renderTriggered:q,errorCaptured:P}=t,U=e.proxy,I=e.ctx,z=e.appContext.mixins;if(s&&M&&e.render===h&&(e.render=M),s||(io=!0,co("beforeCreate","bc",t,e,z),io=!1,po(e,z,n,o,l)),r&&ro(e,r,n,o,l,!0),i&&po(e,i,n,o,l),f)if(k(f))for(let e=0;e<f.length;e++){const t=f[e];I[t]=so(t)}else for(const e in f){const t=f[e];F(t)?I[e]=so(t.from||e,t.default,!0):I[e]=so(t)}if(u)for(const e in u){const t=u[e];V(t)&&(I[e]=t.bind(U))}if(s?c&&n.push(c):(n.length&&n.forEach((t=>fo(e,t,U))),c&&fo(e,c,U)),a)for(const e in a){const t=a[e],n=Vo({get:V(t)?t.bind(U,U):V(t.get)?t.get.bind(U,U):h,set:!V(t)&&V(t.set)?t.set.bind(U):h});Object.defineProperty(I,e,{enumerable:!0,configurable:!0,get:()=>n.value,set:e=>n.value=e})}var D;d&&o.push(d),!s&&o.length&&o.forEach((e=>{for(const t in e)ho(e[t],I,U,t)})),p&&l.push(p),!s&&l.length&&l.forEach((e=>{const t=V(e)?e.call(U):e;for(const e in t)lo(e,t[e])})),s&&(m&&b(e.components||(e.components=b({},e.type.components)),m),v&&b(e.directives||(e.directives=b({},e.type.directives)),v)),s||co("created","c",t,e,z),g&&en(g.bind(U)),y&&tn(y.bind(U)),_&&nn(_.bind(U)),w&&on(w.bind(U)),x&&mn(x.bind(U),"a",D),S&&function(e,t){mn(e,"da",t)}(S.bind(U)),P&&((e,t=wo)=>{Xt("ec",e,t)})(P.bind(U)),R&&cn(R.bind(U)),q&&rn(q.bind(U)),O&&ln(O.bind(U)),L&&sn(L.bind(U))}function co(e,t,n,o,l){uo(e,t,l,o);const{extends:s,mixins:i}=n;s&&ao(e,t,s,o),i&&uo(e,t,i,o);const r=n[e];r&&tt(r.bind(o.proxy),o,t)}function ao(e,t,n,o){n.extends&&ao(e,t,n.extends,o);const l=n[e];l&&tt(l.bind(o.proxy),o,t)}function uo(e,t,n,o){for(let l=0;l<n.length;l++){const s=n[l].mixins;s&&uo(e,t,s,o);const i=n[l][e];i&&tt(i.bind(o.proxy),o,t)}}function po(e,t,n,o,l){for(let s=0;s<t.length;s++)ro(e,t[s],n,o,l,!0)}function fo(e,t,n){const o=t.call(n,n);F(o)&&(e.data===p?e.data=Be(o):b(e.data,o))}function ho(e,t,n,o){const l=o.includes(".")?function(e,t){const n=t.split(".");return()=>{let t=e;for(let e=0;e<n.length&&t;e++)t=t[n[e]];return t}}(n,o):()=>n[o];if(E(e)){const n=t[e];V(n)&&un(l,n)}else if(V(e))un(l,e.bind(n));else if(F(e))if(k(e))e.forEach((e=>ho(e,t,n,o)));else{const o=V(e.handler)?e.handler.bind(n):t[e.handler];V(o)&&un(l,o,e)}}function mo(e,t,n){const o=n.appContext.config.optionMergeStrategies,{mixins:l,extends:s}=t;s&&mo(e,s,n),l&&l.forEach((t=>mo(e,t,n)));for(const l in t)o&&x(o,l)?e[l]=o[l](e[l],t[l],n.proxy,l):e[l]=t[l]}const vo=b(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>e.parent&&e.parent.proxy,$root:e=>e.root&&e.root.proxy,$emit:e=>e.emit,$options:e=>function(e){const t=e.type,{__merged:n,mixins:o,extends:l}=t;if(n)return n;const s=e.appContext.mixins;if(!s.length&&!o&&!l)return t;const i={};return s.forEach((t=>mo(i,t,e))),mo(i,t,e),t.__merged=i}(e),$forceUpdate:e=>()=>gt(e.update),$nextTick:e=>vt.bind(e.proxy),$watch:e=>pn.bind(e)}),go={get({_:e},t){const{ctx:n,setupState:o,data:l,props:s,accessCache:i,type:r,appContext:c}=e;if("__v_skip"===t)return!0;let a;if("$"!==t[0]){const r=i[t];if(void 0!==r)switch(r){case 0:return o[t];case 1:return l[t];case 3:return n[t];case 2:return s[t]}else{if(o!==p&&x(o,t))return i[t]=0,o[t];if(l!==p&&x(l,t))return i[t]=1,l[t];if((a=e.propsOptions[0])&&x(a,t))return i[t]=2,s[t];if(n!==p&&x(n,t))return i[t]=3,n[t];io||(i[t]=4)}}const u=vo[t];let d,f;return u?("$attrs"===t&&re(e,0,t),u(e)):(d=r.__cssModules)&&(d=d[t])?d:n!==p&&x(n,t)?(i[t]=3,n[t]):(f=c.config.globalProperties,x(f,t)?f[t]:void 0)},set({_:e},t,n){const{data:o,setupState:l,ctx:s}=e;if(l!==p&&x(l,t))l[t]=n;else if(o!==p&&x(o,t))o[t]=n;else if(t in e.props)return!1;return("$"!==t[0]||!(t.slice(1)in e))&&(s[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:o,appContext:l,propsOptions:s}},i){let r;return void 0!==n[i]||e!==p&&x(e,i)||t!==p&&x(t,i)||(r=s[0])&&x(r,i)||x(o,i)||x(vo,i)||x(l.config.globalProperties,i)}},yo=b({},go,{get(e,t){if(t!==Symbol.unscopables)return go.get(e,t,e)},has:(e,n)=>"_"!==n[0]&&!t(n)}),bo=Sn();let _o=0;let wo=null;const xo=e=>{wo=e};let ko=!1;function So(e,t,n){var o;V(t)?e.render=t:F(t)&&(e.setupState=Ke(o=t)?o:new Proxy(o,Xe)),Co(e)}function Co(e,t){const n=e.type;e.render||(e.render=n.render||h,e.render._rc&&(e.withProxy=new Proxy(e.ctx,yo))),wo=e,ro(e,n),wo=null}function Oo(e){wo&&(wo.effects||(wo.effects=[])).push(e)}function Vo(e){const t=function(e){let t,n;return V(e)?(t=e,n=h):(t=e.get,n=e.set),new Ze(t,n,V(e)||!e.set)}(e);return Oo(t.effect),t}function Eo(e,t){let n;if(k(e)||E(e)){n=new Array(e.length);for(let o=0,l=e.length;o<l;o++)n[o]=t(e[o],o)}else if("number"==typeof e){n=new Array(e);for(let o=0;o<e;o++)n[o]=t(o+1,o)}else if(F(e))if(e[Symbol.iterator])n=Array.from(e,t);else{const o=Object.keys(e);n=new Array(o.length);for(let l=0,s=o.length;l<s;l++){const s=o[l];n[l]=t(e[s],s,l)}}else n=[];return n}const Lo="3.0.2",Fo="http://www.w3.org/2000/svg",Mo="undefined"!=typeof document?document:null;let Ro,qo;const Po={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n)=>t?Mo.createElementNS(Fo,e):Mo.createElement(e,n?{is:n}:void 0),createText:e=>Mo.createTextNode(e),createComment:e=>Mo.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Mo.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},cloneNode:e=>e.cloneNode(!0),insertStaticContent(e,t,n,o){const l=o?qo||(qo=Mo.createElementNS(Fo,"svg")):Ro||(Ro=Mo.createElement("div"));l.innerHTML=e;const s=l.firstChild;let i=s,r=i;for(;i;)r=i,Po.insert(i,t,n),i=l.firstChild;return[s,r]}};const Uo=/\s*!important$/;function Io(e,t,n){if(k(n))n.forEach((n=>Io(e,t,n)));else if(t.startsWith("--"))e.setProperty(t,n);else{const o=function(e,t){const n=Do[t];if(n)return n;let o=$(t);if("filter"!==o&&o in e)return Do[t]=o;o=j(o);for(let n=0;n<zo.length;n++){const l=zo[n]+o;if(l in e)return Do[t]=l}return t}(e,t);Uo.test(n)?e.setProperty(T(o),n.replace(Uo,""),"important"):e[o]=n}}const zo=["Webkit","Moz","ms"],Do={};const $o="http://www.w3.org/1999/xlink";let Ao=Date.now;"undefined"!=typeof document&&Ao()>document.createEvent("Event").timeStamp&&(Ao=()=>performance.now());let To=0;const jo=Promise.resolve(),No=()=>{To=0};function Bo(e,t,n,o){e.addEventListener(t,n,o)}function Ho(e,t,n,o,l=null){const s=e._vei||(e._vei={}),i=s[t];if(o&&i)i.value=o;else{const[n,r]=function(e){let t;if(Wo.test(e)){let n;for(t={};n=e.match(Wo);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[e.slice(2).toLowerCase(),t]}(t);if(o){Bo(e,n,s[t]=function(e,t){const n=e=>{(e.timeStamp||Ao())>=n.attached-1&&tt(function(e,t){if(k(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map((e=>t=>!t._stopped&&e(t)))}return t}(e,n.value),t,5,[e])};return n.value=e,n.attached=(()=>To||(jo.then(No),To=Ao()))(),n}(o,l),r)}else i&&(!function(e,t,n,o){e.removeEventListener(t,n,o)}(e,n,i,r),s[t]=void 0)}}const Wo=/(?:Once|Passive|Capture)$/;const Ko=/^on[a-z]/;const Qo=e=>{const t=e.props["onUpdate:modelValue"];return k(t)?e=>H(t,e):t};function Go(e){e.target.composing=!0}function Yo(e){const t=e.target;t.composing&&(t.composing=!1,function(e,t){const n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}(t,"input"))}const Jo={created(e,{modifiers:{lazy:t,trim:n,number:o}},l){e._assign=Qo(l);const s=o||"number"===e.type;Bo(e,t?"change":"input",(t=>{if(t.target.composing)return;let o=e.value;n?o=o.trim():s&&(o=K(o)),e._assign(o)})),n&&Bo(e,"change",(()=>{e.value=e.value.trim()})),t||(Bo(e,"compositionstart",Go),Bo(e,"compositionend",Yo),Bo(e,"change",Yo))},mounted(e,{value:t}){e.value=null==t?"":t},beforeUpdate(e,{value:t,modifiers:{trim:n,number:o}},l){if(e._assign=Qo(l),e.composing)return;if(document.activeElement===e){if(n&&e.value.trim()===t)return;if((o||"number"===e.type)&&K(e.value)===t)return}const s=null==t?"":t;e.value!==s&&(e.value=s)}},Xo={created(e,t,n){Zo(e,t,n),e._assign=Qo(n),Bo(e,"change",(()=>{const t=e._modelValue,n=function(e){return"_value"in e?e._value:e.value}(e),o=e.checked,l=e._assign;if(k(t)){const e=a(t,n),s=-1!==e;if(o&&!s)l(t.concat(n));else if(!o&&s){const n=[...t];n.splice(e,1),l(n)}}else C(t)?o?t.add(n):t.delete(n):l(el(e,o))}))},beforeUpdate(e,t,n){e._assign=Qo(n),Zo(e,t,n)}};function Zo(e,{value:t,oldValue:n},o){e._modelValue=t,k(t)?e.checked=a(t,o.props.value)>-1:C(t)?e.checked=t.has(o.props.value):t!==n&&(e.checked=c(t,el(e,!0)))}function el(e,t){const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}const tl=["ctrl","shift","alt","meta"],nl={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&0!==e.button,middle:e=>"button"in e&&1!==e.button,right:e=>"button"in e&&2!==e.button,exact:(e,t)=>tl.some((n=>e[n+"Key"]&&!t.includes(n)))},ol=(e,t)=>(n,...o)=>{for(let e=0;e<t.length;e++){const o=nl[t[e]];if(o&&o(n,t))return}return e(n,...o)},ll={beforeMount(e,{value:t},{transition:n}){e._vod="none"===e.style.display?"":e.style.display,n&&t?n.beforeEnter(e):sl(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:o}){!t!=!n&&(o?t?(o.beforeEnter(e),sl(e,!0),o.enter(e)):o.leave(e,(()=>{sl(e,!1)})):sl(e,t))},beforeUnmount(e,{value:t}){sl(e,t)}};function sl(e,t){e.style.display=t?e._vod:"none"}const il=b({patchProp:(e,t,o,l,s=!1,i,r,c,a)=>{switch(t){case"class":!function(e,t,n){if(null==t&&(t=""),n)e.setAttribute("class",t);else{const n=e._vtc;n&&(t=(t?[t,...n]:[...n]).join(" ")),e.className=t}}(e,l,s);break;case"style":!function(e,t,n){const o=e.style;if(n)if(E(n))t!==n&&(o.cssText=n);else{for(const e in n)Io(o,e,n[e]);if(t&&!E(t))for(const e in t)null==n[e]&&Io(o,e,"")}else e.removeAttribute("style")}(e,o,l);break;default:g(t)?y(t)||Ho(e,t,0,l,r):function(e,t,n,o){if(o)return"innerHTML"===t||!!(t in e&&Ko.test(t)&&V(n));if("spellcheck"===t||"draggable"===t)return!1;if("form"===t&&"string"==typeof n)return!1;if("list"===t&&"INPUT"===e.tagName)return!1;if(Ko.test(t)&&E(n))return!1;return t in e}(e,t,l,s)?function(e,t,n,o,l,s,i){if("innerHTML"===t||"textContent"===t)return o&&i(o,l,s),void(e[t]=null==n?"":n);if("value"!==t||"PROGRESS"===e.tagName)if(""===n&&"boolean"==typeof e[t])e[t]=!0;else if(null==n&&"string"==typeof e[t])e[t]="",e.removeAttribute(t);else try{e[t]=n}catch(e){}else{e._value=n;const t=null==n?"":n;e.value!==t&&(e.value=t)}}(e,t,l,i,r,c,a):("true-value"===t?e._trueValue=l:"false-value"===t&&(e._falseValue=l),function(e,t,o,l){if(l&&t.startsWith("xlink:"))null==o?e.removeAttributeNS($o,t.slice(6,t.length)):e.setAttributeNS($o,t,o);else{const l=n(t);null==o||l&&!1===o?e.removeAttribute(t):e.setAttribute(t,l?"":o)}}(e,t,l,s))}},forcePatchProp:(e,t)=>"value"===t},Po);let rl;class cl{static post(e,t,n){var o=this;window.wcfmoptions.poster(e,t,(function(e){n.call(o,e)}),(function(){console.log("failure")}))}}const al={style:{position:"absolute",width:"0",height:"0"},width:"0",height:"0",version:"1.1",xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink"},ul=Jn("defs",null,[Jn("symbol",{id:"icon-folder",viewBox:"0 0 309.267 309.267"},[Jn("g",null,[Jn("path",{style:{fill:"#D0994B"},d:"M260.944,43.491H125.64c0,0-18.324-28.994-28.994-28.994H48.323c-10.67,0-19.329,8.65-19.329,19.329\n \t\tv222.286c0,10.67,8.659,19.329,19.329,19.329h212.621c10.67,0,19.329-8.659,19.329-19.329V62.82\n \t\tC280.273,52.15,271.614,43.491,260.944,43.491z"}),Jn("path",{style:{fill:"#E4E7E7"},d:"M28.994,72.484h251.279v77.317H28.994V72.484z"}),Jn("path",{style:{fill:"#F4B459"},d:"M19.329,91.814h270.609c10.67,0,19.329,8.65,19.329,19.329l-19.329,164.298\n \t\tc0,10.67-8.659,19.329-19.329,19.329H38.658c-10.67,0-19.329-8.659-19.329-19.329L0,111.143C0,100.463,8.659,91.814,19.329,91.814z\n \t\t"})])]),Jn("symbol",{id:"icon-unfold",viewBox:"0 0 32 32"},[Jn("path",{d:"M28,14H18V4c0-1.104-0.896-2-2-2s-2,0.896-2,2v10H4c-1.104,0-2,0.896-2,2s0.896,2,2,2h10v10c0,1.104,0.896,2,2,2 s2-0.896,2-2V18h10c1.104,0,2-0.896,2-2S29.104,14,28,14z"})]),Jn("symbol",{id:"icon-fold",viewBox:"0 0 24 24"},[Jn("g",{fill:"none",stroke:"#000","stroke-width":"3","stroke-linecap":"round","stroke-linejoin":"round"},[Jn("line",{x1:"5",y1:"12",x2:"19",y2:"12"})])]),Jn("symbol",{id:"icon-file",viewBox:"0 0 56 56"},[Jn("g",null,[Jn("path",{style:{fill:"#E9E9E0"},d:"M36.985,0H7.963C7.155,0,6.5,0.655,6.5,1.926V55c0,0.345,0.655,1,1.463,1h40.074\n \t\tc0.808,0,1.463-0.655,1.463-1V12.978c0-0.696-0.093-0.92-0.257-1.085L37.607,0.257C37.442,0.093,37.218,0,36.985,0z"}),Jn("polygon",{style:{fill:"#D9D7CA"},points:"37.5,0.151 37.5,12 49.349,12 \t"}),Jn("circle",{style:{fill:"#F3D55B"},cx:"18.931",cy:"14.431",r:"4.569"}),Jn("polygon",{style:{fill:"#26B99A"},points:"6.5,39 17.5,39 49.5,39 49.5,28 39.5,18.5 29,30 23.517,24.517 \t"}),Jn("path",{style:{fill:"#14A085"},d:"M48.037,56H7.963C7.155,56,6.5,55.345,6.5,54.537V39h43v15.537C49.5,55.345,48.845,56,48.037,56z"}),Jn("g")])]),Jn("svg",{id:"icon-ok",viewBox:"0 0 256 256"},[Jn("g",{fill:"green",stroke:"none",transform:"translate(0.000000,256.000000) scale(0.100000,-0.100000)"},[Jn("path",{d:"M1064 2545 c-406 -72 -744 -324 -927 -690 -96 -193 -127 -333 -127 -575 0 -243 33 -387 133 -585 177 -351 518 -606 907 -676 118 -22 393 -17 511 8 110 24 252 78 356 136 327 183 569 525 628 887 19 122 19 338 0 460 -81 498 -483 914 -990 1025 -101 22 -389 28 -491 10z m814 -745 c39 -27 73 -59 77 -70 9 -27 10 -25 -372 -590 -345 -510 -357 -524 -420 -512 -19 4 -98 74 -250 225 -123 121 -225 228 -228 238 -3 10 1 31 9 47 20 40 125 132 149 132 11 0 79 -59 162 -140 79 -77 146 -140 149 -140 3 0 38 48 78 108 95 143 465 678 496 720 35 46 64 42 150 -18z"})])]),Jn("svg",{id:"icon-not-available",viewBox:"0 0 500.000000 500.000000",preserveAspectRatio:"xMidYMid meet"},[Jn("g",{fill:"#b11010",stroke:"none",transform:"translate(0.000000,500.000000) scale(0.100000,-0.100000)"},[Jn("path",{d:"M2315 4800 c-479 -35 -928 -217 -1303 -527 -352 -293 -615 -702 -738 -1151 -104 -380 -104 -824 0 -1204 107 -389 302 -724 591 -1013 354 -354 785 -572 1279 -646 196 -30 476 -30 672 0 494 74 925 292 1279 646 354 354 571 784 646 1279 30 197 30 475 0 672 -75 495 -292 925 -646 1279 -289 289 -624 484 -1013 591 -228 62 -528 91 -767 74z m353 -511 c458 -50 874 -272 1170 -624 417 -497 536 -1174 308 -1763 -56 -145 -176 -367 -235 -434 -4 -4 -566 552 -1250 1236 l-1243 1243 94 60 c354 229 754 327 1156 282z m864 -3200 c-67 -59 -289 -179 -434 -235 -946 -366 -2024 172 -2322 1158 -47 155 -66 276 -73 453 -13 362 84 704 290 1023 l60 94 1243 -1243 c684 -684 1240 -1246 1236 -1250z"})])])],-1);const dl={render:function(e,t){return Nn(),Hn("svg",al,[ul])}};var pl={name:"SelectBox",props:{options:Array,optionsLabel:String,optionsKey:String,placeholder:String,modelValue:String},emits:["update:modelValue"],data:()=>({selectedOption:null,isOpen:!1}),watch:{modelValue(e,t){this.select(e)}},mounted(){document.addEventListener("click",this.onDocumentClick),this.select(this.modelValue)},beforeUnmount:function(){document.removeEventListener("click",this.onDocumentClick)},methods:{onDocumentClick(e){e.target.parentNode!=this.$el&&(this.isOpen=!1)},onBoxClick(){this.isOpen=!this.isOpen},onOptionClick(e){this.isOpen=!1,this.select(e.target.getAttribute("data-key")),this.$emit("update:modelValue",this.selectedOption[this.optionsKey])},getOptionByKey(e){return this.options.find((t=>t[this.optionsKey]==e))},select(e){this.selectedOption=this.getOptionByKey(e)}}};const fl=Nt("data-v-a8799ec4");Tt("data-v-a8799ec4");const hl={class:"selectbox"},ml={class:"dropdown"},vl={class:"icon"};jt();const gl=fl((function(e,t,n,o,l,s){return Nn(),Hn("div",hl,[Jn("div",{class:["box",{"is-open":l.isOpen}],onClick:t[1]||(t[1]=(...e)=>s.onBoxClick(...e))},u(l.selectedOption?l.selectedOption[n.optionsLabel]:n.placeholder),3),xn(Jn("div",ml,[(Nn(!0),Hn(zn,null,Eo(n.options,(o=>(Nn(),Hn("div",null,[Jn("div",{class:"option","data-key":o[n.optionsKey],onClick:t[2]||(t[2]=e=>s.onOptionClick(e))},[Zn(u(o[n.optionsLabel])+" ",1),o.icon?zt(e.$slots,"default",{key:0},(()=>[(Nn(),Hn("svg",vl,[Jn("use",{"xlink:href":"#icon-"+o.icon},null,8,["xlink:href"])]))])):eo("",!0)],8,["data-key"])])))),256))],512),[[ll,l.isOpen]])])}));pl.render=gl,pl.__scopeId="data-v-a8799ec4";var yl={name:"EncodingSelector",components:{SelectBox:pl},props:{converter:String,modelValue:String},emits:["update:modelValue"],data(){return{encoding:this.modelValue,encodings:[{id:"auto",name:"Auto"},{id:"lossy",name:"Lossy"},{id:"lossless",name:"Lossless"}]}},watch:{modelValue(e,t){this.encoding=e}},methods:{onUpdate(){this.$emit("update:modelValue",this.encoding)},encodingSupported(){return"gd"!=this.converter&&"ewww"!=this.converter}}};const bl=Nt("data-v-8124cea8");Tt("data-v-8124cea8");const _l={class:"encoding-selector"};jt();const wl=bl((function(e,t,n,o,l,s){const i=Pn("SelectBox");return Nn(),Hn("div",_l,[xn(Jn("div",null,"lossy (gd can only produce lossy webp)",512),[[ll,"gd"==n.converter]]),xn(Jn("div",null,"lossy for jpeg, lossy for png (ewww behaviour)",512),[[ll,"ewww"==n.converter]]),xn(Jn("div",null,[Jn(i,{modelValue:l.encoding,"onUpdate:modelValue":[t[1]||(t[1]=e=>l.encoding=e),s.onUpdate],options:l.encodings,optionsLabel:"name",optionsKey:"id",placeholder:"Select"},null,8,["modelValue","options","onUpdate:modelValue"])],512),[[ll,s.encodingSupported()]])])}));yl.render=wl,yl.__scopeId="data-v-8124cea8";var xl={name:"QualityLossy",components:{},props:{converter:String,qualityDetectionSupported:Boolean,modelValue:Object},emits:["update:modelValue"],mounted(){this.updateLocalModel(this.modelValue)},data:()=>({auto:{max:15,limit:!0},noAuto:{quality:75}}),watch:{modelValue(e,t){this.updateLocalModel(e)}},methods:{updateLocalModel(e){this.auto.max=e.max,this.auto.limit="auto"==e.quality,this.noAuto.quality=e.default},onLocalChange(){this.qualityDetectionSupported?this.$emit("update:modelValue",{quality:this.auto.limit?"auto":this.auto.max,max:this.auto.max,default:this.auto.max}):this.$emit("update:modelValue",{quality:this.noAuto.quality,max:this.noAuto.quality,default:this.noAuto.quality})}}};const kl=Nt("data-v-74dc2e9e");Tt("data-v-74dc2e9e");const Sl={class:"quality-lossy"},Cl={key:0,class:"auto"},Ol=Jn("label",null,"Prevent excess?",-1),Vl={key:1,class:"no-auto"};jt();const El=kl((function(e,t,n,o,l,s){return Nn(),Hn("div",Sl,[n.qualityDetectionSupported?(Nn(),Hn("div",Cl,[xn(Jn("input",{type:"number",class:"input-quality","onUpdate:modelValue":t[1]||(t[1]=e=>l.auto.max=e),onInput:t[2]||(t[2]=e=>s.onLocalChange())},null,544),[[Jo,l.auto.max]]),Ol,xn(Jn("input",{type:"checkbox","onUpdate:modelValue":t[3]||(t[3]=e=>l.auto.limit=e),onInput:t[4]||(t[4]=e=>s.onLocalChange())},null,544),[[Xo,l.auto.limit]])])):eo("",!0),n.qualityDetectionSupported?eo("",!0):(Nn(),Hn("div",Vl,[xn(Jn("input",{class:"input-quality","onUpdate:modelValue":t[5]||(t[5]=e=>l.noAuto.quality=e),type:"number",onInput:t[6]||(t[6]=e=>s.onLocalChange())},null,544),[[Jo,l.noAuto.quality]])]))])}));xl.render=El,xl.__scopeId="data-v-74dc2e9e";var Ll={name:"QualityLossless",components:{},props:{modelValue:Object},emits:["update:modelValue"],mounted(){this.updateLocalModel(this.modelValue)},data:()=>({quality:60}),watch:{modelValue(e,t){this.updateLocalModel(e)}},methods:{updateLocalModel(e){this.quality=e.quality},onLocalChange(){this.$emit("update:modelValue",{quality:this.quality})}}};const Fl=Nt("data-v-db8a8a84");Tt("data-v-db8a8a84");const Ml={class:"quality-lossless"};jt();const Rl=Fl((function(e,t,n,o,l,s){return Nn(),Hn("div",Ml,[xn(Jn("input",{class:"input-quality","onUpdate:modelValue":t[1]||(t[1]=e=>l.quality=e),type:"number",onInput:t[2]||(t[2]=e=>s.onLocalChange())},null,544),[[Jo,l.quality]])])}));Ll.render=Rl,Ll.__scopeId="data-v-db8a8a84";var ql={name:"ConvertOptions",components:{SelectBox:pl,EncodingSelector:yl,QualityLossy:xl,QualityLossless:Ll},props:{item:Object},computed:{converterSupportsEncoding(){return this.converterSupports("encoding")},converterSupportsNearLossless(){return this.converterSupports("nearLossless")},converterSupportsMethod(){return this.converterSupports("method")}},data:()=>({converters:[{id:"cwebp",name:"cwebp"},{id:"vips",name:"vips"},{id:"ewww",name:"ewww"},{id:"gd",name:"gd"}],supportedOptions:{encoding:["cwebp","vips","imagick","gmagick","imagemagick","graphicsmagick","ffmpeg","wpc"],method:["cwebp","imagick","gmagick","imagemagick","graphicsmagick","ffmpeg","wpc"],nearLossless:["cwebp","vips","wpc"]},qualityDetectionSupported:!0,encoding:"auto",converter:"cwebp",qualityLossy:{quality:"auto",max:85,default:75},"use-nice":!0,qualityLossless:{quality:60},method:5,converterOptions:{ewww:{"api-key":"bogus-key-for-testing","check-key-status-before-converting":!1},cwebp:{}}}),mounted(){var e=this;cl.post("conversion-settings",{folder:""},(function(t){if(t.supportedStandardOptions){var n=t.supportedStandardOptions;n.encoding&&(e.supportedOptions.encoding=n.encoding),n.method&&(e.supportedOptions.method=n.method),n.nearLossless&&(e.supportedOptions.nearLossless=n.nearLossless)}var o=null;if(t.converters?o=t.converters:t.systemStatus.converterRequirements&&(o=e.converters),t.systemStatus.converterRequirements)for(var l=t.systemStatus.converterRequirements,s=0;s<o.length;s++){var i=o[s].id;if(l[i]){var r=l[i];for(var c in r)!1===r[c]&&(o[s].icon="not-available")}}if(o){e.converters=o;var a=e.converter;e.converter="vips",e.converter=a}if(t.overrideDefaults){var u=t.overrideDefaults;u.converter&&(e.converter=u.converter),u.encoding&&(e.encoding=u.encoding),u.method&&(e.method=u.method)}})),this.qualityDetectionSupported||(this.qualityLossy.quality=this.qualityLossy.default)},watch:{converter(e,t){}},methods:{optionSupported(e,t){t=this.converter;return this.supportedOptions[e].find((function(e){return e==t}))},converterSupports(e){return this.optionSupported(e,this.converter)}}};const Pl=Nt("data-v-115f004a");Tt("data-v-115f004a");const Ul={class:"table-table convert-options"},Il=Jn("div",null,[Jn("label",null,"Converter")],-1),zl=Jn("div",null,[Jn("label",null,"WebP encoding")],-1),Dl=Jn("div",null,[Jn("label",null,"Method (0-5)")],-1),$l={key:0},Al=Jn("div",null,[Jn("label",null,"Api key")],-1);jt();const Tl=Pl((function(e,t,n,o,l,s){const i=Pn("SelectBox"),r=Pn("EncodingSelector"),c=Pn("QualityLossy"),a=Pn("QualityLossless");return Nn(),Hn(zn,null,[Jn("div",Ul,[Jn("div",null,[Il,Jn("div",null,[Jn(i,{modelValue:l.converter,"onUpdate:modelValue":t[1]||(t[1]=e=>l.converter=e),options:l.converters,optionsLabel:"name",optionsKey:"id",placeholder:"Select converter"},null,8,["modelValue","options"])])]),xn(Jn("div",null,[zl,Jn("div",null,[Jn(r,{modelValue:l.encoding,"onUpdate:modelValue":t[2]||(t[2]=e=>l.encoding=e),converter:l.converter},null,8,["modelValue","converter"])])],512),[[ll,l.converter&&s.converterSupportsEncoding]]),xn(Jn("div",null,[Jn("div",null,[Jn("label",null,"Quality"+u(s.converterSupportsEncoding&&"lossy"!=l.encoding?" (lossy)":""),1)]),Jn("div",null,[Jn(c,{modelValue:l.qualityLossy,"onUpdate:modelValue":t[3]||(t[3]=e=>l.qualityLossy=e),converter:l.converter,qualityDetectionSupported:l.qualityDetectionSupported},null,8,["modelValue","converter","qualityDetectionSupported"])])],512),[[ll,l.converter&&("auto"==l.encoding||"lossy"==l.encoding||!s.converterSupportsEncoding)]]),xn(Jn("div",null,[Jn("div",null,[Jn("label",null,"Quality"+u("lossless"!=l.encoding?" (lossless)":""),1)]),Jn("div",null,[Jn(a,{modelValue:l.qualityLossless,"onUpdate:modelValue":t[4]||(t[4]=e=>l.qualityLossless=e),converter:l.converter},null,8,["modelValue","converter"])])],512),[[ll,s.converterSupportsNearLossless&&("auto"==l.encoding||"lossless"==l.encoding)]]),xn(Jn("div",null,[Dl,Jn("div",null,[xn(Jn("input",{type:"number",class:"method","onUpdate:modelValue":t[5]||(t[5]=e=>l.method=e)},null,512),[[Jo,l.method]])])],512),[[ll,s.converterSupportsMethod]]),"ewww"==l.converter?(Nn(),Hn("div",$l,[Al,Jn("div",null,[xn(Jn("input",{"onUpdate:modelValue":t[6]||(t[6]=e=>l.converterOptions.ewww["api-key"]=e)},null,512),[[Jo,l.converterOptions.ewww["api-key"]]])])])):eo("",!0)]),Jn("pre",null," Converter: "+u(l.converter)+"\n Encoding: "+u(l.encoding)+"\n Quality (lossy):\n quality: "+u(l.qualityLossy.quality)+",\n max: "+u(l.qualityLossy.max)+",\n\n default: "+u(l.qualityLossy.default)+"\n",1)],64)}));ql.render=Tl,ql.__scopeId="data-v-115f004a";var jl={name:"FileItem",emits:["foldUnfoldClick"],props:{item:Object},data:()=>({hover:!1}),inject:["wcfm"],methods:{getWCFM(){return this.wcfm},getFullPath(){for(var e=this.$parent,t=[];null!==e&&null!==e.$parent;)e.item&&t.push(e.item.name),e=e.$parent;return t.reverse().join("/")},infoClick(){this.getWCFM().displayInfo(this.getFullPath())},convertClick(){this.getWCFM().onConvertClick(this.getFullPath())}}};const Nl=Nt("data-v-a2787766");Tt("data-v-a2787766");const Bl={key:0,class:"icon-fold"},Hl=Jn("use",{"xlink:href":"#icon-fold"},null,-1),Wl={key:1,class:"icon-unfold"},Kl=Jn("use",{"xlink:href":"#icon-unfold"},null,-1),Ql=Jn("use",{"xlink:href":"#icon-folder"},null,-1),Gl={key:2,class:"icon-file"},Yl=Jn("use",{"xlink:href":"#icon-file"},null,-1),Jl={class:"buttons"};jt();const Xl=Nl((function(e,t,n,o,l,s){return Nn(),Hn("div",{class:"fileitem",onMouseover:t[5]||(t[5]=e=>l.hover=!0),onMouseleave:t[6]||(t[6]=e=>l.hover=!1)},[Jn("p",null,[n.item.isDir?(Nn(),Hn("span",{key:0,class:"foldUnfold",onClick:t[1]||(t[1]=e=>this.$emit("foldUnfoldClick"))},[n.item.isOpen?(Nn(),Hn("svg",Bl,[Hl])):eo("",!0),n.item.isOpen?eo("",!0):(Nn(),Hn("svg",Wl,[Kl]))])):eo("",!0),n.item.isDir?(Nn(),Hn("svg",{key:1,class:"icon-folder",onClick:t[2]||(t[2]=e=>this.$emit("foldUnfoldClick"))},[Ql])):eo("",!0),n.item.isDir?eo("",!0):(Nn(),Hn("svg",Gl,[Yl])),Zn(" "+u(n.item.name)+" ",1),Jn("div",Jl,[l.hover?(Nn(),Hn("button",{key:0,onClick:t[3]||(t[3]=ol((e=>s.infoClick()),["stop"]))},"Info")):eo("",!0),l.hover?(Nn(),Hn("button",{key:1,onClick:t[4]||(t[4]=ol((e=>s.convertClick()),["stop"])),innerHTML:n.item.isConverted?"Reconvert":"Convert"},null,8,["innerHTML"])):eo("",!0)])])],32)}));jl.render=Xl,jl.__scopeId="data-v-a2787766";var Zl={name:"FileTree",components:{FileItem:jl},props:{item:Object},methods:{toggle(){this.item.isOpen=!this.item.isOpen}}};const es={key:0,class:"tree"};Zl.render=function(e,t,n,o,l,s){const i=Pn("FileItem"),r=Pn("FileTree");return Nn(),Hn(zn,null,[Jn(i,{item:n.item,onDblclick:t[1]||(t[1]=e=>s.toggle()),onClick:t[2]||(t[2]=e=>s.toggle())},null,8,["item"]),void 0!==n.item.children&&n.item.isOpen?(Nn(),Hn("ul",es,[(Nn(!0),Hn(zn,null,Eo(n.item.children,(e=>(Nn(),Hn("li",null,[Jn(r,{item:e},null,8,["item"])])))),256))])):eo("",!0)],64)};var ts={name:"InfoPane",components:{},props:{info:Object},methods:{}};const ns={key:0},os=Jn("h2",null,"Original",-1),ls=Jn("br",null,null,-1),ss=Jn("br",null,null,-1),is={key:1},rs=Jn("h2",null,"Converted",-1),cs=Jn("br",null,null,-1),as=Jn("br",null,null,-1),us={key:2},ds=Jn("h2",null,"Conversion log",-1);ts.render=function(e,t,n,o,l,s){return Nn(),Hn(zn,null,[n.info.original?(Nn(),Hn("div",ns,[os,Zn(" Filename: "+u(n.info.original.name),1),ls,Zn(" Size: "+u(n.info.original.size),1),ss])):eo("",!0),n.info.converted?(Nn(),Hn("div",is,[rs,Zn(" Filename: "+u(n.info.converted.name),1),cs,Zn(" Size: "+u(n.info.converted.size),1),as])):eo("",!0),n.info.log?(Nn(),Hn("div",us,[ds,Zn(" "+u(n.info.log),1)])):eo("",!0)],64)};var ps={name:"multipane",props:{layout:{type:String,default:"vertical"}},data:()=>({isResizing:!1}),computed:{classnames(){return["multipane","layout-"+this.layout.slice(0,1),this.isResizing?"is-resizing":""]},cursor(){return this.isResizing?"vertical"==this.layout?"col-resize":"row-resize":""},userSelect(){return this.isResizing?"none":""}},methods:{onMouseDown({target:e,pageX:t,pageY:n}){if("string"==typeof e.className&&e.className.match("multipane-resizer")){let o=this,{$el:l,layout:s}=o,i=e.previousElementSibling,{offsetWidth:r,offsetHeight:c}=i,a=!!(i.style.width+"").match("%");const{addEventListener:u,removeEventListener:d}=window,p=(e,t=0)=>{if(t-=30,"vertical"==s){let n=l.clientWidth,o=e+t;return i.style.width=a?o/n*100+"%":o+"px"}if("horizontal"==s){let n=l.clientHeight,o=e+t;return i.style.height=a?o/n*100+"%":o+"px"}};o.isResizing=!0;let f=p();o.$emit("paneResizeStart",i,e,f);const h=function({pageX:l,pageY:a}){f="vertical"==s?p(r,l-t):p(c,a-n),o.$emit("paneResize",i,e,f)},m=function(){f=p("vertical"==s?i.clientWidth:i.clientHeight),o.isResizing=!1,d("mousemove",h),d("mouseup",m),o.$emit("paneResizeStop",i,e,f)};u("mousemove",h),u("mouseup",m)}}}};ps.render=function(e,t,n,o,l,s){return Nn(),Hn("div",{class:s.classnames,style:{cursor:s.cursor,userSelect:s.userSelect},onMousedown:t[1]||(t[1]=(...e)=>s.onMouseDown(...e))},[zt(e.$slots,"default")],38)};const fs={class:"multipane-resizer"},hs=Jn("div",null,null,-1);const ms={render:function(e,t){return Nn(),Hn("div",fs,[hs,zt(e.$slots,"default")])}};var vs={name:"WCFM",components:{SVGs:dl,ConvertOptions:ql,FileTree:Zl,InfoPane:ts,Multipane:ps,MultipaneResizer:ms},methods:{onConvertClick(e){alert(e)},displayInfo(e){var t=this;cl.post("info",{path:e},(function(e){t.selectedInfo=e}))}},mounted(){var e=this;cl.post("get-tree",{folder:""},(function(t){e.item=t}))},data:()=>({selectedPath:null,selectedItem:null,item:{},selectedInfo:{}}),provide(){return{wcfm:this}}};const gs={class:"pane",style:{width:"48%"}},ys={class:"pane",style:{flexGrow:1}},bs=Jn("hr",null,null,-1);vs.render=function(e,t,n,o,l,s){const i=Pn("SVGs"),r=Pn("FileTree"),c=Pn("multipane-resizer"),a=Pn("ConvertOptions"),u=Pn("InfoPane"),d=Pn("multipane");return Nn(),Hn(zn,null,[Jn(i),Jn(d,{class:"mainpanel",layout:"vertical"},{default:Dt((()=>[Jn("div",gs,[Jn(r,{item:l.item},null,8,["item"])]),Jn(c),Jn("div",ys,[Jn(a),bs,Jn(u,{info:l.selectedInfo},null,8,["info"])])])),_:1})],64)};const _s=((...e)=>{const t=(rl||(rl=Fn(il))).createApp(...e),{mount:n}=t;return t.mount=e=>{const o=function(e){if(E(e)){return document.querySelector(e)}return e}(e);if(!o)return;const l=t._component;V(l)||l.render||l.template||(l.template=o.innerHTML),o.innerHTML="";const s=n(o);return o.removeAttribute("v-cloak"),o.setAttribute("data-v-app",""),s},t})(vs);window.wcfmoptions||(window.wcfmoptions={},window.wcfmoptions.poster=function(e,t,n,o){switch(e){case"get-tree":var l={name:"root",isDir:!0,isOpen:!0,children:[{name:"folder",isDir:!0,children:[{name:"file",isDir:!1},{name:"subfolder",isDir:!0,children:[{name:"file",isDir:!1}]},{name:"file",isDir:!1}]},{name:"file",isDir:!1,isConverted:!0},{name:"file2",isDir:!1,isConverted:!1}]};break;case"conversion-settings":l={converters:[{id:"cwebp",name:"cwebp"},{id:"vips",name:"vips"},{id:"ewww",name:"ewww"},{id:"gd",name:"gd"}],supportedStandardOptions:{encoding:["cwebp","vips","imagick","gmagick","imagemagick","graphicsmagick","ffmpeg","wpc"],method:["cwebp","imagick","gmagick","imagemagick","graphicsmagick","ffmpeg","wpc"],nearLossless:["cwebp","vips","wpc"]},overrideDefaults:{converter:"cwebp",encoding:"lossless",method:3},systemStatus:{converterRequirements:{gd:{extensionLoaded:!1,compiledWithWebP:!0}}}};break;case"info":l={original:{name:"file.png",size:100,url:""},converted:{name:"file.png.webp",size:70,url:""},log:"blah blah blah"};break;default:l="ok"}n(l)}),_s.mount("#webpconvert-filemanager");
vendor/composer/ClassLoader.php CHANGED
@@ -37,11 +37,13 @@ namespace Composer\Autoload;
37
  *
38
  * @author Fabien Potencier <fabien@symfony.com>
39
  * @author Jordi Boggiano <j.boggiano@seld.be>
40
- * @see http://www.php-fig.org/psr/psr-0/
41
- * @see http://www.php-fig.org/psr/psr-4/
42
  */
43
  class ClassLoader
44
  {
 
 
45
  // PSR-4
46
  private $prefixLengthsPsr4 = array();
47
  private $prefixDirsPsr4 = array();
@@ -57,10 +59,17 @@ class ClassLoader
57
  private $missingClasses = array();
58
  private $apcuPrefix;
59
 
 
 
 
 
 
 
 
60
  public function getPrefixes()
61
  {
62
  if (!empty($this->prefixesPsr0)) {
63
- return call_user_func_array('array_merge', $this->prefixesPsr0);
64
  }
65
 
66
  return array();
@@ -300,6 +309,17 @@ class ClassLoader
300
  public function register($prepend = false)
301
  {
302
  spl_autoload_register(array($this, 'loadClass'), true, $prepend);
 
 
 
 
 
 
 
 
 
 
 
303
  }
304
 
305
  /**
@@ -308,13 +328,17 @@ class ClassLoader
308
  public function unregister()
309
  {
310
  spl_autoload_unregister(array($this, 'loadClass'));
 
 
 
 
311
  }
312
 
313
  /**
314
  * Loads the given class or interface.
315
  *
316
  * @param string $class The name of the class
317
- * @return bool|null True if loaded, null otherwise
318
  */
319
  public function loadClass($class)
320
  {
@@ -323,6 +347,8 @@ class ClassLoader
323
 
324
  return true;
325
  }
 
 
326
  }
327
 
328
  /**
@@ -367,6 +393,16 @@ class ClassLoader
367
  return $file;
368
  }
369
 
 
 
 
 
 
 
 
 
 
 
370
  private function findFileWithExtension($class, $ext)
371
  {
372
  // PSR-4 lookup
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();
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();
309
  public function register($prepend = false)
310
  {
311
  spl_autoload_register(array($this, 'loadClass'), true, $prepend);
312
+
313
+ if (null === $this->vendorDir) {
314
+ return;
315
+ }
316
+
317
+ if ($prepend) {
318
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
319
+ } else {
320
+ unset(self::$registeredLoaders[$this->vendorDir]);
321
+ self::$registeredLoaders[$this->vendorDir] = $this;
322
+ }
323
  }
324
 
325
  /**
328
  public function unregister()
329
  {
330
  spl_autoload_unregister(array($this, 'loadClass'));
331
+
332
+ if (null !== $this->vendorDir) {
333
+ unset(self::$registeredLoaders[$this->vendorDir]);
334
+ }
335
  }
336
 
337
  /**
338
  * Loads the given class or interface.
339
  *
340
  * @param string $class The name of the class
341
+ * @return true|null True if loaded, null otherwise
342
  */
343
  public function loadClass($class)
344
  {
347
 
348
  return true;
349
  }
350
+
351
+ return null;
352
  }
353
 
354
  /**
393
  return $file;
394
  }
395
 
396
+ /**
397
+ * Returns the currently registered loaders indexed by their corresponding vendor directories.
398
+ *
399
+ * @return self[]
400
+ */
401
+ public static function getRegisteredLoaders()
402
+ {
403
+ return self::$registeredLoaders;
404
+ }
405
+
406
  private function findFileWithExtension($class, $ext)
407
  {
408
  // PSR-4 lookup
vendor/composer/InstalledVersions.php ADDED
@@ -0,0 +1,373 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+ namespace Composer;
14
+
15
+ use Composer\Autoload\ClassLoader;
16
+ use Composer\Semver\VersionParser;
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+ class InstalledVersions
26
+ {
27
+ private static $installed = array (
28
+ 'root' =>
29
+ array (
30
+ 'pretty_version' => 'dev-master',
31
+ 'version' => 'dev-master',
32
+ 'aliases' =>
33
+ array (
34
+ ),
35
+ 'reference' => 'acd5aaef5298d5d01345c7fa95723a3f83486412',
36
+ 'name' => 'rosell-dk/webp-express',
37
+ 'dev' => true,
38
+ ),
39
+ 'versions' =>
40
+ array (
41
+ 'composer/installers' =>
42
+ array (
43
+ 'pretty_version' => 'v1.11.0',
44
+ 'version' => '1.11.0.0',
45
+ 'aliases' =>
46
+ array (
47
+ ),
48
+ 'reference' => 'ae03311f45dfe194412081526be2e003960df74b',
49
+ 'dev-requirement' => false,
50
+ ),
51
+ 'onnov/detect-encoding' =>
52
+ array (
53
+ 'pretty_version' => 'v1.2.0',
54
+ 'version' => '1.2.0.0',
55
+ 'aliases' =>
56
+ array (
57
+ ),
58
+ 'reference' => 'c88cea4f0c83d7f7e0675f2801e0995f328de1ce',
59
+ 'dev-requirement' => false,
60
+ ),
61
+ 'rosell-dk/dom-util-for-webp' =>
62
+ array (
63
+ 'pretty_version' => '0.4.0',
64
+ 'version' => '0.4.0.0',
65
+ 'aliases' =>
66
+ array (
67
+ ),
68
+ 'reference' => '5928aecf64d59124b341dce23ce8ecf48a6eded6',
69
+ 'dev-requirement' => false,
70
+ ),
71
+ 'rosell-dk/htaccess-capability-tester' =>
72
+ array (
73
+ 'pretty_version' => '0.9',
74
+ 'version' => '0.9.0.0',
75
+ 'aliases' =>
76
+ array (
77
+ ),
78
+ 'reference' => '2eb2cf38a9f42fc3aa647d6e9c896e124bfebf4c',
79
+ 'dev-requirement' => false,
80
+ ),
81
+ 'rosell-dk/image-mime-type-guesser' =>
82
+ array (
83
+ 'pretty_version' => '0.3',
84
+ 'version' => '0.3.0.0',
85
+ 'aliases' =>
86
+ array (
87
+ ),
88
+ 'reference' => '204fd61ca81e3b0ba46c6165dab8f74816b1fe99',
89
+ 'dev-requirement' => false,
90
+ ),
91
+ 'rosell-dk/webp-convert' =>
92
+ array (
93
+ 'pretty_version' => '2.6.0',
94
+ 'version' => '2.6.0.0',
95
+ 'aliases' =>
96
+ array (
97
+ ),
98
+ 'reference' => 'ed230afe56d3157dc402c33585e3ab7f15c7ac80',
99
+ 'dev-requirement' => false,
100
+ ),
101
+ 'rosell-dk/webp-convert-cloud-service' =>
102
+ array (
103
+ 'pretty_version' => '2.0.1',
104
+ 'version' => '2.0.1.0',
105
+ 'aliases' =>
106
+ array (
107
+ ),
108
+ 'reference' => '703c2f1c76d30468ee3977170bfa3da138d8c4ad',
109
+ 'dev-requirement' => false,
110
+ ),
111
+ 'rosell-dk/webp-express' =>
112
+ array (
113
+ 'pretty_version' => 'dev-master',
114
+ 'version' => 'dev-master',
115
+ 'aliases' =>
116
+ array (
117
+ ),
118
+ 'reference' => 'acd5aaef5298d5d01345c7fa95723a3f83486412',
119
+ 'dev-requirement' => false,
120
+ ),
121
+ 'roundcube/plugin-installer' =>
122
+ array (
123
+ 'dev-requirement' => false,
124
+ 'replaced' =>
125
+ array (
126
+ 0 => '*',
127
+ ),
128
+ ),
129
+ 'shama/baton' =>
130
+ array (
131
+ 'dev-requirement' => false,
132
+ 'replaced' =>
133
+ array (
134
+ 0 => '*',
135
+ ),
136
+ ),
137
+ ),
138
+ );
139
+ private static $canGetVendors;
140
+ private static $installedByVendor = array();
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+ public static function getInstalledPackages()
149
+ {
150
+ $packages = array();
151
+ foreach (self::getInstalled() as $installed) {
152
+ $packages[] = array_keys($installed['versions']);
153
+ }
154
+
155
+ if (1 === \count($packages)) {
156
+ return $packages[0];
157
+ }
158
+
159
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
160
+ }
161
+
162
+
163
+
164
+
165
+
166
+
167
+
168
+
169
+
170
+
171
+ public static function isInstalled($packageName, $includeDevRequirements = true)
172
+ {
173
+ foreach (self::getInstalled() as $installed) {
174
+ if (isset($installed['versions'][$packageName])) {
175
+ return $includeDevRequirements || empty($installed['versions'][$packageName]['dev-requirement']);
176
+ }
177
+ }
178
+
179
+ return false;
180
+ }
181
+
182
+
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
194
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
195
+ {
196
+ $constraint = $parser->parseConstraints($constraint);
197
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
198
+
199
+ return $provided->matches($constraint);
200
+ }
201
+
202
+
203
+
204
+
205
+
206
+
207
+
208
+
209
+
210
+
211
+ public static function getVersionRanges($packageName)
212
+ {
213
+ foreach (self::getInstalled() as $installed) {
214
+ if (!isset($installed['versions'][$packageName])) {
215
+ continue;
216
+ }
217
+
218
+ $ranges = array();
219
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
220
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
221
+ }
222
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
223
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
224
+ }
225
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
226
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
227
+ }
228
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
229
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
230
+ }
231
+
232
+ return implode(' || ', $ranges);
233
+ }
234
+
235
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
236
+ }
237
+
238
+
239
+
240
+
241
+
242
+ public static function getVersion($packageName)
243
+ {
244
+ foreach (self::getInstalled() as $installed) {
245
+ if (!isset($installed['versions'][$packageName])) {
246
+ continue;
247
+ }
248
+
249
+ if (!isset($installed['versions'][$packageName]['version'])) {
250
+ return null;
251
+ }
252
+
253
+ return $installed['versions'][$packageName]['version'];
254
+ }
255
+
256
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
257
+ }
258
+
259
+
260
+
261
+
262
+
263
+ public static function getPrettyVersion($packageName)
264
+ {
265
+ foreach (self::getInstalled() as $installed) {
266
+ if (!isset($installed['versions'][$packageName])) {
267
+ continue;
268
+ }
269
+
270
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
271
+ return null;
272
+ }
273
+
274
+ return $installed['versions'][$packageName]['pretty_version'];
275
+ }
276
+
277
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
278
+ }
279
+
280
+
281
+
282
+
283
+
284
+ public static function getReference($packageName)
285
+ {
286
+ foreach (self::getInstalled() as $installed) {
287
+ if (!isset($installed['versions'][$packageName])) {
288
+ continue;
289
+ }
290
+
291
+ if (!isset($installed['versions'][$packageName]['reference'])) {
292
+ return null;
293
+ }
294
+
295
+ return $installed['versions'][$packageName]['reference'];
296
+ }
297
+
298
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
299
+ }
300
+
301
+
302
+
303
+
304
+
305
+ public static function getRootPackage()
306
+ {
307
+ $installed = self::getInstalled();
308
+
309
+ return $installed[0]['root'];
310
+ }
311
+
312
+
313
+
314
+
315
+
316
+
317
+
318
+ public static function getRawData()
319
+ {
320
+ return self::$installed;
321
+ }
322
+
323
+
324
+
325
+
326
+
327
+
328
+
329
+
330
+
331
+
332
+
333
+
334
+
335
+
336
+
337
+
338
+
339
+
340
+
341
+ public static function reload($data)
342
+ {
343
+ self::$installed = $data;
344
+ self::$installedByVendor = array();
345
+ }
346
+
347
+
348
+
349
+
350
+
351
+ private static function getInstalled()
352
+ {
353
+ if (null === self::$canGetVendors) {
354
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
355
+ }
356
+
357
+ $installed = array();
358
+
359
+ if (self::$canGetVendors) {
360
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
361
+ if (isset(self::$installedByVendor[$vendorDir])) {
362
+ $installed[] = self::$installedByVendor[$vendorDir];
363
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
364
+ $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
365
+ }
366
+ }
367
+ }
368
+
369
+ $installed[] = self::$installed;
370
+
371
+ return $installed;
372
+ }
373
+ }
vendor/composer/autoload_classmap.php CHANGED
@@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
6
  $baseDir = dirname($vendorDir);
7
 
8
  return array(
 
9
  'Composer\\Installers\\AglInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AglInstaller.php',
10
  'Composer\\Installers\\AimeosInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AimeosInstaller.php',
11
  'Composer\\Installers\\AnnotateCmsInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AnnotateCmsInstaller.php',
@@ -58,6 +59,7 @@ return array(
58
  'Composer\\Installers\\MauticInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MauticInstaller.php',
59
  'Composer\\Installers\\MayaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MayaInstaller.php',
60
  'Composer\\Installers\\MediaWikiInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MediaWikiInstaller.php',
 
61
  'Composer\\Installers\\MicroweberInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MicroweberInstaller.php',
62
  'Composer\\Installers\\ModxInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ModxInstaller.php',
63
  'Composer\\Installers\\MoodleInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MoodleInstaller.php',
@@ -74,6 +76,7 @@ return array(
74
  'Composer\\Installers\\Plugin' => $vendorDir . '/composer/installers/src/Composer/Installers/Plugin.php',
75
  'Composer\\Installers\\PortoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PortoInstaller.php',
76
  'Composer\\Installers\\PrestashopInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PrestashopInstaller.php',
 
77
  'Composer\\Installers\\PuppetInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PuppetInstaller.php',
78
  'Composer\\Installers\\PxcmsInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PxcmsInstaller.php',
79
  'Composer\\Installers\\RadPHPInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/RadPHPInstaller.php',
@@ -85,18 +88,21 @@ return array(
85
  'Composer\\Installers\\ShopwareInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ShopwareInstaller.php',
86
  'Composer\\Installers\\SilverStripeInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SilverStripeInstaller.php',
87
  'Composer\\Installers\\SiteDirectInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SiteDirectInstaller.php',
 
88
  'Composer\\Installers\\SyDESInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SyDESInstaller.php',
89
  'Composer\\Installers\\SyliusInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SyliusInstaller.php',
90
  'Composer\\Installers\\Symfony1Installer' => $vendorDir . '/composer/installers/src/Composer/Installers/Symfony1Installer.php',
91
  'Composer\\Installers\\TYPO3CmsInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TYPO3CmsInstaller.php',
92
  'Composer\\Installers\\TYPO3FlowInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TYPO3FlowInstaller.php',
93
  'Composer\\Installers\\TaoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TaoInstaller.php',
 
94
  'Composer\\Installers\\TheliaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TheliaInstaller.php',
95
  'Composer\\Installers\\TuskInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TuskInstaller.php',
96
  'Composer\\Installers\\UserFrostingInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/UserFrostingInstaller.php',
97
  'Composer\\Installers\\VanillaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/VanillaInstaller.php',
98
  'Composer\\Installers\\VgmcpInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/VgmcpInstaller.php',
99
  'Composer\\Installers\\WHMCSInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/WHMCSInstaller.php',
 
100
  'Composer\\Installers\\WolfCMSInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/WolfCMSInstaller.php',
101
  'Composer\\Installers\\WordPressInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/WordPressInstaller.php',
102
  'Composer\\Installers\\YawikInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/YawikInstaller.php',
6
  $baseDir = dirname($vendorDir);
7
 
8
  return array(
9
+ 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
10
  'Composer\\Installers\\AglInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AglInstaller.php',
11
  'Composer\\Installers\\AimeosInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AimeosInstaller.php',
12
  'Composer\\Installers\\AnnotateCmsInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AnnotateCmsInstaller.php',
59
  'Composer\\Installers\\MauticInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MauticInstaller.php',
60
  'Composer\\Installers\\MayaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MayaInstaller.php',
61
  'Composer\\Installers\\MediaWikiInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MediaWikiInstaller.php',
62
+ 'Composer\\Installers\\MiaoxingInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MiaoxingInstaller.php',
63
  'Composer\\Installers\\MicroweberInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MicroweberInstaller.php',
64
  'Composer\\Installers\\ModxInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ModxInstaller.php',
65
  'Composer\\Installers\\MoodleInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MoodleInstaller.php',
76
  'Composer\\Installers\\Plugin' => $vendorDir . '/composer/installers/src/Composer/Installers/Plugin.php',
77
  'Composer\\Installers\\PortoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PortoInstaller.php',
78
  'Composer\\Installers\\PrestashopInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PrestashopInstaller.php',
79
+ 'Composer\\Installers\\ProcessWireInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ProcessWireInstaller.php',
80
  'Composer\\Installers\\PuppetInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PuppetInstaller.php',
81
  'Composer\\Installers\\PxcmsInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PxcmsInstaller.php',
82
  'Composer\\Installers\\RadPHPInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/RadPHPInstaller.php',
88
  'Composer\\Installers\\ShopwareInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ShopwareInstaller.php',
89
  'Composer\\Installers\\SilverStripeInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SilverStripeInstaller.php',
90
  'Composer\\Installers\\SiteDirectInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SiteDirectInstaller.php',
91
+ 'Composer\\Installers\\StarbugInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/StarbugInstaller.php',
92
  'Composer\\Installers\\SyDESInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SyDESInstaller.php',
93
  'Composer\\Installers\\SyliusInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SyliusInstaller.php',
94
  'Composer\\Installers\\Symfony1Installer' => $vendorDir . '/composer/installers/src/Composer/Installers/Symfony1Installer.php',
95
  'Composer\\Installers\\TYPO3CmsInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TYPO3CmsInstaller.php',
96
  'Composer\\Installers\\TYPO3FlowInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TYPO3FlowInstaller.php',
97
  'Composer\\Installers\\TaoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TaoInstaller.php',
98
+ 'Composer\\Installers\\TastyIgniterInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TastyIgniterInstaller.php',
99
  'Composer\\Installers\\TheliaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TheliaInstaller.php',
100
  'Composer\\Installers\\TuskInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TuskInstaller.php',
101
  'Composer\\Installers\\UserFrostingInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/UserFrostingInstaller.php',
102
  'Composer\\Installers\\VanillaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/VanillaInstaller.php',
103
  'Composer\\Installers\\VgmcpInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/VgmcpInstaller.php',
104
  'Composer\\Installers\\WHMCSInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/WHMCSInstaller.php',
105
+ 'Composer\\Installers\\WinterInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/WinterInstaller.php',
106
  'Composer\\Installers\\WolfCMSInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/WolfCMSInstaller.php',
107
  'Composer\\Installers\\WordPressInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/WordPressInstaller.php',
108
  'Composer\\Installers\\YawikInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/YawikInstaller.php',
vendor/composer/autoload_real.php CHANGED
@@ -13,19 +13,24 @@ class ComposerAutoloaderInit16597e36dd1bfcd787ed5a8e6d908243
13
  }
14
  }
15
 
 
 
 
16
  public static function getLoader()
17
  {
18
  if (null !== self::$loader) {
19
  return self::$loader;
20
  }
21
 
 
 
22
  spl_autoload_register(array('ComposerAutoloaderInit16597e36dd1bfcd787ed5a8e6d908243', 'loadClassLoader'), true, true);
23
- self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
  spl_autoload_unregister(array('ComposerAutoloaderInit16597e36dd1bfcd787ed5a8e6d908243', 'loadClassLoader'));
25
 
26
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
27
  if ($useStaticLoader) {
28
- require_once __DIR__ . '/autoload_static.php';
29
 
30
  call_user_func(\Composer\Autoload\ComposerStaticInit16597e36dd1bfcd787ed5a8e6d908243::getInitializer($loader));
31
  } else {
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('ComposerAutoloaderInit16597e36dd1bfcd787ed5a8e6d908243', 'loadClassLoader'), true, true);
28
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
29
  spl_autoload_unregister(array('ComposerAutoloaderInit16597e36dd1bfcd787ed5a8e6d908243', '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\ComposerStaticInit16597e36dd1bfcd787ed5a8e6d908243::getInitializer($loader));
36
  } else {
vendor/composer/autoload_static.php CHANGED
@@ -66,6 +66,7 @@ class ComposerStaticInit16597e36dd1bfcd787ed5a8e6d908243
66
  );
67
 
68
  public static $classMap = array (
 
69
  'Composer\\Installers\\AglInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AglInstaller.php',
70
  'Composer\\Installers\\AimeosInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AimeosInstaller.php',
71
  'Composer\\Installers\\AnnotateCmsInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AnnotateCmsInstaller.php',
@@ -118,6 +119,7 @@ class ComposerStaticInit16597e36dd1bfcd787ed5a8e6d908243
118
  'Composer\\Installers\\MauticInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MauticInstaller.php',
119
  'Composer\\Installers\\MayaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MayaInstaller.php',
120
  'Composer\\Installers\\MediaWikiInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MediaWikiInstaller.php',
 
121
  'Composer\\Installers\\MicroweberInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MicroweberInstaller.php',
122
  'Composer\\Installers\\ModxInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ModxInstaller.php',
123
  'Composer\\Installers\\MoodleInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MoodleInstaller.php',
@@ -134,6 +136,7 @@ class ComposerStaticInit16597e36dd1bfcd787ed5a8e6d908243
134
  'Composer\\Installers\\Plugin' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/Plugin.php',
135
  'Composer\\Installers\\PortoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PortoInstaller.php',
136
  'Composer\\Installers\\PrestashopInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PrestashopInstaller.php',
 
137
  'Composer\\Installers\\PuppetInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PuppetInstaller.php',
138
  'Composer\\Installers\\PxcmsInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PxcmsInstaller.php',
139
  'Composer\\Installers\\RadPHPInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/RadPHPInstaller.php',
@@ -145,18 +148,21 @@ class ComposerStaticInit16597e36dd1bfcd787ed5a8e6d908243
145
  'Composer\\Installers\\ShopwareInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ShopwareInstaller.php',
146
  'Composer\\Installers\\SilverStripeInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SilverStripeInstaller.php',
147
  'Composer\\Installers\\SiteDirectInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SiteDirectInstaller.php',
 
148
  'Composer\\Installers\\SyDESInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SyDESInstaller.php',
149
  'Composer\\Installers\\SyliusInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SyliusInstaller.php',
150
  'Composer\\Installers\\Symfony1Installer' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/Symfony1Installer.php',
151
  'Composer\\Installers\\TYPO3CmsInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TYPO3CmsInstaller.php',
152
  'Composer\\Installers\\TYPO3FlowInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TYPO3FlowInstaller.php',
153
  'Composer\\Installers\\TaoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TaoInstaller.php',
 
154
  'Composer\\Installers\\TheliaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TheliaInstaller.php',
155
  'Composer\\Installers\\TuskInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TuskInstaller.php',
156
  'Composer\\Installers\\UserFrostingInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/UserFrostingInstaller.php',
157
  'Composer\\Installers\\VanillaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/VanillaInstaller.php',
158
  'Composer\\Installers\\VgmcpInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/VgmcpInstaller.php',
159
  'Composer\\Installers\\WHMCSInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/WHMCSInstaller.php',
 
160
  'Composer\\Installers\\WolfCMSInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/WolfCMSInstaller.php',
161
  'Composer\\Installers\\WordPressInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/WordPressInstaller.php',
162
  'Composer\\Installers\\YawikInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/YawikInstaller.php',
66
  );
67
 
68
  public static $classMap = array (
69
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
70
  'Composer\\Installers\\AglInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AglInstaller.php',
71
  'Composer\\Installers\\AimeosInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AimeosInstaller.php',
72
  'Composer\\Installers\\AnnotateCmsInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AnnotateCmsInstaller.php',
119
  'Composer\\Installers\\MauticInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MauticInstaller.php',
120
  'Composer\\Installers\\MayaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MayaInstaller.php',
121
  'Composer\\Installers\\MediaWikiInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MediaWikiInstaller.php',
122
+ 'Composer\\Installers\\MiaoxingInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MiaoxingInstaller.php',
123
  'Composer\\Installers\\MicroweberInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MicroweberInstaller.php',
124
  'Composer\\Installers\\ModxInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ModxInstaller.php',
125
  'Composer\\Installers\\MoodleInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MoodleInstaller.php',
136
  'Composer\\Installers\\Plugin' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/Plugin.php',
137
  'Composer\\Installers\\PortoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PortoInstaller.php',
138
  'Composer\\Installers\\PrestashopInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PrestashopInstaller.php',
139
+ 'Composer\\Installers\\ProcessWireInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ProcessWireInstaller.php',
140
  'Composer\\Installers\\PuppetInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PuppetInstaller.php',
141
  'Composer\\Installers\\PxcmsInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PxcmsInstaller.php',
142
  'Composer\\Installers\\RadPHPInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/RadPHPInstaller.php',
148
  'Composer\\Installers\\ShopwareInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ShopwareInstaller.php',
149
  'Composer\\Installers\\SilverStripeInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SilverStripeInstaller.php',
150
  'Composer\\Installers\\SiteDirectInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SiteDirectInstaller.php',
151
+ 'Composer\\Installers\\StarbugInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/StarbugInstaller.php',
152
  'Composer\\Installers\\SyDESInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SyDESInstaller.php',
153
  'Composer\\Installers\\SyliusInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SyliusInstaller.php',
154
  'Composer\\Installers\\Symfony1Installer' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/Symfony1Installer.php',
155
  'Composer\\Installers\\TYPO3CmsInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TYPO3CmsInstaller.php',
156
  'Composer\\Installers\\TYPO3FlowInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TYPO3FlowInstaller.php',
157
  'Composer\\Installers\\TaoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TaoInstaller.php',
158
+ 'Composer\\Installers\\TastyIgniterInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TastyIgniterInstaller.php',
159
  'Composer\\Installers\\TheliaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TheliaInstaller.php',
160
  'Composer\\Installers\\TuskInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TuskInstaller.php',
161
  'Composer\\Installers\\UserFrostingInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/UserFrostingInstaller.php',
162
  'Composer\\Installers\\VanillaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/VanillaInstaller.php',
163
  'Composer\\Installers\\VgmcpInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/VgmcpInstaller.php',
164
  'Composer\\Installers\\WHMCSInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/WHMCSInstaller.php',
165
+ 'Composer\\Installers\\WinterInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/WinterInstaller.php',
166
  'Composer\\Installers\\WolfCMSInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/WolfCMSInstaller.php',
167
  'Composer\\Installers\\WordPressInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/WordPressInstaller.php',
168
  'Composer\\Installers\\YawikInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/YawikInstaller.php',
vendor/composer/installed.json CHANGED
@@ -1,516 +1,564 @@
1
- [
2
- {
3
- "name": "composer/installers",
4
- "version": "v1.9.0",
5
- "version_normalized": "1.9.0.0",
6
- "source": {
7
- "type": "git",
8
- "url": "https://github.com/composer/installers.git",
9
- "reference": "b93bcf0fa1fccb0b7d176b0967d969691cd74cca"
10
- },
11
- "dist": {
12
- "type": "zip",
13
- "url": "https://api.github.com/repos/composer/installers/zipball/b93bcf0fa1fccb0b7d176b0967d969691cd74cca",
14
- "reference": "b93bcf0fa1fccb0b7d176b0967d969691cd74cca",
15
- "shasum": ""
16
- },
17
- "require": {
18
- "composer-plugin-api": "^1.0 || ^2.0"
19
- },
20
- "replace": {
21
- "roundcube/plugin-installer": "*",
22
- "shama/baton": "*"
23
- },
24
- "require-dev": {
25
- "composer/composer": "1.6.* || 2.0.*@dev",
26
- "composer/semver": "1.0.* || 2.0.*@dev",
27
- "phpunit/phpunit": "^4.8.36",
28
- "sebastian/comparator": "^1.2.4",
29
- "symfony/process": "^2.3"
30
- },
31
- "time": "2020-04-07T06:57:05+00:00",
32
- "type": "composer-plugin",
33
- "extra": {
34
- "class": "Composer\\Installers\\Plugin",
35
- "branch-alias": {
36
- "dev-master": "1.0-dev"
37
- }
38
- },
39
- "installation-source": "dist",
40
- "autoload": {
41
- "psr-4": {
42
- "Composer\\Installers\\": "src/Composer/Installers"
43
- }
44
- },
45
- "notification-url": "https://packagist.org/downloads/",
46
- "license": [
47
- "MIT"
48
- ],
49
- "authors": [
50
- {
51
- "name": "Kyle Robinson Young",
52
- "email": "kyle@dontkry.com",
53
- "homepage": "https://github.com/shama"
54
- }
55
- ],
56
- "description": "A multi-framework Composer library installer",
57
- "homepage": "https://composer.github.io/installers/",
58
- "keywords": [
59
- "Craft",
60
- "Dolibarr",
61
- "Eliasis",
62
- "Hurad",
63
- "ImageCMS",
64
- "Kanboard",
65
- "Lan Management System",
66
- "MODX Evo",
67
- "MantisBT",
68
- "Mautic",
69
- "Maya",
70
- "OXID",
71
- "Plentymarkets",
72
- "Porto",
73
- "RadPHP",
74
- "SMF",
75
- "Thelia",
76
- "Whmcs",
77
- "WolfCMS",
78
- "agl",
79
- "aimeos",
80
- "annotatecms",
81
- "attogram",
82
- "bitrix",
83
- "cakephp",
84
- "chef",
85
- "cockpit",
86
- "codeigniter",
87
- "concrete5",
88
- "croogo",
89
- "dokuwiki",
90
- "drupal",
91
- "eZ Platform",
92
- "elgg",
93
- "expressionengine",
94
- "fuelphp",
95
- "grav",
96
- "installer",
97
- "itop",
98
- "joomla",
99
- "known",
100
- "kohana",
101
- "laravel",
102
- "lavalite",
103
- "lithium",
104
- "magento",
105
- "majima",
106
- "mako",
107
- "mediawiki",
108
- "modulework",
109
- "modx",
110
- "moodle",
111
- "osclass",
112
- "phpbb",
113
- "piwik",
114
- "ppi",
115
- "puppet",
116
- "pxcms",
117
- "reindex",
118
- "roundcube",
119
- "shopware",
120
- "silverstripe",
121
- "sydes",
122
- "sylius",
123
- "symfony",
124
- "typo3",
125
- "wordpress",
126
- "yawik",
127
- "zend",
128
- "zikula"
129
- ]
130
- },
131
- {
132
- "name": "onnov/detect-encoding",
133
- "version": "v1.2.0",
134
- "version_normalized": "1.2.0.0",
135
- "source": {
136
- "type": "git",
137
- "url": "https://github.com/onnov/detect-encoding.git",
138
- "reference": "c88cea4f0c83d7f7e0675f2801e0995f328de1ce"
139
- },
140
- "dist": {
141
- "type": "zip",
142
- "url": "https://api.github.com/repos/onnov/detect-encoding/zipball/c88cea4f0c83d7f7e0675f2801e0995f328de1ce",
143
- "reference": "c88cea4f0c83d7f7e0675f2801e0995f328de1ce",
144
- "shasum": ""
145
- },
146
- "require": {
147
- "ext-iconv": "*",
148
- "php": ">=7.2"
149
- },
150
- "require-dev": {
151
- "infection/infection": "*",
152
- "phpbench/phpbench": "*",
153
- "phpcompatibility/php-compatibility": "*",
154
- "phpmd/phpmd": "*",
155
- "phpstan/phpstan": "*",
156
- "phpstan/phpstan-strict-rules": "*",
157
- "phpunit/phpunit": "*",
158
- "roave/backward-compatibility-check": "*",
159
- "squizlabs/php_codesniffer": "*"
160
- },
161
- "time": "2019-09-20T16:11:46+00:00",
162
- "type": "library",
163
- "installation-source": "dist",
164
- "autoload": {
165
- "psr-4": {
166
- "Onnov\\DetectEncoding\\": "src/"
167
- }
168
- },
169
- "notification-url": "https://packagist.org/downloads/",
170
- "license": [
171
- "MIT"
172
- ],
173
- "authors": [
174
- {
175
- "name": "onnov",
176
- "email": "oblnn@yandex.ru"
177
- }
178
- ],
179
- "description": "Text encoding definition class instead of mb_detect_encoding. Defines: utf-8, windows-1251, koi8-r, iso-8859-5, ibm866, .....",
180
- "homepage": "https://github.com/onnov/detect-encoding",
181
- "keywords": [
182
- "cyrillic",
183
- "encoding",
184
- "ibm866",
185
- "iconv",
186
- "iso-8859-5",
187
- "koi8-r",
188
- "mb_detect_encoding",
189
- "utf-8",
190
- "windows-1251"
191
- ]
192
- },
193
- {
194
- "name": "rosell-dk/dom-util-for-webp",
195
- "version": "0.4.0",
196
- "version_normalized": "0.4.0.0",
197
- "source": {
198
- "type": "git",
199
- "url": "https://github.com/rosell-dk/dom-util-for-webp.git",
200
- "reference": "5928aecf64d59124b341dce23ce8ecf48a6eded6"
201
- },
202
- "dist": {
203
- "type": "zip",
204
- "url": "https://api.github.com/repos/rosell-dk/dom-util-for-webp/zipball/5928aecf64d59124b341dce23ce8ecf48a6eded6",
205
- "reference": "5928aecf64d59124b341dce23ce8ecf48a6eded6",
206
- "shasum": ""
207
- },
208
- "require-dev": {
209
- "friendsofphp/php-cs-fixer": "^2.11",
210
- "phpunit/phpunit": "5.7.27",
211
- "squizlabs/php_codesniffer": "3.*"
212
- },
213
- "suggest": {
214
- "phpstan/phpstan": "Suggested for dev, in order to analyse code before committing"
215
- },
216
- "time": "2020-02-02T11:16:27+00:00",
217
- "type": "library",
218
- "extra": {
219
- "scripts-descriptions": {
220
- "ci": "Run tests before CI",
221
- "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
222
- "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
223
- "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
224
- "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
225
- "test": "Launches the preconfigured PHPUnit"
226
- }
227
- },
228
- "installation-source": "dist",
229
- "autoload": {
230
- "psr-4": {
231
- "DOMUtilForWebP\\": "src/"
232
- }
233
- },
234
- "notification-url": "https://packagist.org/downloads/",
235
- "license": [
236
- "MIT"
237
- ],
238
- "authors": [
239
- {
240
- "name": "Bjørn Rosell",
241
- "homepage": "https://www.bitwise-it.dk/contact",
242
- "role": "Project Author"
243
- }
244
- ],
245
- "description": "Replace image URLs found in HTML",
246
- "keywords": [
247
- "Webp",
248
- "html",
249
- "images",
250
- "replace"
251
- ]
252
- },
253
- {
254
- "name": "rosell-dk/htaccess-capability-tester",
255
- "version": "0.9",
256
- "version_normalized": "0.9.0.0",
257
- "source": {
258
- "type": "git",
259
- "url": "https://github.com/rosell-dk/htaccess-capability-tester.git",
260
- "reference": "2eb2cf38a9f42fc3aa647d6e9c896e124bfebf4c"
261
- },
262
- "dist": {
263
- "type": "zip",
264
- "url": "https://api.github.com/repos/rosell-dk/htaccess-capability-tester/zipball/2eb2cf38a9f42fc3aa647d6e9c896e124bfebf4c",
265
- "reference": "2eb2cf38a9f42fc3aa647d6e9c896e124bfebf4c",
266
- "shasum": ""
267
- },
268
- "require": {
269
- "php": "^5.6 | ^7.0"
270
- },
271
- "require-dev": {
272
- "phpunit/php-code-coverage": "dev-4.0-dev as 4.0.4",
273
- "phpunit/phpunit": "^5.7",
274
- "squizlabs/php_codesniffer": "3.*"
275
- },
276
- "suggest": {
277
- "php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
278
- },
279
- "time": "2020-11-04T10:29:38+00:00",
280
- "type": "library",
281
- "extra": {
282
- "scripts-descriptions": {
283
- "ci": "Run tests before CI",
284
- "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
285
- "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
286
- "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
287
- "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
288
- "test": "Launches the preconfigured PHPUnit"
289
- }
290
- },
291
- "installation-source": "dist",
292
- "autoload": {
293
- "psr-4": {
294
- "HtaccessCapabilityTester\\": "src/"
295
- }
296
- },
297
- "notification-url": "https://packagist.org/downloads/",
298
- "license": [
299
- "MIT"
300
- ],
301
- "authors": [
302
- {
303
- "name": "Bjørn Rosell",
304
- "homepage": "https://www.bitwise-it.dk/contact",
305
- "role": "Project Author"
306
- }
307
- ],
308
- "description": "Test the capabilities of .htaccess files on the server using live tests",
309
- "keywords": [
310
- ".htaccess",
311
- "apache",
312
- "litespeed"
313
- ]
314
- },
315
- {
316
- "name": "rosell-dk/image-mime-type-guesser",
317
- "version": "0.3",
318
- "version_normalized": "0.3.0.0",
319
- "source": {
320
- "type": "git",
321
- "url": "https://github.com/rosell-dk/image-mime-type-guesser.git",
322
- "reference": "204fd61ca81e3b0ba46c6165dab8f74816b1fe99"
323
- },
324
- "dist": {
325
- "type": "zip",
326
- "url": "https://api.github.com/repos/rosell-dk/image-mime-type-guesser/zipball/204fd61ca81e3b0ba46c6165dab8f74816b1fe99",
327
- "reference": "204fd61ca81e3b0ba46c6165dab8f74816b1fe99",
328
- "shasum": ""
329
- },
330
- "require-dev": {
331
- "friendsofphp/php-cs-fixer": "^2.11",
332
- "phpunit/phpunit": "^5.7.27",
333
- "squizlabs/php_codesniffer": "3.*"
334
- },
335
- "time": "2019-03-29T09:33:28+00:00",
336
- "type": "library",
337
- "extra": {
338
- "scripts-descriptions": {
339
- "ci": "Run tests before CI",
340
- "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
341
- "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
342
- "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
343
- "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
344
- "test": "Launches the preconfigured PHPUnit"
345
- }
346
- },
347
- "installation-source": "dist",
348
- "autoload": {
349
- "psr-4": {
350
- "ImageMimeTypeGuesser\\": "src/"
351
- }
352
- },
353
- "notification-url": "https://packagist.org/downloads/",
354
- "license": [
355
- "MIT"
356
- ],
357
- "authors": [
358
- {
359
- "name": "Bjørn Rosell",
360
- "homepage": "https://www.bitwise-it.dk/contact",
361
- "role": "Project Author"
362
- }
363
- ],
364
- "description": "Guess mime type of images",
365
- "keywords": [
366
- "image",
367
- "images",
368
- "mime",
369
- "mime type"
370
- ]
371
- },
372
- {
373
- "name": "rosell-dk/webp-convert",
374
- "version": "2.4.0",
375
- "version_normalized": "2.4.0.0",
376
- "source": {
377
- "type": "git",
378
- "url": "https://github.com/rosell-dk/webp-convert.git",
379
- "reference": "187a578ee55730f7a128a2f07b4351524b10d47b"
380
- },
381
- "dist": {
382
- "type": "zip",
383
- "url": "https://api.github.com/repos/rosell-dk/webp-convert/zipball/187a578ee55730f7a128a2f07b4351524b10d47b",
384
- "reference": "187a578ee55730f7a128a2f07b4351524b10d47b",
385
- "shasum": ""
386
- },
387
- "require": {
388
- "php": "^5.6 | ^7.0",
389
- "rosell-dk/image-mime-type-guesser": "^0.3"
390
- },
391
- "require-dev": {
392
- "friendsofphp/php-cs-fixer": "^2.11",
393
- "phpunit/phpunit": "5.7.27",
394
- "squizlabs/php_codesniffer": "3.*"
395
- },
396
- "suggest": {
397
- "ext-gd": "to use GD extension for converting. Note: Gd must be compiled with webp support",
398
- "ext-imagick": "to use Imagick extension for converting. Note: Gd must be compiled with webp support",
399
- "ext-vips": "to use Vips extension for converting.",
400
- "php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
401
- },
402
- "time": "2020-11-10T11:24:50+00:00",
403
- "type": "library",
404
- "extra": {
405
- "scripts-descriptions": {
406
- "ci": "Run tests before CI",
407
- "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
408
- "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
409
- "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
410
- "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
411
- "test": "Launches the preconfigured PHPUnit"
412
- }
413
- },
414
- "installation-source": "dist",
415
- "autoload": {
416
- "psr-4": {
417
- "WebPConvert\\": "src/"
418
- }
419
- },
420
- "notification-url": "https://packagist.org/downloads/",
421
- "license": [
422
- "MIT"
423
- ],
424
- "authors": [
425
- {
426
- "name": "Bjørn Rosell",
427
- "homepage": "https://www.bitwise-it.dk/contact",
428
- "role": "Project Author"
429
- },
430
- {
431
- "name": "Martin Folkers",
432
- "homepage": "https://twobrain.io",
433
- "role": "Collaborator"
434
- }
435
- ],
436
- "description": "Convert JPEG & PNG to WebP with PHP",
437
- "keywords": [
438
- "Webp",
439
- "cwebp",
440
- "gd",
441
- "image conversion",
442
- "images",
443
- "imagick",
444
- "jpg",
445
- "jpg2webp",
446
- "png",
447
- "png2webp"
448
- ]
449
- },
450
- {
451
- "name": "rosell-dk/webp-convert-cloud-service",
452
- "version": "2.0.1",
453
- "version_normalized": "2.0.1.0",
454
- "source": {
455
- "type": "git",
456
- "url": "https://github.com/rosell-dk/webp-convert-cloud-service.git",
457
- "reference": "703c2f1c76d30468ee3977170bfa3da138d8c4ad"
458
- },
459
- "dist": {
460
- "type": "zip",
461
- "url": "https://api.github.com/repos/rosell-dk/webp-convert-cloud-service/zipball/703c2f1c76d30468ee3977170bfa3da138d8c4ad",
462
- "reference": "703c2f1c76d30468ee3977170bfa3da138d8c4ad",
463
- "shasum": ""
464
- },
465
- "require": {
466
- "rosell-dk/webp-convert": "^2.0.0"
467
- },
468
- "require-dev": {
469
- "friendsofphp/php-cs-fixer": "^2.11",
470
- "phpunit/phpunit": "5.7.27",
471
- "squizlabs/php_codesniffer": "3.*"
472
- },
473
- "time": "2019-06-30T08:28:35+00:00",
474
- "type": "library",
475
- "extra": {
476
- "scripts-descriptions": {
477
- "ci": "Run tests before CI",
478
- "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
479
- "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
480
- "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
481
- "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
482
- "test": "Launches the preconfigured PHPUnit"
483
- }
484
- },
485
- "installation-source": "dist",
486
- "autoload": {
487
- "psr-4": {
488
- "WebPConvertCloudService\\": "src/"
489
- }
490
- },
491
- "notification-url": "https://packagist.org/downloads/",
492
- "license": [
493
- "MIT"
494
- ],
495
- "authors": [
496
- {
497
- "name": "Bjørn Rosell",
498
- "role": "Project Author",
499
- "homepage": "https://www.bitwise-it.dk/contact"
500
- }
501
- ],
502
- "description": "Cloud service for converting JPEG & PNG to WebP",
503
- "keywords": [
504
- "Webp",
505
- "cwebp",
506
- "gd",
507
- "image conversion",
508
- "images",
509
- "imagick",
510
- "jpg",
511
- "jpg2webp",
512
- "png",
513
- "png2webp"
514
- ]
515
- }
516
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "packages": [
3
+ {
4
+ "name": "composer/installers",
5
+ "version": "v1.11.0",
6
+ "version_normalized": "1.11.0.0",
7
+ "source": {
8
+ "type": "git",
9
+ "url": "https://github.com/composer/installers.git",
10
+ "reference": "ae03311f45dfe194412081526be2e003960df74b"
11
+ },
12
+ "dist": {
13
+ "type": "zip",
14
+ "url": "https://api.github.com/repos/composer/installers/zipball/ae03311f45dfe194412081526be2e003960df74b",
15
+ "reference": "ae03311f45dfe194412081526be2e003960df74b",
16
+ "shasum": ""
17
+ },
18
+ "require": {
19
+ "composer-plugin-api": "^1.0 || ^2.0"
20
+ },
21
+ "replace": {
22
+ "roundcube/plugin-installer": "*",
23
+ "shama/baton": "*"
24
+ },
25
+ "require-dev": {
26
+ "composer/composer": "1.6.* || ^2.0",
27
+ "composer/semver": "^1 || ^3",
28
+ "phpstan/phpstan": "^0.12.55",
29
+ "phpstan/phpstan-phpunit": "^0.12.16",
30
+ "symfony/phpunit-bridge": "^4.2 || ^5",
31
+ "symfony/process": "^2.3"
32
+ },
33
+ "time": "2021-04-28T06:42:17+00:00",
34
+ "type": "composer-plugin",
35
+ "extra": {
36
+ "class": "Composer\\Installers\\Plugin",
37
+ "branch-alias": {
38
+ "dev-main": "1.x-dev"
39
+ }
40
+ },
41
+ "installation-source": "dist",
42
+ "autoload": {
43
+ "psr-4": {
44
+ "Composer\\Installers\\": "src/Composer/Installers"
45
+ }
46
+ },
47
+ "notification-url": "https://packagist.org/downloads/",
48
+ "license": [
49
+ "MIT"
50
+ ],
51
+ "authors": [
52
+ {
53
+ "name": "Kyle Robinson Young",
54
+ "email": "kyle@dontkry.com",
55
+ "homepage": "https://github.com/shama"
56
+ }
57
+ ],
58
+ "description": "A multi-framework Composer library installer",
59
+ "homepage": "https://composer.github.io/installers/",
60
+ "keywords": [
61
+ "Craft",
62
+ "Dolibarr",
63
+ "Eliasis",
64
+ "Hurad",
65
+ "ImageCMS",
66
+ "Kanboard",
67
+ "Lan Management System",
68
+ "MODX Evo",
69
+ "MantisBT",
70
+ "Mautic",
71
+ "Maya",
72
+ "OXID",
73
+ "Plentymarkets",
74
+ "Porto",
75
+ "RadPHP",
76
+ "SMF",
77
+ "Starbug",
78
+ "Thelia",
79
+ "Whmcs",
80
+ "WolfCMS",
81
+ "agl",
82
+ "aimeos",
83
+ "annotatecms",
84
+ "attogram",
85
+ "bitrix",
86
+ "cakephp",
87
+ "chef",
88
+ "cockpit",
89
+ "codeigniter",
90
+ "concrete5",
91
+ "croogo",
92
+ "dokuwiki",
93
+ "drupal",
94
+ "eZ Platform",
95
+ "elgg",
96
+ "expressionengine",
97
+ "fuelphp",
98
+ "grav",
99
+ "installer",
100
+ "itop",
101
+ "joomla",
102
+ "known",
103
+ "kohana",
104
+ "laravel",
105
+ "lavalite",
106
+ "lithium",
107
+ "magento",
108
+ "majima",
109
+ "mako",
110
+ "mediawiki",
111
+ "miaoxing",
112
+ "modulework",
113
+ "modx",
114
+ "moodle",
115
+ "osclass",
116
+ "phpbb",
117
+ "piwik",
118
+ "ppi",
119
+ "processwire",
120
+ "puppet",
121
+ "pxcms",
122
+ "reindex",
123
+ "roundcube",
124
+ "shopware",
125
+ "silverstripe",
126
+ "sydes",
127
+ "sylius",
128
+ "symfony",
129
+ "tastyigniter",
130
+ "typo3",
131
+ "wordpress",
132
+ "yawik",
133
+ "zend",
134
+ "zikula"
135
+ ],
136
+ "support": {
137
+ "issues": "https://github.com/composer/installers/issues",
138
+ "source": "https://github.com/composer/installers/tree/v1.11.0"
139
+ },
140
+ "funding": [
141
+ {
142
+ "url": "https://packagist.com",
143
+ "type": "custom"
144
+ },
145
+ {
146
+ "url": "https://github.com/composer",
147
+ "type": "github"
148
+ },
149
+ {
150
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
151
+ "type": "tidelift"
152
+ }
153
+ ],
154
+ "install-path": "./installers"
155
+ },
156
+ {
157
+ "name": "onnov/detect-encoding",
158
+ "version": "v1.2.0",
159
+ "version_normalized": "1.2.0.0",
160
+ "source": {
161
+ "type": "git",
162
+ "url": "https://github.com/onnov/detect-encoding.git",
163
+ "reference": "c88cea4f0c83d7f7e0675f2801e0995f328de1ce"
164
+ },
165
+ "dist": {
166
+ "type": "zip",
167
+ "url": "https://api.github.com/repos/onnov/detect-encoding/zipball/c88cea4f0c83d7f7e0675f2801e0995f328de1ce",
168
+ "reference": "c88cea4f0c83d7f7e0675f2801e0995f328de1ce",
169
+ "shasum": ""
170
+ },
171
+ "require": {
172
+ "ext-iconv": "*",
173
+ "php": ">=7.2"
174
+ },
175
+ "require-dev": {
176
+ "infection/infection": "*",
177
+ "phpbench/phpbench": "*",
178
+ "phpcompatibility/php-compatibility": "*",
179
+ "phpmd/phpmd": "*",
180
+ "phpstan/phpstan": "*",
181
+ "phpstan/phpstan-strict-rules": "*",
182
+ "phpunit/phpunit": "*",
183
+ "roave/backward-compatibility-check": "*",
184
+ "squizlabs/php_codesniffer": "*"
185
+ },
186
+ "time": "2019-09-20T16:11:46+00:00",
187
+ "type": "library",
188
+ "installation-source": "dist",
189
+ "autoload": {
190
+ "psr-4": {
191
+ "Onnov\\DetectEncoding\\": "src/"
192
+ }
193
+ },
194
+ "notification-url": "https://packagist.org/downloads/",
195
+ "license": [
196
+ "MIT"
197
+ ],
198
+ "authors": [
199
+ {
200
+ "name": "onnov",
201
+ "email": "oblnn@yandex.ru"
202
+ }
203
+ ],
204
+ "description": "Text encoding definition class instead of mb_detect_encoding. Defines: utf-8, windows-1251, koi8-r, iso-8859-5, ibm866, .....",
205
+ "homepage": "https://github.com/onnov/detect-encoding",
206
+ "keywords": [
207
+ "cyrillic",
208
+ "encoding",
209
+ "ibm866",
210
+ "iconv",
211
+ "iso-8859-5",
212
+ "koi8-r",
213
+ "mb_detect_encoding",
214
+ "utf-8",
215
+ "windows-1251"
216
+ ],
217
+ "install-path": "../onnov/detect-encoding"
218
+ },
219
+ {
220
+ "name": "rosell-dk/dom-util-for-webp",
221
+ "version": "0.4.0",
222
+ "version_normalized": "0.4.0.0",
223
+ "source": {
224
+ "type": "git",
225
+ "url": "https://github.com/rosell-dk/dom-util-for-webp.git",
226
+ "reference": "5928aecf64d59124b341dce23ce8ecf48a6eded6"
227
+ },
228
+ "dist": {
229
+ "type": "zip",
230
+ "url": "https://api.github.com/repos/rosell-dk/dom-util-for-webp/zipball/5928aecf64d59124b341dce23ce8ecf48a6eded6",
231
+ "reference": "5928aecf64d59124b341dce23ce8ecf48a6eded6",
232
+ "shasum": ""
233
+ },
234
+ "require-dev": {
235
+ "friendsofphp/php-cs-fixer": "^2.11",
236
+ "phpunit/phpunit": "5.7.27",
237
+ "squizlabs/php_codesniffer": "3.*"
238
+ },
239
+ "suggest": {
240
+ "phpstan/phpstan": "Suggested for dev, in order to analyse code before committing"
241
+ },
242
+ "time": "2020-02-02T11:16:27+00:00",
243
+ "type": "library",
244
+ "extra": {
245
+ "scripts-descriptions": {
246
+ "ci": "Run tests before CI",
247
+ "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
248
+ "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
249
+ "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
250
+ "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
251
+ "test": "Launches the preconfigured PHPUnit"
252
+ }
253
+ },
254
+ "installation-source": "dist",
255
+ "autoload": {
256
+ "psr-4": {
257
+ "DOMUtilForWebP\\": "src/"
258
+ }
259
+ },
260
+ "notification-url": "https://packagist.org/downloads/",
261
+ "license": [
262
+ "MIT"
263
+ ],
264
+ "authors": [
265
+ {
266
+ "name": "Bjørn Rosell",
267
+ "homepage": "https://www.bitwise-it.dk/contact",
268
+ "role": "Project Author"
269
+ }
270
+ ],
271
+ "description": "Replace image URLs found in HTML",
272
+ "keywords": [
273
+ "Webp",
274
+ "html",
275
+ "images",
276
+ "replace"
277
+ ],
278
+ "install-path": "../rosell-dk/dom-util-for-webp"
279
+ },
280
+ {
281
+ "name": "rosell-dk/htaccess-capability-tester",
282
+ "version": "0.9",
283
+ "version_normalized": "0.9.0.0",
284
+ "source": {
285
+ "type": "git",
286
+ "url": "https://github.com/rosell-dk/htaccess-capability-tester.git",
287
+ "reference": "2eb2cf38a9f42fc3aa647d6e9c896e124bfebf4c"
288
+ },
289
+ "dist": {
290
+ "type": "zip",
291
+ "url": "https://api.github.com/repos/rosell-dk/htaccess-capability-tester/zipball/2eb2cf38a9f42fc3aa647d6e9c896e124bfebf4c",
292
+ "reference": "2eb2cf38a9f42fc3aa647d6e9c896e124bfebf4c",
293
+ "shasum": ""
294
+ },
295
+ "require": {
296
+ "php": "^5.6 | ^7.0"
297
+ },
298
+ "require-dev": {
299
+ "phpunit/php-code-coverage": "dev-4.0-dev as 4.0.4",
300
+ "phpunit/phpunit": "^5.7",
301
+ "squizlabs/php_codesniffer": "3.*"
302
+ },
303
+ "suggest": {
304
+ "php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
305
+ },
306
+ "time": "2020-11-04T10:29:38+00:00",
307
+ "type": "library",
308
+ "extra": {
309
+ "scripts-descriptions": {
310
+ "ci": "Run tests before CI",
311
+ "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
312
+ "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
313
+ "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
314
+ "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
315
+ "test": "Launches the preconfigured PHPUnit"
316
+ }
317
+ },
318
+ "installation-source": "dist",
319
+ "autoload": {
320
+ "psr-4": {
321
+ "HtaccessCapabilityTester\\": "src/"
322
+ }
323
+ },
324
+ "notification-url": "https://packagist.org/downloads/",
325
+ "license": [
326
+ "MIT"
327
+ ],
328
+ "authors": [
329
+ {
330
+ "name": "Bjørn Rosell",
331
+ "homepage": "https://www.bitwise-it.dk/contact",
332
+ "role": "Project Author"
333
+ }
334
+ ],
335
+ "description": "Test the capabilities of .htaccess files on the server using live tests",
336
+ "keywords": [
337
+ ".htaccess",
338
+ "apache",
339
+ "litespeed"
340
+ ],
341
+ "install-path": "../rosell-dk/htaccess-capability-tester"
342
+ },
343
+ {
344
+ "name": "rosell-dk/image-mime-type-guesser",
345
+ "version": "0.3",
346
+ "version_normalized": "0.3.0.0",
347
+ "source": {
348
+ "type": "git",
349
+ "url": "https://github.com/rosell-dk/image-mime-type-guesser.git",
350
+ "reference": "204fd61ca81e3b0ba46c6165dab8f74816b1fe99"
351
+ },
352
+ "dist": {
353
+ "type": "zip",
354
+ "url": "https://api.github.com/repos/rosell-dk/image-mime-type-guesser/zipball/204fd61ca81e3b0ba46c6165dab8f74816b1fe99",
355
+ "reference": "204fd61ca81e3b0ba46c6165dab8f74816b1fe99",
356
+ "shasum": ""
357
+ },
358
+ "require-dev": {
359
+ "friendsofphp/php-cs-fixer": "^2.11",
360
+ "phpunit/phpunit": "^5.7.27",
361
+ "squizlabs/php_codesniffer": "3.*"
362
+ },
363
+ "time": "2019-03-29T09:33:28+00:00",
364
+ "type": "library",
365
+ "extra": {
366
+ "scripts-descriptions": {
367
+ "ci": "Run tests before CI",
368
+ "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
369
+ "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
370
+ "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
371
+ "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
372
+ "test": "Launches the preconfigured PHPUnit"
373
+ }
374
+ },
375
+ "installation-source": "dist",
376
+ "autoload": {
377
+ "psr-4": {
378
+ "ImageMimeTypeGuesser\\": "src/"
379
+ }
380
+ },
381
+ "notification-url": "https://packagist.org/downloads/",
382
+ "license": [
383
+ "MIT"
384
+ ],
385
+ "authors": [
386
+ {
387
+ "name": "Bjørn Rosell",
388
+ "homepage": "https://www.bitwise-it.dk/contact",
389
+ "role": "Project Author"
390
+ }
391
+ ],
392
+ "description": "Guess mime type of images",
393
+ "keywords": [
394
+ "image",
395
+ "images",
396
+ "mime",
397
+ "mime type"
398
+ ],
399
+ "install-path": "../rosell-dk/image-mime-type-guesser"
400
+ },
401
+ {
402
+ "name": "rosell-dk/webp-convert",
403
+ "version": "2.6.0",
404
+ "version_normalized": "2.6.0.0",
405
+ "source": {
406
+ "type": "git",
407
+ "url": "https://github.com/rosell-dk/webp-convert.git",
408
+ "reference": "ed230afe56d3157dc402c33585e3ab7f15c7ac80"
409
+ },
410
+ "dist": {
411
+ "type": "zip",
412
+ "url": "https://api.github.com/repos/rosell-dk/webp-convert/zipball/ed230afe56d3157dc402c33585e3ab7f15c7ac80",
413
+ "reference": "ed230afe56d3157dc402c33585e3ab7f15c7ac80",
414
+ "shasum": ""
415
+ },
416
+ "require": {
417
+ "php": "^5.6 | ^7.0 | ^8.0",
418
+ "rosell-dk/image-mime-type-guesser": "^0.3"
419
+ },
420
+ "require-dev": {
421
+ "friendsofphp/php-cs-fixer": "^2.11",
422
+ "phpunit/phpunit": "^9.3",
423
+ "squizlabs/php_codesniffer": "3.*"
424
+ },
425
+ "suggest": {
426
+ "ext-gd": "to use GD extension for converting. Note: Gd must be compiled with webp support",
427
+ "ext-imagick": "to use Imagick extension for converting. Note: Gd must be compiled with webp support",
428
+ "ext-vips": "to use Vips extension for converting.",
429
+ "php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
430
+ },
431
+ "time": "2021-05-20T10:56:47+00:00",
432
+ "type": "library",
433
+ "extra": {
434
+ "scripts-descriptions": {
435
+ "ci": "Run tests before CI",
436
+ "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
437
+ "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
438
+ "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
439
+ "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
440
+ "test": "Launches the preconfigured PHPUnit"
441
+ }
442
+ },
443
+ "installation-source": "dist",
444
+ "autoload": {
445
+ "psr-4": {
446
+ "WebPConvert\\": "src/"
447
+ }
448
+ },
449
+ "notification-url": "https://packagist.org/downloads/",
450
+ "license": [
451
+ "MIT"
452
+ ],
453
+ "authors": [
454
+ {
455
+ "name": "Bjørn Rosell",
456
+ "homepage": "https://www.bitwise-it.dk/contact",
457
+ "role": "Project Author"
458
+ },
459
+ {
460
+ "name": "Martin Folkers",
461
+ "homepage": "https://twobrain.io",
462
+ "role": "Collaborator"
463
+ }
464
+ ],
465
+ "description": "Convert JPEG & PNG to WebP with PHP",
466
+ "keywords": [
467
+ "Webp",
468
+ "cwebp",
469
+ "gd",
470
+ "image conversion",
471
+ "images",
472
+ "imagick",
473
+ "jpg",
474
+ "jpg2webp",
475
+ "png",
476
+ "png2webp"
477
+ ],
478
+ "support": {
479
+ "issues": "https://github.com/rosell-dk/webp-convert/issues",
480
+ "source": "https://github.com/rosell-dk/webp-convert/tree/2.6.0"
481
+ },
482
+ "funding": [
483
+ {
484
+ "url": "https://github.com/rosell-dk",
485
+ "type": "github"
486
+ },
487
+ {
488
+ "url": "https://ko-fi.com/rosell",
489
+ "type": "ko_fi"
490
+ }
491
+ ],
492
+ "install-path": "../rosell-dk/webp-convert"
493
+ },
494
+ {
495
+ "name": "rosell-dk/webp-convert-cloud-service",
496
+ "version": "2.0.1",
497
+ "version_normalized": "2.0.1.0",
498
+ "source": {
499
+ "type": "git",
500
+ "url": "https://github.com/rosell-dk/webp-convert-cloud-service.git",
501
+ "reference": "703c2f1c76d30468ee3977170bfa3da138d8c4ad"
502
+ },
503
+ "dist": {
504
+ "type": "zip",
505
+ "url": "https://api.github.com/repos/rosell-dk/webp-convert-cloud-service/zipball/703c2f1c76d30468ee3977170bfa3da138d8c4ad",
506
+ "reference": "703c2f1c76d30468ee3977170bfa3da138d8c4ad",
507
+ "shasum": ""
508
+ },
509
+ "require": {
510
+ "rosell-dk/webp-convert": "^2.0.0"
511
+ },
512
+ "require-dev": {
513
+ "friendsofphp/php-cs-fixer": "^2.11",
514
+ "phpunit/phpunit": "5.7.27",
515
+ "squizlabs/php_codesniffer": "3.*"
516
+ },
517
+ "time": "2019-06-30T08:28:35+00:00",
518
+ "type": "library",
519
+ "extra": {
520
+ "scripts-descriptions": {
521
+ "ci": "Run tests before CI",
522
+ "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
523
+ "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
524
+ "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
525
+ "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
526
+ "test": "Launches the preconfigured PHPUnit"
527
+ }
528
+ },
529
+ "installation-source": "dist",
530
+ "autoload": {
531
+ "psr-4": {
532
+ "WebPConvertCloudService\\": "src/"
533
+ }
534
+ },
535
+ "notification-url": "https://packagist.org/downloads/",
536
+ "license": [
537
+ "MIT"
538
+ ],
539
+ "authors": [
540
+ {
541
+ "name": "Bjørn Rosell",
542
+ "role": "Project Author",
543
+ "homepage": "https://www.bitwise-it.dk/contact"
544
+ }
545
+ ],
546
+ "description": "Cloud service for converting JPEG & PNG to WebP",
547
+ "keywords": [
548
+ "Webp",
549
+ "cwebp",
550
+ "gd",
551
+ "image conversion",
552
+ "images",
553
+ "imagick",
554
+ "jpg",
555
+ "jpg2webp",
556
+ "png",
557
+ "png2webp"
558
+ ],
559
+ "install-path": "../rosell-dk/webp-convert-cloud-service"
560
+ }
561
+ ],
562
+ "dev": true,
563
+ "dev-package-names": []
564
+ }
vendor/composer/installed.php ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php return array (
2
+ 'root' =>
3
+ array (
4
+ 'pretty_version' => 'dev-master',
5
+ 'version' => 'dev-master',
6
+ 'aliases' =>
7
+ array (
8
+ ),
9
+ 'reference' => 'acd5aaef5298d5d01345c7fa95723a3f83486412',
10
+ 'name' => 'rosell-dk/webp-express',
11
+ 'dev' => true,
12
+ ),
13
+ 'versions' =>
14
+ array (
15
+ 'composer/installers' =>
16
+ array (
17
+ 'pretty_version' => 'v1.11.0',
18
+ 'version' => '1.11.0.0',
19
+ 'aliases' =>
20
+ array (
21
+ ),
22
+ 'reference' => 'ae03311f45dfe194412081526be2e003960df74b',
23
+ 'dev-requirement' => false,
24
+ ),
25
+ 'onnov/detect-encoding' =>
26
+ array (
27
+ 'pretty_version' => 'v1.2.0',
28
+ 'version' => '1.2.0.0',
29
+ 'aliases' =>
30
+ array (
31
+ ),
32
+ 'reference' => 'c88cea4f0c83d7f7e0675f2801e0995f328de1ce',
33
+ 'dev-requirement' => false,
34
+ ),
35
+ 'rosell-dk/dom-util-for-webp' =>
36
+ array (
37
+ 'pretty_version' => '0.4.0',
38
+ 'version' => '0.4.0.0',
39
+ 'aliases' =>
40
+ array (
41
+ ),
42
+ 'reference' => '5928aecf64d59124b341dce23ce8ecf48a6eded6',
43
+ 'dev-requirement' => false,
44
+ ),
45
+ 'rosell-dk/htaccess-capability-tester' =>
46
+ array (
47
+ 'pretty_version' => '0.9',
48
+ 'version' => '0.9.0.0',
49
+ 'aliases' =>
50
+ array (
51
+ ),
52
+ 'reference' => '2eb2cf38a9f42fc3aa647d6e9c896e124bfebf4c',
53
+ 'dev-requirement' => false,
54
+ ),
55
+ 'rosell-dk/image-mime-type-guesser' =>
56
+ array (
57
+ 'pretty_version' => '0.3',
58
+ 'version' => '0.3.0.0',
59
+ 'aliases' =>
60
+ array (
61
+ ),
62
+ 'reference' => '204fd61ca81e3b0ba46c6165dab8f74816b1fe99',
63
+ 'dev-requirement' => false,
64
+ ),
65
+ 'rosell-dk/webp-convert' =>
66
+ array (
67
+ 'pretty_version' => '2.6.0',
68
+ 'version' => '2.6.0.0',
69
+ 'aliases' =>
70
+ array (
71
+ ),
72
+ 'reference' => 'ed230afe56d3157dc402c33585e3ab7f15c7ac80',
73
+ 'dev-requirement' => false,
74
+ ),
75
+ 'rosell-dk/webp-convert-cloud-service' =>
76
+ array (
77
+ 'pretty_version' => '2.0.1',
78
+ 'version' => '2.0.1.0',
79
+ 'aliases' =>
80
+ array (
81
+ ),
82
+ 'reference' => '703c2f1c76d30468ee3977170bfa3da138d8c4ad',
83
+ 'dev-requirement' => false,
84
+ ),
85
+ 'rosell-dk/webp-express' =>
86
+ array (
87
+ 'pretty_version' => 'dev-master',
88
+ 'version' => 'dev-master',
89
+ 'aliases' =>
90
+ array (
91
+ ),
92
+ 'reference' => 'acd5aaef5298d5d01345c7fa95723a3f83486412',
93
+ 'dev-requirement' => false,
94
+ ),
95
+ 'roundcube/plugin-installer' =>
96
+ array (
97
+ 'dev-requirement' => false,
98
+ 'replaced' =>
99
+ array (
100
+ 0 => '*',
101
+ ),
102
+ ),
103
+ 'shama/baton' =>
104
+ array (
105
+ 'dev-requirement' => false,
106
+ 'replaced' =>
107
+ array (
108
+ 0 => '*',
109
+ ),
110
+ ),
111
+ ),
112
+ );
vendor/composer/installers/.github/workflows/continuous-integration.yml ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "Continuous Integration"
2
+
3
+ on:
4
+ - push
5
+ - pull_request
6
+
7
+ env:
8
+ COMPOSER_FLAGS: "--ansi --no-interaction --no-progress --prefer-dist"
9
+ SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT: "1"
10
+
11
+ jobs:
12
+ tests:
13
+ name: "CI"
14
+
15
+ runs-on: ubuntu-latest
16
+
17
+ strategy:
18
+ matrix:
19
+ php-version:
20
+ - "5.3"
21
+ - "5.4"
22
+ - "5.5"
23
+ - "5.6"
24
+ - "7.0"
25
+ - "7.1"
26
+ - "7.2"
27
+ - "7.3"
28
+ - "7.4"
29
+ - "8.0"
30
+ - "8.1"
31
+ dependencies: [locked]
32
+ include:
33
+ - php-version: "5.3"
34
+ dependencies: lowest
35
+ - php-version: "8.1"
36
+ dependencies: lowest
37
+
38
+ steps:
39
+ - name: "Checkout"
40
+ uses: "actions/checkout@v2"
41
+
42
+ - name: "Install PHP"
43
+ uses: "shivammathur/setup-php@v2"
44
+ with:
45
+ coverage: "none"
46
+ php-version: "${{ matrix.php-version }}"
47
+
48
+ - name: Get composer cache directory
49
+ id: composercache
50
+ run: echo "::set-output name=dir::$(composer config cache-files-dir)"
51
+
52
+ - name: Cache dependencies
53
+ uses: actions/cache@v2
54
+ with:
55
+ path: ${{ steps.composercache.outputs.dir }}
56
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
57
+ restore-keys: ${{ runner.os }}-composer-
58
+
59
+ - name: "Handle lowest dependencies update"
60
+ if: "contains(matrix.dependencies, 'lowest')"
61
+ run: "echo \"COMPOSER_FLAGS=$COMPOSER_FLAGS --prefer-lowest\" >> $GITHUB_ENV"
62
+
63
+ - name: "Install latest dependencies"
64
+ run: |
65
+ # Remove PHPStan as it requires a newer PHP
66
+ composer remove phpstan/phpstan phpstan/phpstan-phpunit --dev --no-update
67
+ composer update ${{ env.COMPOSER_FLAGS }}
68
+
69
+ - name: "Run tests"
70
+ run: "vendor/bin/simple-phpunit --verbose"
vendor/composer/installers/.github/workflows/lint.yml ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "PHP Lint"
2
+
3
+ on:
4
+ - push
5
+ - pull_request
6
+
7
+ jobs:
8
+ tests:
9
+ name: "Lint"
10
+
11
+ runs-on: ubuntu-latest
12
+
13
+ strategy:
14
+ matrix:
15
+ php-version:
16
+ - "5.3"
17
+ - "8.0"
18
+
19
+ steps:
20
+ - name: "Checkout"
21
+ uses: "actions/checkout@v2"
22
+
23
+ - name: "Install PHP"
24
+ uses: "shivammathur/setup-php@v2"
25
+ with:
26
+ coverage: "none"
27
+ php-version: "${{ matrix.php-version }}"
28
+
29
+ - name: "Lint PHP files"
30
+ run: "find src/ -type f -name '*.php' -print0 | xargs -0 -L1 -P4 -- php -l -f"
vendor/composer/installers/.github/workflows/phpstan.yml ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "PHPStan"
2
+
3
+ on:
4
+ - push
5
+ - pull_request
6
+
7
+ env:
8
+ COMPOSER_FLAGS: "--ansi --no-interaction --no-progress --prefer-dist"
9
+ SYMFONY_PHPUNIT_VERSION: ""
10
+
11
+ jobs:
12
+ tests:
13
+ name: "PHPStan"
14
+
15
+ runs-on: ubuntu-latest
16
+
17
+ strategy:
18
+ matrix:
19
+ php-version:
20
+ # pinned to 7.4 because we need PHPUnit 7.5 which does not support PHP 8
21
+ - "7.4"
22
+
23
+ steps:
24
+ - name: "Checkout"
25
+ uses: "actions/checkout@v2"
26
+
27
+ - name: "Install PHP"
28
+ uses: "shivammathur/setup-php@v2"
29
+ with:
30
+ coverage: "none"
31
+ php-version: "${{ matrix.php-version }}"
32
+
33
+ - name: Get composer cache directory
34
+ id: composercache
35
+ run: echo "::set-output name=dir::$(composer config cache-files-dir)"
36
+
37
+ - name: Cache dependencies
38
+ uses: actions/cache@v2
39
+ with:
40
+ path: ${{ steps.composercache.outputs.dir }}
41
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
42
+ restore-keys: ${{ runner.os }}-composer-
43
+
44
+ - name: "Install latest dependencies"
45
+ run: "composer update ${{ env.COMPOSER_FLAGS }}"
46
+
47
+ - name: Run PHPStan
48
+ # Locked to phpunit 7.5 here as newer ones have void return types which break inheritance
49
+ run: |
50
+ composer require --dev phpunit/phpunit:^7.5.20 --with-all-dependencies ${{ env.COMPOSER_FLAGS }}
51
+ vendor/bin/phpstan analyse
vendor/composer/installers/composer.json CHANGED
@@ -46,6 +46,7 @@
46
  "MODX",
47
  "MODX Evo",
48
  "MediaWiki",
 
49
  "OXID",
50
  "osclass",
51
  "MODULEWork",
@@ -57,15 +58,18 @@
57
  "PPI",
58
  "Puppet",
59
  "Porto",
 
60
  "RadPHP",
61
  "ReIndex",
62
  "Roundcube",
63
  "shopware",
64
  "SilverStripe",
65
  "SMF",
 
66
  "SyDES",
67
  "Sylius",
68
  "symfony",
 
69
  "Thelia",
70
  "TYPO3",
71
  "WHMCS",
@@ -86,10 +90,13 @@
86
  "autoload": {
87
  "psr-4": { "Composer\\Installers\\": "src/Composer/Installers" }
88
  },
 
 
 
89
  "extra": {
90
  "class": "Composer\\Installers\\Plugin",
91
  "branch-alias": {
92
- "dev-master": "1.0-dev"
93
  }
94
  },
95
  "replace": {
@@ -100,13 +107,15 @@
100
  "composer-plugin-api": "^1.0 || ^2.0"
101
  },
102
  "require-dev": {
103
- "composer/composer": "1.6.* || 2.0.*@dev",
104
- "composer/semver": "1.0.* || 2.0.*@dev",
105
- "phpunit/phpunit": "^4.8.36",
106
- "sebastian/comparator": "^1.2.4",
107
- "symfony/process": "^2.3"
 
108
  },
109
  "scripts": {
110
- "test": "phpunit"
 
111
  }
112
  }
46
  "MODX",
47
  "MODX Evo",
48
  "MediaWiki",
49
+ "Miaoxing",
50
  "OXID",
51
  "osclass",
52
  "MODULEWork",
58
  "PPI",
59
  "Puppet",
60
  "Porto",
61
+ "ProcessWire",
62
  "RadPHP",
63
  "ReIndex",
64
  "Roundcube",
65
  "shopware",
66
  "SilverStripe",
67
  "SMF",
68
+ "Starbug",
69
  "SyDES",
70
  "Sylius",
71
  "symfony",
72
+ "TastyIgniter",
73
  "Thelia",
74
  "TYPO3",
75
  "WHMCS",
90
  "autoload": {
91
  "psr-4": { "Composer\\Installers\\": "src/Composer/Installers" }
92
  },
93
+ "autoload-dev": {
94
+ "psr-4": { "Composer\\Installers\\Test\\": "tests/Composer/Installers/Test" }
95
+ },
96
  "extra": {
97
  "class": "Composer\\Installers\\Plugin",
98
  "branch-alias": {
99
+ "dev-main": "1.x-dev"
100
  }
101
  },
102
  "replace": {
107
  "composer-plugin-api": "^1.0 || ^2.0"
108
  },
109
  "require-dev": {
110
+ "composer/composer": "1.6.* || ^2.0",
111
+ "composer/semver": "^1 || ^3",
112
+ "symfony/phpunit-bridge": "^4.2 || ^5",
113
+ "phpstan/phpstan": "^0.12.55",
114
+ "symfony/process": "^2.3",
115
+ "phpstan/phpstan-phpunit": "^0.12.16"
116
  },
117
  "scripts": {
118
+ "test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor/bin/simple-phpunit",
119
+ "phpstan": "vendor/bin/phpstan analyse"
120
  }
121
  }
vendor/composer/installers/phpstan.neon.dist ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ parameters:
2
+ level: 5
3
+ paths:
4
+ - src
5
+ - tests
6
+ excludes_analyse:
7
+ - tests/Composer/Installers/Test/PolyfillTestCase.php
8
+
9
+ includes:
10
+ - vendor/phpstan/phpstan-phpunit/extension.neon
vendor/composer/installers/src/Composer/Installers/BaseInstaller.php CHANGED
@@ -74,8 +74,8 @@ abstract class BaseInstaller
74
  /**
75
  * For an installer to override to modify the vars per installer.
76
  *
77
- * @param array $vars
78
- * @return array
79
  */
80
  public function inflectPackageVars($vars)
81
  {
@@ -85,7 +85,7 @@ abstract class BaseInstaller
85
  /**
86
  * Gets the installer's locations
87
  *
88
- * @return array
89
  */
90
  public function getLocations()
91
  {
@@ -95,8 +95,8 @@ abstract class BaseInstaller
95
  /**
96
  * Replace vars in a path
97
  *
98
- * @param string $path
99
- * @param array $vars
100
  * @return string
101
  */
102
  protected function templatePath($path, array $vars = array())
@@ -121,7 +121,7 @@ abstract class BaseInstaller
121
  * @param string $name
122
  * @param string $type
123
  * @param string $vendor = NULL
124
- * @return string
125
  */
126
  protected function mapCustomInstallPaths(array $paths, $name, $type, $vendor = NULL)
127
  {
74
  /**
75
  * For an installer to override to modify the vars per installer.
76
  *
77
+ * @param array<string, string> $vars This will normally receive array{name: string, vendor: string, type: string}
78
+ * @return array<string, string>
79
  */
80
  public function inflectPackageVars($vars)
81
  {
85
  /**
86
  * Gets the installer's locations
87
  *
88
+ * @return array<string, string> map of package types => install path
89
  */
90
  public function getLocations()
91
  {
95
  /**
96
  * Replace vars in a path
97
  *
98
+ * @param string $path
99
+ * @param array<string, string> $vars
100
  * @return string
101
  */
102
  protected function templatePath($path, array $vars = array())
121
  * @param string $name
122
  * @param string $type
123
  * @param string $vendor = NULL
124
+ * @return string|false
125
  */
126
  protected function mapCustomInstallPaths(array $paths, $name, $type, $vendor = NULL)
127
  {
vendor/composer/installers/src/Composer/Installers/CakePHPInstaller.php CHANGED
@@ -2,6 +2,7 @@
2
  namespace Composer\Installers;
3
 
4
  use Composer\DependencyResolver\Pool;
 
5
 
6
  class CakePHPInstaller extends BaseInstaller
7
  {
@@ -49,14 +50,6 @@ class CakePHPInstaller extends BaseInstaller
49
  */
50
  protected function matchesCakeVersion($matcher, $version)
51
  {
52
- if (class_exists('Composer\Semver\Constraint\MultiConstraint')) {
53
- $multiClass = 'Composer\Semver\Constraint\MultiConstraint';
54
- $constraintClass = 'Composer\Semver\Constraint\Constraint';
55
- } else {
56
- $multiClass = 'Composer\Package\LinkConstraint\MultiConstraint';
57
- $constraintClass = 'Composer\Package\LinkConstraint\VersionConstraint';
58
- }
59
-
60
  $repositoryManager = $this->composer->getRepositoryManager();
61
  if (! $repositoryManager) {
62
  return false;
@@ -67,6 +60,6 @@ class CakePHPInstaller extends BaseInstaller
67
  return false;
68
  }
69
 
70
- return $repos->findPackage('cakephp/cakephp', new $constraintClass($matcher, $version)) !== null;
71
  }
72
  }
2
  namespace Composer\Installers;
3
 
4
  use Composer\DependencyResolver\Pool;
5
+ use Composer\Semver\Constraint\Constraint;
6
 
7
  class CakePHPInstaller extends BaseInstaller
8
  {
50
  */
51
  protected function matchesCakeVersion($matcher, $version)
52
  {
 
 
 
 
 
 
 
 
53
  $repositoryManager = $this->composer->getRepositoryManager();
54
  if (! $repositoryManager) {
55
  return false;
60
  return false;
61
  }
62
 
63
+ return $repos->findPackage('cakephp/cakephp', new Constraint($matcher, $version)) !== null;
64
  }
65
  }
vendor/composer/installers/src/Composer/Installers/CockpitInstaller.php CHANGED
@@ -12,9 +12,7 @@ class CockpitInstaller extends BaseInstaller
12
  *
13
  * Strip `module-` prefix from package name.
14
  *
15
- * @param array @vars
16
- *
17
- * @return array
18
  */
19
  public function inflectPackageVars($vars)
20
  {
12
  *
13
  * Strip `module-` prefix from package name.
14
  *
15
+ * {@inheritDoc}
 
 
16
  */
17
  public function inflectPackageVars($vars)
18
  {
vendor/composer/installers/src/Composer/Installers/Installer.php CHANGED
@@ -9,6 +9,7 @@ use Composer\IO\IOInterface;
9
  use Composer\Package\PackageInterface;
10
  use Composer\Repository\InstalledRepositoryInterface;
11
  use Composer\Util\Filesystem;
 
12
 
13
  class Installer extends LibraryInstaller
14
  {
@@ -49,6 +50,7 @@ class Installer extends LibraryInstaller
49
  'fuelphp' => 'FuelphpInstaller',
50
  'grav' => 'GravInstaller',
51
  'hurad' => 'HuradInstaller',
 
52
  'imagecms' => 'ImageCMSInstaller',
53
  'itop' => 'ItopInstaller',
54
  'joomla' => 'JoomlaInstaller',
@@ -68,6 +70,7 @@ class Installer extends LibraryInstaller
68
  'maya' => 'MayaInstaller',
69
  'mautic' => 'MauticInstaller',
70
  'mediawiki' => 'MediaWikiInstaller',
 
71
  'microweber' => 'MicroweberInstaller',
72
  'modulework' => 'MODULEWorkInstaller',
73
  'modx' => 'ModxInstaller',
@@ -76,7 +79,7 @@ class Installer extends LibraryInstaller
76
  'october' => 'OctoberInstaller',
77
  'ontowiki' => 'OntoWikiInstaller',
78
  'oxid' => 'OxidInstaller',
79
- 'osclass' => 'OsclassInstaller',
80
  'pxcms' => 'PxcmsInstaller',
81
  'phpbb' => 'PhpBBInstaller',
82
  'pimcore' => 'PimcoreInstaller',
@@ -87,6 +90,7 @@ class Installer extends LibraryInstaller
87
  'radphp' => 'RadPHPInstaller',
88
  'phifty' => 'PhiftyInstaller',
89
  'porto' => 'PortoInstaller',
 
90
  'redaxo' => 'RedaxoInstaller',
91
  'redaxo5' => 'Redaxo5Installer',
92
  'reindex' => 'ReIndexInstaller',
@@ -95,6 +99,7 @@ class Installer extends LibraryInstaller
95
  'sitedirect' => 'SiteDirectInstaller',
96
  'silverstripe' => 'SilverStripeInstaller',
97
  'smf' => 'SMFInstaller',
 
98
  'sydes' => 'SyDESInstaller',
99
  'sylius' => 'SyliusInstaller',
100
  'symfony1' => 'Symfony1Installer',
@@ -106,6 +111,7 @@ class Installer extends LibraryInstaller
106
  'userfrosting' => 'UserFrostingInstaller',
107
  'vanilla' => 'VanillaInstaller',
108
  'whmcs' => 'WHMCSInstaller',
 
109
  'wolfcms' => 'WolfCMSInstaller',
110
  'wordpress' => 'WordPressInstaller',
111
  'yawik' => 'YawikInstaller',
@@ -160,9 +166,23 @@ class Installer extends LibraryInstaller
160
 
161
  public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
162
  {
163
- parent::uninstall($repo, $package);
164
  $installPath = $this->getPackageBasePath($package);
165
- $this->io->write(sprintf('Deleting %s - %s', $installPath, !file_exists($installPath) ? '<comment>deleted</comment>' : '<error>not deleted</error>'));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  }
167
 
168
  /**
@@ -184,23 +204,20 @@ class Installer extends LibraryInstaller
184
  /**
185
  * Finds a supported framework type if it exists and returns it
186
  *
187
- * @param string $type
188
- * @return string
189
  */
190
  protected function findFrameworkType($type)
191
  {
192
- $frameworkType = false;
193
-
194
  krsort($this->supportedTypes);
195
 
196
  foreach ($this->supportedTypes as $key => $val) {
197
  if ($key === substr($type, 0, strlen($key))) {
198
- $frameworkType = substr($type, 0, strlen($key));
199
- break;
200
  }
201
  }
202
 
203
- return $frameworkType;
204
  }
205
 
206
  /**
9
  use Composer\Package\PackageInterface;
10
  use Composer\Repository\InstalledRepositoryInterface;
11
  use Composer\Util\Filesystem;
12
+ use React\Promise\PromiseInterface;
13
 
14
  class Installer extends LibraryInstaller
15
  {
50
  'fuelphp' => 'FuelphpInstaller',
51
  'grav' => 'GravInstaller',
52
  'hurad' => 'HuradInstaller',
53
+ 'tastyigniter' => 'TastyIgniterInstaller',
54
  'imagecms' => 'ImageCMSInstaller',
55
  'itop' => 'ItopInstaller',
56
  'joomla' => 'JoomlaInstaller',
70
  'maya' => 'MayaInstaller',
71
  'mautic' => 'MauticInstaller',
72
  'mediawiki' => 'MediaWikiInstaller',
73
+ 'miaoxing' => 'MiaoxingInstaller',
74
  'microweber' => 'MicroweberInstaller',
75
  'modulework' => 'MODULEWorkInstaller',
76
  'modx' => 'ModxInstaller',
79
  'october' => 'OctoberInstaller',
80
  'ontowiki' => 'OntoWikiInstaller',
81
  'oxid' => 'OxidInstaller',
82
+ 'osclass' => 'OsclassInstaller',
83
  'pxcms' => 'PxcmsInstaller',
84
  'phpbb' => 'PhpBBInstaller',
85
  'pimcore' => 'PimcoreInstaller',
90
  'radphp' => 'RadPHPInstaller',
91
  'phifty' => 'PhiftyInstaller',
92
  'porto' => 'PortoInstaller',
93
+ 'processwire' => 'ProcessWireInstaller',
94
  'redaxo' => 'RedaxoInstaller',
95
  'redaxo5' => 'Redaxo5Installer',
96
  'reindex' => 'ReIndexInstaller',
99
  'sitedirect' => 'SiteDirectInstaller',
100
  'silverstripe' => 'SilverStripeInstaller',
101
  'smf' => 'SMFInstaller',
102
+ 'starbug' => 'StarbugInstaller',
103
  'sydes' => 'SyDESInstaller',
104
  'sylius' => 'SyliusInstaller',
105
  'symfony1' => 'Symfony1Installer',
111
  'userfrosting' => 'UserFrostingInstaller',
112
  'vanilla' => 'VanillaInstaller',
113
  'whmcs' => 'WHMCSInstaller',
114
+ 'winter' => 'WinterInstaller',
115
  'wolfcms' => 'WolfCMSInstaller',
116
  'wordpress' => 'WordPressInstaller',
117
  'yawik' => 'YawikInstaller',
166
 
167
  public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
168
  {
 
169
  $installPath = $this->getPackageBasePath($package);
170
+ $io = $this->io;
171
+ $outputStatus = function () use ($io, $installPath) {
172
+ $io->write(sprintf('Deleting %s - %s', $installPath, !file_exists($installPath) ? '<comment>deleted</comment>' : '<error>not deleted</error>'));
173
+ };
174
+
175
+ $promise = parent::uninstall($repo, $package);
176
+
177
+ // Composer v2 might return a promise here
178
+ if ($promise instanceof PromiseInterface) {
179
+ return $promise->then($outputStatus);
180
+ }
181
+
182
+ // If not, execute the code right away as parent::uninstall executed synchronously (composer v1, or v2 without async)
183
+ $outputStatus();
184
+
185
+ return null;
186
  }
187
 
188
  /**
204
  /**
205
  * Finds a supported framework type if it exists and returns it
206
  *
207
+ * @param string $type
208
+ * @return string|false
209
  */
210
  protected function findFrameworkType($type)
211
  {
 
 
212
  krsort($this->supportedTypes);
213
 
214
  foreach ($this->supportedTypes as $key => $val) {
215
  if ($key === substr($type, 0, strlen($key))) {
216
+ return substr($type, 0, strlen($key));
 
217
  }
218
  }
219
 
220
+ return false;
221
  }
222
 
223
  /**
vendor/composer/installers/src/Composer/Installers/MauticInstaller.php CHANGED
@@ -1,22 +1,45 @@
1
  <?php
2
  namespace Composer\Installers;
3
 
 
 
4
  class MauticInstaller extends BaseInstaller
5
  {
6
  protected $locations = array(
7
- 'plugin' => 'plugins/{$name}/',
8
- 'theme' => 'themes/{$name}/',
 
9
  );
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  /**
12
  * Format package name of mautic-plugins to CamelCase
13
  */
14
  public function inflectPackageVars($vars)
15
  {
16
- if ($vars['type'] == 'mautic-plugin') {
17
- $vars['name'] = preg_replace_callback('/(-[a-z])/', function ($matches) {
18
- return strtoupper($matches[0][1]);
19
- }, ucfirst($vars['name']));
20
  }
21
 
22
  return $vars;
1
  <?php
2
  namespace Composer\Installers;
3
 
4
+ use Composer\Package\PackageInterface;
5
+
6
  class MauticInstaller extends BaseInstaller
7
  {
8
  protected $locations = array(
9
+ 'plugin' => 'plugins/{$name}/',
10
+ 'theme' => 'themes/{$name}/',
11
+ 'core' => 'app/',
12
  );
13
 
14
+ private function getDirectoryName()
15
+ {
16
+ $extra = $this->package->getExtra();
17
+ if (!empty($extra['install-directory-name'])) {
18
+ return $extra['install-directory-name'];
19
+ }
20
+
21
+ return $this->toCamelCase($this->package->getPrettyName());
22
+ }
23
+
24
+ /**
25
+ * @param string $packageName
26
+ *
27
+ * @return string
28
+ */
29
+ private function toCamelCase($packageName)
30
+ {
31
+ return str_replace(' ', '', ucwords(str_replace('-', ' ', basename($packageName))));
32
+ }
33
+
34
  /**
35
  * Format package name of mautic-plugins to CamelCase
36
  */
37
  public function inflectPackageVars($vars)
38
  {
39
+
40
+ if ($vars['type'] == 'mautic-plugin' || $vars['type'] == 'mautic-theme') {
41
+ $directoryName = $this->getDirectoryName();
42
+ $vars['name'] = $directoryName;
43
  }
44
 
45
  return $vars;
vendor/composer/installers/src/Composer/Installers/MiaoxingInstaller.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Composer\Installers;
4
+
5
+ class MiaoxingInstaller extends BaseInstaller
6
+ {
7
+ protected $locations = array(
8
+ 'plugin' => 'plugins/{$name}/',
9
+ );
10
+ }
vendor/composer/installers/src/Composer/Installers/MoodleInstaller.php CHANGED
@@ -18,6 +18,7 @@ class MoodleInstaller extends BaseInstaller
18
  'cachestore' => 'cache/stores/{$name}/',
19
  'cachelock' => 'cache/locks/{$name}/',
20
  'calendartype' => 'calendar/type/{$name}/',
 
21
  'format' => 'course/format/{$name}/',
22
  'coursereport' => 'course/report/{$name}/',
23
  'customcertelement' => 'mod/customcert/element/{$name}/',
18
  'cachestore' => 'cache/stores/{$name}/',
19
  'cachelock' => 'cache/locks/{$name}/',
20
  'calendartype' => 'calendar/type/{$name}/',
21
+ 'fileconverter' => 'files/converter/{$name}/',
22
  'format' => 'course/format/{$name}/',
23
  'coursereport' => 'course/report/{$name}/',
24
  'customcertelement' => 'mod/customcert/element/{$name}/',
vendor/composer/installers/src/Composer/Installers/OctoberInstaller.php CHANGED
@@ -6,7 +6,7 @@ class OctoberInstaller extends BaseInstaller
6
  protected $locations = array(
7
  'module' => 'modules/{$name}/',
8
  'plugin' => 'plugins/{$vendor}/{$name}/',
9
- 'theme' => 'themes/{$name}/'
10
  );
11
 
12
  /**
@@ -41,6 +41,7 @@ class OctoberInstaller extends BaseInstaller
41
  protected function inflectThemeVars($vars)
42
  {
43
  $vars['name'] = preg_replace('/^oc-|-theme$/', '', $vars['name']);
 
44
 
45
  return $vars;
46
  }
6
  protected $locations = array(
7
  'module' => 'modules/{$name}/',
8
  'plugin' => 'plugins/{$vendor}/{$name}/',
9
+ 'theme' => 'themes/{$vendor}-{$name}/'
10
  );
11
 
12
  /**
41
  protected function inflectThemeVars($vars)
42
  {
43
  $vars['name'] = preg_replace('/^oc-|-theme$/', '', $vars['name']);
44
+ $vars['vendor'] = preg_replace('/[^a-z0-9_]/i', '', $vars['vendor']);
45
 
46
  return $vars;
47
  }
vendor/composer/installers/src/Composer/Installers/OxidInstaller.php CHANGED
@@ -18,7 +18,7 @@ class OxidInstaller extends BaseInstaller
18
  *
19
  * @param PackageInterface $package
20
  * @param string $frameworkType
21
- * @return void
22
  */
23
  public function getInstallPath(PackageInterface $package, $frameworkType = '')
24
  {
18
  *
19
  * @param PackageInterface $package
20
  * @param string $frameworkType
21
+ * @return string
22
  */
23
  public function getInstallPath(PackageInterface $package, $frameworkType = '')
24
  {
vendor/composer/installers/src/Composer/Installers/ProcessWireInstaller.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Composer\Installers;
4
+
5
+ class ProcessWireInstaller extends BaseInstaller
6
+ {
7
+ protected $locations = array(
8
+ 'module' => 'site/modules/{$name}/',
9
+ );
10
+
11
+ /**
12
+ * Format package name to CamelCase
13
+ */
14
+ public function inflectPackageVars($vars)
15
+ {
16
+ $vars['name'] = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name']));
17
+ $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
18
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
19
+
20
+ return $vars;
21
+ }
22
+ }
vendor/composer/installers/src/Composer/Installers/StarbugInstaller.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Composer\Installers;
3
+
4
+ class StarbugInstaller extends BaseInstaller
5
+ {
6
+ protected $locations = array(
7
+ 'module' => 'modules/{$name}/',
8
+ 'theme' => 'themes/{$name}/',
9
+ 'custom-module' => 'app/modules/{$name}/',
10
+ 'custom-theme' => 'app/themes/{$name}/'
11
+ );
12
+ }
vendor/composer/installers/src/Composer/Installers/SyDESInstaller.php CHANGED
@@ -13,9 +13,7 @@ class SyDESInstaller extends BaseInstaller
13
  *
14
  * Strip `sydes-` prefix and a trailing '-theme' or '-module' from package name if present.
15
  *
16
- * @param array @vars
17
- *
18
- * @return array
19
  */
20
  public function inflectPackageVars($vars)
21
  {
13
  *
14
  * Strip `sydes-` prefix and a trailing '-theme' or '-module' from package name if present.
15
  *
16
+ * {@inerhitDoc}
 
 
17
  */
18
  public function inflectPackageVars($vars)
19
  {
vendor/composer/installers/src/Composer/Installers/TaoInstaller.php CHANGED
@@ -6,7 +6,25 @@ namespace Composer\Installers;
6
  */
7
  class TaoInstaller extends BaseInstaller
8
  {
 
 
9
  protected $locations = array(
10
  'extension' => '{$name}'
11
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  }
6
  */
7
  class TaoInstaller extends BaseInstaller
8
  {
9
+ const EXTRA_TAO_EXTENSION_NAME = 'tao-extension-name';
10
+
11
  protected $locations = array(
12
  'extension' => '{$name}'
13
  );
14
+
15
+ public function inflectPackageVars($vars)
16
+ {
17
+ $extra = $this->package->getExtra();
18
+
19
+ if (array_key_exists(self::EXTRA_TAO_EXTENSION_NAME, $extra)) {
20
+ $vars['name'] = $extra[self::EXTRA_TAO_EXTENSION_NAME];
21
+ return $vars;
22
+ }
23
+
24
+ $vars['name'] = str_replace('extension-', '', $vars['name']);
25
+ $vars['name'] = str_replace('-', ' ', $vars['name']);
26
+ $vars['name'] = lcfirst(str_replace(' ', '', ucwords($vars['name'])));
27
+
28
+ return $vars;
29
+ }
30
  }
vendor/composer/installers/src/Composer/Installers/TastyIgniterInstaller.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Composer\Installers;
4
+
5
+ class TastyIgniterInstaller extends BaseInstaller
6
+ {
7
+ protected $locations = array(
8
+ 'extension' => 'extensions/{$vendor}/{$name}/',
9
+ 'theme' => 'themes/{$name}/',
10
+ );
11
+
12
+ /**
13
+ * Format package name.
14
+ *
15
+ * Cut off leading 'ti-ext-' or 'ti-theme-' if present.
16
+ * Strip vendor name of characters that is not alphanumeric or an underscore
17
+ *
18
+ */
19
+ public function inflectPackageVars($vars)
20
+ {
21
+ if ($vars['type'] === 'tastyigniter-extension') {
22
+ $vars['vendor'] = preg_replace('/[^a-z0-9_]/i', '', $vars['vendor']);
23
+ $vars['name'] = preg_replace('/^ti-ext-/', '', $vars['name']);
24
+ }
25
+
26
+ if ($vars['type'] === 'tastyigniter-theme') {
27
+ $vars['name'] = preg_replace('/^ti-theme-/', '', $vars['name']);
28
+ }
29
+
30
+ return $vars;
31
+ }
32
+ }
vendor/composer/installers/src/Composer/Installers/WinterInstaller.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Composer\Installers;
3
+
4
+ class WinterInstaller extends BaseInstaller
5
+ {
6
+ protected $locations = array(
7
+ 'module' => 'modules/{$name}/',
8
+ 'plugin' => 'plugins/{$vendor}/{$name}/',
9
+ 'theme' => 'themes/{$name}/'
10
+ );
11
+
12
+ /**
13
+ * Format package name.
14
+ *
15
+ * For package type winter-plugin, cut off a trailing '-plugin' if present.
16
+ *
17
+ * For package type winter-theme, cut off a trailing '-theme' if present.
18
+ *
19
+ */
20
+ public function inflectPackageVars($vars)
21
+ {
22
+ if ($vars['type'] === 'winter-module') {
23
+ return $this->inflectModuleVars($vars);
24
+ }
25
+
26
+ if ($vars['type'] === 'winter-plugin') {
27
+ return $this->inflectPluginVars($vars);
28
+ }
29
+
30
+ if ($vars['type'] === 'winter-theme') {
31
+ return $this->inflectThemeVars($vars);
32
+ }
33
+
34
+ return $vars;
35
+ }
36
+
37
+ protected function inflectModuleVars($vars)
38
+ {
39
+ $vars['name'] = preg_replace('/^wn-|-module$/', '', $vars['name']);
40
+
41
+ return $vars;
42
+ }
43
+
44
+ protected function inflectPluginVars($vars)
45
+ {
46
+ $vars['name'] = preg_replace('/^wn-|-plugin$/', '', $vars['name']);
47
+ $vars['vendor'] = preg_replace('/[^a-z0-9_]/i', '', $vars['vendor']);
48
+
49
+ return $vars;
50
+ }
51
+
52
+ protected function inflectThemeVars($vars)
53
+ {
54
+ $vars['name'] = preg_replace('/^wn-|-theme$/', '', $vars['name']);
55
+
56
+ return $vars;
57
+ }
58
+ }
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 >= 70200)) {
8
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.0". 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/rosell-dk/webp-convert/README.md CHANGED
@@ -18,13 +18,25 @@ The library can convert using the following methods:
18
  - *gmagick* (using [Gmagick PHP extension](https://www.php.net/manual/en/book.gmagick.php))
19
  - *imagemagick* (executing [imagemagick](https://imagemagick.org/index.php) binary using an `exec` call)
20
  - *graphicsmagick* (executing [graphicsmagick](http://www.graphicsmagick.org/) binary using an `exec` call)
21
- - **NEW in 2.4.0:** *ffmpeg* (executing [ffmpeg](https://ffmpeg.org/) binary using an `exec` call)
22
  - *wpc* (using [WebPConvert Cloud Service](https://github.com/rosell-dk/webp-convert-cloud-service/) - an open source webp converter for PHP - based on this library)
23
  - *ewwww* (using the [ewww](https://ewww.io/plans/) cloud converter (1 USD startup and then free webp conversion))
24
  - *gd* (using the [Gd PHP extension](https://www.php.net/manual/en/book.image.php))
25
 
26
  In addition to converting, the library also has a method for *serving* converted images, and we have instructions here on how to set up a solution for automatically serving webp images to browsers that supports webp.
27
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  ## Installation
29
  Require the library with *Composer*, like this:
30
 
@@ -98,6 +110,7 @@ The library can be used to create a *WebP On Demand* solution, which automatical
98
  This library is used as the engine to provide webp conversions to a handful of platforms. Hopefully this list will be growing over time. Currently there are plugins / extensions / modules / whatever the term is for the following CMS'es (ordered by [market share](https://w3techs.com/technologies/overview/content_management/all)):
99
 
100
  - [Wordpress](https://github.com/rosell-dk/webp-express)
 
101
  - [Contao](https://github.com/postyou/contao-webp-bundle)
102
  - [Kirby](https://github.com/S1SYPHOS/kirby-webp)
103
  - [October CMS](https://github.com/OFFLINE-GmbH/oc-responsive-images-plugin/)
18
  - *gmagick* (using [Gmagick PHP extension](https://www.php.net/manual/en/book.gmagick.php))
19
  - *imagemagick* (executing [imagemagick](https://imagemagick.org/index.php) binary using an `exec` call)
20
  - *graphicsmagick* (executing [graphicsmagick](http://www.graphicsmagick.org/) binary using an `exec` call)
21
+ - *ffmpeg* (executing [ffmpeg](https://ffmpeg.org/) binary using an `exec` call)
22
  - *wpc* (using [WebPConvert Cloud Service](https://github.com/rosell-dk/webp-convert-cloud-service/) - an open source webp converter for PHP - based on this library)
23
  - *ewwww* (using the [ewww](https://ewww.io/plans/) cloud converter (1 USD startup and then free webp conversion))
24
  - *gd* (using the [Gd PHP extension](https://www.php.net/manual/en/book.image.php))
25
 
26
  In addition to converting, the library also has a method for *serving* converted images, and we have instructions here on how to set up a solution for automatically serving webp images to browsers that supports webp.
27
 
28
+ ## News: 2.6.0 has just been released
29
+ - Introduced [auto-limit](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md#auto-limit) option which replaces setting "quality" to "auto" [#281](https://github.com/rosell-dk/webp-convert/issues/281)
30
+ - Added "sharp-yuv" option and made it default on. [Its great](https://www.ctrl.blog/entry/webp-sharp-yuv.html), use it! Works in most converters (works in cwebp, vips, imagemagick, graphicsmagick, imagick and gmagick) [#267](https://github.com/rosell-dk/webp-convert/issues/267), [#280](https://github.com/rosell-dk/webp-convert/issues/280), [#284](https://github.com/rosell-dk/webp-convert/issues/284)
31
+ - Bumped cwebp binaries to 1.2.0 [#273](https://github.com/rosell-dk/webp-convert/issues/273)
32
+ - vips now supports "method" option and "preset" option.
33
+ - graphicsmagick now supports "auto-filter" potion
34
+ - vips, imagick, imagemagick, graphicsmagick and gmagick now supports "preset" option [#275(https://github.com/rosell-dk/webp-convert/issues/275)
35
+ - cwebp now only validates hash of supplied precompiled binaries when necessary. This cuts down conversion time. [#287](https://github.com/rosell-dk/webp-convert/issues/287)
36
+ - Added [new option](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md#cwebp-skip-these-precompiled-binaries) to cwebp for skipping precompiled binaries that are known not to work on current system. This will cut down on conversion time. [#288](https://github.com/rosell-dk/webp-convert/issues/288)
37
+ - And more (view closed issues [here](https://github.com/rosell-dk/webp-convert/milestone/22?closed=1))
38
+
39
+
40
  ## Installation
41
  Require the library with *Composer*, like this:
42
 
110
  This library is used as the engine to provide webp conversions to a handful of platforms. Hopefully this list will be growing over time. Currently there are plugins / extensions / modules / whatever the term is for the following CMS'es (ordered by [market share](https://w3techs.com/technologies/overview/content_management/all)):
111
 
112
  - [Wordpress](https://github.com/rosell-dk/webp-express)
113
+ - [Drupal 7](https://github.com/HDDen/Webp-Drupal-7)
114
  - [Contao](https://github.com/postyou/contao-webp-bundle)
115
  - [Kirby](https://github.com/S1SYPHOS/kirby-webp)
116
  - [October CMS](https://github.com/OFFLINE-GmbH/oc-responsive-images-plugin/)
vendor/rosell-dk/webp-convert/composer-php56.json ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "rosell-dk/webp-convert",
3
+ "description": "Convert JPEG & PNG to WebP with PHP",
4
+ "type": "library",
5
+ "license": "MIT",
6
+ "keywords": ["webp", "images", "cwebp", "imagick", "gd", "jpg2webp", "png2webp", "jpg", "png", "image conversion"],
7
+ "scripts": {
8
+ "ci": [
9
+ "@test",
10
+ "@phpcs-all",
11
+ "@composer validate --no-check-all --strict",
12
+ "@phpstan-global"
13
+ ],
14
+ "test": "phpunit --coverage-text",
15
+ "phpunit": "phpunit --coverage-text",
16
+ "test-no-cov": "phpunit --no-coverage",
17
+ "cs-fix-all": [
18
+ "php-cs-fixer fix src"
19
+ ],
20
+ "cs-fix": "php-cs-fixer fix",
21
+ "cs-dry": "php-cs-fixer fix --dry-run --diff",
22
+ "phpcs": "phpcs --standard=PSR2",
23
+ "phpcs-all": "phpcs --standard=PSR2 src",
24
+ "phpcbf": "phpcbf --standard=PSR2",
25
+ "phpstan": "vendor/bin/phpstan analyse src --level=4",
26
+ "phpstan-global-old": "~/.composer/vendor/bin/phpstan analyse src --level=4",
27
+ "phpstan-global": "~/.config/composer/vendor/bin/phpstan analyse src --level=4"
28
+ },
29
+ "extra": {
30
+ "scripts-descriptions": {
31
+ "ci": "Run tests before CI",
32
+ "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
33
+ "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
34
+ "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
35
+ "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
36
+ "test": "Launches the preconfigured PHPUnit"
37
+ }
38
+ },
39
+ "autoload": {
40
+ "psr-4": { "WebPConvert\\": "src/" }
41
+ },
42
+ "autoload-dev": {
43
+ "psr-4": { "WebPConvert\\Tests\\": "tests/" }
44
+ },
45
+ "authors": [
46
+ {
47
+ "name": "Bjørn Rosell",
48
+ "homepage": "https://www.bitwise-it.dk/contact",
49
+ "role": "Project Author"
50
+ },
51
+ {
52
+ "name": "Martin Folkers",
53
+ "homepage": "https://twobrain.io",
54
+ "role": "Collaborator"
55
+ }
56
+ ],
57
+ "require": {
58
+ "php": "^5.6",
59
+ "rosell-dk/image-mime-type-guesser": "^0.3"
60
+ },
61
+ "suggest": {
62
+ "ext-gd": "to use GD extension for converting. Note: Gd must be compiled with webp support",
63
+ "ext-imagick": "to use Imagick extension for converting. Note: Gd must be compiled with webp support",
64
+ "ext-vips": "to use Vips extension for converting.",
65
+ "php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
66
+ },
67
+ "require-dev": {
68
+ "friendsofphp/php-cs-fixer": "^2.11",
69
+ "phpunit/phpunit": "5.7.27",
70
+ "squizlabs/php_codesniffer": "3.*"
71
+ },
72
+ "config": {
73
+ "sort-packages": true
74
+ }
75
+ }
vendor/rosell-dk/webp-convert/composer-php72.json ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "rosell-dk/webp-convert",
3
+ "description": "Convert JPEG & PNG to WebP with PHP",
4
+ "type": "library",
5
+ "license": "MIT",
6
+ "keywords": ["webp", "images", "cwebp", "imagick", "gd", "jpg2webp", "png2webp", "jpg", "png", "image conversion"],
7
+ "scripts": {
8
+ "ci": [
9
+ "@test",
10
+ "@phpcs-all",
11
+ "@composer validate --no-check-all --strict",
12
+ "@phpstan-global"
13
+ ],
14
+ "test": "phpunit --coverage-text",
15
+ "phpunit": "phpunit --coverage-text",
16
+ "test-no-cov": "phpunit --no-coverage",
17
+ "cs-fix-all": [
18
+ "php-cs-fixer fix src"
19
+ ],
20
+ "cs-fix": "php-cs-fixer fix",
21
+ "cs-dry": "php-cs-fixer fix --dry-run --diff",
22
+ "phpcs": "phpcs --standard=PSR2",
23
+ "phpcs-all": "phpcs --standard=PSR2 src",
24
+ "phpcbf": "phpcbf --standard=PSR2",
25
+ "phpstan": "vendor/bin/phpstan analyse src --level=4",
26
+ "phpstan-global-old": "~/.composer/vendor/bin/phpstan analyse src --level=4",
27
+ "phpstan-global": "~/.config/composer/vendor/bin/phpstan analyse src --level=4"
28
+ },
29
+ "extra": {
30
+ "scripts-descriptions": {
31
+ "ci": "Run tests before CI",
32
+ "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
33
+ "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
34
+ "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
35
+ "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
36
+ "test": "Launches the preconfigured PHPUnit"
37
+ }
38
+ },
39
+ "autoload": {
40
+ "psr-4": { "WebPConvert\\": "src/" }
41
+ },
42
+ "autoload-dev": {
43
+ "psr-4": { "WebPConvert\\Tests\\": "tests/" }
44
+ },
45
+ "authors": [
46
+ {
47
+ "name": "Bjørn Rosell",
48
+ "homepage": "https://www.bitwise-it.dk/contact",
49
+ "role": "Project Author"
50
+ },
51
+ {
52
+ "name": "Martin Folkers",
53
+ "homepage": "https://twobrain.io",
54
+ "role": "Collaborator"
55
+ }
56
+ ],
57
+ "require": {
58
+ "php": "^7.2",
59
+ "rosell-dk/image-mime-type-guesser": "^0.3"
60
+ },
61
+ "suggest": {
62
+ "ext-gd": "to use GD extension for converting. Note: Gd must be compiled with webp support",
63
+ "ext-imagick": "to use Imagick extension for converting. Note: Gd must be compiled with webp support",
64
+ "ext-vips": "to use Vips extension for converting.",
65
+ "php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
66
+ },
67
+ "require-dev": {
68
+ "friendsofphp/php-cs-fixer": "^2.11",
69
+ "phpunit/phpunit": "^8.0",
70
+ "squizlabs/php_codesniffer": "3.*"
71
+ },
72
+ "config": {
73
+ "sort-packages": true
74
+ }
75
+ }
vendor/rosell-dk/webp-convert/composer.json CHANGED
@@ -12,6 +12,7 @@
12
  "@phpstan-global"
13
  ],
14
  "test": "phpunit --coverage-text",
 
15
  "phpunit": "phpunit --coverage-text",
16
  "test-no-cov": "phpunit --no-coverage",
17
  "cs-fix-all": [
@@ -23,7 +24,8 @@
23
  "phpcs-all": "phpcs --standard=PSR2 src",
24
  "phpcbf": "phpcbf --standard=PSR2",
25
  "phpstan": "vendor/bin/phpstan analyse src --level=4",
26
- "phpstan-global": "~/.composer/vendor/bin/phpstan analyse src --level=4"
 
27
  },
28
  "extra": {
29
  "scripts-descriptions": {
@@ -54,7 +56,7 @@
54
  }
55
  ],
56
  "require": {
57
- "php": "^5.6 | ^7.0",
58
  "rosell-dk/image-mime-type-guesser": "^0.3"
59
  },
60
  "suggest": {
@@ -65,7 +67,7 @@
65
  },
66
  "require-dev": {
67
  "friendsofphp/php-cs-fixer": "^2.11",
68
- "phpunit/phpunit": "5.7.27",
69
  "squizlabs/php_codesniffer": "3.*"
70
  },
71
  "config": {
12
  "@phpstan-global"
13
  ],
14
  "test": "phpunit --coverage-text",
15
+ "test-41": "phpunit --coverage-text --configuration 'phpunit-41.xml.dist'",
16
  "phpunit": "phpunit --coverage-text",
17
  "test-no-cov": "phpunit --no-coverage",
18
  "cs-fix-all": [
24
  "phpcs-all": "phpcs --standard=PSR2 src",
25
  "phpcbf": "phpcbf --standard=PSR2",
26
  "phpstan": "vendor/bin/phpstan analyse src --level=4",
27
+ "phpstan-global-old": "~/.composer/vendor/bin/phpstan analyse src --level=4",
28
+ "phpstan-global": "~/.config/composer/vendor/bin/phpstan analyse src --level=4"
29
  },
30
  "extra": {
31
  "scripts-descriptions": {
56
  }
57
  ],
58
  "require": {
59
+ "php": "^5.6 | ^7.0 | ^8.0",
60
  "rosell-dk/image-mime-type-guesser": "^0.3"
61
  },
62
  "suggest": {
67
  },
68
  "require-dev": {
69
  "friendsofphp/php-cs-fixer": "^2.11",
70
+ "phpunit/phpunit": "^9.3",
71
  "squizlabs/php_codesniffer": "3.*"
72
  },
73
  "config": {
vendor/rosell-dk/webp-convert/docs/development.md DELETED
@@ -1,74 +0,0 @@
1
- # Development
2
-
3
- ## Setting up the environment.
4
-
5
- First, clone the repository:
6
- ```
7
- cd whatever/folder/you/want
8
- git clone https://github.com/rosell-dk/webp-convert.git
9
- ```
10
-
11
- Then install the dev tools with composer:
12
-
13
- ```
14
- composer install
15
- ```
16
-
17
- ## Unit Testing
18
- To run all the unit tests do this:
19
- ```
20
- composer test
21
- ```
22
- This also runs tests on the builds.
23
-
24
-
25
- Individual test files can be executed like this:
26
- ```
27
- composer phpunit tests/Convert/Converters/WPCTest
28
- composer phpunit tests/Serve/ServeConvertedTest
29
- ```
30
-
31
-
32
- ## Coding styles
33
- WebPConvert complies with the [PSR-2](https://www.php-fig.org/psr/psr-2/) coding standard.
34
-
35
- To validate coding style of all files, do this:
36
- ```
37
- composer phpcs src
38
- ```
39
-
40
- To automatically fix the coding style of all files, using [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer), do this:
41
- ```
42
- composer phpcbf src
43
- ```
44
-
45
- Or, alternatively, you can fix with the use the [PHP-CS-FIXER](https://github.com/FriendsOfPHP/PHP-CS-Fixer) library instead:
46
- ```
47
- composer cs-fix
48
- ```
49
-
50
- ## Running all tests in one command
51
- The following script runs the unit tests, checks the coding styles, validates `composer.json` and runs the builds.
52
- Run this before pushing anything to github. "ci" btw stands for *continuous integration*.
53
- ```
54
- composer ci
55
- ```
56
-
57
- ## Generating api docs
58
- Install phpdox and run it in the project root:
59
- ```
60
- phpdox
61
- ```
62
-
63
- ## Committing
64
- Before committing, first make sure to:
65
- - run `composer ci`
66
-
67
- ## Releasing
68
- Before releasing:
69
- - Update the version number in `Converters/AbstractConverter.php` (search for "WebP Convert")
70
- - Make sure that travis build is successful
71
-
72
- When releasing:
73
- - update the [webp-convert-concat](https://github.com/rosell-dk/webp-convert-concat) library
74
- - consider updating the require in the composer file in libraries that uses webp-convert (ie `webp-convert-cloud-service` and `webp-express`)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v1.3/converting/convert-options.md DELETED
@@ -1,322 +0,0 @@
1
- # The webp converters
2
-
3
- ## The converters at a glance
4
- When it comes to webp conversion, there is actually only one library in town: *libwebp* from Google. All conversion methods below ultimately uses that very same library for conversion. This means that it does not matter much, which conversion method you use. Whatever works. There is however one thing to take note of, if you set *quality* to *auto*, and your system cannot determine the quality of the source (this requires imagick or gmagick), and you do not have access to install those, then the only way to get quality-detection is to connect to a *wpc* cloud converter. However, with *cwebp*, you can specify the desired reduction (the *size-in-percentage* option) - at the cost of doubling the conversion time. Read more about those considerations in the API.
5
-
6
- Speed-wise, there is too little difference for it to matter, considering that images usually needs to be converted just once. Anyway, here are the results: *cweb* is the fastest (with method=3). *gd* is right behind, merely 3% slower than *cwebp*. *gmagick* are third place, ~8% slower than *cwebp*. *imagick* comes in ~22% slower than *cwebp*. *ewww* depends on connection speed. On my *digital ocean* account, it takes ~2 seconds to upload, convert, and download a tiny image (10 times longer than the local *cwebp*). A 1MB image however only takes ~4.5 seconds to upload, convert and download (1.5 seconds longer). A 2 MB image takes ~5 seconds to convert (only 16% longer than my *cwebp*). The *ewww* thus converts at a very decent speeds. Probably faster than your average shared host. If multiple big images needs to be converted at the same time, *ewww* will probably perform much better than the local converters.
7
-
8
- [`cwebp`](#cwebp) works by executing the *cwebp* binary from Google, which is build upon the *libwebp* (also from Google). That library is actually the only library in town for generating webp images, which means that the other conversion methods ultimately uses that very same library. Which again means that the results using the different methods are very similar. However, with *cwebp*, we have more parameters to tweak than with the rest. We for example have the *method* option, which controls the trade off between encoding speed and the compressed file size and quality. Setting this to max, we can squeeze the images a few percent extra - without loosing quality (the converter is still pretty fast, so in most cases it is probably worth it).
9
-
10
- Of course, as we here have to call a binary directly, *cwebp* requires the *exec* function to be enabled, and that the webserver user is allowed to execute the `cwebp` binary (either at known system locations, or one of the precompiled binaries, that comes with this library).
11
-
12
- [`vips`](#vips) (**new in 2.0**) works by using the vips extension, if available. Vips is great! It offers many webp options, it is fast and installation is easier than imagick and gd, as it does not need to be configured for webp support.
13
-
14
- [`imagick`](#imagick) does not support any special webp options, but is at least able to strip all metadata, if metadata is set to none. Imagick has a very nice feature - that it is able to detect the quality of a jpeg file. This enables it to automatically use same quality for destination as for source, which eliminates the risk of setting quality higher for the destination than for source (the result of that is that the file size gets higher, but the quality remains the same). As the other converters lends this capability from Imagick, this is however no reason for using Imagick rather than the other converters. Requirements: Imagick PHP extension compiled with WebP support
15
-
16
- [`gmagick`](#gmagick) uses the *gmagick* extension. It is very similar to *imagick*. Requirements: Gmagick PHP extension compiled with WebP support.
17
-
18
- [`gd`](#gd) uses the *Gd* extension to do the conversion. The *Gd* extension is pretty common, so the main feature of this converter is that it may work out of the box. It does not support any webp options, and does not support stripping metadata. Requirements: GD PHP extension compiled with WebP support.
19
-
20
- [`wpc`](#wpc) is an open source cloud service for converting images to webp. To use it, you must either install [webp-convert-cloud-service](https://github.com/rosell-dk/webp-convert-cloud-service) directly on a remote server, or install the Wordpress plugin, [WebP Express](https://github.com/rosell-dk/webp-express) in Wordpress. Btw: Beware that upload limits will prevent conversion of big images. The converter checks your *php.ini* settings and abandons upload right away, if an image is larger than your *upload_max_filesize* or your *post_max_size* setting. Requirements: Access to a running service. The service can be installed [directly](https://github.com/rosell-dk/webp-convert-cloud-service) or by using [this Wordpress plugin](https://wordpress.org/plugins/webp-express/)
21
-
22
- [`ewww`](#ewww) is also a cloud service. Not free, but cheap enough to be considered *practically* free. It supports lossless encoding, but this cannot be controlled. *Ewww* always uses lossy encoding for jpeg and lossless for png. For jpegs this is usually a good choice, however, many pngs are compressed better using lossy encoding. As lossless cannot be controlled, the "lossless:auto" option cannot be used for automatically trying both lossy and lossless and picking the smallest file. Also, unfortunately, *ewww* does not support quality=auto, like *wpc*, and it does not support *size-in-percentage* like *cwebp*, either. I have requested such features, and he is considering... As with *wpc*, beware of upload limits. Requirements: A key to the *EWWW Image Optimizer* cloud service. Can be purchaced [here](https://ewww.io/plans/)
23
-
24
- [`stack`](#stack) takes a stack of converters and tries it from the top, until success. The main convert method actually calls this converter. Stacks within stacks are supported (not really needed, though).
25
-
26
-
27
- **Summary:**
28
-
29
- | | cwebp | vips | imagick / gmagick | imagickbinary | gd | ewww |
30
- | ------------------------------------------ | --------- | ------ | ----------------- | ------------- | --------- | ------ |
31
- | supports lossless encoding ? | yes | yes | no | no | no | yes |
32
- | supports lossless auto ? | yes | yes | no | no | no | no |
33
- | supports near-lossless ? | yes | yes | no | no | no | ? |
34
- | supports metadata stripping / preserving | yes | yes | yes | no | no | ? |
35
- | supports setting alpha quality | no | yes | no | no | no | no |
36
- | supports fixed quality (for lossy) | yes | yes | yes | yes | yes | yes |
37
- | supports auto quality without help | no | no | yes | yes | no | no |
38
-
39
-
40
-
41
- *WebPConvert* currently supports the following converters:
42
-
43
- | Converter | Method | Requirements |
44
- | ------------------------------------ | ------------------------------------------------ | -------------------------------------------------- |
45
- | [`cwebp`](#cwebp) | Calls `cwebp` binary directly | `exec()` function *and* that the webserver user has permission to run `cwebp` binary |
46
- | [`vips`](#vips) (new in 2.0) | Vips extension | Vips extension |
47
- | [`imagick`](#imagick) | Imagick extension (`ImageMagick` wrapper) | Imagick PHP extension compiled with WebP support |
48
- | [`gmagick`](#gmagick) | Gmagick extension (`ImageMagick` wrapper) | Gmagick PHP extension compiled with WebP support |
49
- | [`gd`](#gd) | GD Graphics (Draw) extension (`LibGD` wrapper) | GD PHP extension compiled with WebP support |
50
- | [`imagickbinary`](#imagickbinary) | Calls imagick binary directly | exec() and imagick installed and compiled with WebP support |
51
- | [`wpc`](#wpc) | Connects to an open source cloud service | Access to a running service. The service can be installed [directly](https://github.com/rosell-dk/webp-convert-cloud-service) or by using [this Wordpress plugin](https://wordpress.org/plugins/webp-express/).
52
- | [`ewww`](#ewww) | Connects to *EWWW Image Optimizer* cloud service | Purchasing a key |
53
-
54
- ## Installation
55
- Instructions regarding getting the individual converters to work are [on the wiki](https://github.com/rosell-dk/webp-convert/wiki)
56
-
57
- ## cwebp
58
- <table>
59
- <tr><th>Requirements</th><td><code>exec()</code> function and that the webserver has permission to run `cwebp` binary (either found in system path, or a precompiled version supplied with this library)</td></tr>
60
- <tr><th>Performance</th><td>~40-120ms to convert a 40kb image (depending on *method* option)</td></tr>
61
- <tr><th>Reliability</th><td>No problems detected so far!</td></tr>
62
- <tr><th>Availability</th><td>According to ewww docs, requirements are met on surprisingly many webhosts. Look <a href="https://docs.ewww.io/article/43-supported-web-hosts">here</a> for a list</td></tr>
63
- <tr><th>General options supported</th><td>All (`quality`, `metadata`, `lossless`)</td></tr>
64
- <tr><th>Extra options</th><td>`method` (0-6)<br>`use-nice` (boolean)<br>`try-common-system-paths` (boolean)<br> `try-supplied-binary-for-os` (boolean)<br>`autofilter` (boolean)<br>`size-in-percentage` (number / null)<br>`command-line-options` (string)<br>`low-memory` (boolean)</td></tr>
65
- </table>
66
-
67
- [cwebp](https://developers.google.com/speed/webp/docs/cwebp) is a WebP conversion command line converter released by Google. Our implementation ships with precompiled binaries for Linux, FreeBSD, WinNT, Darwin and SunOS. If however a cwebp binary is found in a usual location, that binary will be preferred. It is executed with [exec()](http://php.net/manual/en/function.exec.php).
68
-
69
- In more detail, the implementation does this:
70
- - It is tested whether cwebp is available in a common system path (eg `/usr/bin/cwebp`, ..)
71
- - If not, then supplied binary is selected from `Converters/Binaries` (according to OS) - after validating checksum
72
- - Command-line options are generated from the options
73
- - If [`nice`]( https://en.wikipedia.org/wiki/Nice_(Unix)) command is found on host, binary is executed with low priority in order to save system resources
74
- - Permissions of the generated file are set to be the same as parent folder
75
-
76
- ### Cwebp options
77
-
78
- The following options are supported, besides the general options (such as quality, lossless etc):
79
-
80
- | Option | Type | Default |
81
- | -------------------------- | ------------------------- | -------------------------- |
82
- | autofilter | boolean | false |
83
- | command-line-options | string | '' |
84
- | low-memory | boolean | false |
85
- | method | integer (0-6) | 6 |
86
- | near-lossless | integer (0-100) | 60 |
87
- | size-in-percentage | integer (0-100) (or null) | null |
88
- | rel-path-to-precompiled-binaries | string | './Binaries' |
89
- | size-in-percentage | number (or null) | is_null |
90
- | try-common-system-paths | boolean | true |
91
- | try-supplied-binary-for-os | boolean | true |
92
- | use-nice | boolean | false |
93
-
94
- Descriptions (only of some of the options):
95
-
96
- #### the `autofilter` option
97
- Turns auto-filter on. This algorithm will spend additional time optimizing the filtering strength to reach a well-balanced quality. Unfortunately, it is extremely expensive in terms of computation. It takes about 5-10 times longer to do a conversion. A 1MB picture which perhaps typically takes about 2 seconds to convert, will takes about 15 seconds to convert with auto-filter. So in most cases, you will want to leave this at its default, which is off.
98
-
99
- #### the `command-line-options` option
100
- This allows you to set any parameter available for cwebp in the same way as you would do when executing *cwebp*. You could ie set it to "-sharpness 5 -mt -crop 10 10 40 40". Read more about all the available parameters in [the docs](https://developers.google.com/speed/webp/docs/cwebp)
101
-
102
- #### the `low-memory` option
103
- Reduce memory usage of lossy encoding at the cost of ~30% longer encoding time and marginally larger output size. Default: `false`. Read more in [the docs](https://developers.google.com/speed/webp/docs/cwebp). Default: *false*
104
-
105
- #### The `method` option
106
- This parameter controls the trade off between encoding speed and the compressed file size and quality. Possible values range from 0 to 6. 0 is fastest. 6 results in best quality.
107
-
108
- #### the `near-lossless` option
109
- Specify the level of near-lossless image preprocessing. This option adjusts pixel values to help compressibility, but has minimal impact on the visual quality. It triggers lossless compression mode automatically. The range is 0 (maximum preprocessing) to 100 (no preprocessing). The typical value is around 60. Read more [here](https://groups.google.com/a/webmproject.org/forum/#!topic/webp-discuss/0GmxDmlexek). Default: 60
110
-
111
- #### The `size-in-percentage` option
112
- This option sets the file size, *cwebp* should aim for, in percentage of the original. If you for example set it to *45*, and the source file is 100 kb, *cwebp* will try to create a file with size 45 kb (we use the `-size` option). This is an excellent alternative to the "quality:auto" option. If the quality detection isn't working on your system (and you do not have the rights to install imagick or gmagick), you should consider using this options instead. *Cwebp* is generally able to create webp files with the same quality at about 45% the size. So *45* would be a good choice. The option overrides the quality option. And note that it slows down the conversion - it takes about 2.5 times longer to do a conversion this way, than when quality is specified. Default is *off* (null)
113
-
114
-
115
- #### final words on cwebp
116
- The implementation is based on the work of Shane Bishop for his plugin, [EWWW Image Optimizer](https://ewww.io). Thanks for letting us do that!
117
-
118
- See [the wiki](https://github.com/rosell-dk/webp-convert/wiki/Installing-cwebp---using-official-precompilations) for instructions regarding installing cwebp or using official precompilations.
119
-
120
- ## vips
121
- <table>
122
- <tr><th>Requirements</th><td>Vips extension</td></tr>
123
- <tr><th>Performance</th><td>Great</td></tr>
124
- <tr><th>Reliability</th><td>No problems detected so far!</td></tr>
125
- <tr><th>Availability</th><td>Not that widespread yet, but gaining popularity</td></tr>
126
- <tr><th>General options supported</th><td>All (`quality`, `metadata`, `lossless`)</td></tr>
127
- <tr><th>Extra options</th><td>`smart-subsample`(boolean)<br>`alpha-quality`(0-100)<br>`near-lossless` (0-100)<br> `preset` (0-6)</td></tr>
128
- </table>
129
-
130
- For installation instructions, go [here](https://github.com/libvips/php-vips-ext).
131
-
132
- The options are described [here](https://jcupitt.github.io/libvips/API/current/VipsForeignSave.html#vips-webpsave)
133
-
134
- *near-lossless* is however an integer (0-100), in order to have the option behave like in cwebp.
135
-
136
-
137
-
138
- ## wpc
139
- *WebPConvert Cloud Service*
140
-
141
- <table>
142
- <tr><th>Requirements</th><td>Access to a server with [webp-convert-cloud-service](https://github.com/rosell-dk/webp-convert-cloud-service) installed, <code>cURL</code> and PHP >= 5.5.0</td></tr>
143
- <tr><th>Performance</th><td>Depends on the server where [webp-convert-cloud-service](https://github.com/rosell-dk/webp-convert-cloud-service) is set up, and the speed of internet connections. But perhaps ~1000ms to convert a 40kb image</td></tr>
144
- <tr><th>Reliability</th><td>Great (depends on the reliability on the server where it is set up)</td></tr>
145
- <tr><th>Availability</th><td>Should work on <em>almost</em> any webhost</td></tr>
146
- <tr><th>General options supported</th><td>All (`quality`, `metadata`, `lossless`)</td></tr>
147
- <tr><th>Extra options (old api)</th><td>`url`, `secret`</td></tr>
148
- <tr><th>Extra options (new api)</th><td>`url`, `api-version`, `api-key`, `crypt-api-key-in-transfer`</td></tr>
149
- </table>
150
-
151
- [wpc](https://github.com/rosell-dk/webp-convert-cloud-service) is an open source cloud service. You do not buy a key, you set it up on a server, or you set up [the Wordpress plugin](https://wordpress.org/plugins/webp-express/). As WebPConvert Cloud Service itself is based on WebPConvert, all options are supported.
152
-
153
- To use it, you need to set the `converter-options` (to add url etc).
154
-
155
- #### Example, where api-key is not crypted, on new API:
156
-
157
- ```php
158
- WebPConvert::convert($source, $destination, [
159
- 'max-quality' => 80,
160
- 'converters' => ['cwebp', 'wpc'],
161
- 'converter-options' => [
162
- 'wpc' => [
163
- 'api-version' => 1, /* from wpc release 1.0.0 */
164
- 'url' => 'http://example.com/wpc.php',
165
- 'api-key' => 'my dog is white',
166
- 'crypt-api-key-in-transfer' => false
167
- ]
168
- ]
169
- ));
170
- ```
171
-
172
- #### Example, where api-key is crypted:
173
-
174
- ```php
175
-
176
- WebPConvert::convert($source, $destination, [
177
- 'max-quality' => 80,
178
- 'converters' => ['cwebp', 'wpc'],
179
- 'converter-options' => [
180
- 'wpc' => [
181
- 'api-version' => 1,
182
- 'url' => 'https://example.com/wpc.php',
183
- 'api-key' => 'my dog is white',
184
- 'crypt-api-key-in-transfer' => true
185
- ],
186
- ]
187
- ));
188
- ```
189
-
190
- In 2.0, you can alternatively set the api key and urls through through the *WPC_API_KEY* and *WPC_API_URL* environment variables. This is a safer place to store it.
191
-
192
- To set an environment variable in Apache, you can use the `SetEnv` directory. Ie, place something like the following in your virtual host / or .htaccess file (replace the key with the one you purchased!)
193
-
194
- ```
195
- SetEnv WPC_API_KEY my-dog-is-dashed
196
- SetEnv WPC_API_URL https://wpc.example.com/wpc.php
197
- ```
198
-
199
-
200
- #### Example, old API:
201
-
202
- ```php
203
- WebPConvert::convert($source, $destination, [
204
- 'max-quality' => 80,
205
- 'converters' => ['cwebp', 'wpc'],
206
- 'converter-options' => [
207
- 'wpc' => [
208
- 'url' => 'https://example.com/wpc.php',
209
- 'secret' => 'my dog is white',
210
- ],
211
- ]
212
- ));
213
- ```
214
-
215
-
216
- ## ewww
217
-
218
- <table>
219
- <tr><th>Requirements</th><td>Valid EWWW Image Optimizer <a href="https://ewww.io/plans/">API key</a>, <code>cURL</code> and PHP >= 5.5.0</td></tr>
220
- <tr><th>Performance</th><td>~1300ms to convert a 40kb image</td></tr>
221
- <tr><th>Reliability</th><td>Great (but, as with any cloud service, there is a risk of downtime)</td></tr>
222
- <tr><th>Availability</th><td>Should work on <em>almost</em> any webhost</td></tr>
223
- <tr><th>General options supported</th><td>`quality`, `metadata` (partly)</td></tr>
224
- <tr><th>Extra options</th><td>`key`</td></tr>
225
- </table>
226
-
227
- EWWW Image Optimizer is a very cheap cloud service for optimizing images. After purchasing an API key, add the converter in the `extra-converters` option, with `key` set to the key. Be aware that the `key` should be stored safely to avoid exploitation - preferably in the environment, ie with [dotenv](https://github.com/vlucas/phpdotenv).
228
-
229
- The EWWW api doesn't support the `lossless` option, but it does automatically convert PNG's losslessly. Metadata is either all or none. If you have set it to something else than one of these, all metadata will be preserved.
230
-
231
- In more detail, the implementation does this:
232
- - Validates that there is a key, and that `curl` extension is working
233
- - Validates the key, using the [/verify/ endpoint](https://ewww.io/api/) (in order to [protect the EWWW service from unnecessary file uploads, when key has expired](https://github.com/rosell-dk/webp-convert/issues/38))
234
- - Converts, using the [/ endpoint](https://ewww.io/api/).
235
-
236
- <details>
237
- <summary><strong>Roadmap</strong> 👁</summary>
238
-
239
- The converter could be improved by using `fsockopen` when `cURL` is not available - which is extremely rare. PHP >= 5.5.0 is also widely available (PHP 5.4.0 reached end of life [more than two years ago!](http://php.net/supported-versions.php)).
240
- </details>
241
-
242
- #### Example:
243
-
244
- ```php
245
- WebPConvert::convert($source, $destination, [
246
- 'max-quality' => 80,
247
- 'converters' => ['gd', 'ewww'],
248
- 'converter-options' => [
249
- 'ewww' => [
250
- 'key' => 'your-api-key-here'
251
- ],
252
- ]
253
- ));
254
- ```
255
- In 2.0, you can alternatively set the api key by through the *EWWW_API_KEY* environment variable. This is a safer place to store it.
256
-
257
- To set an environment variable in Apache, you can use the `SetEnv` directory. Ie, place something like the following in your virtual host / or .htaccess file (replace the key with the one you purchased!)
258
-
259
- ```
260
- SetEnv EWWW_API_KEY sP3LyPpsKWZy8CVBTYegzEGN6VsKKKKA
261
- ```
262
-
263
- ## gd
264
-
265
- <table>
266
- <tr><th>Requirements</th><td>GD PHP extension and PHP >= 5.5.0 (compiled with WebP support)</td></tr>
267
- <tr><th>Performance</th><td>~30ms to convert a 40kb image</td></tr>
268
- <tr><th>Reliability</th><td>Not sure - I have experienced corrupted images, but cannot reproduce</td></tr>
269
- <tr><th>Availability</th><td>Unfortunately, according to <a href="https://stackoverflow.com/questions/25248382/how-to-create-a-webp-image-in-php">this link</a>, WebP support on shared hosts is rare.</td></tr>
270
- <tr><th>General options supported</th><td>`quality`</td></tr>
271
- <tr><th>Extra options</th><td>`skip-pngs`</td></tr>
272
- </table>
273
-
274
- [imagewebp](http://php.net/manual/en/function.imagewebp.php) is a function that comes with PHP (>5.5.0), *provided* that PHP has been compiled with WebP support.
275
-
276
- `gd` neither supports copying metadata nor exposes any WebP options. Lacking the option to set lossless encoding results in poor encoding of PNGs - the filesize is generally much larger than the original. For this reason, PNG conversion is *disabled* by default, but it can be enabled my setting `skip-pngs` option to `false`.
277
-
278
- Installaition instructions are [available in the wiki](https://github.com/rosell-dk/webp-convert/wiki/Installing-Gd-extension).
279
-
280
- <details>
281
- <summary><strong>Known bugs</strong> 👁</summary>
282
- Due to a [bug](https://bugs.php.net/bug.php?id=66590), some versions sometimes created corrupted images. That bug can however easily be fixed in PHP (fix was released [here](https://stackoverflow.com/questions/30078090/imagewebp-php-creates-corrupted-webp-files)). However, I have experienced corrupted images *anyway* (but cannot reproduce that bug). So use this converter with caution. The corrupted images look completely transparent in Google Chrome, but have the correct size.
283
- </details>
284
-
285
- ## imagick
286
-
287
- <table>
288
- <tr><th>Requirements</th><td>Imagick PHP extension (compiled with WebP support)</td></tr>
289
- <tr><th>Quality</th><td>Poor. [See this issue]( https://github.com/rosell-dk/webp-convert/issues/43)</td></tr>
290
- <tr><th>General options supported</th><td>`quality`</td></tr>
291
- <tr><th>Extra options</th><td>None</td></tr>
292
- <tr><th>Performance</th><td>~20-320ms to convert a 40kb image</td></tr>
293
- <tr><th>Reliability</th><td>No problems detected so far</td></tr>
294
- <tr><th>Availability</th><td>Probably only available on few shared hosts (if any)</td></tr>
295
- </table>
296
-
297
- WebP conversion with `imagick` is fast and [exposes many WebP options](http://www.imagemagick.org/script/webp.php). Unfortunately, WebP support for the `imagick` extension is pretty uncommon. At least not on the systems I have tried (Ubuntu 16.04 and Ubuntu 17.04). But if installed, it works great and has several WebP options.
298
-
299
- See [this page](https://github.com/rosell-dk/webp-convert/wiki/Installing-Imagick-extension) in the Wiki for instructions on installing the extension.
300
-
301
- ## imagickbinary
302
- <table>
303
- <tr><th>Requirements</th><td><code>exec()</code> function and that imagick is installed on webserver, compiled with webp support</td></tr>
304
- <tr><th>Performance</th><td>just fine</td></tr>
305
- <tr><th>Reliability</th><td>No problems detected so far!</td></tr>
306
- <tr><th>Availability</th><td>Not sure</td></tr>
307
- <tr><th>General options supported</th><td>`quality`</td></tr>
308
- <tr><th>Extra options</th><td>`use-nice` (boolean)</td></tr>
309
- </table>
310
-
311
- This converter tryes to execute `convert source.jpg webp:destination.jpg.webp`.
312
-
313
- ## stack
314
-
315
- <table>
316
- <tr><th>General options supported</th><td>all (passed to the converters in the stack )</td></tr>
317
- <tr><th>Extra options</th><td>`converters` (array) and `converter-options` (array)</td></tr>
318
- </table>
319
-
320
- Stack implements the functionality you know from `WebPConvert::convert`. In fact, all `WebPConvert::convert` does is to call `Stack::convert($source, $destination, $options, $logger);`
321
-
322
- It has two special options: `converters` and `converter-options`. You can read about those in `docs/api/convert.md`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v1.3/converting/convert.md DELETED
@@ -1,96 +0,0 @@
1
- # API: The convert() method
2
-
3
- **WebPConvert::convert($source, $destination, $options, $logger)**
4
-
5
- | Parameter | Type | Description |
6
- | ---------------- | ------- | ------------------------------------------------------------------------------------------ |
7
- | `$source` | String | Absolute path to source image (only forward slashes allowed) |
8
- | `$destination` | String | Absolute path to converted image (only forward slashes allowed) |
9
- | `$options` (optional) | Array | Array of conversion (option) options |
10
- | `$logger` (optional) | Baselogger | Information about the conversion process will be passed to this object. Read more below |
11
-
12
- Returns true if success or false if no converters are *operational*. If any converter seems to have its requirements met (are *operational*), but fails anyway, and no other converters in the stack could convert the image, an the exception from that converter is rethrown (either *ConverterFailedException* or *ConversionDeclinedException*). Exceptions are also thrown if something is wrong entirely (*InvalidFileExtensionException*, *TargetNotFoundException*, *ConverterNotFoundException*, *CreateDestinationFileException*, *CreateDestinationFolderException*, or any unanticipated exceptions thrown by the converters).
13
-
14
- ### Available options for all converters
15
-
16
- Many options correspond to options of *cwebp*. These are documented [here](https://developers.google.com/speed/webp/docs/cwebp)
17
-
18
-
19
- | Option | Type | Default | Description |
20
- | ----------------- | ------- | -------------------------- | -------------------------------------------------------------------- |
21
- | quality | An integer between 0-100, or "auto" | "auto" | Lossy quality of converted image (JPEG only - PNGs are always losless).<br><br> If set to "auto", *WebPConvert* will try to determine the quality of the JPEG (this is only possible, if Imagick or GraphicsMagic is installed). If successfully determined, the quality of the webp will be set to the same as that of the JPEG. however not to more than specified in the new `max-quality` option. If quality cannot be determined, quality will be set to what is specified in the new `default-quality` option (however, if you use the *wpc* converter, it will also get a shot at detecting the quality) |
22
- | max-quality | An integer between 0-100 | 85 | See the `quality` option. Only relevant, when quality is set to "auto".
23
- | default-quality | An integer between 0-100 | 75 | See the `quality` option. Only relevant, when quality is set to "auto".
24
- | metadata | String | 'none' | Valid values: all, none, exif, icc, xmp. Note: Only *cwebp* supports all values. *gd* will always remove all metadata. *ewww*, *imagick* and *gmagick* can either strip all, or keep all (they will keep all, unless metadata is set to *none*) |
25
- | lossless | Boolean | false ("auto" for pngs in 2.0) | Encode the image without any loss. The option is ignored for PNG's (forced true). In 2.0, it can also be "auto", and it is not forced to anything - rather it deafaults to false for Jpegs and "auto" for PNGs |
26
- | converters | Array | ['cwebp', 'gd', 'imagick'] | Specify conversion methods to use, and their order. Also optionally set converter options (see below) |
27
- | converter-options | Array | [] | Set options of the individual converters (see below) |
28
- | jpeg | Array | null | These options will be merged into the other options when source is jpeg |
29
- | png | Array | null | These options will be merged into the other options when source is jpeg |
30
- | skip (new in 2.0) | Boolean | false | If true, conversion will be skipped (ie for skipping png conversion for some converters) |
31
- | skip-png (removed in 2.0) | Boolean | false | If true, conversion will be skipped for png (ie for skipping png conversion for some converters) |
32
-
33
- #### More on quality=auto
34
- Unfortunately, *libwebp* does not provide a way to use the same quality for the converted image, as for source. This feature is implemented by *imagick* and *gmagick*. No matter which conversion method you choose, if you set *quality* to *auto*, our library will try to detect the quality of the source file using one of these libraries. If this isn't available, it will revert to the value set in the *default-quality* option (75 per default). *However*, with the *wpc* converter you have a second chance: If quality cannot be detected locally, it will send quality="auto" to *wpc*.
35
-
36
- The bottom line is: If you do not have imagick or gmagick installed on your host (and have no way to install it), your best option quality-wise is to install *wpc* on a server that you do have access to, and connect to that. However,... read on:
37
-
38
- **How much does it matter?**
39
- The effect of not having quality detection is that jpeg images with medium quality (say 50) will be converted with higher quality (say 75). Converting a q=50 to a q=50 would typically result in a ~60% reduction. But converting it to q=75 will only result in a ~45% reduction. When converting low quality jpeg images, it gets worse. Converting q=30 to q=75 only achieves ~25% reduction.
40
-
41
- I guess it is a rare case having jpeg images in low quality. Even having middle quality is rare, as there seems to have been a tendency to choose higher quality than actually needed for web. So, in many cases, the impact of not having quality detection is minor. If you set the *default-quality* a bit low, ie 65, you will further minimize the effect.
42
-
43
- To determine if *webp-convert* is able to autodetect quality on your system, run a conversion with the *$logger* parameter set to `new EchoLogger()` (see api).
44
-
45
- #### More on the `converter-options` option
46
- You use this option to set options for the individual converters. Example:
47
-
48
- ```
49
- 'converter-options' => [
50
- 'ewww' => [
51
- 'key' => 'your-api-key-here'
52
- ],
53
- 'wpc' => [
54
- 'url' => 'https://example.com/wpc.php',
55
- 'secret' => 'my dog is white'
56
- ]
57
- ]
58
- ```
59
- Besides options that are special to a converter, you can also override general options. For example, you may generally want the `max-quality` to be 85, but for a single converter, you would like it to be 100 (sorry, it is hard to come up with a useful example).
60
-
61
- #### More on the `converters` option
62
- The *converters* option specifies the conversion methods to use and their order. But it can also be used as an alternative way of setting converter options. Usually, you probably want to use the *converter-options* for that, but there may be cases where it is more convenient to specify them here. Also, specifying here allows you to put the same converter method to the stack multiple times, with different options (this could for example be used to have an extra *ewww* converter as a fallback).
63
-
64
- Example:
65
- ```
66
- WebPConvert::convert($source, $destination, [
67
- 'converters' => [
68
- 'cwebp',
69
- 'imagick',
70
- [
71
- 'converter' => 'ewww',
72
- 'options' => [
73
- 'key' => 'your api key here',
74
- ],
75
- ],
76
- ];
77
- )
78
- ```
79
- In 2.0, it will be possible to use your own custom converter. Instead of the "converter id" (ie "ewww"), specify the full class name of your custom converter. Ie '\\MyProject\\BraveConverter'. The converter must extend `\WebPConvert\Convert\Converters\AbstractConverters\AbstractConverter` and you must implement `doConvert()` and the define the extra options it takes (check out how it is done in the build-in converters).
80
-
81
- ### More on the `$logger` parameter
82
- WebPConvert and the individual converters can provide information regarding the conversion process. Per default (when the parameter isn't provided), they write this to `\WebPConvert\Loggers\VoidLogger`, which does nothing with it.
83
- In order to get this information echoed out, you can use `\WebPConvert\Loggers\EchoLogger` - like this:
84
-
85
- ```php
86
- use WebPConvert\Loggers\EchoLogger;
87
-
88
- WebPConvert::convert($source, $destination, $options, new EchoLogger());
89
- ```
90
-
91
- In order to do something else with the information (perhaps write it to a log file?), you can extend `\WebPConvert\Loggers\BaseLogger`.
92
-
93
- ## Converters
94
- In the most basic design, a converter consists of a static convert function which takes the same arguments as `WebPConvert::convert`. Its job is then to convert `$source` to WebP and save it at `$destination`, preferably taking the options specified in $options into account.
95
-
96
- The converters may be called directly. But you probably don't want to do that, as it really doesn't hurt having other converters ready to take over, in case your preferred converter should fail.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v1.3/converting/converters.md DELETED
@@ -1,322 +0,0 @@
1
- # The webp converters
2
-
3
- ## The converters at a glance
4
- When it comes to webp conversion, there is actually only one library in town: *libwebp* from Google. All conversion methods below ultimately uses that very same library for conversion. This means that it does not matter much, which conversion method you use. Whatever works. There is however one thing to take note of, if you set *quality* to *auto*, and your system cannot determine the quality of the source (this requires imagick or gmagick), and you do not have access to install those, then the only way to get quality-detection is to connect to a *wpc* cloud converter. However, with *cwebp*, you can specify the desired reduction (the *size-in-percentage* option) - at the cost of doubling the conversion time. Read more about those considerations in the API.
5
-
6
- Speed-wise, there is too little difference for it to matter, considering that images usually needs to be converted just once. Anyway, here are the results: *cweb* is the fastest (with method=3). *gd* is right behind, merely 3% slower than *cwebp*. *gmagick* are third place, ~8% slower than *cwebp*. *imagick* comes in ~22% slower than *cwebp*. *ewww* depends on connection speed. On my *digital ocean* account, it takes ~2 seconds to upload, convert, and download a tiny image (10 times longer than the local *cwebp*). A 1MB image however only takes ~4.5 seconds to upload, convert and download (1.5 seconds longer). A 2 MB image takes ~5 seconds to convert (only 16% longer than my *cwebp*). The *ewww* thus converts at a very decent speeds. Probably faster than your average shared host. If multiple big images needs to be converted at the same time, *ewww* will probably perform much better than the local converters.
7
-
8
- [`cwebp`](#cwebp) works by executing the *cwebp* binary from Google, which is build upon the *libwebp* (also from Google). That library is actually the only library in town for generating webp images, which means that the other conversion methods ultimately uses that very same library. Which again means that the results using the different methods are very similar. However, with *cwebp*, we have more parameters to tweak than with the rest. We for example have the *method* option, which controls the trade off between encoding speed and the compressed file size and quality. Setting this to max, we can squeeze the images a few percent extra - without loosing quality (the converter is still pretty fast, so in most cases it is probably worth it).
9
-
10
- Of course, as we here have to call a binary directly, *cwebp* requires the *exec* function to be enabled, and that the webserver user is allowed to execute the `cwebp` binary (either at known system locations, or one of the precompiled binaries, that comes with this library).
11
-
12
- [`vips`](#vips) (**new in 2.0**) works by using the vips extension, if available. Vips is great! It offers many webp options, it is fast and installation is easier than imagick and gd, as it does not need to be configured for webp support.
13
-
14
- [`imagick`](#imagick) does not support any special webp options, but is at least able to strip all metadata, if metadata is set to none. Imagick has a very nice feature - that it is able to detect the quality of a jpeg file. This enables it to automatically use same quality for destination as for source, which eliminates the risk of setting quality higher for the destination than for source (the result of that is that the file size gets higher, but the quality remains the same). As the other converters lends this capability from Imagick, this is however no reason for using Imagick rather than the other converters. Requirements: Imagick PHP extension compiled with WebP support
15
-
16
- [`gmagick`](#gmagick) uses the *gmagick* extension. It is very similar to *imagick*. Requirements: Gmagick PHP extension compiled with WebP support.
17
-
18
- [`gd`](#gd) uses the *Gd* extension to do the conversion. The *Gd* extension is pretty common, so the main feature of this converter is that it may work out of the box. It does not support any webp options, and does not support stripping metadata. Requirements: GD PHP extension compiled with WebP support.
19
-
20
- [`wpc`](#wpc) is an open source cloud service for converting images to webp. To use it, you must either install [webp-convert-cloud-service](https://github.com/rosell-dk/webp-convert-cloud-service) directly on a remote server, or install the Wordpress plugin, [WebP Express](https://github.com/rosell-dk/webp-express) in Wordpress. Btw: Beware that upload limits will prevent conversion of big images. The converter checks your *php.ini* settings and abandons upload right away, if an image is larger than your *upload_max_filesize* or your *post_max_size* setting. Requirements: Access to a running service. The service can be installed [directly](https://github.com/rosell-dk/webp-convert-cloud-service) or by using [this Wordpress plugin](https://wordpress.org/plugins/webp-express/)
21
-
22
- [`ewww`](#ewww) is also a cloud service. Not free, but cheap enough to be considered *practically* free. It supports lossless encoding, but this cannot be controlled. *Ewww* always uses lossy encoding for jpeg and lossless for png. For jpegs this is usually a good choice, however, many pngs are compressed better using lossy encoding. As lossless cannot be controlled, the "lossless:auto" option cannot be used for automatically trying both lossy and lossless and picking the smallest file. Also, unfortunately, *ewww* does not support quality=auto, like *wpc*, and it does not support *size-in-percentage* like *cwebp*, either. I have requested such features, and he is considering... As with *wpc*, beware of upload limits. Requirements: A key to the *EWWW Image Optimizer* cloud service. Can be purchaced [here](https://ewww.io/plans/)
23
-
24
- [`stack`](#stack) takes a stack of converters and tries it from the top, until success. The main convert method actually calls this converter. Stacks within stacks are supported (not really needed, though).
25
-
26
-
27
- **Summary:**
28
-
29
- | | cwebp | vips | imagickbinary | imagick / gmagick | gd | ewww |
30
- | ------------------------------------------ | --------- | ------ | -------------- | ----------------- | --------- | ------ |
31
- | supports lossless encoding ? | yes | yes | yes | no | no | yes |
32
- | supports lossless auto ? | yes | yes | yes | no | no | no |
33
- | supports near-lossless ? | yes | yes | no | no | no | ? |
34
- | supports metadata stripping / preserving | yes | yes | yes | yes | no | ? |
35
- | supports setting alpha quality | yes | yes | yes | no | no | no |
36
- | supports fixed quality (for lossy) | yes | yes | yes | yes | yes | yes |
37
- | supports auto quality without help | no | no | yes | yes | no | no |
38
-
39
-
40
-
41
- *WebPConvert* currently supports the following converters:
42
-
43
- | Converter | Method | Requirements |
44
- | ------------------------------------ | ------------------------------------------------ | -------------------------------------------------- |
45
- | [`cwebp`](#cwebp) | Calls `cwebp` binary directly | `exec()` function *and* that the webserver user has permission to run `cwebp` binary |
46
- | [`vips`](#vips) (new in 2.0) | Vips extension | Vips extension |
47
- | [`imagick`](#imagick) | Imagick extension (`ImageMagick` wrapper) | Imagick PHP extension compiled with WebP support |
48
- | [`gmagick`](#gmagick) | Gmagick extension (`ImageMagick` wrapper) | Gmagick PHP extension compiled with WebP support |
49
- | [`gd`](#gd) | GD Graphics (Draw) extension (`LibGD` wrapper) | GD PHP extension compiled with WebP support |
50
- | [`imagickbinary`](#imagickbinary) | Calls imagick binary directly | exec() and imagick installed and compiled with WebP support |
51
- | [`wpc`](#wpc) | Connects to an open source cloud service | Access to a running service. The service can be installed [directly](https://github.com/rosell-dk/webp-convert-cloud-service) or by using [this Wordpress plugin](https://wordpress.org/plugins/webp-express/).
52
- | [`ewww`](#ewww) | Connects to *EWWW Image Optimizer* cloud service | Purchasing a key |
53
-
54
- ## Installation
55
- Instructions regarding getting the individual converters to work are [on the wiki](https://github.com/rosell-dk/webp-convert/wiki)
56
-
57
- ## cwebp
58
- <table>
59
- <tr><th>Requirements</th><td><code>exec()</code> function and that the webserver has permission to run `cwebp` binary (either found in system path, or a precompiled version supplied with this library)</td></tr>
60
- <tr><th>Performance</th><td>~40-120ms to convert a 40kb image (depending on *method* option)</td></tr>
61
- <tr><th>Reliability</th><td>No problems detected so far!</td></tr>
62
- <tr><th>Availability</th><td>According to ewww docs, requirements are met on surprisingly many webhosts. Look <a href="https://docs.ewww.io/article/43-supported-web-hosts">here</a> for a list</td></tr>
63
- <tr><th>General options supported</th><td>All (`quality`, `metadata`, `lossless`)</td></tr>
64
- <tr><th>Extra options</th><td>`method` (0-6)<br>`use-nice` (boolean)<br>`try-common-system-paths` (boolean)<br> `try-supplied-binary-for-os` (boolean)<br>`autofilter` (boolean)<br>`size-in-percentage` (number / null)<br>`command-line-options` (string)<br>`low-memory` (boolean)</td></tr>
65
- </table>
66
-
67
- [cwebp](https://developers.google.com/speed/webp/docs/cwebp) is a WebP conversion command line converter released by Google. Our implementation ships with precompiled binaries for Linux, FreeBSD, WinNT, Darwin and SunOS. If however a cwebp binary is found in a usual location, that binary will be preferred. It is executed with [exec()](http://php.net/manual/en/function.exec.php).
68
-
69
- In more detail, the implementation does this:
70
- - It is tested whether cwebp is available in a common system path (eg `/usr/bin/cwebp`, ..)
71
- - If not, then supplied binary is selected from `Converters/Binaries` (according to OS) - after validating checksum
72
- - Command-line options are generated from the options
73
- - If [`nice`]( https://en.wikipedia.org/wiki/Nice_(Unix)) command is found on host, binary is executed with low priority in order to save system resources
74
- - Permissions of the generated file are set to be the same as parent folder
75
-
76
- ### Cwebp options
77
-
78
- The following options are supported, besides the general options (such as quality, lossless etc):
79
-
80
- | Option | Type | Default |
81
- | -------------------------- | ------------------------- | -------------------------- |
82
- | autofilter | boolean | false |
83
- | command-line-options | string | '' |
84
- | low-memory | boolean | false |
85
- | method | integer (0-6) | 6 |
86
- | near-lossless | integer (0-100) | 60 |
87
- | size-in-percentage | integer (0-100) (or null) | null |
88
- | rel-path-to-precompiled-binaries | string | './Binaries' |
89
- | size-in-percentage | number (or null) | is_null |
90
- | try-common-system-paths | boolean | true |
91
- | try-supplied-binary-for-os | boolean | true |
92
- | use-nice | boolean | false |
93
-
94
- Descriptions (only of some of the options):
95
-
96
- #### the `autofilter` option
97
- Turns auto-filter on. This algorithm will spend additional time optimizing the filtering strength to reach a well-balanced quality. Unfortunately, it is extremely expensive in terms of computation. It takes about 5-10 times longer to do a conversion. A 1MB picture which perhaps typically takes about 2 seconds to convert, will takes about 15 seconds to convert with auto-filter. So in most cases, you will want to leave this at its default, which is off.
98
-
99
- #### the `command-line-options` option
100
- This allows you to set any parameter available for cwebp in the same way as you would do when executing *cwebp*. You could ie set it to "-sharpness 5 -mt -crop 10 10 40 40". Read more about all the available parameters in [the docs](https://developers.google.com/speed/webp/docs/cwebp)
101
-
102
- #### the `low-memory` option
103
- Reduce memory usage of lossy encoding at the cost of ~30% longer encoding time and marginally larger output size. Default: `false`. Read more in [the docs](https://developers.google.com/speed/webp/docs/cwebp). Default: *false*
104
-
105
- #### The `method` option
106
- This parameter controls the trade off between encoding speed and the compressed file size and quality. Possible values range from 0 to 6. 0 is fastest. 6 results in best quality.
107
-
108
- #### the `near-lossless` option
109
- Specify the level of near-lossless image preprocessing. This option adjusts pixel values to help compressibility, but has minimal impact on the visual quality. It triggers lossless compression mode automatically. The range is 0 (maximum preprocessing) to 100 (no preprocessing). The typical value is around 60. Read more [here](https://groups.google.com/a/webmproject.org/forum/#!topic/webp-discuss/0GmxDmlexek). Default: 60
110
-
111
- #### The `size-in-percentage` option
112
- This option sets the file size, *cwebp* should aim for, in percentage of the original. If you for example set it to *45*, and the source file is 100 kb, *cwebp* will try to create a file with size 45 kb (we use the `-size` option). This is an excellent alternative to the "quality:auto" option. If the quality detection isn't working on your system (and you do not have the rights to install imagick or gmagick), you should consider using this options instead. *Cwebp* is generally able to create webp files with the same quality at about 45% the size. So *45* would be a good choice. The option overrides the quality option. And note that it slows down the conversion - it takes about 2.5 times longer to do a conversion this way, than when quality is specified. Default is *off* (null)
113
-
114
-
115
- #### final words on cwebp
116
- The implementation is based on the work of Shane Bishop for his plugin, [EWWW Image Optimizer](https://ewww.io). Thanks for letting us do that!
117
-
118
- See [the wiki](https://github.com/rosell-dk/webp-convert/wiki/Installing-cwebp---using-official-precompilations) for instructions regarding installing cwebp or using official precompilations.
119
-
120
- ## vips
121
- <table>
122
- <tr><th>Requirements</th><td>Vips extension</td></tr>
123
- <tr><th>Performance</th><td>Great</td></tr>
124
- <tr><th>Reliability</th><td>No problems detected so far!</td></tr>
125
- <tr><th>Availability</th><td>Not that widespread yet, but gaining popularity</td></tr>
126
- <tr><th>General options supported</th><td>All (`quality`, `metadata`, `lossless`)</td></tr>
127
- <tr><th>Extra options</th><td>`smart-subsample`(boolean)<br>`alpha-quality`(0-100)<br>`near-lossless` (0-100)<br> `preset` (0-6)</td></tr>
128
- </table>
129
-
130
- For installation instructions, go [here](https://github.com/libvips/php-vips-ext).
131
-
132
- The options are described [here](https://jcupitt.github.io/libvips/API/current/VipsForeignSave.html#vips-webpsave)
133
-
134
- *near-lossless* is however an integer (0-100), in order to have the option behave like in cwebp.
135
-
136
-
137
-
138
- ## wpc
139
- *WebPConvert Cloud Service*
140
-
141
- <table>
142
- <tr><th>Requirements</th><td>Access to a server with [webp-convert-cloud-service](https://github.com/rosell-dk/webp-convert-cloud-service) installed, <code>cURL</code> and PHP >= 5.5.0</td></tr>
143
- <tr><th>Performance</th><td>Depends on the server where [webp-convert-cloud-service](https://github.com/rosell-dk/webp-convert-cloud-service) is set up, and the speed of internet connections. But perhaps ~1000ms to convert a 40kb image</td></tr>
144
- <tr><th>Reliability</th><td>Great (depends on the reliability on the server where it is set up)</td></tr>
145
- <tr><th>Availability</th><td>Should work on <em>almost</em> any webhost</td></tr>
146
- <tr><th>General options supported</th><td>All (`quality`, `metadata`, `lossless`)</td></tr>
147
- <tr><th>Extra options (old api)</th><td>`url`, `secret`</td></tr>
148
- <tr><th>Extra options (new api)</th><td>`url`, `api-version`, `api-key`, `crypt-api-key-in-transfer`</td></tr>
149
- </table>
150
-
151
- [wpc](https://github.com/rosell-dk/webp-convert-cloud-service) is an open source cloud service. You do not buy a key, you set it up on a server, or you set up [the Wordpress plugin](https://wordpress.org/plugins/webp-express/). As WebPConvert Cloud Service itself is based on WebPConvert, all options are supported.
152
-
153
- To use it, you need to set the `converter-options` (to add url etc).
154
-
155
- #### Example, where api-key is not crypted, on new API:
156
-
157
- ```php
158
- WebPConvert::convert($source, $destination, [
159
- 'max-quality' => 80,
160
- 'converters' => ['cwebp', 'wpc'],
161
- 'converter-options' => [
162
- 'wpc' => [
163
- 'api-version' => 1, /* from wpc release 1.0.0 */
164
- 'url' => 'http://example.com/wpc.php',
165
- 'api-key' => 'my dog is white',
166
- 'crypt-api-key-in-transfer' => false
167
- ]
168
- ]
169
- ));
170
- ```
171
-
172
- #### Example, where api-key is crypted:
173
-
174
- ```php
175
-
176
- WebPConvert::convert($source, $destination, [
177
- 'max-quality' => 80,
178
- 'converters' => ['cwebp', 'wpc'],
179
- 'converter-options' => [
180
- 'wpc' => [
181
- 'api-version' => 1,
182
- 'url' => 'https://example.com/wpc.php',
183
- 'api-key' => 'my dog is white',
184
- 'crypt-api-key-in-transfer' => true
185
- ],
186
- ]
187
- ));
188
- ```
189
-
190
- In 2.0, you can alternatively set the api key and urls through through the *WPC_API_KEY* and *WPC_API_URL* environment variables. This is a safer place to store it.
191
-
192
- To set an environment variable in Apache, you can use the `SetEnv` directory. Ie, place something like the following in your virtual host / or .htaccess file (replace the key with the one you purchased!)
193
-
194
- ```
195
- SetEnv WPC_API_KEY my-dog-is-dashed
196
- SetEnv WPC_API_URL https://wpc.example.com/wpc.php
197
- ```
198
-
199
-
200
- #### Example, old API:
201
-
202
- ```php
203
- WebPConvert::convert($source, $destination, [
204
- 'max-quality' => 80,
205
- 'converters' => ['cwebp', 'wpc'],
206
- 'converter-options' => [
207
- 'wpc' => [
208
- 'url' => 'https://example.com/wpc.php',
209
- 'secret' => 'my dog is white',
210
- ],
211
- ]
212
- ));
213
- ```
214
-
215
-
216
- ## ewww
217
-
218
- <table>
219
- <tr><th>Requirements</th><td>Valid EWWW Image Optimizer <a href="https://ewww.io/plans/">API key</a>, <code>cURL</code> and PHP >= 5.5.0</td></tr>
220
- <tr><th>Performance</th><td>~1300ms to convert a 40kb image</td></tr>
221
- <tr><th>Reliability</th><td>Great (but, as with any cloud service, there is a risk of downtime)</td></tr>
222
- <tr><th>Availability</th><td>Should work on <em>almost</em> any webhost</td></tr>
223
- <tr><th>General options supported</th><td>`quality`, `metadata` (partly)</td></tr>
224
- <tr><th>Extra options</th><td>`key`</td></tr>
225
- </table>
226
-
227
- EWWW Image Optimizer is a very cheap cloud service for optimizing images. After purchasing an API key, add the converter in the `extra-converters` option, with `key` set to the key. Be aware that the `key` should be stored safely to avoid exploitation - preferably in the environment, ie with [dotenv](https://github.com/vlucas/phpdotenv).
228
-
229
- The EWWW api doesn't support the `lossless` option, but it does automatically convert PNG's losslessly. Metadata is either all or none. If you have set it to something else than one of these, all metadata will be preserved.
230
-
231
- In more detail, the implementation does this:
232
- - Validates that there is a key, and that `curl` extension is working
233
- - Validates the key, using the [/verify/ endpoint](https://ewww.io/api/) (in order to [protect the EWWW service from unnecessary file uploads, when key has expired](https://github.com/rosell-dk/webp-convert/issues/38))
234
- - Converts, using the [/ endpoint](https://ewww.io/api/).
235
-
236
- <details>
237
- <summary><strong>Roadmap</strong> 👁</summary>
238
-
239
- The converter could be improved by using `fsockopen` when `cURL` is not available - which is extremely rare. PHP >= 5.5.0 is also widely available (PHP 5.4.0 reached end of life [more than two years ago!](http://php.net/supported-versions.php)).
240
- </details>
241
-
242
- #### Example:
243
-
244
- ```php
245
- WebPConvert::convert($source, $destination, [
246
- 'max-quality' => 80,
247
- 'converters' => ['gd', 'ewww'],
248
- 'converter-options' => [
249
- 'ewww' => [
250
- 'key' => 'your-api-key-here'
251
- ],
252
- ]
253
- ));
254
- ```
255
- In 2.0, you can alternatively set the api key by through the *EWWW_API_KEY* environment variable. This is a safer place to store it.
256
-
257
- To set an environment variable in Apache, you can use the `SetEnv` directory. Ie, place something like the following in your virtual host / or .htaccess file (replace the key with the one you purchased!)
258
-
259
- ```
260
- SetEnv EWWW_API_KEY sP3LyPpsKWZy8CVBTYegzEGN6VsKKKKA
261
- ```
262
-
263
- ## gd
264
-
265
- <table>
266
- <tr><th>Requirements</th><td>GD PHP extension and PHP >= 5.5.0 (compiled with WebP support)</td></tr>
267
- <tr><th>Performance</th><td>~30ms to convert a 40kb image</td></tr>
268
- <tr><th>Reliability</th><td>Not sure - I have experienced corrupted images, but cannot reproduce</td></tr>
269
- <tr><th>Availability</th><td>Unfortunately, according to <a href="https://stackoverflow.com/questions/25248382/how-to-create-a-webp-image-in-php">this link</a>, WebP support on shared hosts is rare.</td></tr>
270
- <tr><th>General options supported</th><td>`quality`</td></tr>
271
- <tr><th>Extra options</th><td>`skip-pngs`</td></tr>
272
- </table>
273
-
274
- [imagewebp](http://php.net/manual/en/function.imagewebp.php) is a function that comes with PHP (>5.5.0), *provided* that PHP has been compiled with WebP support.
275
-
276
- `gd` neither supports copying metadata nor exposes any WebP options. Lacking the option to set lossless encoding results in poor encoding of PNGs - the filesize is generally much larger than the original. For this reason, PNG conversion is *disabled* by default, but it can be enabled my setting `skip-pngs` option to `false`.
277
-
278
- Installaition instructions are [available in the wiki](https://github.com/rosell-dk/webp-convert/wiki/Installing-Gd-extension).
279
-
280
- <details>
281
- <summary><strong>Known bugs</strong> 👁</summary>
282
- Due to a [bug](https://bugs.php.net/bug.php?id=66590), some versions sometimes created corrupted images. That bug can however easily be fixed in PHP (fix was released [here](https://stackoverflow.com/questions/30078090/imagewebp-php-creates-corrupted-webp-files)). However, I have experienced corrupted images *anyway* (but cannot reproduce that bug). So use this converter with caution. The corrupted images look completely transparent in Google Chrome, but have the correct size.
283
- </details>
284
-
285
- ## imagick
286
-
287
- <table>
288
- <tr><th>Requirements</th><td>Imagick PHP extension (compiled with WebP support)</td></tr>
289
- <tr><th>Quality</th><td>Poor. [See this issue]( https://github.com/rosell-dk/webp-convert/issues/43)</td></tr>
290
- <tr><th>General options supported</th><td>`quality`</td></tr>
291
- <tr><th>Extra options</th><td>None</td></tr>
292
- <tr><th>Performance</th><td>~20-320ms to convert a 40kb image</td></tr>
293
- <tr><th>Reliability</th><td>No problems detected so far</td></tr>
294
- <tr><th>Availability</th><td>Probably only available on few shared hosts (if any)</td></tr>
295
- </table>
296
-
297
- WebP conversion with `imagick` is fast and [exposes many WebP options](http://www.imagemagick.org/script/webp.php). Unfortunately, WebP support for the `imagick` extension is pretty uncommon. At least not on the systems I have tried (Ubuntu 16.04 and Ubuntu 17.04). But if installed, it works great and has several WebP options.
298
-
299
- See [this page](https://github.com/rosell-dk/webp-convert/wiki/Installing-Imagick-extension) in the Wiki for instructions on installing the extension.
300
-
301
- ## imagickbinary
302
- <table>
303
- <tr><th>Requirements</th><td><code>exec()</code> function and that imagick is installed on webserver, compiled with webp support</td></tr>
304
- <tr><th>Performance</th><td>just fine</td></tr>
305
- <tr><th>Reliability</th><td>No problems detected so far!</td></tr>
306
- <tr><th>Availability</th><td>Not sure</td></tr>
307
- <tr><th>General options supported</th><td>`quality`</td></tr>
308
- <tr><th>Extra options</th><td>`use-nice` (boolean)</td></tr>
309
- </table>
310
-
311
- This converter tryes to execute `convert source.jpg webp:destination.jpg.webp`.
312
-
313
- ## stack
314
-
315
- <table>
316
- <tr><th>General options supported</th><td>all (passed to the converters in the stack )</td></tr>
317
- <tr><th>Extra options</th><td>`converters` (array) and `converter-options` (array)</td></tr>
318
- </table>
319
-
320
- Stack implements the functionality you know from `WebPConvert::convert`. In fact, all `WebPConvert::convert` does is to call `Stack::convert($source, $destination, $options, $logger);`
321
-
322
- It has two special options: `converters` and `converter-options`. You can read about those in `docs/api/convert.md`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v1.3/serving/convert-and-serve.md DELETED
@@ -1,167 +0,0 @@
1
- # API: The WebPConvert::convertAndServe() method
2
-
3
- *NOTE:* In 2.0, the method is renamed to *serveConverted* ("convertAndServe" was implying that a conversion was always made, but the method simply serves destination if it exists and is smaller and newer than source)
4
-
5
- The method tries to serve a converted image. If destination already exists, the already converted image will be served. Unless the original is newer or smaller. If the method fails, it will serve original image, a 404, or whatever the 'fail' option is set to.
6
-
7
- **WebPConvert::convertAndServe($source, $destination, $options)**
8
-
9
- | Parameter | Type | Description |
10
- | ---------------- | ------- | ------------------------------------------------------------------- |
11
- | `$source` | String | Absolute path to source image (only forward slashes allowed) |
12
- | `$destination` | String | Absolute path to converted image (only forward slashes allowed) |
13
- | `$options` | Array | Array of options (see below) |
14
-
15
- ## The *$options* argument
16
- The options argument is a named array. Besides the options described below, you can also use any options that the *convert* method takes (if a fresh convertion needs to be created, this method will call the *convert* method and hand over the options argument)
17
-
18
- ### *convert*
19
- Conversion options, handed over to the convert method, in case a conversion needs to be made. The convert options are documented [here](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md).
20
-
21
- ### *fail*
22
- Indicate what to do, in case of normal conversion failure.
23
- Default value: *"original"*
24
-
25
- | Possible values | Meaning |
26
- | ----------------- | ----------------------------------------------- |
27
- | "serve-original" | Serve the original image. |
28
- | "404" | Serve 404 status (not found) |
29
- | "report-as-image" | Serve an image with text explaining the problem |
30
- | "report" | Serve a textual report explaining the problem |
31
-
32
- ### *fail-when-original-unavailable*
33
- Possible values: Same as above, except that "original" is not an option.
34
- Default value: *"404"*
35
-
36
- ### *show-report*
37
- Produce a report rather than serve an image.
38
- Default value: *false*
39
-
40
- ### *reconvert*
41
- Force a conversion, discarding existing converted image (if any).
42
- Default value: *false*
43
-
44
- ### *serve-original*
45
- Forces serving original image. This will skip conversion.
46
- Default value: *false*
47
-
48
- ### *add-x-header-status*
49
- When set to *true*, a *X-WebP-Convert-Status* header will be added describing how things went.
50
- Default value: *true*
51
-
52
- Depending on how things goes, the header will be set to one of the following:
53
- - "Failed (missing source argument)"
54
- - "Failed (source not found)""
55
- - "Failed (missing destination argument)"
56
- - "Reporting..."
57
- - "Serving original image (was explicitly told to)"
58
- - "Serving original image - because it is smaller than the converted!"
59
- - "Serving freshly converted image (the original had changed)"
60
- - "Serving existing converted image"
61
- - "Converting image (handed over to WebPConvertAndServe)"
62
- - "Serving freshly converted image"
63
- - "Failed (could not convert image)"
64
-
65
- ### *add-vary-header*
66
- Add a "Vary: Accept" header when an image is served. Experimental.
67
- Default value: *true*
68
-
69
- ### *add-content-type-header*
70
- Add a "Content-Type" header
71
- Default value: *true*
72
- If set, a *Content-Type* header will be added. It will be set to "image/webp" if a converted image is served, "image/jpeg" or "image/png", if the original is served or "image/gif", if an error message is served (as image). You can set it to false when debugging (to check if any errors are being outputted)
73
-
74
- ### *add-last-modified-header*
75
- Add a "Last-Modified" header
76
- Default value: *true*
77
- If set, a *Last-Modified* header will be added. When a cached image is served, it will be set to the modified time of the converted file. When a fresh image is served, it is set to current time.
78
-
79
- ### *cache-control-header*
80
- Specify a cache control header, which will be served when caching is appropriate.
81
- Default value: "public, max-age=86400" (1 day)
82
- Caching is "deemed appropriate", when destination is served, source is served, because it is lighter or a fresh conversion is made, due to there not being any converted image at the destination yet. Caching is not deemed appropriate when something fails, a report is requested, or the *reconvert* option have been set. Note: in version 1.3.2 and below, the *serve-original* option also prevented caching, but it no longer does. previous In those cases, standard headers will be used for preventing caching.
83
- For your convenience, here is a little table:
84
-
85
- | duration | max-age |
86
- | -------- | ---------------- |
87
- | 1 second | max-age=1 |
88
- | 1 minute | max-age=60 |
89
- | 1 hour | max-age=3600 |
90
- | 1 day | max-age=86400 |
91
- | 1 week | max-age=604800 |
92
- | 1 month | max-age=2592000 |
93
- | 1 year | max-age=31536000 |
94
-
95
- To learn about the options for the Cache-Control header, go [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)
96
-
97
- ### *error-reporting*
98
- Set error reporting
99
- Allowed values: *"auto"*, *"dont-mess"*, *true*, *false*
100
- Default value: *"auto"*
101
-
102
- If set to true, error reporting will be turned on, like this:
103
- ```
104
- error_reporting(E_ALL);
105
- ini_set('display_errors', 'On');
106
- ```
107
-
108
- If set to false, error reporting will be turned off, like this:
109
- ```
110
- error_reporting(0);
111
- ini_set('display_errors', 'Off');
112
- ```
113
- If set to "auto", errors will be turned off, unless the `show-report` option is set, in which case errors will be turned off.
114
- If set to "dont-mess", error reporting will not be touched.
115
-
116
- ### *aboutToServeImageCallBack*
117
- This callback is called right before response headers and image is served. This is a great chance to adding headers. You can stop the image and the headers from being served by returning *false*.
118
-
119
- **Arguments:**
120
- The first argument to the callback contains a string that tells what is about to be served. It can be 'fresh-conversion', 'destination' or 'source'.
121
-
122
- The second argument tells you why that is served. It can be one of the following:
123
- for 'source':
124
- - "explicitly-told-to" (when the "serve-original" option is set)
125
- - "source-lighter" (when original image is actually smaller than the converted)
126
-
127
- for 'fresh-conversion':
128
- - "explicitly-told-to" (when the "reconvert" option is set)
129
- - "source-modified" (when source is newer than existing)
130
- - "no-existing" (when there is no existing at the destination)
131
-
132
- for 'destination':
133
- - "no-reason-not-to" (it is lighter than source, its not older, and we were not told to do otherwise)
134
-
135
- Example of callback:
136
- ```
137
- function aboutToServeImageCallBack($servingWhat, $whyServingThis, $obj)
138
- {
139
- echo 'about to serve: ' . $servingWhat . '<br>';
140
- echo 'Why? - because: ' . $whyServingThis;
141
- return false; // Do not serve! (this also prevents any response headers from being added)
142
- }
143
- ```
144
-
145
- ### *aboutToPerformFailActionCallback*
146
- This callback is called right before doing the action specified in the `fail` option, or the `fail-when-original-unavailable` option. You can stop the fail action from being executod by returning *false*.
147
-
148
- Documentation by example:
149
- ```
150
- function aboutToPerformFailActionCallback($errorTitle, $errorDescription, $actionAboutToBeTaken, $serveConvertedObj)
151
- {
152
- echo '<h1>' . $errorTitle . '</h1>';
153
- echo $errorDescription;
154
- if (actionAboutToBeTaken == '404') {
155
- // handle 404 differently than webp-convert would
156
- $protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : 'HTTP/1.0';
157
- $serveConvertedObj->header($protocol . " 404 Not Found. We take this very seriously. Heads will roll.");
158
-
159
- return false; // stop webp-convert from doing what it would do
160
- }
161
-
162
- }
163
- ```
164
-
165
- ### *require-for-conversion*
166
- If set, makes the library 'require in' a file just before doing an actual conversion with `ConvertAndServe::convertAndServe()`. This is not needed for composer projects, as composer takes care of autoloading classes when needed.
167
- Default value: *null*
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v1.3/webp-on-demand/tweaks.md DELETED
@@ -1,167 +0,0 @@
1
- # Tweaks
2
-
3
- ## Store converted images in separate folder
4
-
5
- In most cases, you probably want the cache of converted images to be stored in their own folder rather than have them mingled with the source files.
6
-
7
- To have have the cache folder contain a file structure mirroring the structure of the original files, you can do this:
8
-
9
- ```php
10
- $applicationRoot = $_SERVER["DOCUMENT_ROOT"]; // If your application is not in document root, you can change accordingly.
11
- $imageRoot = $applicationRoot . '/webp-images'; // Change to where you want the webp images to be saved
12
- $sourceRel = substr($source, strlen($applicationRoot));
13
- $destination = $imageRoot . $sourceRel . '.webp';
14
- ```
15
-
16
- If your images are stored outside document root (a rare case), you can simply use the complete absolute path:
17
- ```php
18
- $destination = $imageRoot . $source . '.webp'; // pst: $source is an absolute path, and starts with '/'
19
- ```
20
- This will ie store a converted image in */var/www/example.com/public_html/app/webp-images/var/www/example.com/images/logo.jpg.webp*
21
-
22
- If your application can be configured to store outside document root, but rarely is, you can go for this structure:
23
-
24
- ```php
25
- $docRoot = $_SERVER["DOCUMENT_ROOT"];
26
- $imageRoot = $contentDirAbs . '/webp-images';
27
-
28
- if (substr($source, 0, strlen($docRoot)) === $docRoot) {
29
- // Source file is residing inside document root.
30
- // We can store relative to that.
31
- $sourceRel = substr($source, strlen($docRoot));
32
- $destination = $imageRoot . '/doc-root' . $sourceRel . '.webp';
33
- } else {
34
- // Source file is residing outside document root.
35
- // we must add complete path to structure
36
- $destination = $imageRoot . '/abs' . $source . '.webp';
37
- }
38
- ```
39
-
40
- If you do not know the application root beforehand, and thus do not know the appropriate root for the converted images, see next tweak.
41
-
42
-
43
- ## Get the application root automatically
44
- When you want destination files to be put in their own folder, you need to know the root of the application (the folder in which the .htaccess rules resides). In most applications, you know the root. In many cases, it is simply the document root. However, if you are writing an extension, plugin or module to a framework that can be installed in a subfolder, you may have trouble finding it. Many applications have a *index.php* in the root, which can get it with `__DIR__`. However, you do not want to run an entire bootstrap each time you serve an image. Obviously, to get around this, you can place *webp-on-demand.php* in the webroot. However, some frameworks, such as Wordpress, will not allow a plugin to put a file in the root. Now, how could we determine the application root from a file inside some subdir? Here are three suggestions:
45
-
46
- 1. You could traverse parent folders until you find a file you expect to be in application root (ie a .htaccess containing the string "webp-on-demand.php"). This should work.
47
- 2. If the rules in the *.htaccess* file are generated by your application, you probably have access to the path at generation time. You can then simply put the path in the *.htaccess*, as an extra parameter to the script (or better: the relative path from document root to the application).
48
- 3. You can use the following hack:
49
-
50
- ### The hack
51
- The idea is to grab the URL path of the image in the *.htaccess* and pass it to the script. Assuming that the URL paths always matches the file paths, we can get the application root by subtracting that relative path to source from the absolute path to source.
52
-
53
- In *.htaccess*, we grab the url-path by appending "&url-path=$1.$2" to the rewrite rule:
54
- ```
55
- RewriteRule ^(.*)\.(jpe?g|png)$ webp-on-demand.php?source=%{SCRIPT_FILENAME}&url-path=$1.$2 [NC,L]
56
- ```
57
-
58
- In the script, we can then calculate the application root like this:
59
-
60
- ```php
61
- $applicationRoot = substr($_GET['source'], 0, -strlen($_GET['url-path']));
62
- ```
63
-
64
- ## CDN
65
- To work properly with a CDN, a "Vary Accept" header should be added when serving images. This is a declaration that the response varies with the *Accept* header (recall that we inspect *Accept* header in the .htaccess to determine if the browsers supports webp images). If this header is missing, the CDN will see no reason to cache separate images depending on the Accept header.
66
-
67
- Add this snippet to the *.htaccess* to make webp-on-demand work with CDN's:
68
-
69
- ```
70
- <IfModule mod_headers.c>
71
- SetEnvIf Request_URI "\.(jpe?g|png)" ADDVARY
72
-
73
- # Declare that the response varies depending on the accept header.
74
- # The purpose is to make CDN cache both original images and converted images.
75
- Header append "Vary" "Accept" env=ADDVARY
76
- </IfModule>
77
- ```
78
-
79
- ***Note:*** When configuring the CDN, you must make sure to set it up to forward the the "Accept" header to your origin server.
80
-
81
-
82
-
83
- ## Make .htaccess route directly to existing images
84
-
85
- There may be a performance benefit of using the *.htaccess* file to route to already converted images, instead of letting the PHP script serve it. Note however:
86
- - If you do the routing in .htaccess, the solution will not be able to discard converted images when original images are updated.
87
- - Performance benefit may be insignificant (*WebPConvertAndServe* class is not autoloaded when serving existing images)
88
-
89
- Add the following to the *.htaccess* to make it route to existing converted images. Place it above the # Redirect images to webp-on-demand.php" comment. Take care of replacing [[your-base-path]] with the directory your *.htaccess* lives in (relative to document root, and [[your-destination-root]] with the directory the converted images resides.
90
- ```
91
- # Redirect to existing converted image (under appropriate circumstances)
92
- RewriteCond %{HTTP_ACCEPT} image/webp
93
- RewriteCond %{DOCUMENT_ROOT}/[[your-base-path]]/[[your-destination-root]]/$1.$2.webp -f
94
- RewriteRule ^\/?(.*)\.(jpe?g|png)$ /[[your-base-path]]/[[your-destination-root]]/$1.$2.webp [NC,T=image/webp,L]
95
- ```
96
- *edit:* Removed the QSD flag from the RewriteRule because it is not supported in Apache < 2.4 (and it [triggers error](https://github.com/rosell-dk/webp-express/issues/155))
97
-
98
- ### Redirect with CDN support
99
- If you are using a CDN, and want to redirect to existing images with the .htaccess, it is a good idea to add a "Vary Accept" header. This instructs the CDN that the response varies with the *Accept* header (we do not need to do that when routing to webp-on-demand.php, because the script takes care of adding this header, when appropriate.)
100
-
101
- You can achieve redirect with CDN support with the following rules:
102
- ```
103
- <IfModule mod_rewrite.c>
104
-
105
- RewriteEngine On
106
-
107
- # Redirect to existing converted image (under appropriate circumstances)
108
- RewriteCond %{HTTP_ACCEPT} image/webp
109
- RewriteCond %{DOCUMENT_ROOT}/[[your-base-path]]/[[your-destination-root]]/$1.$2.webp -f
110
- RewriteRule ^\/?(.*)\.(jpe?g|png)$ /[[your-base-path]]/[[your-destination-root]]/$1.$2.webp [NC,T=image/webp,QSD,E=WEBPACCEPT:1,L]
111
-
112
- # Redirect images to webp-on-demand.php (if browser supports webp)
113
- RewriteCond %{HTTP_ACCEPT} image/webp
114
- RewriteRule ^(.*)\.(jpe?g|png)$ webp-on-demand.php?source=%{SCRIPT_FILENAME}&url-path=$1.$2 [NC,L]
115
-
116
- </IfModule>
117
-
118
- <IfModule mod_headers.c>
119
- # Apache appends "REDIRECT_" in front of the environment variables, but LiteSpeed does not.
120
- # These next line is for Apache, in order to set environment variables without "REDIRECT_"
121
- SetEnvIf REDIRECT_WEBPACCEPT 1 WEBPACCEPT=1
122
-
123
- # Make CDN caching possible.
124
- # The effect is that the CDN will cache both the webp image and the jpeg/png image and return the proper
125
- # image to the proper clients (for this to work, make sure to set up CDN to forward the "Accept" header)
126
- Header append Vary Accept env=WEBPACCEPT
127
- </IfModule>
128
-
129
- AddType image/webp .webp
130
- ```
131
-
132
- ## Forward the querystring
133
- By forwarding the query string, you can allow control directly from the URL. You could for example make it possible to add "?debug" to an image URL, and thereby getting a conversion report. Or make "?reconvert" force reconversion.
134
-
135
- In order to forward the query string, you need to add this condition before the RewriteRule that redirects to *webp-on-demand.php*:
136
- ```
137
- RewriteCond %{QUERY_STRING} (.*)
138
- ```
139
- That condition will always be met. The side effect is that it stores the match (the complete querystring). That match will be available as %1 in the RewriteRule. So, in the RewriteRule, we will have to add "&%1" after the last argument. Here is a complete solution:
140
- ```
141
- <IfModule mod_rewrite.c>
142
- RewriteEngine On
143
-
144
- # Redirect images to webp-on-demand.php (if browser supports webp)
145
- RewriteCond %{HTTP_ACCEPT} image/webp
146
- RewriteCond %{QUERY_STRING} (.*)
147
- RewriteRule ^(.*)\.(jpe?g|png)$ webp-on-demand.php?source=%{SCRIPT_FILENAME}&%1 [NC,L]
148
- </IfModule>
149
-
150
- AddType image/webp .webp
151
- ```
152
-
153
- Of course, in order to *do* something with that querystring, you must use them in your *webp-on-demand.php* script. You could for example use them directly in the options array sent to the *convertAndServe()* method. To achieve the mentioned "debug" and "reconvert" features, do this:
154
- ```php
155
- $options = [
156
- 'show-report' => isset($_GET['debug']),
157
- 'reconvert' => isset($_GET['reconvert']),
158
- 'serve-original' => isset($_GET['original']),
159
- ];
160
- ```
161
-
162
- *EDIT:*
163
- I have just discovered a simpler way to achieve the querystring forward: The [QSA flag](https://httpd.apache.org/docs/trunk/rewrite/flags.html).
164
- So, simply set the QSA flag in the RewriteRule, and nothing more:
165
- ```
166
- RewriteRule ^(.*)\.(jpe?g|png)$ webp-on-demand.php?source=%{SCRIPT_FILENAME} [NC,QSA,L]
167
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v1.3/webp-on-demand/webp-on-demand.md DELETED
@@ -1,133 +0,0 @@
1
- # WebP on demand
2
-
3
- This is a solution for automatically serving WebP images instead of jpeg/pngs [for browsers that supports WebP](https://caniuse.com/#feat=webp) (At the time of writing, 78% of all mobile users and 72% of all desktop users uses browsers supporting webp)
4
-
5
- Once set up, it will automatically convert images, no matter how they are referenced. It for example also works on images referenced in CSS. As the solution does not require any change in the HTML, it can easily be integrated into any website / framework
6
-
7
- ## Overview
8
-
9
- A setup consists of a PHP script that serves converted images and some *redirect rules* that redirects JPG/PNG images to the script.
10
-
11
-
12
- ## Requirements
13
-
14
- * *Apache* or *LiteSpeed* web server. Can be made to work with *NGINX* as well. Documentation is on the roadmap.
15
- * *mod_rewrite* module for Apache
16
- * PHP >= 5.6 (we are only testing down to 5.6. It should however work in 5.5 as well)
17
- * That one of the *webp-convert* converters are working (these have different requirements)
18
-
19
- ## Installation
20
-
21
- Here we assume you are using Composer. [Not using composer? - Follow me!](https://github.com/rosell-dk/webp-convert/blob/master/docs/v1.3/webp-on-demand/without-composer.md)
22
-
23
- ### 1. Require the webp-convert library with composer
24
- ```
25
- composer require rosell-dk/webp-convert
26
- ```
27
-
28
-
29
- ### 2. Create the script
30
-
31
- Create a file *webp-on-demand.php*, and place it in webroot, or where-ever you like in you web-application.
32
-
33
- Here is a minimal example to get started with:
34
-
35
- ```php
36
- <?php
37
- require 'vendor/autoload.php'; // Make sure to point this correctly
38
-
39
- use WebPConvert\WebPConvert;
40
-
41
- $source = $_GET['source']; // Absolute file path to source file. Comes from the .htaccess
42
- $destination = $source . '.webp'; // Store the converted images besides the original images (other options are available!)
43
-
44
- $options = [
45
-
46
- // UNCOMMENT NEXT LINE, WHEN YOU ARE UP AND RUNNING!
47
- 'show-report' => true // Show a conversion report instead of serving the converted image.
48
-
49
- // More options available!
50
- ];
51
- WebPConvert::convertAndServe($source, $destination, $options);
52
- ```
53
-
54
- ### 3. Add redirect rules
55
- Place the following rewrite rules in a *.htaccess* file in the directory where you want the solution to take effect:
56
-
57
- ```
58
- <IfModule mod_rewrite.c>
59
- RewriteEngine On
60
-
61
- # Redirect images to webp-on-demand.php (if browser supports webp)
62
- RewriteCond %{HTTP_ACCEPT} image/webp
63
- RewriteCond %{REQUEST_FILENAME} -f
64
- RewriteRule ^(.*)\.(jpe?g|png)$ webp-on-demand.php?source=%{SCRIPT_FILENAME} [NC,L]
65
- </IfModule>
66
-
67
- AddType image/webp .webp
68
- ```
69
- If you have placed *webp-on-demand.php* in a subfolder, you will need to change the rewrite rule accordingly.
70
-
71
- The `RewriteCond %{REQUEST_FILENAME} -f` is not strictly necessary, but there to be sure that we got an existing file, and it could perhaps also prevent some undiscovered way of misuse.
72
-
73
- ### 4. Validate that it works
74
-
75
- Browse to a JPEG image. Instead of an image, you should see a conversion report. Hopefully, you get a success. Otherwise, you need to hook up to a cloud converter or try to meet the requirements for cwebp, gd or imagick.
76
-
77
- Once you get a successful conversion, you can uncomment the "show-report" option in the script.
78
-
79
- It should work now, but to be absolute sure:
80
-
81
- - Visit a page on your site with an image on it, using *Google Chrome*.
82
- - Right-click the page and choose "Inspect"
83
- - Click the "Network" tab
84
- - Reload the page
85
- - Find a jpeg or png image in the list. In the "type" column, it should say "webp". There should also be a *X-WebP-Convert-Status* header on the image that provides some insights on how things went.
86
-
87
-
88
- ### 5. Try this improvement and see if it works
89
-
90
- It seems that it is not necessary to pass the filename in the query string.
91
-
92
- Try replacing `$source = $_GET['source'];` in the script with the following:
93
-
94
- ```php
95
- $docRoot = rtrim($_SERVER["DOCUMENT_ROOT"], '/');
96
- $requestUriNoQS = explode('?', $_SERVER['REQUEST_URI'])[0];
97
- $source = $docRoot . urldecode($requestUriNoQS);
98
- ```
99
-
100
- And you can then remove `?source=%{SCRIPT_FILENAME}` from the `.htaccess` file.
101
-
102
- There are some benefits of not passing in query string:
103
- 1. Passing a path in the query string may be blocked by a firewall, as it looks suspicious.
104
- 2. The script called to convert arbitrary files
105
- 3. One person experienced problems with spaces in filenames passed in the query string. See [this issue](https://github.com/rosell-dk/webp-convert/issues/95)
106
-
107
-
108
- ### 6. Customizing and tweaking
109
-
110
- Basic customizing is done by setting options in the `$options` array. Check out the [docs on convert()](https://github.com/rosell-dk/webp-convert/blob/master/docs/v1.3/converting/convert.md) and the [docs on convertAndServe()](https://github.com/rosell-dk/webp-convert/blob/master/docs/v1.3/serving/convert-and-serve.md)
111
-
112
- Other tweaking is described in *docs/webp-on-demand/tweaks.md*:
113
- - [Store converted images in separate folder](https://github.com/rosell-dk/webp-convert/blob/master/docs/v1.3/webp-on-demand/tweaks.md#store-converted-images-in-separate-folder)
114
- - [CDN](https://github.com/rosell-dk/webp-convert/blob/master/docs/v1.3/webp-on-demand/tweaks.md#cdn)
115
- - [Make .htaccess route directly to existing images](https://github.com/rosell-dk/webp-convert/blob/master/docs/v1.3/webp-on-demand/tweaks.md#make-htaccess-route-directly-to-existing-images)
116
- - [Forward the query string](https://github.com/rosell-dk/webp-convert/blob/master/docs/v1.3/webp-on-demand/tweaks.md#forward-the-querystring)
117
-
118
-
119
- ## Troubleshooting
120
-
121
- ### The redirect rule doesn't seem to be working
122
- If images are neither routed to the converter or a 404, it means that the redirect rule isn't taking effect. Common reasons for this includes:
123
-
124
- - Perhaps there are other rules in your *.htaccess* that interfere with the rules?
125
- - Perhaps your site is on *Apache*, but it has been configured to use *Nginx* to serve image files. To find out which server that is handling the images, browse to an image and eximine the "Server" response header. In case *NGINX* are serving images, see if you can reconfigure your server setup. Alternatively, you can create *NGINX* rewrite rules. There are some [here](https://github.com/S1SYPHOS/kirby-webp#nginx) and [there](https://github.com/uhop/grunt-tight-sprite/wiki/Recipe:-serve-WebP-with-nginx-conditionally).
126
- - Perhaps the server isn't configured to allow *.htaccess* files? Try inserting rubbish in the top of the *.htaccess* file and refresh. You should now see an *Internal Server Error* error page. If you don't, your *.htaccess* file is ignored. Probably you will need to set *AllowOverride All* in your Virtual Host. [Look here for more help](
127
- https://docs.bolt.cm/3.4/howto/making-sure-htaccess-works#test-if-htaccess-is-working)
128
- - Perhaps the Apache *mod_rewrite* extension isn't enabled? Try removing both `<IfModule mod_rewrite.c>` and `</IfModule>` lines: if you get an *Internal Server Error* error page after this change, it's probably that it's indeed not enabled.
129
-
130
-
131
- ## Related
132
- * https://www.maxcdn.com/blog/how-to-reduce-image-size-with-webp-automagically/
133
- * https://www.digitalocean.com/community/tutorials/how-to-create-and-serve-webp-images-to-speed-up-your-website
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v1.3/webp-on-demand/without-composer.md DELETED
@@ -1,45 +0,0 @@
1
- # WebP On Demand without composer
2
-
3
- For your convenience, the library has been cooked down to two files: *webp-on-demand-1.inc* and *webp-on-demand-2.inc*. The second one is loaded when the first one decides it needs to do a conversion (and not simply serve existing image).
4
-
5
- ## Installing
6
-
7
- ### 1. Copy the latest build files into your website
8
- Copy *webp-on-demand-1.inc* and *webp-on-demand-2.inc* from the *build* folder into your website (in 2.0, they are located in "src-build"). They can be located wherever you like.
9
-
10
- ### 2. Create a *webp-on-demand.php*
11
-
12
- Create a file *webp-on-demand.php*, and place it in webroot, or where-ever you like in you web-application.
13
-
14
- Here is a minimal example to get started with. Note that this example only works in version 1.x. In 2.0, the `require-for-conversion` option has been removed, so the [procedure is different](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/webp-on-demand/without-composer.md).
15
-
16
- ```php
17
- <?php
18
- // To start with, lets display any errors.
19
- // You can later comment these out
20
- error_reporting(E_ALL);
21
- ini_set("display_errors", 1);
22
-
23
- require 'webp-on-demand-1.inc';
24
-
25
- use WebPConvert\WebPConvert;
26
-
27
- $source = $_GET['source']; // Absolute file path to source file. Comes from the .htaccess
28
- $destination = $source . '.webp'; // Store the converted images besides the original images (other options are available!)
29
-
30
- $options = [
31
-
32
- // Tell where to find the webp-convert-and-serve library, which will
33
- // be dynamically loaded, if need be.
34
- 'require-for-conversion' => 'webp-on-demand-2.inc',
35
-
36
- // UNCOMMENT NEXT LINE, WHEN YOU ARE UP AND RUNNING!
37
- 'show-report' => true // Show a conversion report instead of serving the converted image.
38
-
39
- // More options available!
40
- ];
41
- WebPConvert::convertAndServe($source, $destination, $options);
42
- ```
43
-
44
- ### 3. Continue the main install instructions from step 3
45
- [Click here to continue...](https://github.com/rosell-dk/webp-on-demand#3-add-redirect-rules)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v2.0/converting/architecture-q50-w600.jpg DELETED
Binary file
vendor/rosell-dk/webp-convert/docs/v2.0/converting/converters/stack.md DELETED
@@ -1,248 +0,0 @@
1
- # Stack converter
2
-
3
- The stack converter is a mechanism for trying all available converters until success. Well, the default is to try all converters, but this can be configured.
4
-
5
- When calling `WebPConvert::convert($source, $destination, $options);`, you are actually invoking the stack converter.
6
-
7
- ## Passing options down to the individual converters
8
-
9
- Any option that you pass to the Stack converter will be passed on to the individual converters. For example, setting options to the following will set the metadata option on all converters:
10
-
11
- ```php
12
- $options = [
13
- 'metadata' => 'all',
14
- ];
15
- ```
16
-
17
- If you need the option to be different for a single converter there are several ways to do it:
18
-
19
- #### 1. Prefixing
20
-
21
- Options prefixed with a converter id are only effective for that converter, and overrides the non-prefixed option.
22
-
23
- Ie, the following will set "metadata" to "all" for all converters, except *cwebp*, where "metadata" is set to "exif"
24
-
25
- ```php
26
- $options = [
27
- 'metadata' => 'all',
28
- 'cwebp-metadata' => 'exif'
29
- ];
30
- ```
31
-
32
- Prefixing is by the way a general feature in the way options are handled and thus not confined to the stack converter. (though it admittedly only finds its use in the context of a stack converter).
33
-
34
-
35
- #### 2. Using the `converter-options` option
36
- The *converter-options* option is convenient for setting a whole bunch of converter-specific options in one go.
37
-
38
- Example:
39
- ```php
40
- $options = [
41
- 'converter-options' => [
42
- 'wpc' => [
43
- 'crypt-api-key-in-transfer' => true
44
- 'api-key' => 'my dog is white',
45
- 'api-url' => 'https://example.com/wpc.php',
46
- 'api-version' => 1,
47
- ],
48
- ],
49
- ]
50
- ```
51
-
52
- #### 3. As part of the `converters` option
53
- This option is explained further down this document.
54
-
55
-
56
- ## Modifying the stack
57
-
58
- The default stack consists of the following converters:
59
- - cwebp
60
- - vips
61
- - imagick
62
- - gmagick
63
- - imagemagick
64
- - graphicsmagick
65
- - wpc
66
- - ewww
67
- - gd
68
-
69
- The order has carefully been chosen based on the capabilities of the converters. It is a rank, if you will.
70
-
71
- Now, say that on your system, you only have *gd* working. With the default stack, this means that eight converters will be tested for operationality before getting to *gd* &ndash; each time a conversion is made. You might be tempted to optimizing the flow by putting *gd* on the top. *I would generally advise against this* for the following reasons:
72
-
73
- 1. It might be that one of the other (and better) converters starts working without you noticing. You will then miss out.
74
- 2. All converters have all been designed to exit very quickly when they are not operational. It only takes a few milliseconds for the library to detect that a converter is not operational - literally. For example, if no api key is provided for ewww, it will exit immediately.
75
-
76
- However, there are valid reasons to modify the stack. For example, you may prefer *vips* over *cwebp*, or you may wish to remove a converter completely due to problems with that converter on your platform.
77
-
78
- ### Changing the order of the converters
79
- To change the order, you can use the `preferred-converters` option. With this option you move selected converters to the top of the stack.
80
-
81
- So, if you want the stack to start with *vips* and then *ewww*, but keep the rest of the order, you can set the following:
82
-
83
- ```php
84
- $options[
85
- 'preferred-converters' => ['vips', 'ewww'];
86
- ];
87
- ```
88
-
89
- ### Removing converters from the stack
90
- To remove converters, you can use the `skip` option and prefixing. For example, to remove *cwebp* and *gd*:
91
-
92
- ```php
93
- $options = [
94
- 'ewww-skip' => true,
95
- 'cwebp-skip' => true,
96
- ];
97
- ```
98
-
99
- ### Adding converters to the stack
100
- If you are using a custom converter, you can add it to the stack like this:
101
-
102
- ```php
103
- $options = [
104
- 'extra-converters' => [
105
- '\\MyNameSpace\\WonderConverter'
106
- ],
107
- ];
108
- ```
109
-
110
- It will be added to the bottom of the stack. To place it differently, use the `preferred-converters` option and set it to ie `'preferred-converters' => ['vips','\\MyNameSpace\\WonderConverter']`
111
-
112
-
113
- Here is an example which adds an extra ewww converter. This way you can have a backup api-key in case the quota of the first has been exceeded.
114
-
115
- ```
116
- $options = [
117
- 'extra-converters' => [
118
- [
119
- 'converter' => 'ewww',
120
- 'options' => [
121
- 'api-key' => 'provide-backup-key-here',
122
- ]
123
- ]
124
- ]
125
- ];
126
- ```
127
- Note however that you will not be able to reorder that new ewww converter using `preferred-converters`, as there are now two converters with id=ewww, and that option has not been designed for that. Instead, you can add a sub-stack of ewww converters - see the "Stacking" section below.
128
-
129
-
130
- ### Setting the converter array explicitly
131
- Using the `converters` option, you can set the converter array explicitly. What differentiates this from the `preferred-converters` option (besides that it completely redefines the converter ordering) is that it allows you to set both the converters *and* options for each converter in one go and that it allows a complex structure - such as a stack within a stack. Also, this structure can simplify things in some cases, such as when the options is generated by a GUI, as it is in WebP Express.
132
-
133
- The array specifies the converters to try and their order. Each item can be:
134
-
135
- - An id (ie "cwebp")
136
- - A fully qualified class name (in case you have programmed your own custom converter)
137
- - An array with two keys: "converter" and "options".
138
-
139
- Example:
140
-
141
- ```php
142
- $options = [
143
- 'quality' => 71,
144
- 'converters' => [
145
- 'cwebp',
146
- [
147
- 'converter' => 'vips',
148
- 'options' => [
149
- 'quality' => 72
150
- ]
151
- ],
152
- [
153
- 'converter' => 'ewww',
154
- 'options' => [
155
- 'quality' => 73
156
- ]
157
- ],
158
- 'wpc',
159
- 'imagemagick',
160
- '\\MyNameSpace\\WonderConverter'
161
- ],
162
- ];
163
- ```
164
-
165
- ### Stacking
166
- Stack converters behave just like regular converters. They ARE in fact "regular", as they extend the same base class as all converters. This means that you can have a stack within a stack. You can for example utilize this for supplying a backup api key for the ewww converter. Like this:
167
-
168
- ```php
169
- $options = [
170
- 'ewww-skip' => true, // skip the default ewww converter (we use stack of ewww converters instead)
171
- 'extra-converters' => [
172
- [
173
- // stack of ewww converters
174
- 'converter' => 'stack',
175
- 'options' => [
176
- 'ewww-skip' => false, // do not skip ewww from here on
177
- 'converters' => [
178
- [
179
- 'converter' => 'ewww',
180
- 'options' => [
181
- 'api-key' => 'provide-preferred-key-here',
182
- ]
183
- ],
184
- [
185
- 'converter' => 'ewww',
186
- 'options' => [
187
- 'api-key' => 'provide-backup-key-here',
188
- ]
189
- ]
190
- ],
191
- ]
192
- ]
193
- ],
194
- 'preferred-converters' => ['cwebp', 'vips', 'stack'], // set our stack of ewww converters third in queue
195
- ];
196
- ```
197
- Note that we set `ewww-skip` in order to disable the *ewww* converter which is part of the defaults. As options are inherited, we have to reset this option again. These steps are not necessary when using the `converters` option.
198
-
199
- Also note that the options for modifying the converters (`converters`, `extra-converters`, `converter-options`) does not get passed down.
200
-
201
- Also note that if you want to add two stacks with `extra-converters`, the `preferred-converters` option will not work, as there are two converters called "stack". One workaround is to add those two stacks to their own stack, so you have three levels. Or you can of course simply use the `converters` option to get complete control.
202
-
203
-
204
- ### Shuffling
205
-
206
- The stack can be configured to shuffling, meaning that the the order will be random. This can for example be used to balance load between several wpc instances in a sub stack.
207
-
208
- Shuffling is enabled with the `shuffle` option.
209
-
210
- Here is an example of balancing load between several *wpc* instances:
211
-
212
- ```php
213
- $options = [
214
- 'wpc-skip' => true, // skip the default wpc converter (we use stack of wpc converters instead)
215
- 'extra-converters' => [
216
- [
217
- // stack of wpc converters
218
- 'converter' => 'stack',
219
- 'options' => [
220
- 'wpc-skip' => false, // do not skip wpc from here on
221
- 'shuffle' => true,
222
-
223
- 'converters' => [
224
- [
225
- 'converter' => 'wpc',
226
- 'options' => [
227
- 'api-key' => 'my-dog',
228
- 'api-url' => 'my-wpc.com/wpc.php',
229
- 'api-version' => 1,
230
- 'crypt-api-key-in-transfer' => true,
231
- ]
232
- ],
233
- [
234
- 'converter' => 'wpc',
235
- 'options' => [
236
- 'api-key' => 'my-other-dog',
237
- 'api-url' => 'my-other-wpc.com/wpc.php',
238
- 'api-version' => 1,
239
- 'crypt-api-key-in-transfer' => true,
240
- ]
241
- ]
242
- ],
243
- ]
244
- ]
245
- ],
246
- 'preferred-converters' => ['cwebp', 'vips', 'stack'], // set our stack of wpc converters third in queue
247
- ];
248
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v2.0/converting/dice.png DELETED
Binary file
vendor/rosell-dk/webp-convert/docs/v2.0/converting/introduction-for-converting.md DELETED
@@ -1,218 +0,0 @@
1
- # Introduction to converting with WebPConvert
2
-
3
- **NOTE: This document only applies to the upcoming 2.0 version**
4
-
5
- The library is able to convert images to webp using a variety of methods (*gd*, *imagick*, *vips* etc.), which we call "converters". A converter is called like this:
6
-
7
- ```php
8
- use WebPConvert\Convert\Converters\Gd;
9
-
10
- Gd::convert($source, $destination, $options=[], $logger=null);
11
- ```
12
-
13
- All converters comes with requirements. For example, the *Gd* converter requires that Gd is installed and compiled with webp support. The cloud converters requires an api key. In case the conversion fails, an exception is thrown.
14
-
15
- ## Insights to the process
16
- If *$logger* is supplied, the converter will log the details of how the conversion process went to that logger. You can for example use the supplied *EchoLogger* to print directly to screen or the *BufferLogger* to collect the log entries. Here is a simple example which prints the process to screen:
17
-
18
- ```php
19
- use WebPConvert\Convert\Converters\Gd;
20
- use WebPConvert\Loggers\EchoLogger;
21
-
22
- Gd::convert($source, $destination, $options=[], new EchoLogger());
23
- ```
24
-
25
- It will output something like this:
26
-
27
- ```text
28
- GD Version: 2.2.5
29
- image is true color
30
- Quality set to same as source: 61
31
-
32
- Converted image in 20 ms, reducing file size with 34% (went from 12 kb to 8 kb)
33
- ```
34
-
35
- ## The stack converter
36
- When your software is going to be installed on a variety of systems which you do not control, you can try the converters one at the time until success. The converters has been designed to exit quickly when system requirements are not met. To make this task easy, a *Stack* converter has been created.
37
-
38
- The stack converter has two special options:
39
-
40
- | option | description |
41
- | ------------------------- | ----------- |
42
- | converters (array) | Converters to try (ids or class names, in case you have your own custom converter) |
43
- | converter-options (array) | Extra options for specific converters. |
44
-
45
- Alternatively to the converter-options array, you can simply prefix options with the converter id.
46
-
47
- I recommend leave the converters array at the default unless you have strong reasons not to. Otherwise you might miss out when new converters are added.
48
-
49
- ### Example:
50
-
51
- ```php
52
- <?php
53
- use WebPConvert\Convert\Converters\Stack;
54
-
55
- Stack::convert($source, $destination, $options = [
56
-
57
- // PS: only set converters if you have strong reasons to do so
58
- 'converters' => [
59
- 'cwebp', 'vips', 'imagick', 'gmagick', 'imagemagick', 'graphicsmagick', 'wpc', 'ewww', 'gd'
60
- ],
61
-
62
- // Any available options can be set here, they dribble down to all converters.
63
- 'metadata' => 'all',
64
-
65
- // To override for specific converter, you can prefix with converter id:
66
- 'cwebp-metadata' => 'exif',
67
-
68
- // This can for example be used for setting ewww api key:
69
- 'ewww-api-key' => 'your-ewww-api-key-here',
70
-
71
- // As an alternative to prefixing, you can use "converter-options" to set a whole bunch of overrides in one go:
72
- 'converter-options' => [
73
- 'wpc' => [
74
- 'crypt-api-key-in-transfer' => true
75
- 'api-key' => 'my dog is white',
76
- 'api-url' => 'https://example.com/wpc.php',
77
- 'api-version' => 1,
78
- ],
79
- ],
80
- ], $logger=null);
81
- ```
82
-
83
- Note: As an alternative to setting the third party credentials in the options, you can set them through constants or environment variables ("WEBPCONVERT_EWWW_API_KEY", "WEBPCONVERT_WPC_API_KEY", "WEBPCONVERT_WPC_API_URL"). Paths to binaries can also be set like that (it is rarely needed to do this): "WEBPCONVERT_CWEBP_PATH", "WEBPCONVERT_GRAPHICSMAGICK_PATH" and WEBPCONVERT_IMAGEMAGICK_PATH"
84
-
85
- To set an environment variable in Apache, you can add a line like this in your `.htaccess` or vhost configuration:
86
- ```
87
- # Set ewww api key for WebP Convert
88
- SetEnv WEBPCONVERT_EWWW_API_KEY yourVerySecretApiKeyGoesHere
89
-
90
- # Set custom path to imagick for WebP Convert
91
- SetEnv WEBPCONVERT_IMAGEMAGICK_PATH /usr/local/bin/magick
92
- ```
93
- To set a constant:
94
- ```php
95
- define('WEBPCONVERT_IMAGEMAGICK_PATH', '/usr/local/bin/magick');
96
- ```
97
-
98
-
99
- ## Configuring the options
100
-
101
- ### Auto quality
102
- **Q:** What do you get if you convert a low quality jpeg (ie q=50) into a high quality webp (ie q=90) ?\
103
- **A:** You maintain the low quality, but you get a large file`
104
-
105
- What should we have done instead? We should have converted with a quality around 50. Of course, quality is still low - we cannot fix that - but it will not be less, *and the converted file will be much smaller*.
106
-
107
- As unnecessary large conversions are rarely desirable, this library per default converts jpeg files with the same quality level as the source. This functionality requires that either *imagemagick*, *graphicsmagick* or *imagick* is installed (not necessarily compiled with webp support). When they are, all converters will have the "auto" quality functionality. The *wpc* cloud converter supports auto quality if these are installed on the server that *wpc* is installed on.
108
-
109
- How much can be gained? A lot!
110
- The following low quality (q=50) jpeg weighs 54 kb. If this is converted to webp with quality=80, the size of the converted file is 52kb - almost no reduction! With auto, the quality of the webp will be set to 50, and the size will be 34kb. Visually, the results are indistinguable.
111
-
112
- ![A low quality jpeg](https://raw.githubusercontent.com/rosell-dk/webp-convert/master/docs/v2.0/converting/architecture-q50-w600.jpg)
113
-
114
- **Q:** What do you get if you convert an excessively high quality jpeg into an excessively high quality webp?\
115
- **A:** An excessively big file
116
-
117
- The size of a webp file grows enormously with the quality setting. For the web however, a quality above 80 is rarely needed. For this reason the library has a per default limits the quality to the value of the *max-quality* option (default: 85).
118
-
119
- In case quality detection is unavailable, the quality gets the value of the *default-quality* option (default is 70 for JPEGs and 85 for PNGs).
120
-
121
- So, how much can be gained? A lot!
122
- The following excessively high quality jpeg (q=100) weighs 146 kb. Converting it to webp with q=100 results in a 99kb image (this would happen if we had the auto feature, but not the max-quality feature). Converting it to q=85 results in a 40kb image.
123
-
124
- ![A (too) high quality jpeg](https://raw.githubusercontent.com/rosell-dk/webp-convert/master/docs/v2.0/converting/mouse-q100.jpg)
125
-
126
-
127
- ### Auto selecting between lossless/lossy encoding
128
- WebP files can be encoded using either *lossless* or *lossy* encoding. The JPEG format is lossy and the PNG is lossless. However, this does not mean that you necessarily get the best conversion by always encoding JPEG to lossy and PNG to lossless. With JPEGs it is often the case, as they are usually pictures and pictures usually best encoded as lossy. With PNG it is however a different story, as you often can get a better compression using lossy encoding, also when using high quality level of say 85, which should be enough for the web.
129
-
130
- As unnecessary large conversions are rarely desirable, this library per default tries to convert images using both lossy and lossless encoding and automatically selects the smallest. This is controlled using the *encoding* option, which per default is "auto", but can also be set to "lossy" or "lossless".
131
-
132
- As an example, the following PNG (231 kb) will be compressed to 156 kb when converting to *lossless* webp. But when converting to *lossy* (quality: 85), it is compressed to merely 68 kb - less than half. (in case you are confused about the combination of lossy and transparency: Yes, you can have both at the same time with webp).
133
-
134
- ![Dice](https://raw.githubusercontent.com/rosell-dk/webp-convert/master/docs/v2.0/converting/dice.png)
135
-
136
- Unless you changed the `near-lossless` option described below, the choice is actually between lossy and *near-lossless*.
137
-
138
- Note that *gd* and *ewww* doesn't support this feature. *gd* can only produce lossy, and will simply do that. *ewww* can not be configured to use a certain encoding, but automatically chooses *lossless* encoding for PNGs and lossy for JPEGs.
139
-
140
- ### Near-lossless
141
- *cwebp* and *vips* supports "near-lossless" mode. Near lossless produces a webp with lossless encoding but adjusts pixel values to help compressibility. The result is a smaller file. The price is described as a minimal impact on the visual quality.
142
-
143
- As unnecessary large conversions are rarely desirable, this library per default sets *near-lossless* to 60. To disable near-lossless, set it to 100.
144
-
145
- When compressing the image above (231 kb) to lossless, it compressed to 156 kb when near-lossless is set to 100. Setting near-lossless to 60 gets the size down to 110 kb while still looking great.
146
-
147
- You can read more about the near-lossless mode [here](https://groups.google.com/a/webmproject.org/forum/#!topic/webp-discuss/0GmxDmlexek)
148
-
149
- ### Alpha-quality
150
- All converters, except *gd* and *ewww* supports "alpha-quality" option. This allows lossy compressing of the alpha channel.
151
-
152
- As unnecessary large conversions are rarely desirable, this library per default sets *alpha-quality* to 85. Set it to 100 to achieve lossless compression of alhpa.
153
-
154
- Btw, the image above gets compressed to 68 kb with alpha quality set to 100. Surprisingly, it gets slightly larger (70 kb) with alpha quality set to 85. Setting alpha quality to 50 gets it down to merely 35 kb - about half - while still looking great.
155
-
156
- You can read more about the alpha-quality option [here](https://developers.google.com/speed/webp/docs/cwebp)
157
-
158
-
159
- ### PNG og JPEG-specific options.
160
-
161
- To have options depending on the image type of the source, you can use the `png` and `jpeg` keys.
162
-
163
- The following options mimics the default behaviour:
164
-
165
- ```php
166
- $options = [
167
- 'png' => [
168
- 'encoding' => 'auto', /* Try both lossy and lossless and pick smallest */
169
- 'near-lossless' => 60, /* The level of near-lossless image preprocessing (when trying lossless) */
170
- 'quality' => 85, /* Quality when trying lossy. It is set high because pngs is often selected to ensure high quality */
171
- ],
172
- 'jpeg' => [
173
- 'encoding' => 'auto', /* If you are worried about the longer conversion time, you could set it to "lossy" instead (lossy will often be smaller than lossless for jpegs) */
174
- 'quality' => 'auto', /* Set to same as jpeg (requires imagick or gmagick extension, not necessarily compiled with webp) */
175
- 'max-quality' => 80, /* Only relevant if quality is set to "auto" */
176
- 'default-quality' => 75, /* Fallback quality if quality detection isnt working */
177
- ]
178
- ];
179
- ```
180
-
181
- You can use it for any option, also the converter specific options.
182
- A use case could for example be to use different converters for png and jpeg:
183
-
184
- ```php
185
- $options = [
186
- 'png' => [
187
- 'converters' => ['ewww'],
188
- ],
189
- 'jpeg' => [
190
- 'converters' => ['gd'],
191
- ]
192
- ];
193
- ```
194
-
195
- ## Available options
196
-
197
- **All** available options are documented [here](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md).
198
-
199
- Here is a quick overview of the few ones discussed here.
200
-
201
- | Option | Default (jpeg) | Default (png) | Description |
202
- | ----------------- | ------------------ | ------------------- | ---------------------------------------------------------------------------------- |
203
- | quality | "auto" | 85 | See the "Auto quality" section above. |
204
- | max-quality | 85 | 85 | Only relevant for jpegs and when quality is set to "auto". |
205
- | default-quality | 75 | 85 | |
206
- | metadata | "none" | "none" | Valid values: "all", "none", "exif", "icc", "xmp".<br><br>Note: Currently only *cwebp* supports all values. *gd* will always remove all metadata. *ewww*, *imagick* and *gmagick* can either strip all, or keep all (they will keep all, unless metadata is set to *none*) |
207
- | encoding | "auto" | "auto" | See the "Auto selecting between lossless/lossy encoding" section above |
208
- | jpeg | - | - | Array of options which will be merged into the other options when source is a JPEG |
209
- | png | - | - | Array of options which will be merged into the other options when source is a PNG |
210
- | skip | false | false | If true, conversion will be skipped (ie for skipping png conversion for some converters) |
211
-
212
-
213
- ## More info
214
-
215
- - The complete api is available [here](https://www.bitwise-it.dk/webp-convert/api/2.0/html/index.xhtml)
216
- - The converters are described in more detail here (for 1.3.9): [docs/v1.3/converting/converters.md](https://github.com/rosell-dk/webp-convert/blob/master/docs/v1.3/converting/converters.md).
217
- - On the github wiki you can find installation instructions for imagick with webp, gd with webp, etc.
218
- - This document is a newly written introduction to the convert api, which has been created as part of the 2.0 release. The old introduction, which was made for 1.3 is available here: [docs/converting/v1.3/convert.md](https://github.com/rosell-dk/webp-convert/blob/master/docs/v1.3/converting/convert.md).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v2.0/converting/mouse-q100.jpg DELETED
Binary file
vendor/rosell-dk/webp-convert/docs/v2.0/converting/options.md DELETED
@@ -1,346 +0,0 @@
1
- # Options
2
-
3
- This is a list of all options available for converting.
4
-
5
- Note that as the *stack* and *wpc* converters delegates the options to their containing converters, the options that they supports depend upon the converters they have been configured to use (and which of them that are operational)<br><br>
6
-
7
- ### `alpha-quality`
8
- ```
9
- Type: integer (0-100)
10
- Default: 85
11
- Supported by: cwebp, vips, imagick, gmagick, imagemagick and graphicsmagick
12
- ```
13
- Quality of alpha channel. Only relevant for lossy encoding and only relevant for images with alpha channel.<br><br>
14
-
15
- ### `auto-filter`
16
- ```
17
- Type: boolean
18
- Default: false
19
- Supported by: cwebp, vips, imagick, gmagick and imagemagick
20
- ```
21
- Turns auto-filter on. This algorithm will spend additional time optimizing the filtering strength to reach a well-balanced quality. Unfortunately, it is extremely expensive in terms of computation. It takes about 5-10 times longer to do a conversion. A 1MB picture which perhaps typically takes about 2 seconds to convert, will takes about 15 seconds to convert with auto-filter. So in most cases, you will want to leave this at its default, which is off.<br><br>
22
-
23
- ### `cwebp-command-line-options`
24
- ```
25
- Type: string
26
- Default: ''
27
- Supported by: cwebp
28
- ```
29
- This allows you to set any parameter available for cwebp in the same way as you would do when executing *cwebp*. You could ie set it to "-sharpness 5 -mt -crop 10 10 40 40". Read more about all the available parameters in [the docs](https://developers.google.com/speed/webp/docs/cwebp).<br><br>
30
-
31
- ### `cwebp-rel-path-to-precompiled-binaries`
32
- ```
33
- Type: string
34
- Default: './Binaries'
35
- Supported by: cwebp
36
- ```
37
- Allows you to change where to look for the precompiled binaries. While this may look as a risk, it is completely safe, as the binaries are hash-checked before being executed. The option is needed when you are using two-file version of webp-on-demand.
38
-
39
- ### `cwebp-try-common-system-paths`
40
- ```
41
- Type: boolean
42
- Default: true
43
- Supported by: cwebp
44
- ```
45
- If set, the converter will try to look for cwebp in locations such as `/usr/bin/cwebp`. It is a limited list. It might find something that isn't found using `try-discovering-cwebp` if these common paths are not within PATH or neither `which` or `whereis` are available.
46
-
47
- ### `cwebp-try-cwebp`
48
- ```
49
- Type: boolean
50
- Default: true
51
- Supported by: cwebp
52
- ```
53
- If set, the converter will try the a plain "cwebp" command (without specifying a path).
54
-
55
- ### `try-discovering-cwebp`
56
- ```
57
- Type: boolean
58
- Default: true
59
- Supported by: cwebp
60
- ```
61
- If set, the converter will try to discover installed cwebp binaries using the `which -a cwebp` command, or in case that fails, the `whereis -b cwebp` command. These commands will find cwebp binaries residing in PATH. They might find cwebp binaries which are not found by enabling `cwebp-try-common-system-paths`
62
-
63
-
64
- ### `cwebp-try-supplied-binary-for-os`
65
- ```
66
- Type: boolean
67
- Default: true
68
- Supported by: cwebp
69
- ```
70
- If set, the converter will try the precompiled cwebp binary that are located in `src/Convert/Converters/Binaries`, for the current OS. The binaries are hash-checked before executed.
71
-
72
- ### `default-quality`
73
- ```
74
- Type: integer (0-100)
75
- Default: 75 for jpegs and 85 for pngs
76
- Supported by: all (cwebp, ewww, gd, ffmpeg, gmagick, graphicsmagick, imagick, imagemagick, vips)
77
- ```
78
- Read about this option in the ["auto quality" section in the introduction](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#auto-quality).<br><br>
79
-
80
- ### `encoding`
81
- ```
82
- Type: string ("lossy" | "lossless" | "auto")
83
- Default: "auto"
84
- Supported by: cwebp, vips, ffmpeg, imagick, gmagick, imagemagick and graphicsmagick (gd always uses lossy encoding, ewww uses lossless for pngs and lossy for jpegs)
85
- ```
86
- Read about this option in the ["lossy/lossless" section in the introduction](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#auto-selecting-between-losslesslossy-encoding).<br><br>
87
-
88
- ### `ewww-api-key`
89
- ```
90
- Type: string
91
- Default: ''
92
- Supported by: ewww
93
- ```
94
- Api key for the ewww converter. The option is actually called *api-key*, however, any option can be prefixed with a converter id to only apply to that converter. As this option is only for the ewww converter, it is natural to use the "ewww-" prefix.
95
-
96
- Note: This option can alternatively be set through the *EWWW_API_KEY* environment variable.<br><br>
97
-
98
- ### `ewww-check-key-status-before-converting`
99
- ```
100
- Type: boolean
101
- Default: true
102
- Supported by: ewww
103
- ```
104
- Decides whether or not the ewww service should be invoked in order to check if the api key is valid. Doing this for every conversion is not optimal. However, it would be worse if the service was contacted repeatedly to do conversions with an invalid api key - as conversion requests carries a big upload with them. As this library cannot prevent such repeated failures (it is stateless), it per default does the additional check. However, your application can prevent it from happening by picking up invalid / exceeded api keys discovered during conversion. Such failures are stored in `Ewww::$nonFunctionalApiKeysDiscoveredDuringConversion` (this is also set even though a converter later in the stack succeeds. Do not only read this value off in a catch clauses).
105
-
106
- You should only set this option to *false* if you handle when the converter discovers invalid api keys during conversion.
107
-
108
- ### `jpeg`
109
- ```
110
- Type: array
111
- Default: []
112
- Supported by: all
113
- ```
114
- Override selected options when the source is a jpeg. The options provided here are simply merged into the other options when the source is a jpeg.
115
- Read about this option in the [introduction](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#png-og-jpeg-specific-options).<br><br>
116
-
117
- ### `log-call-arguments`
118
- ```
119
- Type: boolean
120
- Default: false
121
- Supported by: all
122
- ```
123
- Enabling this simply puts some more in the log - namely the arguments that was supplied to the call. Sensitive information is starred out.
124
-
125
- ### `low-memory`
126
- ```
127
- Type: boolean
128
- Default: false
129
- Supported by: cwebp, imagick, imagemagick and graphicsmagick
130
- ```
131
- Reduce memory usage of lossy encoding at the cost of ~30% longer encoding time and marginally larger output size. Read more in [the docs](https://developers.google.com/speed/webp/docs/cwebp).<br><br>
132
-
133
- ### `max-quality`
134
- ```
135
- Type: integer (0-100)
136
- Default: 85
137
- Supported by: all (cwebp, ewww, ffmpeg, gd, gmagick, graphicsmagick, imagick, imagemagick, vips)
138
- ```
139
- Read about this option in the ["auto quality" section in the introduction](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#auto-quality).<br><br>
140
-
141
- ### `metadata`
142
- ```
143
- Type: string ("all" | "none" | "exif" | "icc" | "xmp")
144
- Default: 'none'
145
- Supported by: 'none' is supported by all. 'all' is supported by all, except *gd* and *ffmpeg*. The rest is only supported by *cwebp*
146
- ```
147
- Only *cwebp* supports all values. *gd* will always remove all metadata. The rest can either strip all or keep all (they will keep all, unless the option is set to *none*).<br><br>
148
-
149
- ### `method`
150
- ```
151
- Type: integer (0-6)
152
- Default: 6
153
- Supported by: cwebp, imagick, gmagick, imagemagick, graphicsmagick and ffmpeg
154
- ```
155
- This parameter controls the trade off between encoding speed and the compressed file size and quality. Possible values range from 0 to 6. 0 is fastest. 6 results in best quality. In ffmpeg, this value is used for the "compression_level" option (same thing)<br><br>
156
-
157
- ### `near-lossless`
158
- ```
159
- Type: integer (0-100)
160
- Default: 60
161
- Supported by: cwebp, vips
162
- ```
163
- Specify the level of near-lossless image preprocessing. This option adjusts pixel values to help compressibility, but has minimal impact on the visual quality. It triggers lossless compression mode automatically. The range is 0 (maximum preprocessing) to 100 (no preprocessing). The typical value is around 60. Read more [here](https://groups.google.com/a/webmproject.org/forum/#!topic/webp-discuss/0GmxDmlexek).<br><br>
164
-
165
- ### `png`
166
- ```
167
- Type: array
168
- Default: []
169
- Supported by: all
170
- ```
171
- Override selected options when the source is a png. The options provided here are simply merged into the other options when the source is a png.
172
- Read about this option in the [introduction](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#png-og-jpeg-specific-options).<br><br>
173
-
174
- ### `preset`
175
- ```
176
- Type: string ('none', 'default', 'photo', 'picture', 'drawing', 'icon' or 'text')
177
- Default: "none"
178
- Supported by: cwebp, vips, ffmpeg
179
- ```
180
- Using a preset will set many of the other options to suit a particular type of source material. It even overrides them. It does however not override the quality option. "none" means that no preset will be set<br><br>
181
-
182
- ### `quality`
183
- ```
184
- Type: integer (0-100) | "auto"
185
- Default: "auto" for jpegs and 85 for pngs
186
- Supported by: all (cwebp, ewww, gd, gmagick, graphicsmagick, imagick, imagemagick, vips, ffmpeg)
187
- ```
188
- Quality for lossy encoding. Read about the "auto" option in the [introduction](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#auto-quality).<br><br>
189
-
190
- ### `size-in-percentage`
191
- ```
192
- Type: integer (0-100) | null
193
- Default: null
194
- Supported by: cwebp
195
- ```
196
- This option sets the file size, *cwebp* should aim for, in percentage of the original. If you for example set it to *45*, and the source file is 100 kb, *cwebp* will try to create a file with size 45 kb (we use the `-size` option). This is an excellent alternative to the "quality:auto" option. If the quality detection isn't working on your system (and you do not have the rights to install imagick or gmagick), you should consider using this options instead. *Cwebp* is generally able to create webp files with the same quality at about 45% the size. So *45* would be a good choice. The option overrides the quality option. And note that it slows down the conversion - it takes about 2.5 times longer to do a conversion this way, than when quality is specified. Default is *off* (null).<br><br>
197
-
198
- ### `skip`
199
- ```
200
- Type: boolean
201
- Default: false
202
- Supported by: all
203
- ```
204
- Simply skips conversion. For example this can be used to skip png conversion for a specific converter like this:
205
- ```php
206
- $options = [
207
- 'png' => [
208
- 'gd-skip' => true,
209
- ]
210
- ];
211
- ```
212
-
213
- Or it can be used to skip unwanted converters from the default stack, like this:
214
- ```php
215
- $options = [
216
- 'ewww-skip' => true,
217
- 'wpc-skip' => true,
218
- 'gd-skip' => true,
219
- 'imagick-skip' => true,
220
- 'gmagick-skip' => true,
221
- ];
222
- ```
223
- <br>
224
-
225
- ### `stack-converters`
226
- ```
227
- Type: array
228
- Default: ['cwebp', 'vips', 'imagick', 'gmagick', 'imagemagick', 'graphicsmagick', 'wpc', 'ewww', 'gd']
229
- Supported by: stack
230
- ```
231
-
232
- Specify the converters to try and their order.
233
-
234
- Beware that if you use this option, you will miss out when more converters are added in future updates. If the purpose of setting this option is to remove converters that you do not want to use, you can use the *skip* option instead. Ie, to skip ewww, set *ewww-skip* to true. On the other hand, if what you actually want is to change the order, you can use the *stack-preferred-converters* option, ie setting *stack-preferred-converters* to `['vips', 'wpc']` will move vips and wpc in front of the others. Should they start to fail, you will still have the others as backup.
235
-
236
- The array specifies the converters to try and their order. Each item can be:
237
-
238
- - An id (ie "cwebp")
239
- - A fully qualified class name (in case you have programmed your own custom converter)
240
- - An array with two keys: "converter" and "options".
241
-
242
- `
243
- Alternatively, converter options can be set using the *converter-options* option.
244
-
245
- Read more about the stack converter in the [introduction](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#the-stack-converter).<br><br>
246
-
247
- ### `stack-converter-options`
248
- ```
249
- Type: array
250
- Default: []
251
- Supported by: stack
252
- ```
253
- Extra options for specific converters. Example:
254
-
255
- ```php
256
- $options = [
257
- 'converter-options' => [
258
- 'vips' => [
259
- 'quality' => 72
260
- ],
261
- ]
262
- ]
263
- ```
264
- <br>
265
-
266
- ### `stack-extra-converters`
267
- ```
268
- Type: array
269
- Default: []
270
- Supported by: stack
271
- ```
272
- Add extra converters to the bottom of the stack. The items are similar to those in the `stack-converters` option.<br><br>
273
-
274
- ### `stack-preferred-converters`
275
- ```
276
- Type: array
277
- Default: []
278
- Supported by: stack
279
- ```
280
- With this option you can move specified converters to the top of the stack. The converters are specified by id. For example, setting this option to ['vips', 'wpc'] ensures that *vips* will be tried first and - in case that fails - *wpc* will be tried. The rest of the converters keeps their relative order.<br><br>
281
-
282
- ### `stack-shuffle`
283
- ```
284
- Type: boolean
285
- Default: false
286
- Supported by: stack
287
- ```
288
- Shuffle the converters in the stack. This can for example be used to balance load between several wpc instances in a substack, as illustrated [here](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/converters/stack.md)<br><br>
289
-
290
- ### `use-nice`
291
- ```
292
- Type: boolean
293
- Default: false
294
- Supported by: cwebp, graphicsmagick, imagemagick, ffmpeg
295
- ```
296
- This option only applies to converters which are using exec() to execute a binary directly on the host. If *use-nice* is set, it will be examined if the [`nice`]( https://en.wikipedia.org/wiki/Nice_(Unix)) command is available on the host. If it is, the binary is executed using *nice*. This assigns low priority to the process and will save system resources - but result in slower conversion.<br><br>
297
-
298
- ### `vips-smart-subsample`
299
- ```
300
- Type: boolean
301
- Default: false
302
- Supported by: vips
303
- ```
304
- This feature seems not to be part of *libwebp* but intrinsic to vips. According to the [vips docs](https://jcupitt.github.io/libvips/API/current/VipsForeignSave.html#vips-webpsave), it enables high quality chroma subsampling.<br><br>
305
-
306
- ### `wpc-api-key`
307
- ```
308
- Type: string
309
- Default: ''
310
- Supported by: wpc
311
- ```
312
- Api key for the wpc converter. The option is actually called *api-key*, however, any option can be prefixed with a converter id to only apply to that converter. As this option is only for the wpc converter, it is natural to use the "wpc-" prefix. Same goes for the other "wpc-" options.
313
-
314
- Note: You can alternatively set the api key through the *WPC_API_KEY* environment variable.<br><br>
315
-
316
- ### `wpc-api-url`
317
- ```
318
- Type: string
319
- Default: ''
320
- Supported by: wpc
321
- ```
322
- Note: You can alternatively set the api url through the *WPC_API_URL* environment variable.<br><br>
323
-
324
- ### `wpc-api-version`
325
- ```
326
- Type: integer (0 - 1)
327
- Default: 0
328
- Supported by: wpc
329
- ```
330
- <br>
331
-
332
- ### `wpc-crypt-api-key-in-transfer`
333
- ```
334
- Type: boolean
335
- Default: false
336
- Supported by: wpc
337
- ```
338
- <br>
339
-
340
- ### `wpc-secret`
341
- ```
342
- Type: string
343
- Default: ''
344
- Supported by: wpc
345
- ```
346
- Note: This option is only relevant for api version 0.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v2.0/migrating-to-2.0.md DELETED
@@ -1,73 +0,0 @@
1
- convert# Migrating to 2.0
2
-
3
- ## Converting
4
-
5
- ### Changes in conversion api
6
- While the code have been refactored quite extensively, if you have stuck to `WebPConvert::convert()` and/or `WebPConvert::convertAndServe()`, there is only a few things you need to know.
7
-
8
- First and foremost: *`WebPConvert::convert` no longer returns a boolean indicating the result*. So, if conversion fails, an exception is thrown, no matter what the reason is. When migrating, you will probably need to remove some lines of code where you test the result.
9
-
10
- Also, a few options has been renamed and a few option defaults has been changed.
11
-
12
- #### The options that has been renamed are the following:
13
-
14
- - Two converters have changed IDs and class names: The ids that are changed are: *imagickbinary* => *imagemagick* and *gmagickbinary* => *graphicsmagick*
15
- - In *ewww*, the `key` option has been renamed to `api-key` (or [`ewww-api-key`](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md#ewww-api-key))
16
- - In *wpc*, the `url` option has been renamed to `api-url` (or [`wpc-api-url`](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md#wpc-api-url))
17
- * In *cwebp*, the [`lossless`] option is now replaced with the new `encoding` option (which is not boolean, but "lossy", "lossless" or ["auto"](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#auto-selecting-between-losslesslossy-encoding))
18
- * In *cwebp*, the [`autofilter`] option has been renamed to "auto-filter"
19
- - In *gd*, the `skip-pngs` option has been removed and replaced with the general `skip` option and prefixing. So `gd-skip` amounts to the same thing, but notice that Gd no longer skips per default.
20
-
21
- #### The option defaults that has been changed are the following:
22
- - the `converters` default now includes the cloud converters (*ewww* and *wpc*) and also two new converters, *vips* and *graphicsmagick*. So it is not necessary to add *ewww* or *wpc* explicitly. Also, when you set options with `converter-options` and point to a converter that isn't in the stack, in 1.3.9, this resulted in the converter automatically being added. This behavior has been removed.
23
- - *gd* no longer skips pngs per default. To make it skip pngs, set `gd-skip` to *true*
24
- - Default quality is now 75 for jpegs and 85 for pngs (it was 75 for both)
25
- - For *cwebp*, the `lossless` has been removed. Use the new `encoding` option instead.
26
- - For *wpc*, default `secret` and `api-key` are now "" (they were "my dog is white")
27
-
28
- ### New convert options
29
- You might also be interested in the new options available in 2.0:
30
-
31
- - Added a syntax for conveniently targeting specific converters. If you for example prefix the "quality" option with "gd-", it will override the "quality" option, but only for gd.
32
- - Certain options can now be set with environment variables too ("EWWW_API_KEY", "WPC_API_KEY" and "WPC_API_URL")
33
- - Added new *vips* converter.
34
- - Added new *graphicsmagick* converter.
35
- - Added new *stack* converter (the stack functionality has been moved into a converter)
36
- - Added [`jpeg`](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md#jpeg) and [`png`](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md#png) options
37
- - Added [`alpha-quality`](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md#alpha-quality) option for *cwebp*, *vips*, *imagick*, *imagemagick* and *graphicsmagick*.
38
- - Added [`auto-filter`](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md#autofilter) option for *cwebp*, *imagick*, *imagemagick* and the new *vips* converter.
39
- - Added [`encoding`](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md#encoding) option (lossy | lossless | auto). lossless and auto is supported for *cwebp*, *imagick*, *imagemagick*, *graphicsmagick* and the new *vips* converter.
40
- - Added [`near-lossless`](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md#near-lossless) option for *cwebp* and *imagemagick*.
41
- - Added [`preset`](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md#preset) option for *cwebp* and the new *vips* converter.
42
- - Added [`skip`](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md#skip) option (its general and works for all converters)
43
- - Besides the ones mentioned above, *imagemagick* now also supports [`low-memory`](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md#low-memory), [`metadata`](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md#metadata) ("all" or "none") and [`method`](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md#method). *imagemagick* has become very potent!
44
-
45
- ## Serving
46
- The classes for serving has also been refactored quite extensively, but again, if you have stuck to `WebPConvert::convertAndServe`, there is only a few things you need to know.
47
-
48
- First and foremost, *`WebPConvert::convertAndServe` has been renamed to `WebPConvert::serveConverted()`*. The reason for this change is that it more accurately describes what is happening: A converted file is served. The old name implied that a conversion was always going on, which is not the case (if the file at destination already exists, which is not bigger or older than the source, that file is served directly).
49
-
50
- Besides this, there is the following changes in options:
51
-
52
- - A new option `convert` has been created for supplying the conversion options. So the conversion options are no longer "mingled" with the serving options, but has its own option.
53
- - Options regarding serving the image are now organized into its own `serve-image` setting, which again has been reorganized.
54
- - A new option `serve-image > headers > cache-control` controls whether to set cache control header (default: false).
55
- - The `fail` option no longer support the "report-as-image" value. It however supports a new value: "throw".
56
- - The `fail-when-original-unavailable` option has been renamed to `fail-when-fail-fails`. In 2.0, the original not being available is no longer the only thing that can cause the fail action to fail &ndash; the library now checks the mime type of the source file and only serves it if it is either png or jpeg.
57
- - The `error-reporting` option has been removed. The reason for it being removed is that it is considered bad practice for a library to mess with error handling. However, *this pushes the responsibility to you*. You should make sure that no warnings ends up in the output, as this will corrupt the image being served. You can for example ensure that by calling `ini_set('display_errors', '0');` or `error_reporting(0);` (or both), or by creating your own error handler.
58
- - The `aboutToServeImageCallBack` option has been removed. You can instead extend the `ServeConvertedWebP` class and override `serveOriginal` and `serveDestination`. You can call the serve method of your extended class, but then you will not have the error handling (the `fail` and `fail-if-fail-fails` options). Too add this, you can call `ServeConvertedWebPWithErrorHandling::serve` and make sure to override the default of the last argument.
59
- - The `aboutToPerformFailAction` option has been removed. You can instead set `fail` to `throw` and handle the exception in a *catch* clause. Or you can extend the `ServeConvertedWebPWithErrorHandling` class and override the `performFailAction` method.
60
- - The `add-x-header-status` and `add-x-header-options` options have been removed.
61
- - The `require-for-conversion` option has been removed. You must either use with composer or create a simple autoloader (see next section)
62
-
63
- ## WebP On demand
64
- If you are using the "non-composer" version of webp demand (the one where you only upload two files - `webp-on-demand-1.inc` and `webp-on-demand-2.inc`), you were probably using the `require-for-conversion` option. This option is no longer supported. But you never really needed it in the first place, because the you create and register an autoloader instead:
65
-
66
- ```php
67
- function autoloader($class) {
68
- if (strpos($class, 'WebPConvert\\') === 0) {
69
- require_once __DIR__ . '/webp-on-demand-2.inc';
70
- }
71
- }
72
- spl_autoload_register('autoloader', true, true);
73
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v2.0/serving/introduction-for-serving.md DELETED
@@ -1,157 +0,0 @@
1
- # Introduction to serving converted WebP files with WebPConvert
2
-
3
- **NOTE: This document only applies to the upcoming 2.0 version**
4
-
5
- The classes for serving first and foremost helps you handle the cached files intelligently (not serving them if they are larger or older than the original). It also provides a convenient way to deal with conversion failures and setting headers.
6
-
7
-
8
- In the following example, all available *serve* options are explicitly set to their default values.
9
-
10
- ```php
11
- use WebPConvert\WebPConvert;
12
-
13
- WebPConvert::serveConverted($source, $destination, [
14
-
15
- // failure handling
16
- 'fail' => 'original', // ('original' | 404' | 'throw' | 'report')
17
- 'fail-when-fail-fails' => 'throw', // ('original' | 404' | 'throw' | 'report')
18
-
19
- // options influencing the decision process of what to be served
20
- 'reconvert' => false, // if true, existing (cached) image will be discarded
21
- 'serve-original' => false, // if true, the original image will be served rather than the converted
22
- 'show-report' => false, // if true, a report will be output rather than the raw image
23
-
24
- // warning handling
25
- 'suppress-warnings' => true, // if you set to false, make sure that warnings are not echoed out!
26
-
27
- // options when serving an image (be it the webp or the original, if the original is smaller than the webp)
28
- 'serve-image' => [
29
- 'headers' => [
30
- 'cache-control' => true,
31
- 'content-length' => true,
32
- 'content-type' => true,
33
- 'expires' => false,
34
- 'last-modified' => true,
35
- 'vary-accept' => false
36
- ],
37
- 'cache-control-header' => 'public, max-age=31536000',
38
- ],
39
-
40
- // redirect tweak
41
- 'redirect-to-self-instead-of-serving' => false, // if true, a redirect will be issues rather than serving
42
-
43
- 'convert' => [
44
- // options for converting goes here
45
- 'quality' => 'auto',
46
- ]
47
- ]);
48
- ```
49
-
50
- ## Failure handling
51
- The `fail` option gives you an easy way to handle errors. Setting it to 'original' tells it to handle errors by serving the original file instead (*$source*). This could be a good choice on production servers. On development servers, 'throw' might be a good option. It simply rethrows the exception that was thrown by *WebPConvert::convert()*. '404' could also be an option, but it has the weakness that it will probably only be discovered by real persons seeing a missing image.
52
-
53
- The fail action might fail too. For example, if it is set to 'original' and the failure is that the original file doesn't exist. Or, more delicately, it may have a wrong mime type - our serve method will not let itself be tricked into serving *exe* files as the 'original'. Anyway, you can control what to do when fail fails using the *fail-when-fail-fails* option. If that fails too, the original exception is thrown. The fun stops there, there is no "fail-when-fail-when-fail-fails" option to customize this.
54
-
55
- The failure handling is implemented as an extra layer. You can bypass it by calling `WebPConvert\Serve\ServeConvertedWebP::serve()` directly. Doing that will give the same result as if you set `fail` to 'throw'.
56
-
57
- ## Options influencing the decision process
58
- The default process is like this:
59
-
60
- 1. Is there a file at the destination? If not, trigger conversion
61
- 2. Is the destination older than the source? If yes, delete destination and trigger conversion
62
- 3. Serve the smallest file (destination or source)
63
-
64
- You can influence the process with the following options:
65
-
66
- *reconvert*
67
- If you set *reconvert* to true, the destination and conversion is triggered (between step 1 and 2)
68
-
69
- *serve-original*
70
- If you set *serve-original* to true, process will take its cause from (1) to (2) and then end with source being served.
71
-
72
- *show-report*
73
- If you set `show-report`, the process is skipped entirely, and instead a report is generated of how a fresh conversion using the supplied options goes.
74
-
75
- ## Headers
76
- Leaving errors and reports out of account for a moment, the *WebPConvert::serveConverted()* ultimately has two possible outcomes: Either a converted image is served or - if smaller - the source image. If the source is to be served, its mime type will be detected in order to make sure it is an image and to be able to set the content type header. Either way, the actual serving is passed to `Serve\ServeFile::serve`. The main purpose of this class is to add/set headers.
77
-
78
- #### *Cache-Control* and *Expires* headers
79
- Default behavior is to neither set the *Cache-Control* nor the *Expires* header. Once you are on production, you will probably want to turn these on. The default is btw one year (31536000 seconds). I recommend the following for production:
80
-
81
- ```
82
- 'serve-image' => [
83
- 'headers' => [
84
- 'cache-control' => true,
85
- 'expires' => false,
86
- ],
87
- 'cache-control-header' => 'public, max-age=31536000',
88
- ],
89
- ```
90
-
91
- The value for the *Expires* header is calculated from "max-age" found in the *cache-control-header* option and the time of the request. The result is an absolute time, ie "Expires: Thu, 07 May 2020 07:02:37 GMT". As most browsers now supports the *Cache-Control* header, *from a performance perspective*, there is no need to also add the expires header. However, some tools complains if you don't (gtmetrix allegedly), and there is no harm in adding both headers. More on this discussion [[here]](https://github.com/rosell-dk/webp-convert/issues/126).
92
-
93
- #### *Vary: Accept* header
94
- This library can be used as part of a solution that serves webp files to browsers that supports it, while serving the original file to browsers that does not *on the same URL*. Such a solution typically inspects the *Accept* request header in order to determine if the client supports webp or not. Thus, the response will *vary* along with the "Accept" header and the world (and proxies) should be informed about this, so they don't end up serving cached webps to browsers that does not support it. To add the "Vary: Accept" header, simply set the *serve-image > headers > vary-accept* option to true.
95
-
96
- #### *Last-Modified* header
97
- The Last-Modified header is also used for caching purposes. You should leave that setting on, unless you set it by other means. You control it with the *serve-image > headers > last-modified* option.
98
-
99
- #### *Content-Type* header
100
- The *Content-Type* header tells browsers what they are receiving. This is important information and you should leave the *serve-image > headers > content-type* option at its default (true), unless you set it by other means.
101
-
102
- When the outcome is to serve a webp, the header will be set to: "Content-Type: image/webp". When the original is to be served, the library will try to detect the mime type of the file and set the content type accordingly. The [image-mime-type-guesser](https://github.com/rosell-dk/image-mime-type-guesser) library is used for that.
103
-
104
- #### *Content-Length* header
105
- The *Content-Length* header tells browsers the length of the content. According to [the specs](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13), it should be set unless it is prohibited by rules in [section 4.4](https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4). In that section we learn that it should not be set when the *Transfer-Encoding* header is set (which it often is, to "chunked"). However, no harm done, because it also says that clients should ignore the header in case *Transfer-Encoding* is set. From this I concluded that it makes sense to default the *serve-image > headers > content-length* to true. I might however change this in case I should learn that the header could be problematic in some way. So if you decided you want it, do not rely on the default, but set it to *true*. See discussion on this subject [here](https://stackoverflow.com/questions/3854842/content-length-header-with-head-requests/3854983#3854983).
106
-
107
- #### *X-WebP-Convert-Log* headers
108
- The serve method adds *X-WebP-Convert-Log* headers in order to let you know what went on.
109
- For example, if there is no converted image and conversion was successful, the following headers will be sent:
110
-
111
- ```
112
- X-WebP-Convert-Log: Converting (there were no file at destination)
113
- X-WebP-Convert-Log: Serving converted file
114
- ```
115
-
116
- On the next call (presuming the webp has not been deleted), no conversion is needed and you should simply see:
117
- ```
118
- X-WebP-Convert-Log: Serving converted file
119
- ```
120
-
121
- But say that the first conversion actually failed. In case you have permission problems, the output could be:
122
- ```
123
- X-WebP-Convert-Log: Converting (there were no file at destination)
124
- X-WebP-Convert-Log: Failed creating folder. Check the permissions!
125
- X-WebP-Convert-Log: Performing fail action: original
126
- ```
127
-
128
- In case the problem is that the conversion failed, you could see the following:
129
- ```
130
- X-WebP-Convert-Log: Converting (there were no file at destination)
131
- X-WebP-Convert-Log: None of the converters in the stack are operational
132
- X-WebP-Convert-Log: Performing fail action: original
133
- ```
134
-
135
- If you need more info about the conversion process in order to learn why the converters aren't working, enable the *show-report* option.
136
-
137
- As a last example, say you have supplied a non-existing file as source and `fail` is set to "original" (which will also fail). Result:
138
- ```
139
- X-WebP-Convert-Log: Source file was not found
140
- X-WebP-Convert-Log: Performing fail action: original
141
- X-WebP-Convert-Log: Performing fail action: throw
142
- ```
143
-
144
- ## The redirect tweak (will be available in 2.3.0)
145
- There are cases where serving the image directly with PHP isn't optimal.
146
-
147
- One case is WP Engine. Even though webp-convert adds a Vary:Accept header, the header is not present in the response on WP Engine. It is somehow overwritten by the caching machinery and set to Vary:Accept-Encoding, Cookie.
148
-
149
- If however rules have been set up to redirect images directly to existing webps, one can overcome the problem by redirecting the image request back to itself rather than serving the webp directly.
150
-
151
- You can achieve this by setting the *redirect-to-self-instead-of-serving* option to true.
152
-
153
- Beware of risk of an endless redirect loop. Such loop will happen if the redirection to existing webp rules aren't set up correctly. To prevent this, it is recommended that you only set the option to true after checking that the destination file does not exist. But note that this check does not completely prevent such loops occurring when redirection to existing rules are missing - as the 302 redirect could get cached (it does that on WP Engine). So bottom line: Only use this feature when you have server rules set up for redirecting images to their corresponding webp images (for client that supports webp) - *and you are certain that these rules works*.
154
-
155
- ## More info
156
-
157
- - The complete api is available [here](https://www.bitwise-it.dk/webp-convert/api/2.0/html/index.xhtml)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v2.0/serving/laravel-nginx-serving.md DELETED
@@ -1,116 +0,0 @@
1
- # Serving WebP from a Laravel Nginx site
2
-
3
- **NOTE: This document only applies to the upcoming 2.0 version**
4
-
5
- This should work with most php sites although I'm basing the Nginx configuration around what's commonly seen with Laravel installations.
6
-
7
- Create webp converter script in ```project_root/public/webp-on-demand.php```
8
-
9
- ```
10
- <?php
11
-
12
- require '../vendor/autoload.php';
13
-
14
- use WebPConvert\WebPConvert;
15
-
16
- $source = __DIR__ . $_GET['source'];
17
- $destination = $source . '.webp';
18
-
19
- WebPConvert::serveConverted($source, $destination, [
20
- 'fail' => 'original', // If failure, serve the original image (source). Other options include 'throw', '404' and 'report'
21
- // 'show-report' => true, // Generates a report instead of serving an image
22
-
23
- 'serve-image' => [
24
- 'headers' => [
25
- 'cache-control' => true,
26
- 'vary-accept' => true,
27
- // other headers can be toggled...
28
- ],
29
- 'cache-control-header' => 'max-age=2',
30
- ],
31
-
32
- 'convert' => [
33
- // all convert option can be entered here (ie "quality")
34
- ],
35
- ]);
36
-
37
- ```
38
-
39
-
40
- ### Configure Nginx
41
-
42
- We just need to add the following block to our site in ```/etc/sites-enabled/```
43
-
44
- ```
45
- location ~* ^/.*\.(png|jpe?g)$ {
46
- add_header Vary Accept;
47
- expires 365d;
48
- if ($http_accept !~* "webp"){
49
- break;
50
- }
51
- try_files
52
- $uri.webp
53
- /webp-on-demand.php?source=$uri
54
- ;
55
- }
56
- ```
57
-
58
- Then reload Nginx ```sudo systemctl restart nginx```
59
-
60
- The full Nginx block should look like
61
-
62
- ```
63
- server {
64
- server_name webp-testing.com;
65
- root /home/forge/webp-testing.com/public;
66
-
67
- index index.html index.htm index.php;
68
-
69
- charset utf-8;
70
-
71
- location / {
72
- try_files $uri $uri/ /index.php?$query_string;
73
- }
74
-
75
- location ~* ^/.*\.(png|jpe?g)$ {
76
- add_header Vary Accept;
77
- expires 365d;
78
- if ($http_accept !~* "webp"){
79
- break;
80
- }
81
- try_files
82
- $uri.webp
83
- /webp-on-demand.php?source=$uri
84
- ;
85
- }
86
-
87
- location = /favicon.ico { access_log off; log_not_found off; }
88
- location = /robots.txt { access_log off; log_not_found off; }
89
-
90
- access_log off;
91
- error_log /var/log/nginx/webp-testing.com-error.log error;
92
-
93
- error_page 404 /index.php;
94
-
95
- location ~ \.php$ {
96
- fastcgi_split_path_info ^(.+\.php)(/.+)$;
97
- fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;
98
- fastcgi_index index.php;
99
- include fastcgi_params;
100
- }
101
-
102
- location ~ /\.(?!well-known).* {
103
- deny all;
104
- }
105
-
106
- # cache static assets
107
- location ~* \.(gif|ico|css|pdf|svg)$ {
108
- expires 365d;
109
- }
110
-
111
- location ~* \.(js)$ {
112
- add_header Cache-Control no-cache;
113
- }
114
-
115
- }
116
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v2.0/webp-on-demand/tweaks.md DELETED
@@ -1,181 +0,0 @@
1
- # Tweaks
2
-
3
- ## Store converted images in separate folder
4
-
5
- In most cases, you probably want the cache of converted images to be stored in their own folder rather than have them mingled with the source files.
6
-
7
- To have have the cache folder contain a file structure mirroring the structure of the original files, you can do this:
8
-
9
- ```php
10
- $applicationRoot = $_SERVER["DOCUMENT_ROOT"]; // If your application is not in document root, you can change accordingly.
11
- $imageRoot = $applicationRoot . '/webp-images'; // Change to where you want the webp images to be saved
12
- $sourceRel = substr($source, strlen($applicationRoot));
13
- $destination = $imageRoot . $sourceRel . '.webp';
14
- ```
15
-
16
- If your images are stored outside document root (a rare case), you can simply use the complete absolute path:
17
- ```php
18
- $destination = $imageRoot . $source . '.webp'; // pst: $source is an absolute path, and starts with '/'
19
- ```
20
- This will ie store a converted image in */var/www/example.com/public_html/app/webp-images/var/www/example.com/images/logo.jpg.webp*
21
-
22
- If your application can be configured to store outside document root, but rarely is, you can go for this structure:
23
-
24
- ```php
25
- $docRoot = $_SERVER["DOCUMENT_ROOT"];
26
- $imageRoot = $contentDirAbs . '/webp-images';
27
-
28
- if (substr($source, 0, strlen($docRoot)) === $docRoot) {
29
- // Source file is residing inside document root.
30
- // We can store relative to that.
31
- $sourceRel = substr($source, strlen($docRoot));
32
- $destination = $imageRoot . '/doc-root' . $sourceRel . '.webp';
33
- } else {
34
- // Source file is residing outside document root.
35
- // we must add complete path to structure
36
- $destination = $imageRoot . '/abs' . $source . '.webp';
37
- }
38
- ```
39
-
40
- If you do not know the application root beforehand, and thus do not know the appropriate root for the converted images, see next tweak.
41
-
42
-
43
- ## Get the application root automatically
44
- When you want destination files to be put in their own folder, you need to know the root of the application (the folder in which the .htaccess rules resides). In most applications, you know the root. In many cases, it is simply the document root. However, if you are writing an extension, plugin or module to a framework that can be installed in a subfolder, you may have trouble finding it. Many applications have a *index.php* in the root, which can get it with `__DIR__`. However, you do not want to run an entire bootstrap each time you serve an image. Obviously, to get around this, you can place *webp-on-demand.php* in the webroot. However, some frameworks, such as Wordpress, will not allow a plugin to put a file in the root. Now, how could we determine the application root from a file inside some subdir? Here are three suggestions:
45
-
46
- 1. You could traverse parent folders until you find a file you expect to be in application root (ie a .htaccess containing the string "webp-on-demand.php"). This should work.
47
- 2. If the rules in the *.htaccess* file are generated by your application, you probably have access to the path at generation time. You can then simply put the path in the *.htaccess*, as an extra parameter to the script (or better: the relative path from document root to the application).
48
- 3. You can use the following hack:
49
-
50
- ### The hack
51
- The idea is to grab the URL path of the image in the *.htaccess* and pass it to the script. Assuming that the URL paths always matches the file paths, we can get the application root by subtracting that relative path to source from the absolute path to source.
52
-
53
- In *.htaccess*, we grab the url-path by appending "&url-path=$1.$2" to the rewrite rule:
54
- ```
55
- RewriteRule ^(.*)\.(jpe?g|png)$ webp-on-demand.php?source=%{SCRIPT_FILENAME}&url-path=$1.$2 [NC,L]
56
- ```
57
-
58
- In the script, we can then calculate the application root like this:
59
-
60
- ```php
61
- $applicationRoot = substr($_GET['source'], 0, -strlen($_GET['url-path']));
62
- ```
63
-
64
- ## CDN
65
- To work properly with a CDN, a "Vary Accept" header should be added when serving images. This is a declaration that the response varies with the *Accept* header (recall that we inspect *Accept* header in the .htaccess to determine if the browsers supports webp images). If this header is missing, the CDN will see no reason to cache separate images depending on the Accept header.
66
-
67
- Add this snippet to the *.htaccess* to make webp-on-demand work with CDN's:
68
-
69
- ```
70
- <IfModule mod_headers.c>
71
- SetEnvIf Request_URI "\.(jpe?g|png)" ADDVARY
72
-
73
- # Declare that the response varies depending on the accept header.
74
- # The purpose is to make CDN cache both original images and converted images.
75
- Header append "Vary" "Accept" env=ADDVARY
76
- </IfModule>
77
- ```
78
-
79
- ***Note:*** When configuring the CDN, you must make sure to set it up to forward the the "Accept" header to your origin server.
80
-
81
-
82
-
83
- ## Make .htaccess route directly to existing images
84
-
85
- There may be a performance benefit of using the *.htaccess* file to route to already converted images, instead of letting the PHP script serve it. Note however:
86
- - If you do the routing in .htaccess, the solution will not be able to discard converted images when original images are updated.
87
- - Performance benefit may be insignificant (*WebPConvertAndServe* class is not autoloaded when serving existing images)
88
-
89
- Add the following to the *.htaccess* to make it route to existing converted images. Place it above the # Redirect images to webp-on-demand.php" comment. Take care of replacing [[your-base-path]] with the directory your *.htaccess* lives in (relative to document root, and [[your-destination-root]] with the directory the converted images resides.
90
- ```
91
- # Redirect to existing converted image (under appropriate circumstances)
92
- RewriteCond %{HTTP_ACCEPT} image/webp
93
- RewriteCond %{DOCUMENT_ROOT}/[[your-base-path]]/[[your-destination-root]]/$1.$2.webp -f
94
- RewriteRule ^\/?(.*)\.(jpe?g|png)$ /[[your-base-path]]/[[your-destination-root]]/$1.$2.webp [NC,T=image/webp,L]
95
- ```
96
- *edit:* Removed the QSD flag from the RewriteRule because it is not supported in Apache < 2.4 (and it [triggers error](https://github.com/rosell-dk/webp-express/issues/155))
97
-
98
- Note however that DOCUMENT_ROOT can be unreliable.
99
-
100
- If you store the webp images in the same folder as the originals and append ".webp" (rather than replace the file extension), you can do this instead:
101
-
102
- ```
103
- # Redirect to existing converted image (under appropriate circumstances)
104
- RewriteCond %{HTTP_ACCEPT} image/webp
105
- RewriteCond %{REQUEST_FILENAME}.webp -f
106
- RewriteRule ^/?(.+)\.(jpe?g|png)$ $1.$2.webp [T=image/webp,L]
107
- ```
108
-
109
-
110
- RewriteCond %{REQUEST_FILENAME}.webp -f
111
-
112
- ### Redirect with CDN support
113
- If you are using a CDN, and want to redirect to existing images with the .htaccess, it is a good idea to add a "Vary Accept" header. This instructs the CDN that the response varies with the *Accept* header (we do not need to do that when routing to webp-on-demand.php, because the script takes care of adding this header, when appropriate.)
114
-
115
- You can achieve redirect with CDN support with the following rules:
116
- ```
117
- <IfModule mod_rewrite.c>
118
-
119
- RewriteEngine On
120
-
121
- # Redirect to existing converted image (under appropriate circumstances)
122
- RewriteCond %{HTTP_ACCEPT} image/webp
123
- RewriteCond %{DOCUMENT_ROOT}/[[your-base-path]]/[[your-destination-root]]/$1.$2.webp -f
124
- RewriteRule ^\/?(.*)\.(jpe?g|png)$ /[[your-base-path]]/[[your-destination-root]]/$1.$2.webp [NC,T=image/webp,QSD,E=WEBPACCEPT:1,L]
125
-
126
- # Redirect images to webp-on-demand.php (if browser supports webp)
127
- RewriteCond %{HTTP_ACCEPT} image/webp
128
- RewriteRule ^(.*)\.(jpe?g|png)$ webp-on-demand.php?source=%{SCRIPT_FILENAME}&url-path=$1.$2 [NC,L]
129
-
130
- </IfModule>
131
-
132
- <IfModule mod_headers.c>
133
- # Apache appends "REDIRECT_" in front of the environment variables, but LiteSpeed does not.
134
- # These next line is for Apache, in order to set environment variables without "REDIRECT_"
135
- SetEnvIf REDIRECT_WEBPACCEPT 1 WEBPACCEPT=1
136
-
137
- # Make CDN caching possible.
138
- # The effect is that the CDN will cache both the webp image and the jpeg/png image and return the proper
139
- # image to the proper clients (for this to work, make sure to set up CDN to forward the "Accept" header)
140
- Header append Vary Accept env=WEBPACCEPT
141
- </IfModule>
142
-
143
- AddType image/webp .webp
144
- ```
145
-
146
- ## Forward the querystring
147
- By forwarding the query string, you can allow control directly from the URL. You could for example make it possible to add "?debug" to an image URL, and thereby getting a conversion report. Or make "?reconvert" force reconversion.
148
-
149
- In order to forward the query string, you need to add this condition before the RewriteRule that redirects to *webp-on-demand.php*:
150
- ```
151
- RewriteCond %{QUERY_STRING} (.*)
152
- ```
153
- That condition will always be met. The side effect is that it stores the match (the complete querystring). That match will be available as %1 in the RewriteRule. So, in the RewriteRule, we will have to add "&%1" after the last argument. Here is a complete solution:
154
- ```
155
- <IfModule mod_rewrite.c>
156
- RewriteEngine On
157
-
158
- # Redirect images to webp-on-demand.php (if browser supports webp)
159
- RewriteCond %{HTTP_ACCEPT} image/webp
160
- RewriteCond %{QUERY_STRING} (.*)
161
- RewriteRule ^(.*)\.(jpe?g|png)$ webp-on-demand.php?source=%{SCRIPT_FILENAME}&%1 [NC,L]
162
- </IfModule>
163
-
164
- AddType image/webp .webp
165
- ```
166
-
167
- Of course, in order to *do* something with that querystring, you must use them in your *webp-on-demand.php* script. You could for example use them directly in the options array sent to the *convertAndServe()* method. To achieve the mentioned "debug" and "reconvert" features, do this:
168
- ```php
169
- $options = [
170
- 'show-report' => isset($_GET['debug']),
171
- 'reconvert' => isset($_GET['reconvert']),
172
- 'serve-original' => isset($_GET['original']),
173
- ];
174
- ```
175
-
176
- *EDIT:*
177
- I have just discovered a simpler way to achieve the querystring forward: The [QSA flag](https://httpd.apache.org/docs/trunk/rewrite/flags.html).
178
- So, simply set the QSA flag in the RewriteRule, and nothing more:
179
- ```
180
- RewriteRule ^(.*)\.(jpe?g|png)$ webp-on-demand.php?source=%{SCRIPT_FILENAME} [NC,QSA,L]
181
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v2.0/webp-on-demand/webp-on-demand.md DELETED
@@ -1,145 +0,0 @@
1
- # WebP on demand
2
-
3
- This is a solution for automatically serving WebP images instead of jpeg/pngs [for browsers that supports WebP](https://caniuse.com/#feat=webp) (At the time of writing, 78% of all mobile users and 72% of all desktop users uses browsers supporting webp)
4
-
5
- Once set up, it will automatically convert images, no matter how they are referenced. It for example also works on images referenced in CSS. As the solution does not require any change in the HTML, it can easily be integrated into any website / framework
6
-
7
- ## Overview
8
-
9
- A setup consists of a PHP script that serves converted images and some *redirect rules* that redirects JPG/PNG images to the script.
10
-
11
-
12
- ## Requirements
13
-
14
- * *Apache* or *LiteSpeed* web server. Can be made to work with *NGINX* as well. Documentation is on the roadmap.
15
- * *mod_rewrite* module for Apache
16
- * PHP >= 5.6 (we are only testing down to 5.6. It should however work in 5.5 as well)
17
- * That one of the *webp-convert* converters are working (these have different requirements)
18
-
19
- ## Installation
20
-
21
- Here we assume you are using Composer. [Not using composer? - Follow me!](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/webp-on-demand/without-composer.md)
22
-
23
- ### 1. Require the webp-convert library with composer
24
- ```
25
- composer require rosell-dk/webp-convert
26
- ```
27
-
28
- ### 2. Create the script
29
-
30
- Create a file *webp-on-demand.php*, and place it in webroot, or where-ever you like in you web-application.
31
-
32
- Here is a minimal example to get started with:
33
-
34
- ```php
35
- <?php
36
- // To start with, lets display any errors.
37
- // - this will reveal if you entered wrong paths
38
- error_reporting(E_ALL);
39
- ini_set("display_errors", 1);
40
-
41
- // Once you got it working, make sure that PHP warnings are not send to the output
42
- // - this will corrupt the image
43
- // For example, you can do it by commenting out the lines below:
44
- // error_reporting(0);
45
- // ini_set("display_errors", 0);
46
-
47
- require 'vendor/autoload.php'; // Make sure to point this correctly
48
-
49
- use WebPConvert\WebPConvert;
50
-
51
- $source = $_GET['source']; // Absolute file path to source file. Comes from the .htaccess
52
- $destination = $source . '.webp'; // Store the converted images besides the original images (other options are available!)
53
-
54
- $options = [
55
-
56
- // UNCOMMENT NEXT LINE, WHEN YOU ARE UP AND RUNNING!
57
- 'show-report' => true // Show a conversion report instead of serving the converted image.
58
-
59
- // More options available!
60
- // https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md
61
- // https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/serving/introduction-for-serving.md
62
- ];
63
- WebPConvert::serveConverted($source, $destination, $options);
64
- ```
65
-
66
- ### 3. Add redirect rules
67
- Place the following rewrite rules in a *.htaccess* file in the directory where you want the solution to take effect:
68
-
69
- ```
70
- <IfModule mod_rewrite.c>
71
- RewriteEngine On
72
-
73
- # Redirect images to webp-on-demand.php (if browser supports webp)
74
- RewriteCond %{HTTP_ACCEPT} image/webp
75
- RewriteCond %{REQUEST_FILENAME} -f
76
- RewriteRule ^(.*)\.(jpe?g|png)$ webp-on-demand.php?source=%{SCRIPT_FILENAME} [NC,L]
77
- </IfModule>
78
-
79
- AddType image/webp .webp
80
- ```
81
- If you have placed *webp-on-demand.php* in a subfolder, you will need to change the rewrite rule accordingly.
82
-
83
- The `RewriteCond %{REQUEST_FILENAME} -f` is not strictly necessary, but there to be sure that we got an existing file, and it could perhaps also prevent some undiscovered way of misuse.
84
-
85
- ### 4. Validate that it works
86
-
87
- Browse to a JPEG image. Instead of an image, you should see a conversion report. Hopefully, you get a success. Otherwise, you need to hook up to a cloud converter or try to meet the requirements for cwebp, gd or imagick.
88
-
89
- Once you get a successful conversion, you can uncomment the "show-report" option in the script.
90
-
91
- It should work now, but to be absolute sure:
92
-
93
- - Visit a page on your site with an image on it, using *Google Chrome*.
94
- - Right-click the page and choose "Inspect"
95
- - Click the "Network" tab
96
- - Reload the page
97
- - Find a jpeg or png image in the list. In the "type" column, it should say "webp". There should also be a *X-WebP-Convert-Status* header on the image that provides some insights on how things went.
98
-
99
-
100
- ### 5. Try this improvement and see if it works
101
-
102
- It seems that it is not necessary to pass the filename in the query string.
103
-
104
- Try replacing `$source = $_GET['source'];` in the script with the following:
105
-
106
- ```php
107
- $docRoot = rtrim($_SERVER["DOCUMENT_ROOT"], '/');
108
- $requestUriNoQS = explode('?', $_SERVER['REQUEST_URI'])[0];
109
- $source = $docRoot . urldecode($requestUriNoQS);
110
- ```
111
-
112
- And you can then remove `?source=%{SCRIPT_FILENAME}` from the `.htaccess` file.
113
-
114
- There are some benefits of not passing in query string:
115
- 1. Passing a path in the query string may be blocked by a firewall, as it looks suspicious.
116
- 2. The script called to convert arbitrary files
117
- 3. One person experienced problems with spaces in filenames passed in the query string. See [this issue](https://github.com/rosell-dk/webp-convert/issues/95)
118
-
119
-
120
- ### 6. Customizing and tweaking
121
-
122
- Basic customizing is done by setting options in the `$options` array. Check out the [docs on convert()](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/convert.md) and the [docs on convertAndServe()](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/serving/convert-and-serve.md)
123
-
124
- Other tweaking is described in *docs/webp-on-demand/tweaks.md*:
125
- - [Store converted images in separate folder](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/webp-on-demand/tweaks.md#store-converted-images-in-separate-folder)
126
- - [CDN](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/webp-on-demand/tweaks.md#cdn)
127
- - [Make .htaccess route directly to existing images](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/webp-on-demand/tweaks.md#make-htaccess-route-directly-to-existing-images)
128
- - [Forward the query string](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/webp-on-demand/tweaks.md#forward-the-querystring)
129
-
130
-
131
- ## Troubleshooting
132
-
133
- ### The redirect rule doesn't seem to be working
134
- If images are neither routed to the converter or a 404, it means that the redirect rule isn't taking effect. Common reasons for this includes:
135
-
136
- - Perhaps there are other rules in your *.htaccess* that interfere with the rules?
137
- - Perhaps your site is on *Apache*, but it has been configured to use *Nginx* to serve image files. To find out which server that is handling the images, browse to an image and eximine the "Server" response header. In case *NGINX* are serving images, see if you can reconfigure your server setup. Alternatively, you can create *NGINX* rewrite rules. There are some [here](https://github.com/S1SYPHOS/kirby-webp#nginx) and [there](https://github.com/uhop/grunt-tight-sprite/wiki/Recipe:-serve-WebP-with-nginx-conditionally).
138
- - Perhaps the server isn't configured to allow *.htaccess* files? Try inserting rubbish in the top of the *.htaccess* file and refresh. You should now see an *Internal Server Error* error page. If you don't, your *.htaccess* file is ignored. Probably you will need to set *AllowOverride All* in your Virtual Host. [Look here for more help](
139
- https://docs.bolt.cm/3.4/howto/making-sure-htaccess-works#test-if-htaccess-is-working)
140
- - Perhaps the Apache *mod_rewrite* extension isn't enabled? Try removing both `<IfModule mod_rewrite.c>` and `</IfModule>` lines: if you get an *Internal Server Error* error page after this change, it's probably that it's indeed not enabled.
141
-
142
-
143
- ## Related
144
- * https://www.maxcdn.com/blog/how-to-reduce-image-size-with-webp-automagically/
145
- * https://www.digitalocean.com/community/tutorials/how-to-create-and-serve-webp-images-to-speed-up-your-website
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/docs/v2.0/webp-on-demand/without-composer.md DELETED
@@ -1,58 +0,0 @@
1
- # WebP On Demand without composer
2
-
3
- For your convenience, the library has been cooked down to two files: *webp-on-demand-1.inc* and *webp-on-demand-2.inc*. The second one is loaded when the first one decides it needs to do a conversion (and not simply serve existing image).
4
-
5
- ## Installing
6
-
7
- ### 1. Copy the latest build files into your website
8
- The build files are distributed [here](https://github.com/rosell-dk/webp-convert-concat/tree/master/build). Open the "latest" folder and copy *webp-on-demand-1.inc* and *webp-on-demand-2.inc* into your website. They can be located wherever you like.
9
-
10
- ### 2. Create a *webp-on-demand.php*
11
-
12
- Create a file *webp-on-demand.php*, and place it in webroot, or where-ever you like in you web-application.
13
-
14
- Here is a minimal example to get started with:
15
-
16
- ```php
17
- <?php
18
- // To start with, lets display any errors.
19
- // - this will reveal if you entered wrong paths
20
- error_reporting(E_ALL);
21
- ini_set("display_errors", 1);
22
-
23
- // Once you got it working, make sure that PHP warnings are not send to the output
24
- // - this will corrupt the image
25
- // For example, you can do it by commenting out the lines below:
26
- // error_reporting(0);
27
- // ini_set("display_errors", 0);
28
-
29
- use WebPConvert\WebPConvert;
30
-
31
- require 'webp-on-demand-1.inc';
32
-
33
- function webpconvert_autoloader($class) {
34
- if (strpos($class, 'WebPConvert\\') === 0) {
35
- require_once __DIR__ . '/webp-on-demand-2.inc';
36
- }
37
- }
38
- spl_autoload_register('webpconvert_autoloader', true, true);
39
-
40
- $source = $_GET['source']; // Absolute file path to source file. Comes from the .htaccess
41
- $destination = $source . '.webp'; // Store the converted images besides the original images (other options are available!)
42
-
43
- $options = [
44
-
45
- // UNCOMMENT NEXT LINE, WHEN YOU ARE UP AND RUNNING!
46
- 'show-report' => true // Show a conversion report instead of serving the converted image.
47
-
48
- // More options available!
49
- // https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md
50
- // https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/serving/introduction-for-serving.md
51
- ];
52
- WebPConvert::serveConverted($source, $destination, $options);
53
- ```
54
-
55
- Note that the procedure has changed in 2.0. In 1.x, the library supported a `require-for-conversion` option, but this option has been removed in 2.0. It was not really needed, as the example above illustrates.
56
-
57
- ### 3. Continue the regular install instructions from step 3
58
- [Click here to continue...](https://github.com/rosell-dk/webp-on-demand#3-add-redirect-rules)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/phpunit-41.xml.dist ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+
3
+ <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+ xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
5
+ backupGlobals="false"
6
+ backupStaticAttributes="false"
7
+ colors="true"
8
+ convertErrorsToExceptions="true"
9
+ convertNoticesToExceptions="true"
10
+ convertWarningsToExceptions="false"
11
+ processIsolation="false"
12
+ stopOnFailure="false"
13
+ bootstrap="vendor/autoload.php"
14
+
15
+ >
16
+ <testsuites>
17
+ <testsuite name="WebPConvert Test Suite">
18
+ <directory>./tests/</directory>
19
+ </testsuite>
20
+ </testsuites>
21
+
22
+ <filter>
23
+ <whitelist>
24
+ <directory suffix=".php">src/</directory>
25
+ <exclude>
26
+ <directory>./vendor</directory>
27
+ <directory>./tests</directory>
28
+ </exclude>
29
+ </whitelist>
30
+ </filter>
31
+
32
+ <logging>
33
+ <log type="junit" target="build/report.junit.xml"/>
34
+ <log type="coverage-clover" target="coverage.clover"/>
35
+ <log type="coverage-text" target="build/coverage.txt"/>
36
+ <log type="coverage-html" target="build/coverage"/>
37
+ </logging>
38
+
39
+ </phpunit>
vendor/rosell-dk/webp-convert/src/Convert/Converters/AbstractConverter.php CHANGED
@@ -106,8 +106,11 @@ abstract class AbstractConverter
106
  * @param array $options (optional) options for conversion
107
  * @param BaseLogger $logger (optional)
108
  */
109
- final public function __construct($source, $destination, $options = [], $logger = null)
110
  {
 
 
 
111
  InputValidator::checkSourceAndDestination($source, $destination);
112
 
113
  $this->source = $source;
@@ -117,7 +120,7 @@ abstract class AbstractConverter
117
  $this->setProvidedOptions($options);
118
 
119
  if (!isset($this->options['_skip_input_check'])) {
120
- $this->log('WebP Convert 2.3.2', 'italic');
121
  $this->logLn(' ignited.');
122
  $this->logLn('- PHP version: ' . phpversion());
123
  if (isset($_SERVER['SERVER_SOFTWARE'])) {
106
  * @param array $options (optional) options for conversion
107
  * @param BaseLogger $logger (optional)
108
  */
109
+ final public function __construct($source = '', $destination = '', $options = [], $logger = null)
110
  {
111
+ if ($source == '') {
112
+ return;
113
+ }
114
  InputValidator::checkSourceAndDestination($source, $destination);
115
 
116
  $this->source = $source;
120
  $this->setProvidedOptions($options);
121
 
122
  if (!isset($this->options['_skip_input_check'])) {
123
+ $this->log('WebP Convert 2.6.0', 'italic');
124
  $this->logLn(' ignited.');
125
  $this->logLn('- PHP version: ' . phpversion());
126
  if (isset($_SERVER['SERVER_SOFTWARE'])) {
vendor/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/AutoQualityTrait.php CHANGED
@@ -92,33 +92,71 @@ trait AutoQualityTrait
92
  $options = $this->options;
93
  $source = $this->source;
94
 
 
 
 
 
 
 
 
 
95
  $q = $options['quality'];
96
  if ($q == 'auto') {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  if (($this->/** @scrutinizer ignore-call */getMimeTypeOfSource() == 'image/jpeg')) {
 
 
 
 
98
  $q = JpegQualityDetector::detectQualityOfJpg($source);
99
  if (is_null($q)) {
100
- $q = $options['default-quality'];
101
  $this->/** @scrutinizer ignore-call */logLn(
102
- 'Quality of source could not be established (Imagick or GraphicsMagick is required)' .
103
- ' - Using default instead (' . $options['default-quality'] . ').'
104
  );
105
 
106
  $this->qualityCouldNotBeDetected = true;
107
  } else {
108
- if ($q > $options['max-quality']) {
 
 
 
109
  $this->logLn(
110
- 'Quality of source is ' . $q . '. ' .
111
- 'This is higher than max-quality, so using max-quality instead (' .
112
- $options['max-quality'] . ')'
113
  );
114
  } else {
115
- $this->logLn('Quality set to same as source: ' . $q);
 
 
 
 
116
  }
117
  }
118
  $q = min($q, $options['max-quality']);
119
  } else {
120
- //$q = $options['default-quality'];
121
- $q = min($options['default-quality'], $options['max-quality']);
122
  $this->logLn('Quality: ' . $q . '. ');
123
  }
124
  } else {
@@ -127,7 +165,7 @@ trait AutoQualityTrait
127
  );
128
  if (($this->getMimeTypeOfSource() == 'image/jpeg')) {
129
  $this->logLn(
130
- 'Consider setting quality to "auto" instead. It is generally a better idea'
131
  );
132
  }
133
  }
92
  $options = $this->options;
93
  $source = $this->source;
94
 
95
+ /*
96
+ Mapping from old options to new options:
97
+ quality: "auto", max-quality: 85, default-quality: 75
98
+ becomes: quality: 85, auto-limit: true
99
+
100
+ quality: 80
101
+ becomes: quality: 80, auto-limit: false
102
+ */
103
  $q = $options['quality'];
104
  if ($q == 'auto') {
105
+ $q = $options['quality'] = $options['max-quality'];
106
+ $this->logLn(
107
+ '*Setting "quality" to "auto" is deprecated. ' .
108
+ 'Instead, set "quality" to a number (0-100) and "auto-limit" to true. '
109
+ );
110
+ $this->logLn(
111
+ '*"quality" has been set to: ' . $options['max-quality'] . ' (took the value of "max-quality").*'
112
+ );
113
+ if (!$this->options2->getOptionById('auto-limit')->isValueExplicitlySet()) {
114
+ $options['auto-limit'] = true;
115
+ $this->logLn(
116
+ '*"auto-limit" has been set to: true."*'
117
+ );
118
+ } else {
119
+ $this->logLn(
120
+ '*PS: "auto-limit" is set to false, as it was set explicitly to false in the options."*'
121
+ );
122
+ }
123
+ }
124
+
125
+ if ($options['auto-limit']) {
126
  if (($this->/** @scrutinizer ignore-call */getMimeTypeOfSource() == 'image/jpeg')) {
127
+ $this->logLn('Running auto-limit');
128
+ $this->logLn(
129
+ 'Quality setting: ' . $q . '. '
130
+ );
131
  $q = JpegQualityDetector::detectQualityOfJpg($source);
132
  if (is_null($q)) {
133
+ $q = $options['quality'];
134
  $this->/** @scrutinizer ignore-call */logLn(
135
+ 'Quality of source image could not be established (Imagick or GraphicsMagick is required). ' .
136
+ 'Sorry, no auto-limit functionality for you. Using supplied quality (' . $q . ').'
137
  );
138
 
139
  $this->qualityCouldNotBeDetected = true;
140
  } else {
141
+ $this->logLn(
142
+ 'Quality of jpeg: ' . $q . '. '
143
+ );
144
+ if ($q < $options['quality']) {
145
  $this->logLn(
146
+ 'Auto-limit result: ' . $q . ' ' .
147
+ '(limiting applied).'
 
148
  );
149
  } else {
150
+ $q = $options['quality'];
151
+ $this->logLn(
152
+ 'Auto-limit result: ' . $q . ' ' .
153
+ '(no limiting needed this time).'
154
+ );
155
  }
156
  }
157
  $q = min($q, $options['max-quality']);
158
  } else {
159
+ $this->logLn('Bypassing auto-limit (it is only active for jpegs)');
 
160
  $this->logLn('Quality: ' . $q . '. ');
161
  }
162
  } else {
165
  );
166
  if (($this->getMimeTypeOfSource() == 'image/jpeg')) {
167
  $this->logLn(
168
+ 'Consider enabling "auto-limit" option. This will prevent unnecessary high quality'
169
  );
170
  }
171
  }
vendor/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/OptionsTrait.php CHANGED
@@ -34,7 +34,7 @@ trait OptionsTrait
34
  abstract public function logLn($msg, $style = '');
35
  abstract protected function getMimeTypeOfSource();
36
 
37
- /** @var array Provided conversion options */
38
  public $providedOptions;
39
 
40
  /** @var array Calculated conversion options (merge of default options and provided options)*/
@@ -43,39 +43,76 @@ trait OptionsTrait
43
  /** @var Options */
44
  protected $options2;
45
 
46
-
47
  /**
48
- * Create options.
 
49
  *
50
- * The options created here will be available to all converters.
51
- * Individual converters may add options by overriding this method.
52
  *
53
- * @return void
54
  */
55
- protected function createOptions()
56
  {
57
- $isPng = ($this->getMimeTypeOfSource() == 'image/png');
58
 
59
- $this->options2 = new Options();
60
- $this->options2->addOptions(
 
 
 
 
 
61
  new IntegerOption('alpha-quality', 85, 0, 100),
 
 
62
  new BooleanOption('auto-filter', false),
63
- new IntegerOption('default-quality', ($isPng ? 85 : 75), 0, 100),
64
  new StringOption('encoding', 'auto', ['lossy', 'lossless', 'auto']),
65
  new BooleanOption('low-memory', false),
66
  new BooleanOption('log-call-arguments', false),
67
- new IntegerOption('max-quality', 85, 0, 100),
68
  new MetadataOption('metadata', 'none'),
69
  new IntegerOption('method', 6, 0, 6),
70
  new IntegerOption('near-lossless', 60, 0, 100),
71
  new StringOption('preset', 'none', ['none', 'default', 'photo', 'picture', 'drawing', 'icon', 'text']),
72
- new QualityOption('quality', ($isPng ? 85 : 'auto')),
73
  new IntegerOrNullOption('size-in-percentage', null, 0, 100),
 
74
  new BooleanOption('skip', false),
75
  new BooleanOption('use-nice', false),
76
  new ArrayOption('jpeg', []),
77
  new ArrayOption('png', [])
78
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  }
80
 
81
  /**
@@ -91,7 +128,8 @@ trait OptionsTrait
91
  */
92
  public function setProvidedOptions($providedOptions = [])
93
  {
94
- $this->createOptions();
 
95
 
96
  $this->providedOptions = $providedOptions;
97
 
@@ -269,4 +307,14 @@ trait OptionsTrait
269
  {
270
  return [];
271
  }
 
 
 
 
 
 
 
 
 
 
272
  }
34
  abstract public function logLn($msg, $style = '');
35
  abstract protected function getMimeTypeOfSource();
36
 
37
+ /** @var array Provided conversion options (array of simple objects)*/
38
  public $providedOptions;
39
 
40
  /** @var array Calculated conversion options (merge of default options and provided options)*/
43
  /** @var Options */
44
  protected $options2;
45
 
 
46
  /**
47
+ * Get the "general" options (options that are standard in the meaning that they
48
+ * are generally available (unless specifically marked as unsupported by a given converter)
49
  *
50
+ * @param string $imageType (png | jpeg) The image type - determines the defaults
 
51
  *
52
+ * @return array Array of options
53
  */
54
+ public function getGeneralOptions($imageType)
55
  {
56
+ $isPng = ($imageType == 'png');
57
 
58
+ $defaultQualityOption = new IntegerOption('default-quality', ($isPng ? 85 : 75), 0, 100);
59
+ $defaultQualityOption->markDeprecated();
60
+
61
+ $maxQualityOption = new IntegerOption('max-quality', 85, 0, 100);
62
+ $maxQualityOption->markDeprecated();
63
+
64
+ return [
65
  new IntegerOption('alpha-quality', 85, 0, 100),
66
+ new BooleanOption('auto-limit', true),
67
+ //new IntegerOption('auto-limit-adjustment', 5, -100, 100),
68
  new BooleanOption('auto-filter', false),
69
+ $defaultQualityOption,
70
  new StringOption('encoding', 'auto', ['lossy', 'lossless', 'auto']),
71
  new BooleanOption('low-memory', false),
72
  new BooleanOption('log-call-arguments', false),
73
+ $maxQualityOption,
74
  new MetadataOption('metadata', 'none'),
75
  new IntegerOption('method', 6, 0, 6),
76
  new IntegerOption('near-lossless', 60, 0, 100),
77
  new StringOption('preset', 'none', ['none', 'default', 'photo', 'picture', 'drawing', 'icon', 'text']),
78
+ new QualityOption('quality', ($isPng ? 85 : 75)),
79
  new IntegerOrNullOption('size-in-percentage', null, 0, 100),
80
+ new BooleanOption('sharp-yuv', true),
81
  new BooleanOption('skip', false),
82
  new BooleanOption('use-nice', false),
83
  new ArrayOption('jpeg', []),
84
  new ArrayOption('png', [])
85
+ ];
86
+ }
87
+
88
+ /**
89
+ * Get the unique options for a converter
90
+ *
91
+ * @param string $imageType (png | jpeg) The image type - determines the defaults
92
+ *
93
+ * @return array Array of options
94
+ */
95
+ public function getUniqueOptions($imageType)
96
+ {
97
+ return [];
98
+ }
99
+
100
+
101
+ /**
102
+ * Create options.
103
+ *
104
+ * The options created here will be available to all converters.
105
+ * Individual converters may add options by overriding this method.
106
+ *
107
+ * @param string $imageType (png | jpeg) The image type - determines the defaults
108
+ *
109
+ * @return void
110
+ */
111
+ protected function createOptions($imageType = 'png')
112
+ {
113
+ $this->options2 = new Options();
114
+ $this->options2->addOptions(... $this->getGeneralOptions($imageType));
115
+ $this->options2->addOptions(... $this->getUniqueOptions($imageType));
116
  }
117
 
118
  /**
128
  */
129
  public function setProvidedOptions($providedOptions = [])
130
  {
131
+ $imageType = ($this->getMimeTypeOfSource() == 'image/png' ? 'png' : 'jpeg');
132
+ $this->createOptions($imageType);
133
 
134
  $this->providedOptions = $providedOptions;
135
 
307
  {
308
  return [];
309
  }
310
+
311
+ /*
312
+ public static function getUniqueOptions($imageType = 'png')
313
+ {
314
+ $options = new Options();
315
+ // $options->addOptions(... self::getGeneralOptions($imageType));
316
+ // $options->addOptions(... self::getUniqueOptions($imageType));
317
+
318
+ return $options->getDefinitions();
319
+ }*/
320
  }
vendor/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/WarningLoggerTrait.php CHANGED
@@ -31,14 +31,18 @@ trait WarningLoggerTrait
31
  * The function is a callback used with "set_error_handler".
32
  * It is declared public because it needs to be accessible from the point where the warning is triggered.
33
  *
 
 
 
34
  * @param integer $errno
35
  * @param string $errstr
36
  * @param string $errfile
37
  * @param integer $errline
 
38
  *
39
  * @return false|null|void
40
  */
41
- public function warningHandler($errno, $errstr, $errfile, $errline)
42
  {
43
  /*
44
  We do NOT do the following (even though it is generally recommended):
@@ -113,7 +117,7 @@ trait WarningLoggerTrait
113
  ) {
114
  return false;
115
  } else {
116
- return call_user_func($this->previousErrorHandler, $errno, $errstr, $errfile, $errline);
117
  }
118
  } else {
119
  return false;
31
  * The function is a callback used with "set_error_handler".
32
  * It is declared public because it needs to be accessible from the point where the warning is triggered.
33
  *
34
+ * PS: The fifth parameter ($errcontext) of an error handler is deprecated since PHP 7.2, however we have
35
+ * it here to avoid calling another error handler with too few parameters (see #266)
36
+ *
37
  * @param integer $errno
38
  * @param string $errstr
39
  * @param string $errfile
40
  * @param integer $errline
41
+ * @param array $errcontext
42
  *
43
  * @return false|null|void
44
  */
45
+ public function warningHandler($errno, $errstr, $errfile, $errline, $errcontext = null)
46
  {
47
  /*
48
  We do NOT do the following (even though it is generally recommended):
117
  ) {
118
  return false;
119
  } else {
120
+ return call_user_func($this->previousErrorHandler, $errno, $errstr, $errfile, $errline, $errcontext);
121
  }
122
  } else {
123
  return false;
vendor/rosell-dk/webp-convert/src/Convert/Converters/Binaries/cwebp-120-linux-x86-64 ADDED
Binary file
vendor/rosell-dk/webp-convert/src/Convert/Converters/Binaries/cwebp-120-windows-x64.exe ADDED
Binary file
vendor/rosell-dk/webp-convert/src/Convert/Converters/ConverterTraits/CurlTrait.php CHANGED
@@ -58,12 +58,12 @@ trait CurlTrait
58
  * Init curl.
59
  *
60
  * @throws SystemRequirementsNotMetException if curl could not be initialized
61
- * @return resource curl handle
62
  */
63
  protected static function initCurl()
64
  {
65
  // Get curl handle
66
- $ch = curl_init();
67
  if ($ch === false) {
68
  throw new SystemRequirementsNotMetException('Could not initialise cURL.');
69
  }
58
  * Init curl.
59
  *
60
  * @throws SystemRequirementsNotMetException if curl could not be initialized
61
+ * @return resource|\CurlHandle curl handle (from PHP8: CurlHandle)
62
  */
63
  protected static function initCurl()
64
  {
65
  // Get curl handle
66
+ $ch = \curl_init();
67
  if ($ch === false) {
68
  throw new SystemRequirementsNotMetException('Could not initialise cURL.');
69
  }
vendor/rosell-dk/webp-convert/src/Convert/Converters/Cwebp.php CHANGED
@@ -32,20 +32,27 @@ class Cwebp extends AbstractConverter
32
  return [];
33
  }
34
 
35
- protected function createOptions()
 
 
 
 
 
36
  {
37
- parent::createOptions();
38
-
39
- $this->options2->addOptions(
40
- new StringOption('command-line-options', ''),
41
- new SensitiveStringOption('rel-path-to-precompiled-binaries', './Binaries'),
42
  new BooleanOption('try-cwebp', true),
43
  new BooleanOption('try-common-system-paths', true),
44
  new BooleanOption('try-discovering-cwebp', true),
45
- new BooleanOption('try-supplied-binary-for-os', true)
46
- );
 
 
 
47
  }
48
 
 
 
 
49
  // OS-specific binaries included in this library, along with hashes
50
  // If other binaries are going to be added, notice that the first argument is what PHP_OS returns.
51
  // (possible values, see here: https://stackoverflow.com/questions/738823/possible-values-for-php-os)
@@ -55,32 +62,45 @@ class Cwebp extends AbstractConverter
55
  // 2: Set permission to 775. 755 causes unzipping to fail on some hosts
56
  private static $suppliedBinariesInfo = [
57
  'WINNT' => [
58
- ['cwebp-110-windows-x64.exe', '442682869402f92ad2c8b3186c02b0ea6d6da68d2f908df38bf905b3411eb9fb'],
 
 
 
59
  ],
60
  'Darwin' => [
61
- ['cwebp-110-mac-10_15', 'bfce742da09b959f9f2929ba808fed9ade25c8025530434b6a47d217a6d2ceb5'],
62
  ],
63
  'SunOS' => [
64
  // Got this from ewww Wordpress plugin, which unfortunately still uses the old 0.6.0 versions
65
  // Can you help me get a 1.0.3 version?
66
- ['cwebp-060-solaris', '1febaffbb18e52dc2c524cda9eefd00c6db95bc388732868999c0f48deb73b4f']
67
  ],
68
  'FreeBSD' => [
69
  // Got this from ewww Wordpress plugin, which unfortunately still uses the old 0.6.0 versions
70
  // Can you help me get a 1.0.3 version?
71
- ['cwebp-060-fbsd', 'e5cbea11c97fadffe221fdf57c093c19af2737e4bbd2cb3cd5e908de64286573']
72
  ],
73
  'Linux' => [
74
- // Dynamically linked executable.
75
- // It seems it is slightly faster than the statically linked
76
- ['cwebp-110-linux-x86-64', '1603b07b592876dd9fdaa62b44aead800234c9474ff26dc7dd01bc0f4785c9c6'],
 
 
 
 
 
 
77
 
78
  // Statically linked executable
79
  // It may be that it on some systems works, where the dynamically linked does not (see #196)
80
- ['cwebp-103-linux-x86-64-static', 'ab96f01b49336da8b976c498528080ff614112d5985da69943b48e0cb1c5228a'],
81
-
82
- // Old executable for systems in case both of the above fails
83
- ['cwebp-061-linux-x86-64', '916623e5e9183237c851374d969aebdb96e0edc0692ab7937b95ea67dc3b2568'],
 
 
 
 
84
  ]
85
  ];
86
 
@@ -129,13 +149,20 @@ class Cwebp extends AbstractConverter
129
  {
130
  //$version = $this->detectVersion($binary);
131
 
 
 
 
132
  $command = ($useNice ? 'nice ' : '') . $binary . ' ' . $commandOptions . ' 2>&1';
133
 
134
  //$logger->logLn('command options:' . $commandOptions);
135
  $this->logLn('Trying to convert by executing the following command:');
 
 
136
  $this->logLn($command);
137
  exec($command, $output, $returnCode);
138
  $this->logExecOutput($output);
 
 
139
  /*
140
  if ($returnCode == 255) {
141
  if (isset($output[0])) {
@@ -232,12 +259,14 @@ class Cwebp extends AbstractConverter
232
  // Comma-separated list of existing metadata to copy from input to output
233
  if ($versionNum >= 0.3) {
234
  $cmdOptions[] = '-metadata ' . $options['metadata'];
 
 
235
  }
236
 
237
  // preset. Appears first in the list as recommended in the docs
238
  if (!is_null($options['preset'])) {
239
  if ($options['preset'] != 'none') {
240
- $cmdOptions[] = '-preset ' . $options['preset'];
241
  }
242
  }
243
 
@@ -273,15 +302,10 @@ class Cwebp extends AbstractConverter
273
  // Near-lossles
274
  if ($options['near-lossless'] !== 100) {
275
  if ($versionNum < 0.5) {
276
- $this->logLn(
277
- 'The near-lossless option is not supported on this (rather old) version of cwebp' .
278
- '- skipping it.',
279
- 'italic'
280
- );
281
  } else {
282
  // We only let near_lossless have effect when encoding is set to "lossless"
283
  // otherwise encoding=auto would not work as expected
284
-
285
  if ($options['encoding'] == 'lossless') {
286
  $cmdOptions[] = '-near_lossless ' . $options['near-lossless'];
287
  } else {
@@ -292,10 +316,21 @@ class Cwebp extends AbstractConverter
292
  }
293
  }
294
 
 
295
  if ($options['auto-filter'] === true) {
296
  $cmdOptions[] = '-af';
297
  }
298
 
 
 
 
 
 
 
 
 
 
 
299
  // Built-in method option
300
  $cmdOptions[] = '-m ' . strval($options['method']);
301
 
@@ -324,22 +359,54 @@ class Cwebp extends AbstractConverter
324
  // Output
325
  $cmdOptions[] = '-o ' . escapeshellarg($this->destination);
326
 
327
- // Redirect stderr to same place as stdout
328
- // https://www.brianstorti.com/understanding-shell-script-idiom-redirect/
329
- $cmdOptions[] = '2>&1';
330
-
331
  $commandOptions = implode(' ', $cmdOptions);
332
  //$this->logLn('command line options:' . $commandOptions);
333
 
334
  return $commandOptions;
335
  }
336
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  /**
338
- * Get path for supplied binary for current OS - and validate hash.
 
339
  *
340
- * @return array Array of supplied binaries (which actually exists, and where hash validates)
 
 
341
  */
342
- private function getSuppliedBinaryPathForOS()
343
  {
344
  $this->log('Checking if we have a supplied precompiled binary for your OS (' . PHP_OS . ')... ');
345
 
@@ -350,7 +417,8 @@ class Cwebp extends AbstractConverter
350
  return [];
351
  }
352
 
353
- $result = [];
 
354
  $files = self::$suppliedBinariesInfo[PHP_OS];
355
  if (count($files) == 1) {
356
  $this->logLn('We do.');
@@ -358,7 +426,11 @@ class Cwebp extends AbstractConverter
358
  $this->logLn('We do. We in fact have ' . count($files));
359
  }
360
 
361
- foreach ($files as $i => list($file, $hash)) {
 
 
 
 
362
  //$file = $info[0];
363
  //$hash = $info[1];
364
 
@@ -372,6 +444,11 @@ class Cwebp extends AbstractConverter
372
  $this->logLn('Supplied binary not found! It ought to be here:' . $binaryFile, 'italic');
373
  return false;
374
  }*/
 
 
 
 
 
375
 
376
  $realPathResult = realpath($binaryFile);
377
  if ($realPathResult === false) {
@@ -379,30 +456,10 @@ class Cwebp extends AbstractConverter
379
  continue;
380
  }
381
  $binaryFile = $realPathResult;
382
-
383
- // File exists, now generate its hash
384
- // hash_file() is normally available, but it is not always
385
- // - https://stackoverflow.com/questions/17382712/php-5-3-20-undefined-function-hash
386
- // If available, validate that hash is correct.
387
-
388
- if (function_exists('hash_file')) {
389
- $binaryHash = hash_file('sha256', $binaryFile);
390
-
391
- if ($binaryHash != $hash) {
392
- $this->logLn(
393
- 'Binary checksum of supplied binary is invalid! ' .
394
- 'Did you transfer with FTP, but not in binary mode? ' .
395
- 'File:' . $binaryFile . '. ' .
396
- 'Expected checksum: ' . $hash . '. ' .
397
- 'Actual checksum:' . $binaryHash . '.',
398
- 'bold'
399
- );
400
- continue;
401
- }
402
- }
403
- $result[] = $binaryFile;
404
  }
405
- return $result;
406
  }
407
 
408
  private function who()
@@ -478,12 +535,12 @@ class Cwebp extends AbstractConverter
478
  return ['detected' => $binariesWithVersions, 'failed' => $binariesWithFailCodes];
479
  }
480
 
481
- private function logBinariesFound($binaries)
482
  {
483
  if (count($binaries) == 0) {
484
- $this->logLn('Found 0 binaries');
485
  } else {
486
- $this->logLn('Found ' . count($binaries) . ' binaries: ');
487
  foreach ($binaries as $binary) {
488
  $this->logLn('- ' . $binary);
489
  }
@@ -505,11 +562,56 @@ class Cwebp extends AbstractConverter
505
  }
506
  }
507
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
508
  private function discoverCwebpBinaries()
509
  {
510
  $this->logLn(
511
  'Looking for cwebp binaries.'
512
  );
 
 
 
513
  $binaries = [];
514
 
515
  if (defined('WEBPCONVERT_CWEBP_PATH')) {
@@ -524,15 +626,17 @@ class Cwebp extends AbstractConverter
524
  }
525
 
526
  if ($this->options['try-cwebp']) {
 
527
  $this->logLn(
528
  'Discovering if a plain cwebp call works (to skip this step, disable the "try-cwebp" option)'
529
  );
530
  $result = $this->detectVersion('cwebp');
531
  if (gettype($result) == 'string') {
532
- $this->logLn('We could get the version, so yes, a plain cwebp call works');
 
533
  $binaries[] = 'cwebp';
534
  } else {
535
- $this->logLn('Nope a plain cwebp call does not work');
536
  }
537
  } else {
538
  $this->logLn(
@@ -542,30 +646,38 @@ class Cwebp extends AbstractConverter
542
  }
543
 
544
  // try-discovering-cwebp
 
545
  $this->logDiscoverAction('try-discovering-cwebp', 'using "which -a cwebp" command.');
546
  if ($this->options['try-discovering-cwebp']) {
547
  $moreBinaries = BinaryDiscovery::discoverInstalledBinaries('cwebp');
548
- $this->logBinariesFound($moreBinaries);
549
  $binaries = array_merge($binaries, $moreBinaries);
550
  }
551
 
552
  // 'try-common-system-paths'
 
553
  $this->logDiscoverAction('try-common-system-paths', 'by peeking in common system paths');
554
  if ($this->options['try-common-system-paths']) {
555
  $moreBinaries = BinaryDiscovery::discoverInCommonSystemPaths('cwebp');
556
- $this->logBinariesFound($moreBinaries);
557
  $binaries = array_merge($binaries, $moreBinaries);
558
  }
559
 
560
  // try-supplied-binary-for-os
 
 
561
  $this->logDiscoverAction('try-supplied-binary-for-os', 'which are distributed with the webp-convert library');
562
  if ($this->options['try-supplied-binary-for-os']) {
563
- $moreBinaries = $this->getSuppliedBinaryPathForOS();
564
- $this->logBinariesFound($moreBinaries);
565
- $binaries = array_merge($binaries, $moreBinaries);
 
566
  }
567
 
568
- return array_values(array_unique($binaries));
 
 
 
569
  }
570
 
571
  /**
@@ -609,6 +721,7 @@ class Cwebp extends AbstractConverter
609
  */
610
  private function composeMeaningfullErrorMessageNoVersionsWorking($versions)
611
  {
 
612
 
613
  // PS: array_values() is used to reindex
614
  $uniqueFailCodes = array_values(array_unique(array_values($versions['failed'])));
@@ -645,8 +758,12 @@ class Cwebp extends AbstractConverter
645
 
646
  protected function doActualConvert()
647
  {
648
- $binaries = $this->discoverCwebpBinaries();
649
- if (count($binaries) == 0) {
 
 
 
 
650
  $this->logLn('No cwebp binaries found!');
651
 
652
  $discoverOptions = [
@@ -672,19 +789,45 @@ class Cwebp extends AbstractConverter
672
  );
673
  }
674
  }
675
- $this->logLn(
676
- 'Detecting versions of the cwebp binaries found'
677
- );
678
- $versions = $this->detectVersions($binaries);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
679
 
680
  $binaryVersions = $versions['detected'];
681
- if (count($binaryVersions) == 0) {
682
- // No working cwebp binaries found.
683
 
684
  throw new SystemRequirementsNotMetException(
685
  $this->composeMeaningfullErrorMessageNoVersionsWorking($versions)
686
  );
687
- }
688
 
689
  // Sort binaries so those with highest numbers comes first
690
  arsort($binaryVersions);
@@ -697,14 +840,27 @@ class Cwebp extends AbstractConverter
697
 
698
  // Execute!
699
  $this->logLn(
700
- 'Trying the first of these. If that should fail (it should not), the next will be tried and so on.'
 
701
  );
702
  $useNice = (($this->options['use-nice']) && self::hasNiceSupport());
703
  $success = false;
704
  foreach ($binaryVersions as $binary => $version) {
 
 
 
 
 
705
  if ($this->tryCwebpBinary($binary, $version, $useNice)) {
706
  $success = true;
707
  break;
 
 
 
 
 
 
 
708
  }
709
  }
710
 
32
  return [];
33
  }
34
 
35
+ /**
36
+ * Get the options unique for this converter
37
+ *
38
+ * @return array Array of options
39
+ */
40
+ public function getUniqueOptions($imageType)
41
  {
42
+ return [
 
 
 
 
43
  new BooleanOption('try-cwebp', true),
44
  new BooleanOption('try-common-system-paths', true),
45
  new BooleanOption('try-discovering-cwebp', true),
46
+ new BooleanOption('try-supplied-binary-for-os', true),
47
+ new StringOption('command-line-options', ''),
48
+ new SensitiveStringOption('rel-path-to-precompiled-binaries', './Binaries'),
49
+ new StringOption('skip-these-precompiled-binaries', '')
50
+ ];
51
  }
52
 
53
+
54
+
55
+
56
  // OS-specific binaries included in this library, along with hashes
57
  // If other binaries are going to be added, notice that the first argument is what PHP_OS returns.
58
  // (possible values, see here: https://stackoverflow.com/questions/738823/possible-values-for-php-os)
62
  // 2: Set permission to 775. 755 causes unzipping to fail on some hosts
63
  private static $suppliedBinariesInfo = [
64
  'WINNT' => [
65
+ ['cwebp-120-windows-x64.exe', '2849fd06012a9eb311b02a4f8918ae4b16775693bc21e95f4cc6a382eac299f9', '1.2.0'],
66
+
67
+ // Keep the 1.1.0 version a while, in case some may have problems with the 1.2.0 version
68
+ ['cwebp-110-windows-x64.exe', '442682869402f92ad2c8b3186c02b0ea6d6da68d2f908df38bf905b3411eb9fb', '1.1.0'],
69
  ],
70
  'Darwin' => [
71
+ ['cwebp-110-mac-10_15', 'bfce742da09b959f9f2929ba808fed9ade25c8025530434b6a47d217a6d2ceb5', '1.1.0'],
72
  ],
73
  'SunOS' => [
74
  // Got this from ewww Wordpress plugin, which unfortunately still uses the old 0.6.0 versions
75
  // Can you help me get a 1.0.3 version?
76
+ ['cwebp-060-solaris', '1febaffbb18e52dc2c524cda9eefd00c6db95bc388732868999c0f48deb73b4f', '0.6.0']
77
  ],
78
  'FreeBSD' => [
79
  // Got this from ewww Wordpress plugin, which unfortunately still uses the old 0.6.0 versions
80
  // Can you help me get a 1.0.3 version?
81
+ ['cwebp-060-fbsd', 'e5cbea11c97fadffe221fdf57c093c19af2737e4bbd2cb3cd5e908de64286573', '0.6.0']
82
  ],
83
  'Linux' => [
84
+
85
+ // PS: Some experience the following error with 1.20:
86
+ // /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.29' not found
87
+ // (see #278)
88
+
89
+ ['cwebp-120-linux-x86-64', 'f1b7dc03e95535a6b65852de07c0404be4dba078af48369f434ee39b2abf8f4e', '1.2.0'],
90
+
91
+ // As some experience the an error with 1.20 (see #278), we keep the 1.10
92
+ ['cwebp-110-linux-x86-64', '1603b07b592876dd9fdaa62b44aead800234c9474ff26dc7dd01bc0f4785c9c6', '1.1.0'],
93
 
94
  // Statically linked executable
95
  // It may be that it on some systems works, where the dynamically linked does not (see #196)
96
+ [
97
+ 'cwebp-103-linux-x86-64-static',
98
+ 'ab96f01b49336da8b976c498528080ff614112d5985da69943b48e0cb1c5228a',
99
+ '1.0.3'
100
+ ],
101
+
102
+ // Old executable for systems in case all of the above fails
103
+ ['cwebp-061-linux-x86-64', '916623e5e9183237c851374d969aebdb96e0edc0692ab7937b95ea67dc3b2568', '0.6.1'],
104
  ]
105
  ];
106
 
149
  {
150
  //$version = $this->detectVersion($binary);
151
 
152
+ // Redirect stderr to same place as stdout with "2>&1"
153
+ // https://www.brianstorti.com/understanding-shell-script-idiom-redirect/
154
+
155
  $command = ($useNice ? 'nice ' : '') . $binary . ' ' . $commandOptions . ' 2>&1';
156
 
157
  //$logger->logLn('command options:' . $commandOptions);
158
  $this->logLn('Trying to convert by executing the following command:');
159
+ $startExecuteBinaryTime = self::startTimer();
160
+ ;
161
  $this->logLn($command);
162
  exec($command, $output, $returnCode);
163
  $this->logExecOutput($output);
164
+ $this->logTimeSpent($startExecuteBinaryTime, 'Executing cwebp binary took: ');
165
+ $this->logLn('');
166
  /*
167
  if ($returnCode == 255) {
168
  if (isset($output[0])) {
259
  // Comma-separated list of existing metadata to copy from input to output
260
  if ($versionNum >= 0.3) {
261
  $cmdOptions[] = '-metadata ' . $options['metadata'];
262
+ } else {
263
+ $this->logLn('Ignoring metadata option (requires cwebp 0.3)', 'italic');
264
  }
265
 
266
  // preset. Appears first in the list as recommended in the docs
267
  if (!is_null($options['preset'])) {
268
  if ($options['preset'] != 'none') {
269
+ $cmdOptions[] = '-preset ' . escapeshellarg($options['preset']);
270
  }
271
  }
272
 
302
  // Near-lossles
303
  if ($options['near-lossless'] !== 100) {
304
  if ($versionNum < 0.5) {
305
+ $this->logLn('Ignoring near-lossless option (requires cwebp 0.5)', 'italic');
 
 
 
 
306
  } else {
307
  // We only let near_lossless have effect when encoding is set to "lossless"
308
  // otherwise encoding=auto would not work as expected
 
309
  if ($options['encoding'] == 'lossless') {
310
  $cmdOptions[] = '-near_lossless ' . $options['near-lossless'];
311
  } else {
316
  }
317
  }
318
 
319
+ // Autofilter
320
  if ($options['auto-filter'] === true) {
321
  $cmdOptions[] = '-af';
322
  }
323
 
324
+ // SharpYUV
325
+ if ($options['sharp-yuv'] === true) {
326
+ if ($versionNum >= 0.6) { // #284
327
+ $cmdOptions[] = '-sharp_yuv';
328
+ } else {
329
+ $this->logLn('Ignoring sharp-yuv option (requires cwebp 0.6)', 'italic');
330
+ }
331
+ }
332
+
333
+
334
  // Built-in method option
335
  $cmdOptions[] = '-m ' . strval($options['method']);
336
 
359
  // Output
360
  $cmdOptions[] = '-o ' . escapeshellarg($this->destination);
361
 
 
 
 
 
362
  $commandOptions = implode(' ', $cmdOptions);
363
  //$this->logLn('command line options:' . $commandOptions);
364
 
365
  return $commandOptions;
366
  }
367
 
368
+ private function checkHashForSuppliedBinary($binaryFile, $hash)
369
+ {
370
+ // File exists, now generate its hash
371
+ // hash_file() is normally available, but it is not always
372
+ // - https://stackoverflow.com/questions/17382712/php-5-3-20-undefined-function-hash
373
+ // If available, validate that hash is correct.
374
+
375
+ if (function_exists('hash_file')) {
376
+ $this->logLn(
377
+ 'Checking checksum for supplied binary: ' . $binaryFile
378
+ );
379
+ $startHashCheckTime = self::startTimer();
380
+
381
+ $binaryHash = hash_file('sha256', $binaryFile);
382
+
383
+ if ($binaryHash != $hash) {
384
+ $this->logLn(
385
+ 'Binary checksum of supplied binary is invalid! ' .
386
+ 'Did you transfer with FTP, but not in binary mode? ' .
387
+ 'File:' . $binaryFile . '. ' .
388
+ 'Expected checksum: ' . $hash . '. ' .
389
+ 'Actual checksum:' . $binaryHash . '.',
390
+ 'bold'
391
+ );
392
+ return false;
393
+ ;
394
+ }
395
+
396
+ $this->logTimeSpent($startHashCheckTime, 'Checksum test took: ');
397
+ }
398
+ return true;
399
+ }
400
+
401
  /**
402
+ * Get supplied binary info for current OS.
403
+ * paths are made absolute and checked. Missing are removed
404
  *
405
+ * @return array Two arrays.
406
+ * First array: array of files (absolute paths)
407
+ * Second array: array of info objects (absolute path, hash and version)
408
  */
409
+ private function getSuppliedBinaryInfoForCurrentOS()
410
  {
411
  $this->log('Checking if we have a supplied precompiled binary for your OS (' . PHP_OS . ')... ');
412
 
417
  return [];
418
  }
419
 
420
+ $filesFound = [];
421
+ $info = [];
422
  $files = self::$suppliedBinariesInfo[PHP_OS];
423
  if (count($files) == 1) {
424
  $this->logLn('We do.');
426
  $this->logLn('We do. We in fact have ' . count($files));
427
  }
428
 
429
+ $skipThese = explode(',', $this->options['skip-these-precompiled-binaries']);
430
+
431
+ //$this->logLn('However, skipping' . print_r($skipThese, true));
432
+
433
+ foreach ($files as $i => list($file, $hash, $version)) {
434
  //$file = $info[0];
435
  //$hash = $info[1];
436
 
444
  $this->logLn('Supplied binary not found! It ought to be here:' . $binaryFile, 'italic');
445
  return false;
446
  }*/
447
+ if (in_array($file, $skipThese)) {
448
+ $this->logLn('Skipped: ' . $file . ' (was told to in the "skip-these-precompiled-binaries" option)');
449
+ continue;
450
+ }
451
+
452
 
453
  $realPathResult = realpath($binaryFile);
454
  if ($realPathResult === false) {
456
  continue;
457
  }
458
  $binaryFile = $realPathResult;
459
+ $filesFound[] = $realPathResult;
460
+ $info[] = [$realPathResult, $hash, $version, $file];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
  }
462
+ return [$filesFound, $info];
463
  }
464
 
465
  private function who()
535
  return ['detected' => $binariesWithVersions, 'failed' => $binariesWithFailCodes];
536
  }
537
 
538
+ private function logBinariesFound($binaries, $startTime)
539
  {
540
  if (count($binaries) == 0) {
541
+ $this->logLn('Found 0 binaries' . self::getTimeStr($startTime));
542
  } else {
543
+ $this->logLn('Found ' . count($binaries) . ' binaries' . self::getTimeStr($startTime));
544
  foreach ($binaries as $binary) {
545
  $this->logLn('- ' . $binary);
546
  }
562
  }
563
  }
564
 
565
+ private static function startTimer()
566
+ {
567
+ if (function_exists('microtime')) {
568
+ return microtime(true);
569
+ } else {
570
+ return 0;
571
+ }
572
+ }
573
+
574
+ private static function readTimer($startTime)
575
+ {
576
+ if (function_exists('microtime')) {
577
+ $endTime = microtime(true);
578
+ $seconds = ($endTime - $startTime);
579
+ return round(($seconds * 1000));
580
+ } else {
581
+ return 0;
582
+ }
583
+ }
584
+
585
+ private static function getTimeStr($startTime, $pre = ' (spent ', $post = ')')
586
+ {
587
+ if (function_exists('microtime')) {
588
+ $ms = self::readTimer($startTime);
589
+ return $pre . $ms . ' ms' . $post;
590
+ }
591
+ return '';
592
+ }
593
+
594
+ private function logTimeSpent($startTime, $pre = 'Spent: ')
595
+ {
596
+ if (function_exists('microtime')) {
597
+ $ms = self::readTimer($startTime);
598
+ $this->logLn($pre . $ms . ' ms');
599
+ }
600
+ }
601
+
602
+ /**
603
+ * @return array Two arrays (in an array).
604
+ * First array: binaries found,
605
+ * Second array: supplied binaries info for current OS
606
+ */
607
  private function discoverCwebpBinaries()
608
  {
609
  $this->logLn(
610
  'Looking for cwebp binaries.'
611
  );
612
+
613
+ $startDiscoveryTime = self::startTimer();
614
+
615
  $binaries = [];
616
 
617
  if (defined('WEBPCONVERT_CWEBP_PATH')) {
626
  }
627
 
628
  if ($this->options['try-cwebp']) {
629
+ $startTime = self::startTimer();
630
  $this->logLn(
631
  'Discovering if a plain cwebp call works (to skip this step, disable the "try-cwebp" option)'
632
  );
633
  $result = $this->detectVersion('cwebp');
634
  if (gettype($result) == 'string') {
635
+ $this->logLn('We could get the version, so yes, a plain cwebp call works ' .
636
+ '(spent ' . self::readTimer($startTime) . ' ms)');
637
  $binaries[] = 'cwebp';
638
  } else {
639
+ $this->logLn('Nope a plain cwebp call does not work' . self::getTimeStr($startTime));
640
  }
641
  } else {
642
  $this->logLn(
646
  }
647
 
648
  // try-discovering-cwebp
649
+ $startTime = self::startTimer();
650
  $this->logDiscoverAction('try-discovering-cwebp', 'using "which -a cwebp" command.');
651
  if ($this->options['try-discovering-cwebp']) {
652
  $moreBinaries = BinaryDiscovery::discoverInstalledBinaries('cwebp');
653
+ $this->logBinariesFound($moreBinaries, $startTime);
654
  $binaries = array_merge($binaries, $moreBinaries);
655
  }
656
 
657
  // 'try-common-system-paths'
658
+ $startTime = self::startTimer();
659
  $this->logDiscoverAction('try-common-system-paths', 'by peeking in common system paths');
660
  if ($this->options['try-common-system-paths']) {
661
  $moreBinaries = BinaryDiscovery::discoverInCommonSystemPaths('cwebp');
662
+ $this->logBinariesFound($moreBinaries, $startTime);
663
  $binaries = array_merge($binaries, $moreBinaries);
664
  }
665
 
666
  // try-supplied-binary-for-os
667
+ $suppliedBinariesInfo = [[], []];
668
+ $startTime = self::startTimer();
669
  $this->logDiscoverAction('try-supplied-binary-for-os', 'which are distributed with the webp-convert library');
670
  if ($this->options['try-supplied-binary-for-os']) {
671
+ $suppliedBinariesInfo = $this->getSuppliedBinaryInfoForCurrentOS();
672
+ $moreBinaries = $suppliedBinariesInfo[0];
673
+ $this->logBinariesFound($moreBinaries, $startTime);
674
+ //$binaries = array_merge($binaries, $moreBinaries);
675
  }
676
 
677
+ $this->logTimeSpent($startDiscoveryTime, 'Discovering cwebp binaries took: ');
678
+ $this->logLn('');
679
+
680
+ return [array_values(array_unique($binaries)), $suppliedBinariesInfo];
681
  }
682
 
683
  /**
721
  */
722
  private function composeMeaningfullErrorMessageNoVersionsWorking($versions)
723
  {
724
+ // TODO: Take "supplied" into account
725
 
726
  // PS: array_values() is used to reindex
727
  $uniqueFailCodes = array_values(array_unique(array_values($versions['failed'])));
758
 
759
  protected function doActualConvert()
760
  {
761
+ list($foundBinaries, $suppliedBinariesInfo) = $this->discoverCwebpBinaries();
762
+ $suppliedBinaries = $suppliedBinariesInfo[0];
763
+ $allBinaries = array_merge($foundBinaries, $suppliedBinaries);
764
+
765
+ //$binaries = $this->discoverCwebpBinaries();
766
+ if (count($allBinaries) == 0) {
767
  $this->logLn('No cwebp binaries found!');
768
 
769
  $discoverOptions = [
789
  );
790
  }
791
  }
792
+
793
+ $detectedVersions = [];
794
+ if (count($foundBinaries) > 0) {
795
+ $this->logLn(
796
+ 'Detecting versions of the cwebp binaries found' .
797
+ (count($suppliedBinaries) > 0 ? ' (except supplied binaries)' : '.')
798
+ );
799
+ $startDetectionTime = self::startTimer();
800
+ $versions = $this->detectVersions($foundBinaries);
801
+ $detectedVersions = $versions['detected'];
802
+
803
+ $this->logTimeSpent($startDetectionTime, 'Detecting versions took: ');
804
+ }
805
+
806
+ //$suppliedVersions = [];
807
+ $suppliedBinariesHash = [];
808
+ $suppliedBinariesFilename = [];
809
+
810
+ $binaryVersions = $detectedVersions;
811
+ foreach ($suppliedBinariesInfo[1] as list($path, $hash, $version, $filename)) {
812
+ $binaryVersions[$path] = $version;
813
+ $suppliedBinariesHash[$path] = $hash;
814
+ $suppliedBinariesFilename[$path] = $filename;
815
+ }
816
+
817
+ //$binaryVersions = array_merge($detectedVersions, $suppliedBinariesInfo);
818
+
819
+ // TODO: reimplement
820
+ /*
821
+ $versions['supplied'] = $suppliedBinariesInfo;
822
 
823
  $binaryVersions = $versions['detected'];
824
+ if ((count($binaryVersions) == 0) && (count($suppliedBinaries) == 0)) {
825
+ // No working cwebp binaries found, no supplied binaries found
826
 
827
  throw new SystemRequirementsNotMetException(
828
  $this->composeMeaningfullErrorMessageNoVersionsWorking($versions)
829
  );
830
+ }*/
831
 
832
  // Sort binaries so those with highest numbers comes first
833
  arsort($binaryVersions);
840
 
841
  // Execute!
842
  $this->logLn(
843
+ 'Starting conversion, using the first of these. If that should fail, ' .
844
+ 'the next will be tried and so on.'
845
  );
846
  $useNice = (($this->options['use-nice']) && self::hasNiceSupport());
847
  $success = false;
848
  foreach ($binaryVersions as $binary => $version) {
849
+ if (isset($suppliedBinariesHash[$binary])) {
850
+ if (!$this->checkHashForSuppliedBinary($binary, $suppliedBinariesHash[$binary])) {
851
+ continue;
852
+ }
853
+ }
854
  if ($this->tryCwebpBinary($binary, $version, $useNice)) {
855
  $success = true;
856
  break;
857
+ } else {
858
+ if (isset($suppliedBinariesFilename[$binary])) {
859
+ $this->logLn(
860
+ 'Note: You can prevent trying this precompiled binary, by setting the ' .
861
+ '"skip-these-precompiled-binaries" option to "' . $suppliedBinariesFilename[$binary] . '"'
862
+ );
863
+ }
864
  }
865
  }
866
 
vendor/rosell-dk/webp-convert/src/Convert/Converters/Ewww.php CHANGED
@@ -27,6 +27,14 @@ class Ewww extends AbstractConverter
27
  /** @var array Array of invalid or exceeded api keys discovered during conversions (during the request) */
28
  public static $nonFunctionalApiKeysDiscoveredDuringConversion;
29
 
 
 
 
 
 
 
 
 
30
  protected function getUnsupportedDefaultOptions()
31
  {
32
  return [
@@ -37,21 +45,12 @@ class Ewww extends AbstractConverter
37
  'method',
38
  'near-lossless',
39
  'preset',
 
40
  'size-in-percentage',
41
  'use-nice'
42
  ];
43
  }
44
 
45
- protected function createOptions()
46
- {
47
- parent::createOptions();
48
-
49
- $this->options2->addOptions(
50
- new SensitiveStringOption('api-key', ''),
51
- new BooleanOption('check-key-status-before-converting', true)
52
- );
53
- }
54
-
55
  /**
56
  * Get api key from options or environment variable
57
  *
27
  /** @var array Array of invalid or exceeded api keys discovered during conversions (during the request) */
28
  public static $nonFunctionalApiKeysDiscoveredDuringConversion;
29
 
30
+ public function getUniqueOptions($imageType)
31
+ {
32
+ return [
33
+ new SensitiveStringOption('api-key', ''),
34
+ new BooleanOption('check-key-status-before-converting', true)
35
+ ];
36
+ }
37
+
38
  protected function getUnsupportedDefaultOptions()
39
  {
40
  return [
45
  'method',
46
  'near-lossless',
47
  'preset',
48
+ 'sharp-yuv',
49
  'size-in-percentage',
50
  'use-nice'
51
  ];
52
  }
53
 
 
 
 
 
 
 
 
 
 
 
54
  /**
55
  * Get api key from options or environment variable
56
  *
vendor/rosell-dk/webp-convert/src/Convert/Converters/FFMpeg.php CHANGED
@@ -31,6 +31,7 @@ class FFMpeg extends AbstractConverter
31
  'low-memory',
32
  'near-lossless',
33
  'preset',
 
34
  'size-in-percentage',
35
  'use-nice'
36
  ];
31
  'low-memory',
32
  'near-lossless',
33
  'preset',
34
+ 'sharp-yuv',
35
  'size-in-percentage',
36
  'use-nice'
37
  ];
vendor/rosell-dk/webp-convert/src/Convert/Converters/Gd.php CHANGED
@@ -32,6 +32,7 @@ class Gd extends AbstractConverter
32
  'method',
33
  'near-lossless',
34
  'preset',
 
35
  'size-in-percentage',
36
  'use-nice'
37
  ];
@@ -106,7 +107,7 @@ class Gd extends AbstractConverter
106
  * So, if the image is already rgb, nothing will be done, and true will be returned
107
  * PS: Got the workaround here: https://secure.php.net/manual/en/function.imagepalettetotruecolor.php
108
  *
109
- * @param resource $image
110
  * @return boolean TRUE if the convertion was complete, or if the source image already is a true color image,
111
  * otherwise FALSE is returned.
112
  */
@@ -166,7 +167,7 @@ class Gd extends AbstractConverter
166
  * Try to convert image pallette to true color. If imagepalettetotruecolor() exists, that is used (available from
167
  * PHP >= 5.5.0). Otherwise using workaround found on the net.
168
  *
169
- * @param resource $image
170
  * @return boolean TRUE if the convertion was complete, or if the source image already is a true color image,
171
  * otherwise FALSE is returned.
172
  */
@@ -185,7 +186,7 @@ class Gd extends AbstractConverter
185
  *
186
  * @throws InvalidInputException if mime type is unsupported or could not be detected
187
  * @throws ConversionFailedException if imagecreatefrompng or imagecreatefromjpeg fails
188
- * @return resource $image The created image
189
  */
190
  private function createImageResource()
191
  {
@@ -217,7 +218,7 @@ class Gd extends AbstractConverter
217
  /**
218
  * Try to make image resource true color if it is not already.
219
  *
220
- * @param resource $image The image to work on
221
  * @return void
222
  */
223
  protected function tryToMakeTrueColorIfNot(&$image)
@@ -249,7 +250,7 @@ class Gd extends AbstractConverter
249
 
250
  /**
251
  *
252
- * @param resource $image
253
  * @return boolean true if alpha blending was set successfully, false otherwise
254
  */
255
  protected function trySettingAlphaBlending($image)
@@ -297,7 +298,7 @@ class Gd extends AbstractConverter
297
 
298
  /**
299
  *
300
- * @param resource $image
301
  * @return void
302
  */
303
  protected function destroyAndRemove($image)
@@ -310,7 +311,7 @@ class Gd extends AbstractConverter
310
 
311
  /**
312
  *
313
- * @param resource $image
314
  * @return void
315
  */
316
  protected function tryConverting($image)
32
  'method',
33
  'near-lossless',
34
  'preset',
35
+ 'sharp-yuv',
36
  'size-in-percentage',
37
  'use-nice'
38
  ];
107
  * So, if the image is already rgb, nothing will be done, and true will be returned
108
  * PS: Got the workaround here: https://secure.php.net/manual/en/function.imagepalettetotruecolor.php
109
  *
110
+ * @param resource|\GdImage $image
111
  * @return boolean TRUE if the convertion was complete, or if the source image already is a true color image,
112
  * otherwise FALSE is returned.
113
  */
167
  * Try to convert image pallette to true color. If imagepalettetotruecolor() exists, that is used (available from
168
  * PHP >= 5.5.0). Otherwise using workaround found on the net.
169
  *
170
+ * @param resource|\GdImage $image
171
  * @return boolean TRUE if the convertion was complete, or if the source image already is a true color image,
172
  * otherwise FALSE is returned.
173
  */
186
  *
187
  * @throws InvalidInputException if mime type is unsupported or could not be detected
188
  * @throws ConversionFailedException if imagecreatefrompng or imagecreatefromjpeg fails
189
+ * @return resource|\GdImage $image The created image
190
  */
191
  private function createImageResource()
192
  {
218
  /**
219
  * Try to make image resource true color if it is not already.
220
  *
221
+ * @param resource|\GdImage $image The image to work on
222
  * @return void
223
  */
224
  protected function tryToMakeTrueColorIfNot(&$image)
250
 
251
  /**
252
  *
253
+ * @param resource|\GdImage $image
254
  * @return boolean true if alpha blending was set successfully, false otherwise
255
  */
256
  protected function trySettingAlphaBlending($image)
298
 
299
  /**
300
  *
301
+ * @param resource|\GdImage $image
302
  * @return void
303
  */
304
  protected function destroyAndRemove($image)
311
 
312
  /**
313
  *
314
+ * @param resource|\GdImage $image
315
  * @return void
316
  */
317
  protected function tryConverting($image)
vendor/rosell-dk/webp-convert/src/Convert/Converters/Gmagick.php CHANGED
@@ -24,7 +24,6 @@ class Gmagick extends AbstractConverter
24
  {
25
  return [
26
  'near-lossless',
27
- 'preset',
28
  'size-in-percentage',
29
  'use-nice'
30
  ];
@@ -91,6 +90,9 @@ class Gmagick extends AbstractConverter
91
  protected function doActualConvert()
92
  {
93
 
 
 
 
94
  $options = $this->options;
95
 
96
  try {
@@ -110,6 +112,23 @@ class Gmagick extends AbstractConverter
110
  // Finally cracked setting webp options.
111
  // See #167
112
  // - and https://stackoverflow.com/questions/47294962/how-to-write-lossless-webp-files-with-perlmagick
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  $im->setimageoption('webp', 'method', $options['method']);
114
  $im->setimageoption('webp', 'lossless', $options['encoding'] == 'lossless' ? 'true' : 'false');
115
  $im->setimageoption('webp', 'alpha-quality', $options['alpha-quality']);
@@ -117,6 +136,10 @@ class Gmagick extends AbstractConverter
117
  if ($options['auto-filter'] === true) {
118
  $im->setimageoption('webp', 'auto-filter', 'true');
119
  }
 
 
 
 
120
  }
121
 
122
  /*
24
  {
25
  return [
26
  'near-lossless',
 
27
  'size-in-percentage',
28
  'use-nice'
29
  ];
90
  protected function doActualConvert()
91
  {
92
 
93
+ // PS: graphicsmagick options are documented here: (search for "webp:")
94
+ // http://www.graphicsmagick.org/GraphicsMagick.html
95
+
96
  $options = $this->options;
97
 
98
  try {
112
  // Finally cracked setting webp options.
113
  // See #167
114
  // - and https://stackoverflow.com/questions/47294962/how-to-write-lossless-webp-files-with-perlmagick
115
+
116
+ if (!is_null($options['preset'])) {
117
+ if ($options['preset'] != 'none') {
118
+ $imageHint = $options['preset'];
119
+ switch ($imageHint) {
120
+ case 'drawing':
121
+ case 'icon':
122
+ case 'text':
123
+ $imageHint = 'graph';
124
+ $this->logLn(
125
+ 'The "preset" value was mapped to "graph" because gmagick does not support "drawing",' .
126
+ ' "icon" and "text", but grouped these into one option: "graph".'
127
+ );
128
+ }
129
+ $im->setimageoption('webp', 'image-hint', $imageHint);
130
+ }
131
+ }
132
  $im->setimageoption('webp', 'method', $options['method']);
133
  $im->setimageoption('webp', 'lossless', $options['encoding'] == 'lossless' ? 'true' : 'false');
134
  $im->setimageoption('webp', 'alpha-quality', $options['alpha-quality']);
136
  if ($options['auto-filter'] === true) {
137
  $im->setimageoption('webp', 'auto-filter', 'true');
138
  }
139
+
140
+ if ($options['sharp-yuv'] === true) {
141
+ $im->setimageoption('webp', 'use-sharp-yuv', 'true');
142
+ }
143
  }
144
 
145
  /*
vendor/rosell-dk/webp-convert/src/Convert/Converters/GraphicsMagick.php CHANGED
@@ -26,9 +26,7 @@ class GraphicsMagick extends AbstractConverter
26
  protected function getUnsupportedDefaultOptions()
27
  {
28
  return [
29
- 'auto-filter',
30
  'near-lossless',
31
- 'preset',
32
  'size-in-percentage',
33
  ];
34
  }
@@ -95,6 +93,10 @@ class GraphicsMagick extends AbstractConverter
95
  */
96
  private function createCommandLineOptions()
97
  {
 
 
 
 
98
  $commandArguments = [];
99
 
100
  /*
@@ -108,9 +110,28 @@ class GraphicsMagick extends AbstractConverter
108
  */
109
  $commandArguments[] = '-quality ' . escapeshellarg($this->getCalculatedQuality());
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
  // encoding
113
- if ($this->options['encoding'] == 'lossless') {
114
  // Btw:
115
  // I am not sure if we should set "quality" for lossless.
116
  // Quality should not apply to lossless, but my tests shows that it does in some way for gmagick
@@ -124,19 +145,27 @@ class GraphicsMagick extends AbstractConverter
124
  $commandArguments[] = '-define webp:lossless=false';
125
  }
126
 
127
- if ($this->options['alpha-quality'] !== 100) {
128
- $commandArguments[] = '-define webp:alpha-quality=' . strval($this->options['alpha-quality']);
129
  }
130
 
131
- if ($this->options['low-memory']) {
 
 
 
 
132
  $commandArguments[] = '-define webp:low-memory=true';
133
  }
134
 
135
- if ($this->options['metadata'] == 'none') {
 
 
 
 
136
  $commandArguments[] = '-strip';
137
  }
138
 
139
- $commandArguments[] = '-define webp:method=' . $this->options['method'];
140
 
141
  $commandArguments[] = escapeshellarg($this->source);
142
  $commandArguments[] = escapeshellarg('webp:' . $this->destination);
26
  protected function getUnsupportedDefaultOptions()
27
  {
28
  return [
 
29
  'near-lossless',
 
30
  'size-in-percentage',
31
  ];
32
  }
93
  */
94
  private function createCommandLineOptions()
95
  {
96
+ // I cannot find any documentation on available webp options for graphicsmagick :(
97
+ // Checking for new supported options is currently done by searching for "webp" in the
98
+ // news page: http://www.graphicsmagick.org/NEWS.html
99
+
100
  $commandArguments = [];
101
 
102
  /*
110
  */
111
  $commandArguments[] = '-quality ' . escapeshellarg($this->getCalculatedQuality());
112
 
113
+ $options = $this->options;
114
+
115
+ // preset
116
+ if (!is_null($options['preset'])) {
117
+ if ($options['preset'] != 'none') {
118
+ $imageHint = $options['preset'];
119
+ switch ($imageHint) {
120
+ case 'drawing':
121
+ case 'icon':
122
+ case 'text':
123
+ $imageHint = 'graph';
124
+ $this->logLn(
125
+ 'Note: the preset was mapped to "graph" because graphicsmagick does not support ' .
126
+ '"drawing", "icon" and "text", but grouped these into one option: "graph".'
127
+ );
128
+ }
129
+ $commandArguments[] = '-define webp:image-hint=' . escapeshellarg($imageHint);
130
+ }
131
+ }
132
 
133
  // encoding
134
+ if ($options['encoding'] == 'lossless') {
135
  // Btw:
136
  // I am not sure if we should set "quality" for lossless.
137
  // Quality should not apply to lossless, but my tests shows that it does in some way for gmagick
145
  $commandArguments[] = '-define webp:lossless=false';
146
  }
147
 
148
+ if ($options['auto-filter'] === true) {
149
+ $commandArguments[] = '-define webp:auto-filter=true';
150
  }
151
 
152
+ if ($options['alpha-quality'] !== 100) {
153
+ $commandArguments[] = '-define webp:alpha-quality=' . strval($options['alpha-quality']);
154
+ }
155
+
156
+ if ($options['low-memory']) {
157
  $commandArguments[] = '-define webp:low-memory=true';
158
  }
159
 
160
+ if ($options['sharp-yuv'] === true) {
161
+ $commandArguments[] = '-define webp:use-sharp-yuv=true';
162
+ }
163
+
164
+ if ($options['metadata'] == 'none') {
165
  $commandArguments[] = '-strip';
166
  }
167
 
168
+ $commandArguments[] = '-define webp:method=' . $options['method'];
169
 
170
  $commandArguments[] = escapeshellarg($this->source);
171
  $commandArguments[] = escapeshellarg('webp:' . $this->destination);
vendor/rosell-dk/webp-convert/src/Convert/Converters/ImageMagick.php CHANGED
@@ -26,7 +26,6 @@ class ImageMagick extends AbstractConverter
26
  {
27
  return [
28
  'near-lossless',
29
- 'preset',
30
  'size-in-percentage',
31
  ];
32
  }
@@ -113,6 +112,9 @@ class ImageMagick extends AbstractConverter
113
  // PS: Available webp options for imagemagick are documented here:
114
  // https://imagemagick.org/script/webp.php
115
 
 
 
 
116
  $commandArguments = [];
117
  if ($this->isQualityDetectionRequiredButFailing()) {
118
  // quality:auto was specified, but could not be determined.
@@ -121,26 +123,48 @@ class ImageMagick extends AbstractConverter
121
  } else {
122
  $commandArguments[] = '-quality ' . escapeshellarg($this->getCalculatedQuality());
123
  }
124
- if ($this->options['encoding'] == 'lossless') {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  $commandArguments[] = '-define webp:lossless=true';
126
  }
127
- if ($this->options['low-memory']) {
128
  $commandArguments[] = '-define webp:low-memory=true';
129
  }
130
- if ($this->options['auto-filter'] === true) {
131
  $commandArguments[] = '-define webp:auto-filter=true';
132
  }
133
- if ($this->options['metadata'] == 'none') {
134
  $commandArguments[] = '-strip';
135
  }
136
- if ($this->options['alpha-quality'] !== 100) {
137
- $commandArguments[] = '-define webp:alpha-quality=' . strval($this->options['alpha-quality']);
 
 
 
138
  }
139
 
140
  // Unfortunately, near-lossless does not seem to be supported.
141
  // it does have a "preprocessing" option, which may be doing something similar
142
 
143
- $commandArguments[] = '-define webp:method=' . $this->options['method'];
144
 
145
  $commandArguments[] = escapeshellarg($this->source);
146
  $commandArguments[] = escapeshellarg('webp:' . $this->destination);
26
  {
27
  return [
28
  'near-lossless',
 
29
  'size-in-percentage',
30
  ];
31
  }
112
  // PS: Available webp options for imagemagick are documented here:
113
  // https://imagemagick.org/script/webp.php
114
 
115
+ // We should perhaps implement low-memory. Its already in cwebp, it
116
+ // could perhaps be promoted to a general option
117
+
118
  $commandArguments = [];
119
  if ($this->isQualityDetectionRequiredButFailing()) {
120
  // quality:auto was specified, but could not be determined.
123
  } else {
124
  $commandArguments[] = '-quality ' . escapeshellarg($this->getCalculatedQuality());
125
  }
126
+
127
+ $options = $this->options;
128
+
129
+ if (!is_null($options['preset'])) {
130
+ if ($options['preset'] != 'none') {
131
+ $imageHint = $options['preset'];
132
+ switch ($imageHint) {
133
+ case 'drawing':
134
+ case 'icon':
135
+ case 'text':
136
+ $imageHint = 'graph';
137
+ $this->logLn(
138
+ 'The "preset" value was mapped to "graph" because imagemagick does not support "drawing",' .
139
+ ' "icon" and "text", but grouped these into one option: "graph".'
140
+ );
141
+ }
142
+ $commandArguments[] = '-define webp:image-hint=' . escapeshellarg($imageHint);
143
+ }
144
+ }
145
+ if ($options['encoding'] == 'lossless') {
146
  $commandArguments[] = '-define webp:lossless=true';
147
  }
148
+ if ($options['low-memory']) {
149
  $commandArguments[] = '-define webp:low-memory=true';
150
  }
151
+ if ($options['auto-filter'] === true) {
152
  $commandArguments[] = '-define webp:auto-filter=true';
153
  }
154
+ if ($options['metadata'] == 'none') {
155
  $commandArguments[] = '-strip';
156
  }
157
+ if ($options['alpha-quality'] !== 100) {
158
+ $commandArguments[] = '-define webp:alpha-quality=' . strval($options['alpha-quality']);
159
+ }
160
+ if ($options['sharp-yuv'] === true) {
161
+ $commandArguments[] = '-define webp:use-sharp-yuv=true';
162
  }
163
 
164
  // Unfortunately, near-lossless does not seem to be supported.
165
  // it does have a "preprocessing" option, which may be doing something similar
166
 
167
+ $commandArguments[] = '-define webp:method=' . $options['method'];
168
 
169
  $commandArguments[] = escapeshellarg($this->source);
170
  $commandArguments[] = escapeshellarg('webp:' . $this->destination);
vendor/rosell-dk/webp-convert/src/Convert/Converters/Imagick.php CHANGED
@@ -25,7 +25,6 @@ class Imagick extends AbstractConverter
25
  {
26
  return [
27
  'near-lossless',
28
- 'preset',
29
  'size-in-percentage',
30
  'use-nice'
31
  ];
@@ -113,6 +112,23 @@ class Imagick extends AbstractConverter
113
 
114
  $im->setImageFormat('WEBP');
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  $im->setOption('webp:method', $options['method']);
117
  $im->setOption('webp:lossless', $options['encoding'] == 'lossless' ? 'true' : 'false');
118
  $im->setOption('webp:low-memory', $options['low-memory'] ? 'true' : 'false');
@@ -122,6 +138,10 @@ class Imagick extends AbstractConverter
122
  $im->setOption('webp:auto-filter', 'true');
123
  }
124
 
 
 
 
 
125
  if ($options['metadata'] == 'none') {
126
  // To strip metadata, we need to use the stripImage() method. However, that method does not only remove
127
  // metadata, but color profiles as well. We want to keep the color profiles, so we grab it now to be able
25
  {
26
  return [
27
  'near-lossless',
 
28
  'size-in-percentage',
29
  'use-nice'
30
  ];
112
 
113
  $im->setImageFormat('WEBP');
114
 
115
+ if (!is_null($options['preset'])) {
116
+ if ($options['preset'] != 'none') {
117
+ $imageHint = $options['preset'];
118
+ switch ($imageHint) {
119
+ case 'drawing':
120
+ case 'icon':
121
+ case 'text':
122
+ $imageHint = 'graph';
123
+ $this->logLn(
124
+ 'The "preset" value was mapped to "graph" because imagick does not support "drawing",' .
125
+ ' "icon" and "text", but grouped these into one option: "graph".'
126
+ );
127
+ }
128
+ $im->setOption('webp:image-hint', $imageHint);
129
+ }
130
+ }
131
+
132
  $im->setOption('webp:method', $options['method']);
133
  $im->setOption('webp:lossless', $options['encoding'] == 'lossless' ? 'true' : 'false');
134
  $im->setOption('webp:low-memory', $options['low-memory'] ? 'true' : 'false');
138
  $im->setOption('webp:auto-filter', 'true');
139
  }
140
 
141
+ if ($options['sharp-yuv'] === true) {
142
+ $im->setOption('webp:use-sharp-yuv', 'true');
143
+ }
144
+
145
  if ($options['metadata'] == 'none') {
146
  // To strip metadata, we need to use the stripImage() method. However, that method does not only remove
147
  // metadata, but color profiles as well. We want to keep the color profiles, so we grab it now to be able
vendor/rosell-dk/webp-convert/src/Convert/Converters/Stack.php CHANGED
@@ -36,6 +36,7 @@ class Stack extends AbstractConverter
36
  'method',
37
  'near-lossless',
38
  'preset',
 
39
  'size-in-percentage',
40
  'use-nice',
41
  'skip',
@@ -45,17 +46,15 @@ class Stack extends AbstractConverter
45
  ];
46
  }
47
 
48
- protected function createOptions()
49
  {
50
- parent::createOptions();
51
-
52
- $this->options2->addOptions(
53
  new SensitiveArrayOption('converters', self::getAvailableConverters()),
54
  new SensitiveArrayOption('converter-options', []),
55
  new BooleanOption('shuffle', false),
56
  new ArrayOption('preferred-converters', []),
57
  new SensitiveArrayOption('extra-converters', [])
58
- );
59
  }
60
 
61
  /**
36
  'method',
37
  'near-lossless',
38
  'preset',
39
+ 'sharp-yuv',
40
  'size-in-percentage',
41
  'use-nice',
42
  'skip',
46
  ];
47
  }
48
 
49
+ public function getUniqueOptions($imageType)
50
  {
51
+ return [
 
 
52
  new SensitiveArrayOption('converters', self::getAvailableConverters()),
53
  new SensitiveArrayOption('converter-options', []),
54
  new BooleanOption('shuffle', false),
55
  new ArrayOption('preferred-converters', []),
56
  new SensitiveArrayOption('extra-converters', [])
57
+ ];
58
  }
59
 
60
  /**
vendor/rosell-dk/webp-convert/src/Convert/Converters/Vips.php CHANGED
@@ -7,6 +7,7 @@ use WebPConvert\Convert\Converters\ConverterTraits\EncodingAutoTrait;
7
  use WebPConvert\Convert\Exceptions\ConversionFailedException;
8
  use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
9
  use WebPConvert\Options\BooleanOption;
 
10
 
11
  //require '/home/rosell/.composer/vendor/autoload.php';
12
 
@@ -24,18 +25,24 @@ class Vips extends AbstractConverter
24
  protected function getUnsupportedDefaultOptions()
25
  {
26
  return [
 
27
  'size-in-percentage',
28
  'use-nice'
29
  ];
30
  }
31
 
32
- protected function createOptions()
 
 
 
 
 
33
  {
34
- parent::createOptions();
35
-
36
- $this->options2->addOptions(
37
- new BooleanOption('smart-subsample', false)
38
- );
39
  }
40
 
41
  /**
@@ -56,8 +63,31 @@ class Vips extends AbstractConverter
56
  );
57
  }
58
 
59
- // TODO: Should we also test if webp is available? (It seems not to be neccessary - it seems
60
- // that webp be well intergrated part of vips)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
62
 
63
  /**
@@ -120,12 +150,11 @@ class Vips extends AbstractConverter
120
  private function createParamsForVipsWebPSave()
121
  {
122
  // webpsave options are described here:
123
- // v 8.8.0: https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-webpsave
124
- // v ?.?.?: https://jcupitt.github.io/libvips/API/current/VipsForeignSave.html#vips-webpsave
125
  // near_lossless option is described here: https://github.com/libvips/libvips/pull/430
126
 
127
- // Note that "method" is currently not supported (27 may 2019)
128
-
129
  $options = [
130
  "Q" => $this->getCalculatedQuality(),
131
  'lossless' => ($this->options['encoding'] == 'lossless'),
@@ -135,8 +164,18 @@ class Vips extends AbstractConverter
135
  // Only set the following options if they differ from the default of vipslib
136
  // This ensures we do not get warning if that property isn't supported
137
  if ($this->options['smart-subsample'] !== false) {
 
 
138
  $options['smart_subsample'] = $this->options['smart-subsample'];
 
 
 
 
139
  }
 
 
 
 
140
  if ($this->options['alpha-quality'] !== 100) {
141
  $options['alpha_q'] = $this->options['alpha-quality'];
142
  }
@@ -162,6 +201,9 @@ class Vips extends AbstractConverter
162
  $options['Q'] = $this->options['near-lossless'];
163
  }
164
  }
 
 
 
165
 
166
  return $options;
167
  }
@@ -178,6 +220,7 @@ class Vips extends AbstractConverter
178
  */
179
  private function webpsave($im, $options)
180
  {
 
181
  $result = /** @scrutinizer ignore-call */ vips_call('webpsave', $im, $this->destination, $options);
182
 
183
  //trigger_error('test-warning', E_USER_WARNING);
@@ -190,16 +233,43 @@ class Vips extends AbstractConverter
190
  } elseif (preg_match("#(.*)\\sunsupported$#", $message, $matches)) {
191
  // Actually, I am not quite sure if this ever happens.
192
  // I got a "near_lossless unsupported" error message in a build, but perhaps it rather a warning
193
- if (in_array($matches[1], ['lossless', 'alpha_q', 'near_lossless', 'smart_subsample'])) {
 
 
 
 
 
 
 
194
  $nameOfPropertyNotFound = $matches[1];
195
  }
196
  }
197
 
198
  if ($nameOfPropertyNotFound != '') {
199
- $this->logLn(
200
- 'Your version of vipslib does not support the "' . $nameOfPropertyNotFound . '" property. ' .
201
- 'The option is ignored.'
202
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  unset($options[$nameOfPropertyNotFound]);
204
  $this->webpsave($im, $options);
205
  } else {
7
  use WebPConvert\Convert\Exceptions\ConversionFailedException;
8
  use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
9
  use WebPConvert\Options\BooleanOption;
10
+ use WebPConvert\Options\IntegerOption;
11
 
12
  //require '/home/rosell/.composer/vendor/autoload.php';
13
 
25
  protected function getUnsupportedDefaultOptions()
26
  {
27
  return [
28
+ 'auto-filter',
29
  'size-in-percentage',
30
  'use-nice'
31
  ];
32
  }
33
 
34
+ /**
35
+ * Get the options unique for this converter
36
+ *
37
+ * @return array Array of options
38
+ */
39
+ public function getUniqueOptions($imageType)
40
  {
41
+ $ssOption = new BooleanOption('smart-subsample', false);
42
+ $ssOption->markDeprecated();
43
+ return [
44
+ $ssOption
45
+ ];
46
  }
47
 
48
  /**
63
  );
64
  }
65
 
66
+ if (!function_exists('vips_call')) {
67
+ throw new SystemRequirementsNotMetException(
68
+ 'Vips extension seems to be installed, however something is not right: ' .
69
+ 'the function "vips_call" is not available.'
70
+ );
71
+ }
72
+
73
+ if (!function_exists('vips_error_buffer')) {
74
+ throw new SystemRequirementsNotMetException(
75
+ 'Vips extension seems to be installed, however something is not right: ' .
76
+ 'the function "vips_error_buffer" is not available.'
77
+ );
78
+ }
79
+
80
+
81
+ /** @scrutinizer ignore-call */ vips_error_buffer(); // clear error buffer
82
+ $result = /** @scrutinizer ignore-call */ vips_call('webpsave', null);
83
+ if ($result == -1) {
84
+ $message = vips_error_buffer();
85
+ if (strpos($message, 'VipsOperation: class "webpsave" not found') === 0) {
86
+ throw new SystemRequirementsNotMetException(
87
+ 'Vips has not been compiled with webp support.'
88
+ );
89
+ }
90
+ }
91
  }
92
 
93
  /**
150
  private function createParamsForVipsWebPSave()
151
  {
152
  // webpsave options are described here:
153
+ // https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-webpsave
 
154
  // near_lossless option is described here: https://github.com/libvips/libvips/pull/430
155
 
156
+ // NOTE: When a new option becomes available, we MUST remember to add
157
+ // it to the array of possibly unsupported options in webpsave() !
158
  $options = [
159
  "Q" => $this->getCalculatedQuality(),
160
  'lossless' => ($this->options['encoding'] == 'lossless'),
164
  // Only set the following options if they differ from the default of vipslib
165
  // This ensures we do not get warning if that property isn't supported
166
  if ($this->options['smart-subsample'] !== false) {
167
+ // PS: The smart-subsample option is now deprecated, as it turned out
168
+ // it was corresponding to the "sharp-yuv" option (see #280)
169
  $options['smart_subsample'] = $this->options['smart-subsample'];
170
+ $this->logLn(
171
+ '*Note: the "smart-subsample" option is now deprecated. It turned out it corresponded to ' .
172
+ 'the general option "sharp-yuv". You should use "sharp-yuv" instead.*'
173
+ );
174
  }
175
+ if ($this->options['sharp-yuv'] !== false) {
176
+ $options['smart_subsample'] = $this->options['sharp-yuv'];
177
+ }
178
+
179
  if ($this->options['alpha-quality'] !== 100) {
180
  $options['alpha_q'] = $this->options['alpha-quality'];
181
  }
201
  $options['Q'] = $this->options['near-lossless'];
202
  }
203
  }
204
+ if ($this->options['method'] !== 4) {
205
+ $options['reduction_effort'] = $this->options['method'];
206
+ }
207
 
208
  return $options;
209
  }
220
  */
221
  private function webpsave($im, $options)
222
  {
223
+ /** @scrutinizer ignore-call */ vips_error_buffer(); // clear error buffer
224
  $result = /** @scrutinizer ignore-call */ vips_call('webpsave', $im, $this->destination, $options);
225
 
226
  //trigger_error('test-warning', E_USER_WARNING);
233
  } elseif (preg_match("#(.*)\\sunsupported$#", $message, $matches)) {
234
  // Actually, I am not quite sure if this ever happens.
235
  // I got a "near_lossless unsupported" error message in a build, but perhaps it rather a warning
236
+ if (in_array($matches[1], [
237
+ 'lossless',
238
+ 'alpha_q',
239
+ 'near_lossless',
240
+ 'smart_subsample',
241
+ 'reduction_effort',
242
+ 'preset'
243
+ ])) {
244
  $nameOfPropertyNotFound = $matches[1];
245
  }
246
  }
247
 
248
  if ($nameOfPropertyNotFound != '') {
249
+ $msg = 'Your version of vipslib does not support the "' . $nameOfPropertyNotFound . '" property';
250
+
251
+ switch ($nameOfPropertyNotFound) {
252
+ case 'alpha_q':
253
+ $msg .= ' (It was introduced in vips 8.4)';
254
+ break;
255
+ case 'near_lossless':
256
+ $msg .= ' (It was introduced in vips 8.4)';
257
+ break;
258
+ case 'smart_subsample':
259
+ $msg .= ' (its the vips equalent to the "sharp-yuv" option. It was introduced in vips 8.4)';
260
+ break;
261
+ case 'reduction_effort':
262
+ $msg .= ' (its the vips equalent to the "method" option. It was introduced in vips 8.8.0)';
263
+ break;
264
+ case 'preset':
265
+ $msg .= ' (It was introduced in vips 8.4)';
266
+ break;
267
+ }
268
+ $msg .= '. The option is ignored.';
269
+
270
+
271
+ $this->logLn($msg);
272
+
273
  unset($options[$nameOfPropertyNotFound]);
274
  $this->webpsave($im, $options);
275
  } else {
vendor/rosell-dk/webp-convert/src/Convert/Converters/Wpc.php CHANGED
@@ -32,6 +32,18 @@ class Wpc extends AbstractConverter
32
  return [];
33
  }
34
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  public function supportsLossless()
36
  {
37
  return ($this->options['api-version'] >= 2);
@@ -44,20 +56,6 @@ class Wpc extends AbstractConverter
44
  return ($this->options['api-version'] >= 2);
45
  }
46
 
47
- protected function createOptions()
48
- {
49
- parent::createOptions();
50
-
51
- $this->options2->addOptions(
52
- new SensitiveStringOption('api-key', ''), /* for communicating with wpc api v.1+ */
53
- new SensitiveStringOption('secret', ''), /* for communicating with wpc api v.0 */
54
- new SensitiveStringOption('api-url', ''),
55
- new SensitiveStringOption('url', ''), /* DO NOT USE. Only here to keep the protection */
56
- new IntegerOption('api-version', 2, 0, 2),
57
- new BooleanOption('crypt-api-key-in-transfer', false) /* new in api v.1 */
58
- );
59
- }
60
-
61
  private static function createRandomSaltForBlowfish()
62
  {
63
  $salt = '';
32
  return [];
33
  }
34
 
35
+ public function getUniqueOptions($imageType)
36
+ {
37
+ return [
38
+ new SensitiveStringOption('api-key', ''), /* for communicating with wpc api v.1+ */
39
+ new SensitiveStringOption('secret', ''), /* for communicating with wpc api v.0 */
40
+ new SensitiveStringOption('api-url', ''),
41
+ new SensitiveStringOption('url', ''), /* DO NOT USE. Only here to keep the protection */
42
+ new IntegerOption('api-version', 2, 0, 2),
43
+ new BooleanOption('crypt-api-key-in-transfer', false) /* new in api v.1 */
44
+ ];
45
+ }
46
+
47
  public function supportsLossless()
48
  {
49
  return ($this->options['api-version'] >= 2);
56
  return ($this->options['api-version'] >= 2);
57
  }
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  private static function createRandomSaltForBlowfish()
60
  {
61
  $salt = '';
vendor/rosell-dk/webp-convert/src/Helpers/BinaryDiscovery.php CHANGED
@@ -19,7 +19,8 @@ class BinaryDiscovery
19
  '/usr/bin',
20
  '/usr/local/bin',
21
  '/usr/gnu/bin',
22
- '/usr/syno/bin'
 
23
  ];
24
 
25
  /**
19
  '/usr/bin',
20
  '/usr/local/bin',
21
  '/usr/gnu/bin',
22
+ '/usr/syno/bin',
23
+ '/bin',
24
  ];
25
 
26
  /**
vendor/rosell-dk/webp-convert/src/Helpers/SanityCheck.txt DELETED
@@ -1,255 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Helpers;
4
-
5
- use WebPConvert\Helpers\Sanitize;
6
- use WebPConvert\Exceptions\SanityException;
7
-
8
- class SanityCheck
9
- {
10
-
11
- /**
12
- *
13
- * @param string $input string to test for NUL char
14
- */
15
- public static function mustBeString($input, $errorMsg = 'String expected')
16
- {
17
- if (gettype($input) !== 'string') {
18
- throw new SanityException($errorMsg);
19
- }
20
- return $input;
21
- }
22
-
23
- /**
24
- * The NUL character is a demon, because it can be used to bypass other tests
25
- * See https://st-g.de/2011/04/doing-filename-checks-securely-in-PHP.
26
- *
27
- * @param string $input string to test for NUL char
28
- */
29
- public static function noNUL($input, $errorMsg = 'NUL character is not allowed')
30
- {
31
- self::mustBeString($input);
32
- if (strpos($input, chr(0)) !== false) {
33
- throw new SanityException($errorMsg);
34
- }
35
- return $input;
36
- }
37
-
38
- /**
39
- * Prevent control chararters (#00 - #20).
40
- *
41
- * This prevents line feed, new line, tab, charater return, tab, ets.
42
- * https://www.rapidtables.com/code/text/ascii-table.html
43
- *
44
- * @param string $input string to test for control characters
45
- */
46
- public static function noControlChars($input)
47
- {
48
- self::mustBeString($input);
49
- self::noNUL($input);
50
- if (preg_match('#[\x{0}-\x{1f}]#', $input)) {
51
- throw new SanityException('Control characters are not allowed');
52
- }
53
- return $input;
54
- }
55
-
56
-
57
- /**
58
- *
59
- * @param mixed $input something that may not be empty
60
- */
61
- public static function notEmpty($input, $errorMsg = 'Must be non-empty')
62
- {
63
- if (empty($input)) {
64
- throw new SanityException($input);
65
- }
66
- return $input;
67
- }
68
-
69
-
70
-
71
- public static function noDirectoryTraversal($input, $errorMsg = 'Directory traversal is not allowed')
72
- {
73
- self::mustBeString($input);
74
- self::noControlChars($input);
75
- if (preg_match('#\.\.\/#', $input)) {
76
- throw new SanityException($errorMsg);
77
- }
78
- return $input;
79
- }
80
-
81
- public static function noStreamWrappers($input, $errorMsg = 'Stream wrappers are not allowed')
82
- {
83
- self::mustBeString($input);
84
- self::noControlChars($input);
85
-
86
- // Prevent stream wrappers ("phar://", "php://" and the like)
87
- // https://www.php.net/manual/en/wrappers.phar.php
88
- if (preg_match('#^\\w+://#', Sanitize::removeNUL($input))) {
89
- throw new SanityException($errorMsg);
90
- }
91
- return $input;
92
- }
93
-
94
- public static function path($input)
95
- {
96
- self::notEmpty($input);
97
- self::mustBeString($input);
98
- self::noControlChars($input);
99
- self::noDirectoryTraversal($input);
100
- self::noStreamWrappers($input);
101
- return $input;
102
- }
103
-
104
- public static function pathWithoutDirectoryTraversal($input)
105
- {
106
- return self::path($input);
107
- }
108
-
109
- public static function absPathMicrosoftStyle($input, $errorMsg = 'Not an fully qualified Windows path')
110
- {
111
- // On microsoft we allow [drive letter]:\
112
- if (!preg_match("#^[A-Z]:\\\\|/#", $input)) {
113
- throw new SanityException($errorMsg . ':' . $input);
114
- }
115
- return $input;
116
- }
117
-
118
- public static function absPath($input, $errorMsg = 'Not an absolute path')
119
- {
120
- if ((strpos($input, '/') !== 0)) {
121
-
122
- // Check if we are on Microsoft
123
- $onMicrosoft = false;
124
- if (isset($_SERVER['SERVER_SOFTWARE'])) {
125
- if (strpos(strtolower($_SERVER['SERVER_SOFTWARE']), 'microsoft') !== false) {
126
- $onMicrosoft = true;
127
- }
128
- }
129
- switch (PHP_OS) {
130
- case "WINNT":
131
- case "WIN32":
132
- case "INTERIX":
133
- case "UWIN":
134
- case "UWIN-W7":
135
- $onMicrosoft = true;
136
- break;
137
- }
138
-
139
- if (!$onMicrosoft) {
140
- throw new SanityException($errorMsg . ':' . $input);
141
- }
142
- self::absPathMicrosoftStyle($input);
143
-
144
- }
145
- return self::path($input);
146
- }
147
-
148
- public static function pathBeginsWith($input, $beginsWith, $errorMsg = 'Path is outside allowed path')
149
- {
150
- self::path($input);
151
- if (!(strpos($input, $beginsWith) === 0)) {
152
- throw new SanityException($errorMsg);
153
- }
154
- return $input;
155
- }
156
-
157
- public static function findClosestExistingFolderSymLinksExpanded($input) {
158
- $levelsUp = 1;
159
- //echo 'input:' . $input;
160
- while (true) {
161
- $dir = dirname($input, $levelsUp);
162
- //echo 'dir:' . $dir . '<br>';
163
- $realPathResult = realpath($dir);
164
- if ($realPathResult !== false) {
165
- return $realPathResult;
166
- }
167
- if (($dir == '/') || (strlen($dir) < 4)) {
168
- return $dir;
169
- }
170
- $levelsUp++;
171
- }
172
- return '/';
173
- }
174
-
175
- public static function pathBeginsWithSymLinksExpanded($input, $beginsWith, $errorMsg = 'Path is outside allowed path') {
176
- $closestExistingFolder = self::findClosestExistingFolderSymLinksExpanded($input);
177
- //throw new SanityException('hm.' . $input . ' : <br>' . $closestExistingFolder);
178
- self::pathBeginsWith($closestExistingFolder, $beginsWith, $errorMsg);
179
- }
180
-
181
-
182
-
183
- public static function absPathExists($input, $errorMsg = 'Path does not exist')
184
- {
185
- self::absPath($input);
186
- if (@!file_exists($input)) {
187
- throw new SanityException($errorMsg);
188
- }
189
- return $input;
190
- }
191
-
192
- public static function absPathExistsAndIsDir(
193
- $input,
194
- $errorMsg = 'Path points to a file (it should point to a directory)'
195
- ) {
196
- self::absPathExists($input);
197
- if (!is_dir($input)) {
198
- throw new SanityException($errorMsg);
199
- }
200
- return $input;
201
- }
202
-
203
- public static function absPathExistsAndIsFile(
204
- $input,
205
- $errorMsg = 'Path points to a directory (it should not do that)'
206
- ) {
207
- self::absPathExists($input, 'File does not exist');
208
- if (@is_dir($input)) {
209
- throw new SanityException($errorMsg);
210
- }
211
- return $input;
212
- }
213
-
214
- public static function absPathExistsAndIsNotDir(
215
- $input,
216
- $errorMsg = 'Path points to a directory (it should point to a file)'
217
- ) {
218
- self::absPathExistsAndIsFile($input, $errorMsg);
219
- return $input;
220
- }
221
-
222
-
223
- public static function pregMatch($pattern, $input, $errorMsg = 'Does not match expected pattern')
224
- {
225
- self::noNUL($input);
226
- self::mustBeString($input);
227
- if (!preg_match($pattern, $input)) {
228
- throw new SanityException($errorMsg);
229
- }
230
- return $input;
231
- }
232
-
233
- public static function isJSONArray($input, $errorMsg = 'Not a JSON array')
234
- {
235
- self::noNUL($input);
236
- self::mustBeString($input);
237
- self::notEmpty($input);
238
- if ((strpos($input, '[') !== 0) || (!is_array(json_decode($input)))) {
239
- throw new SanityException($errorMsg);
240
- }
241
- return $input;
242
- }
243
-
244
- public static function isJSONObject($input, $errorMsg = 'Not a JSON object')
245
- {
246
- self::noNUL($input);
247
- self::mustBeString($input);
248
- self::notEmpty($input);
249
- if ((strpos($input, '{') !== 0) || (!is_object(json_decode($input)))) {
250
- throw new SanityException($errorMsg);
251
- }
252
- return $input;
253
- }
254
-
255
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/src/Options/ArrayOption.php CHANGED
@@ -15,6 +15,9 @@ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
15
  class ArrayOption extends Option
16
  {
17
 
 
 
 
18
  public function check()
19
  {
20
  $this->checkType('array');
@@ -28,4 +31,11 @@ class ArrayOption extends Option
28
  return parent::getValueForPrint();
29
  }
30
  }
 
 
 
 
 
 
 
31
  }
15
  class ArrayOption extends Option
16
  {
17
 
18
+ protected $typeId = 'array';
19
+ protected $allowedValueTypes = ['array'];
20
+
21
  public function check()
22
  {
23
  $this->checkType('array');
31
  return parent::getValueForPrint();
32
  }
33
  }
34
+ /*
35
+ public function getDefinition()
36
+ {
37
+ $obj = parent::getDefinition();
38
+ $obj['sensitive'] = false;
39
+ return $obj;
40
+ }*/
41
  }
vendor/rosell-dk/webp-convert/src/Options/BooleanOption.php CHANGED
@@ -15,6 +15,9 @@ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
15
  class BooleanOption extends Option
16
  {
17
 
 
 
 
18
  public function check()
19
  {
20
  $this->checkType('boolean');
15
  class BooleanOption extends Option
16
  {
17
 
18
+ protected $typeId = 'boolean';
19
+ protected $allowedValueTypes = ['boolean'];
20
+
21
  public function check()
22
  {
23
  $this->checkType('boolean');
vendor/rosell-dk/webp-convert/src/Options/GhostOption.php CHANGED
@@ -15,6 +15,8 @@ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
15
  class GhostOption extends Option
16
  {
17
 
 
 
18
  public function getValueForPrint()
19
  {
20
  return '(not defined for this converter)';
15
  class GhostOption extends Option
16
  {
17
 
18
+ protected $typeId = 'ghost';
19
+
20
  public function getValueForPrint()
21
  {
22
  return '(not defined for this converter)';
vendor/rosell-dk/webp-convert/src/Options/IntegerOption.php CHANGED
@@ -14,7 +14,8 @@ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
14
  */
15
  class IntegerOption extends Option
16
  {
17
-
 
18
  protected $minValue;
19
  protected $maxValue;
20
 
14
  */
15
  class IntegerOption extends Option
16
  {
17
+ protected $typeId = 'integer';
18
+ protected $allowedValueTypes = ['integer'];
19
  protected $minValue;
20
  protected $maxValue;
21
 
vendor/rosell-dk/webp-convert/src/Options/IntegerOrNullOption.php CHANGED
@@ -14,6 +14,7 @@ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
14
  */
15
  class IntegerOrNullOption extends IntegerOption
16
  {
 
17
 
18
  public function __construct($id, $defaultValue, $minValue = null, $maxValue = null)
19
  {
14
  */
15
  class IntegerOrNullOption extends IntegerOption
16
  {
17
+ protected $allowedValueTypes = ['integer', 'null'];
18
 
19
  public function __construct($id, $defaultValue, $minValue = null, $maxValue = null)
20
  {
vendor/rosell-dk/webp-convert/src/Options/MetadataOption.php CHANGED
@@ -15,6 +15,9 @@ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
15
  class MetadataOption extends StringOption
16
  {
17
 
 
 
 
18
  public function __construct($id, $defaultValue)
19
  {
20
  parent::__construct($id, $defaultValue);
15
  class MetadataOption extends StringOption
16
  {
17
 
18
+ protected $typeId = 'metadata';
19
+ protected $allowedValueTypes = ['string'];
20
+
21
  public function __construct($id, $defaultValue)
22
  {
23
  parent::__construct($id, $defaultValue);
vendor/rosell-dk/webp-convert/src/Options/Option.php CHANGED
@@ -23,9 +23,22 @@ class Option
23
  /** @var mixed The value of the option */
24
  protected $value;
25
 
26
- /** @var boolean Whether the value has been explicitly set */
27
  protected $isExplicitlySet = false;
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  /**
30
  * Constructor.
31
  *
@@ -80,7 +93,7 @@ class Option
80
  }
81
 
82
  /**
83
- * Get to know if value has been explicitly set.
84
  *
85
  * @return boolean Whether or not the value has been set explicitly
86
  */
@@ -128,8 +141,36 @@ class Option
128
  }
129
  }
130
 
 
 
 
 
 
 
 
 
 
 
131
  public function getValueForPrint()
132
  {
133
  return print_r($this->getValue(), true);
134
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  }
23
  /** @var mixed The value of the option */
24
  protected $value;
25
 
26
+ /** @var boolean Whether the value has been set (if not, getValue() will return the default value) */
27
  protected $isExplicitlySet = false;
28
 
29
+ /** @var string An option must supply a type id */
30
+ protected $typeId;
31
+
32
+ /** @var array Type constraints for the value */
33
+ protected $allowedValueTypes = [];
34
+
35
+ /** @var boolean Whether the option has been deprecated */
36
+ protected $deprecated = false;
37
+
38
+ /** @var string Help text */
39
+ protected $helpText = '';
40
+
41
+
42
  /**
43
  * Constructor.
44
  *
93
  }
94
 
95
  /**
96
+ * Get to know if value has been set.
97
  *
98
  * @return boolean Whether or not the value has been set explicitly
99
  */
141
  }
142
  }
143
 
144
+ public function markDeprecated()
145
+ {
146
+ $this->deprecated = true;
147
+ }
148
+
149
+ public function isDeprecated()
150
+ {
151
+ return $this->deprecated;
152
+ }
153
+
154
  public function getValueForPrint()
155
  {
156
  return print_r($this->getValue(), true);
157
  }
158
+
159
+
160
+ /* POST-PONED till 2.7.0
161
+
162
+ public function getDefinition()
163
+ {
164
+ $obj = [
165
+ 'id' => $this->id,
166
+ 'type' => $this->typeId,
167
+ 'allowed-value-types' => $this->allowedValueTypes,
168
+ 'default' => $this->defaultValue,
169
+ 'help-text' => $this->helpText,
170
+ ];
171
+ if ($this->deprecated) {
172
+ $obj['deprecated'] = true;
173
+ }
174
+ return $obj;
175
+ }*/
176
  }
vendor/rosell-dk/webp-convert/src/Options/Options.php CHANGED
@@ -94,23 +94,48 @@ class Options
94
  /**
95
  * Get the value of an option in the collection - by id.
96
  *
 
97
  * @param string $id Id of the option to get
98
  * @throws OptionNotFoundException if the option is not in the collection
99
  * @return mixed The value of the option
100
  */
101
  public function getOption($id)
 
 
 
 
 
 
 
 
 
 
 
 
102
  {
103
  if (!isset($this->options[$id])) {
104
  throw new OptionNotFoundException(
105
  'There is no option called "' . $id . '" in the collection.'
106
  );
107
  }
108
- $option = $this->options[$id];
 
 
 
 
 
 
 
 
 
 
 
 
109
  return $option->getValue();
110
  }
111
 
112
  /**
113
- * Return map of option objects.
114
  *
115
  * @return array map of option objects
116
  */
@@ -120,7 +145,7 @@ class Options
120
  }
121
 
122
  /**
123
- * Return flat associative array of options.
124
  *
125
  * @return array associative array of options
126
  */
@@ -142,4 +167,18 @@ class Options
142
  $option->check();
143
  }
144
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  }
94
  /**
95
  * Get the value of an option in the collection - by id.
96
  *
97
+ * @deprecated Use getOptionValue() instead
98
  * @param string $id Id of the option to get
99
  * @throws OptionNotFoundException if the option is not in the collection
100
  * @return mixed The value of the option
101
  */
102
  public function getOption($id)
103
+ {
104
+ return $this->getOptionValue($id);
105
+ }
106
+
107
+ /**
108
+ * Get the Option in the collection by id.
109
+ *
110
+ * @param string $id Id of the option to get
111
+ * @throws OptionNotFoundException if the option is not in the collection
112
+ * @return mixed The value of the option
113
+ */
114
+ public function getOptionById($id)
115
  {
116
  if (!isset($this->options[$id])) {
117
  throw new OptionNotFoundException(
118
  'There is no option called "' . $id . '" in the collection.'
119
  );
120
  }
121
+ return $this->options[$id];
122
+ }
123
+
124
+ /**
125
+ * Get the value of an option in the collection - by id.
126
+ *
127
+ * @param string $id Id of the option to get
128
+ * @throws OptionNotFoundException if the option is not in the collection
129
+ * @return mixed The value of the option
130
+ */
131
+ public function getOptionValue($id)
132
+ {
133
+ $option = $this->getOptionById($id);
134
  return $option->getValue();
135
  }
136
 
137
  /**
138
+ * Return map of Option objects.
139
  *
140
  * @return array map of option objects
141
  */
145
  }
146
 
147
  /**
148
+ * Return flat associative array of options (simple objects).
149
  *
150
  * @return array associative array of options
151
  */
167
  $option->check();
168
  }
169
  }
170
+
171
+
172
+
173
+ /* POST-PONED till 2.7.0
174
+ public function getDefinitions($deprecatedToo = false)
175
+ {
176
+ $defs = [];
177
+ foreach ($this->options as $option) {
178
+ if ($deprecatedToo || !($option->isDeprecated())) {
179
+ $defs[] = $option->getDefinition();
180
+ }
181
+ }
182
+ return $defs;
183
+ }*/
184
  }
vendor/rosell-dk/webp-convert/src/Options/QualityOption.php CHANGED
@@ -16,6 +16,8 @@ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
16
  */
17
  class QualityOption extends Option
18
  {
 
 
19
 
20
  public function __construct($id, $defaultValue)
21
  {
16
  */
17
  class QualityOption extends Option
18
  {
19
+ protected $typeId = 'quality';
20
+ protected $allowedValueTypes = ['integer', 'string'];
21
 
22
  public function __construct($id, $defaultValue)
23
  {
vendor/rosell-dk/webp-convert/src/Options/StringOption.php CHANGED
@@ -15,7 +15,9 @@ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
15
  class StringOption extends Option
16
  {
17
 
18
- public $allowedValues;
 
 
19
 
20
  public function __construct($id, $defaultValue, $allowedValues = null)
21
  {
15
  class StringOption extends Option
16
  {
17
 
18
+ protected $typeId = 'string';
19
+ protected $allowedValues;
20
+ protected $allowedValueTypes = ['string'];
21
 
22
  public function __construct($id, $defaultValue, $allowedValues = null)
23
  {
vendor/rosell-dk/webp-convert/src/WebPConvert.php CHANGED
@@ -5,6 +5,7 @@ namespace WebPConvert;
5
  //use WebPConvert\Convert\Converters\ConverterHelper;
6
  use WebPConvert\Convert\Converters\Stack;
7
  //use WebPConvert\Serve\ServeExistingOrHandOver;
 
8
  use WebPConvert\Serve\ServeConvertedWebP;
9
  use WebPConvert\Serve\ServeConvertedWebPWithErrorHandling;
10
 
@@ -74,4 +75,46 @@ class WebPConvert
74
  ServeConvertedWebP::serve($source, $destination, $options, $serveLogger, $convertLogger);
75
  }
76
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  }
5
  //use WebPConvert\Convert\Converters\ConverterHelper;
6
  use WebPConvert\Convert\Converters\Stack;
7
  //use WebPConvert\Serve\ServeExistingOrHandOver;
8
+ use WebPConvert\Convert\ConverterFactory;
9
  use WebPConvert\Serve\ServeConvertedWebP;
10
  use WebPConvert\Serve\ServeConvertedWebPWithErrorHandling;
11
 
75
  ServeConvertedWebP::serve($source, $destination, $options, $serveLogger, $convertLogger);
76
  }
77
  }
78
+
79
+ /**
80
+ * Get ids of all converters available in webp-convert.
81
+ *
82
+ * @return array Array of ids.
83
+ */
84
+ public static function getConverterIds()
85
+ {
86
+ $all = Stack::getAvailableConverters();
87
+ $all[] = 'stack';
88
+ return $all;
89
+ }
90
+
91
+ /**
92
+ * Get option definitions for all converters
93
+ *
94
+ * Added in order to give GUI's a way to automatically adjust their setting screens.
95
+ *
96
+ * @param string $imageType (png | jpeg) The image type - determines the defaults
97
+ * @param bool $returnGeneral Whether the general setting definitions should be returned
98
+ * @param bool $returnGeneralSupport Whether the ids of supported/unsupported general options
99
+ * should be returned
100
+ *
101
+ * @return array Array of options definitions - ready to be json encoded, or whatever
102
+ */
103
+ /* POST-PONED till 2.7.0
104
+ public static function getConverterOptionDefinitions(
105
+ $imageType = 'png',
106
+ $returnGeneral = true,
107
+ $returnGeneralSupport = true
108
+ ) {
109
+
110
+ $ids = self::getConverterIds();
111
+ $result = [];
112
+ foreach ($ids as $id) {
113
+ $c = ConverterFactory::makeConverter($id, '', '');
114
+ $optionDefinitions = $c->getOptionDefinitions($imageType, $returnGeneral, $returnGeneralSupport);
115
+
116
+ $result[$id] = $optionDefinitions;
117
+ }
118
+ return $result;
119
+ }*/
120
  }
webp-express.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: WebP Express
4
  * Plugin URI: https://github.com/rosell-dk/webp-express
5
  * Description: Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP. Works on anything (media library images, galleries, theme images etc).
6
- * Version: 0.19.1
7
  * Author: Bjørn Rosell
8
  * Author URI: https://www.bitwise-it.dk
9
  * License: GPL2
@@ -33,6 +33,10 @@ if (is_admin()) {
33
  \WebPExpress\AdminInit::init();
34
  }
35
 
 
 
 
 
36
  function webp_express_process_post() {
37
  // strip query string
38
  $requestUriNoQS = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
3
  * Plugin Name: WebP Express
4
  * Plugin URI: https://github.com/rosell-dk/webp-express
5
  * Description: Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP. Works on anything (media library images, galleries, theme images etc).
6
+ * Version: 0.20.0
7
  * Author: Bjørn Rosell
8
  * Author URI: https://www.bitwise-it.dk
9
  * License: GPL2
33
  \WebPExpress\AdminInit::init();
34
  }
35
 
36
+ if ( class_exists( 'WP_CLI' ) ) {
37
+ \WP_CLI::add_command('webp-express', '\WebPExpress\CLI');
38
+ }
39
+
40
  function webp_express_process_post() {
41
  // strip query string
42
  $requestUriNoQS = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);