WebP Express - Version 0.14.0

Version Description

New awesome conversion options that gets you even smaller webp files without compromising quality.

Download this release

Release Info

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

Code changes from version 0.13.2 to 0.14.0

Files changed (128) hide show
  1. README.md +14 -1
  2. README.txt +21 -2
  3. composer.json +2 -13
  4. composer.lock +88 -106
  5. lib/classes/AdminInit.php +6 -3
  6. lib/classes/AlterHtmlHelper.php +2 -3
  7. lib/classes/BulkConvert.php +0 -10
  8. lib/classes/CachePurge.php +3 -0
  9. lib/classes/Config.php +144 -44
  10. lib/classes/Convert.php +58 -4
  11. lib/classes/ConvertHelperIndependent.php +111 -3
  12. lib/classes/ConvertLog.php +37 -0
  13. lib/classes/ConvertersHelper.php +185 -11
  14. lib/classes/DismissableMessages.php +82 -0
  15. lib/classes/HTAccess.php +28 -7
  16. lib/classes/HandleDeleteFileHook.php +1 -2
  17. lib/classes/Messenger.php +8 -5
  18. lib/classes/Paths.php +12 -2
  19. lib/classes/PlatformInfo.php +5 -0
  20. lib/classes/TestRun.php +21 -20
  21. lib/dismissable-messages/0.14.0/say-hello-to-vips.php +39 -0
  22. lib/dismissable-messages/0.14.0/suggest-enable-pngs.php +10 -0
  23. lib/dismissable-messages/0.14.0/suggest-wipe-because-lossless.php +45 -0
  24. lib/migrate/migrate9.php +204 -0
  25. lib/options/css/images/checker.png +0 -0
  26. lib/options/css/images/drag-handle.svg +8 -0
  27. lib/options/css/test-convert.css +101 -0
  28. lib/options/css/webp-express-options-page.css +33 -3
  29. lib/options/enqueue_scripts.php +24 -1
  30. lib/options/js/bulk-convert.js +48 -3
  31. lib/options/js/converters.js +55 -19
  32. lib/options/js/image-comparison-slider.js +68 -0
  33. lib/options/js/page.js +68 -6
  34. lib/options/js/test-convert.js +278 -0
  35. lib/options/options/conversion-options/bulk-convert.inc +7 -0
  36. lib/options/options/conversion-options/conversion-options.inc +3 -1
  37. lib/options/options/conversion-options/converter-options/ewww.php +4 -4
  38. lib/options/options/conversion-options/converter-options/gd.php +9 -2
  39. lib/options/options/conversion-options/converter-options/graphicsmagick.php +32 -0
  40. lib/options/options/conversion-options/converter-options/imagemagick.php +30 -0
  41. lib/options/options/conversion-options/converter-options/imagick.php +4 -1
  42. lib/options/options/conversion-options/converter-options/imagickbinary.php +0 -24
  43. lib/options/options/conversion-options/converter-options/vips.php +42 -0
  44. lib/options/options/conversion-options/converter-options/wpc.php +5 -3
  45. lib/options/options/conversion-options/converters.inc +33 -72
  46. lib/options/options/conversion-options/jpeg.inc +164 -0
  47. lib/options/options/conversion-options/metadata.inc +1 -1
  48. lib/options/options/conversion-options/png.inc +98 -0
  49. lib/options/options/conversion-options/quality.inc +63 -20
  50. lib/options/options/general/image-types.inc +1 -1
  51. lib/options/options/redirection-rules/enable-redirection-to-converter.inc +9 -19
  52. lib/options/options/redirection-rules/enable-redirection-to-webp-realizer.inc +7 -5
  53. lib/options/options/redirection-rules/redirect-to-existing.inc +12 -6
  54. lib/options/options/redirection-rules/redirection-rules.inc +2 -12
  55. lib/options/options/serve-options/response-on-failure.inc +1 -1
  56. lib/options/page-messages.php +137 -6
  57. lib/options/page.php +7 -0
  58. lib/options/submit.php +41 -6
  59. test/alphatest.png +0 -0
  60. test/architecture-q85-w600.jpg +0 -0
  61. test/dice.png +0 -0
  62. test/palette-based-colors.png +0 -0
  63. test/{test.jpg → test-pattern-tv.jpg} +0 -0
  64. test/test-run.php +0 -191
  65. test/test.png +0 -0
  66. vendor/composer/autoload_classmap.php +0 -31
  67. vendor/composer/autoload_psr4.php +1 -0
  68. vendor/composer/autoload_static.php +8 -35
  69. vendor/composer/installed.json +93 -109
  70. vendor/rosell-dk/image-mime-type-guesser/.php_cs.dist +19 -0
  71. vendor/rosell-dk/image-mime-type-guesser/LICENSE +9 -0
  72. vendor/rosell-dk/image-mime-type-guesser/README.md +108 -0
  73. vendor/rosell-dk/image-mime-type-guesser/composer.json +55 -0
  74. vendor/rosell-dk/image-mime-type-guesser/phpunit.xml.dist +28 -0
  75. vendor/rosell-dk/image-mime-type-guesser/src/Detectors/AbstractDetector.php +55 -0
  76. vendor/rosell-dk/image-mime-type-guesser/src/Detectors/ExifImageType.php +40 -0
  77. vendor/rosell-dk/image-mime-type-guesser/src/Detectors/FInfo.php +38 -0
  78. vendor/rosell-dk/image-mime-type-guesser/src/Detectors/GetImageSize.php +36 -0
  79. vendor/rosell-dk/image-mime-type-guesser/src/Detectors/MimeContentType.php +41 -0
  80. vendor/rosell-dk/image-mime-type-guesser/src/Detectors/SniffFirstFourBytes.php +48 -0
  81. vendor/rosell-dk/image-mime-type-guesser/src/Detectors/Stack.php +42 -0
  82. vendor/rosell-dk/image-mime-type-guesser/src/GuessFromExtension.php +90 -0
  83. vendor/rosell-dk/image-mime-type-guesser/src/ImageMimeTypeGuesser.php +134 -0
  84. vendor/rosell-dk/webp-convert-cloud-service/README.md +5 -0
  85. vendor/rosell-dk/webp-convert-cloud-service/composer.json +1 -1
  86. vendor/rosell-dk/webp-convert-cloud-service/src/Serve.php +7 -10
  87. vendor/rosell-dk/webp-convert-cloud-service/src/WebPConvertCloudService.php +2 -2
  88. vendor/rosell-dk/webp-convert/.travis.yml +123 -7
  89. vendor/rosell-dk/webp-convert/README.md +52 -38
  90. vendor/rosell-dk/webp-convert/build-scripts/PHPMerger.php +24 -12
  91. vendor/rosell-dk/webp-convert/build-scripts/build-all +0 -1
  92. vendor/rosell-dk/webp-convert/build-scripts/build-webp-on-demand.php +0 -89
  93. vendor/rosell-dk/webp-convert/build-scripts/build.php +122 -0
  94. vendor/rosell-dk/webp-convert/build-scripts/generate-require-all.php +0 -83
  95. vendor/rosell-dk/webp-convert/build-tests-webp-convert/WebPConvertBuildTest.php +46 -0
  96. vendor/rosell-dk/webp-convert/build-tests-wod/WodBuildTest.php +167 -0
  97. vendor/rosell-dk/webp-convert/build/webp-convert.inc +0 -3089
  98. vendor/rosell-dk/webp-convert/build/webp-on-demand-1.inc +0 -295
  99. vendor/rosell-dk/webp-convert/build/webp-on-demand-2.inc +0 -2795
  100. vendor/rosell-dk/webp-convert/composer.json +33 -7
  101. vendor/rosell-dk/webp-convert/docs/development.md +30 -3
  102. vendor/rosell-dk/webp-convert/docs/{converters.md → v1.3/converting/convert-options.md} +106 -20
  103. vendor/rosell-dk/webp-convert/docs/{api → v1.3/converting}/convert.md +8 -6
  104. vendor/rosell-dk/webp-convert/docs/v1.3/converting/converters.md +322 -0
  105. vendor/rosell-dk/webp-convert/docs/{api → v1.3/serving}/convert-and-serve.md +5 -0
  106. vendor/rosell-dk/webp-convert/docs/{webp-on-demand → v1.3/webp-on-demand}/tweaks.md +0 -0
  107. vendor/rosell-dk/webp-convert/docs/{webp-on-demand → v1.3/webp-on-demand}/webp-on-demand.md +6 -6
  108. vendor/rosell-dk/webp-convert/docs/{webp-on-demand → v1.3/webp-on-demand}/without-composer.md +2 -2
  109. vendor/rosell-dk/webp-convert/docs/v2.0/converting/architecture-q50-w600.jpg +0 -0
  110. vendor/rosell-dk/webp-convert/docs/v2.0/converting/converters/stack.md +248 -0
  111. vendor/rosell-dk/webp-convert/docs/v2.0/converting/dice.png +0 -0
  112. vendor/rosell-dk/webp-convert/docs/v2.0/converting/introduction-for-converting.md +214 -0
  113. vendor/rosell-dk/webp-convert/docs/v2.0/converting/mouse-q100.jpg +0 -0
  114. vendor/rosell-dk/webp-convert/docs/v2.0/converting/options.md +298 -0
  115. vendor/rosell-dk/webp-convert/docs/v2.0/migrating-to-2.0.md +73 -0
  116. vendor/rosell-dk/webp-convert/docs/v2.0/serving/introduction-for-serving.md +143 -0
  117. vendor/rosell-dk/webp-convert/docs/v2.0/webp-on-demand/tweaks.md +167 -0
  118. vendor/rosell-dk/webp-convert/docs/v2.0/webp-on-demand/webp-on-demand.md +145 -0
  119. vendor/rosell-dk/webp-convert/docs/v2.0/webp-on-demand/without-composer.md +58 -0
  120. vendor/rosell-dk/webp-convert/install-gmagick-with-webp.sh +74 -0
  121. vendor/rosell-dk/webp-convert/install-imagemagick-with-webp.sh +40 -0
  122. vendor/rosell-dk/webp-convert/install-vips.sh +40 -0
  123. vendor/rosell-dk/webp-convert/phpdox.xml +9 -0
  124. vendor/rosell-dk/webp-convert/phpstan.neon +25 -0
  125. vendor/rosell-dk/webp-convert/phpunit.xml.dist +12 -1
  126. vendor/rosell-dk/webp-convert/src-build/webp-convert.inc +6907 -0
  127. vendor/rosell-dk/webp-convert/src-build/webp-on-demand-1.inc +616 -0
  128. vendor/rosell-dk/webp-convert/src-build/webp-on-demand-2.inc +3626 -0
README.md CHANGED
@@ -593,10 +593,23 @@ If you got any further questions, look at, or comment on [this topic](https://wo
593
  ### When is feature X coming? / Roadmap
594
  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.
595
 
596
- Here are my current plans ahead: 0.13 might be bulk conversion and addition of a diagnose tool this should release some time spend in the forum. 0.14 could be focused on PNG. 0.15 might be displaying rules for NGINX. 0.16 might be supporting Save-Data header (send extra compressed images to clients who wants to use as little bandwidth as possible). 0.17 might be a file manager-like interface for inspecting generated webp files. 0.18 might be an effort to allow webp for all browsers using [this javascript library](http://libwebpjs.hohenlimburg.org/v0.6.0/). Unfortunately, the javascript librare 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.19 might be WAMP support. The current milestones, their subtasks and their progress can be viewed here: https://github.com/rosell-dk/webp-express/milestones
597
 
598
  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.
599
 
 
 
 
 
 
 
 
 
 
 
 
 
 
600
  ## Changes in 0.13.2
601
  - 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.
602
  - It seems we finally nailed the blank settings page bug. Thanks to all involved, especially Richard Spenceley from the UK
593
  ### When is feature X coming? / Roadmap
594
  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.
595
 
596
+ Here are my current plans ahead: 0.15 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.16 might be various improvements such as option to choose which folders webp express should process (Just uploads / Just uploads and templates / Whole wp-content / Whole system) and options to exclude certain files and folders. 0.17 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.18 might be a diagnose tool this should release some time spend in the forum. 0.18 might be displaying rules for NGINX. 0.19 might be an effort to allow webp for all browsers using [this javascript library](http://libwebpjs.hohenlimburg.org/v0.6.0/). Unfortunately, the javascript librare 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.20 might be WAMP support. The current milestones, their subtasks and their progress can be viewed here: https://github.com/rosell-dk/webp-express/milestones
597
 
598
  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.
599
 
600
+ ## Changes in 0.14.0
601
+ - 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.
602
+ - 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
603
+ - 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
604
+ - Added new conversion methods: Vips and GraphicsMagick
605
+ - Imagick conversion method now supports webp options (finally cracked it!)
606
+ - Using MimeType detection instead of relying on file extensions
607
+ - In "test" converter you now change options and also test PNG files
608
+ - Added conversion logs
609
+ - PNGs are now enabled by default (with the new conversion features especially PNGs are compressed much better)
610
+
611
+ 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
612
+
613
  ## Changes in 0.13.2
614
  - 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.
615
  - It seems we finally nailed the blank settings page bug. Thanks to all involved, especially Richard Spenceley from the UK
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.2
7
- Stable tag: 0.13.2
8
  Requires PHP: 5.6
9
  License: GPLv3
10
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -38,6 +38,7 @@ The plugin builds on [WebPConvert](https://github.com/rosell-dk/webp-convert) an
38
  - Currently ~73% of all traffic, and ~79% of mobile browsing traffic are done with browsers supporting webp. With Mozilla and Microsoft [finally on board](https://medium.com/@richard_90141/webp-image-support-an-8-year-saga-7aa2bedb8d02), these numbers are bound to increase. Check current numbers on [caniuse.com](https://caniuse.com/webp)).
39
 
40
  ### Recent news
 
41
  Feb 2019: Multisite is now supported (0.12.0)
42
  Jan 2019: Plugin can now alter HTML (0.11.0)
43
 
@@ -591,7 +592,7 @@ If you got any further questions, look at, or comment on [this topic](https://wo
591
  = When is feature X coming? / Roadmap =
592
  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.
593
 
594
- Here are my current plans ahead: 0.13 might be bulk conversion and addition of a diagnose tool this should release some time spend in the forum. 0.14 could be focused on PNG. 0.15 might be displaying rules for NGINX. 0.16 might be supporting Save-Data header (send extra compressed images to clients who wants to use as little bandwidth as possible). 0.17 might be a file manager-like interface for inspecting generated webp files. 0.18 might be an effort to allow webp for all browsers using [this javascript library](http://libwebpjs.hohenlimburg.org/v0.6.0/). Unfortunately, the javascript librare 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.19 might be WAMP support. The current milestones, their subtasks and their progress can be viewed here: https://github.com/rosell-dk/webp-express/milestones
595
 
596
  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.
597
 
@@ -604,6 +605,21 @@ Easy enough! - [Go here!](https://ko-fi.com/rosell). Or [here](https://buymeacof
604
 
605
  == Changelog ==
606
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
607
  = 0.13.2 =
608
  *(released: 16 may 2019)*
609
 
@@ -781,6 +797,9 @@ For older releases, check out changelog.txt
781
 
782
  == Upgrade Notice ==
783
 
 
 
 
784
  = 0.13.2 =
785
  Fixed critical error on upload (can occur in combination with some plugins), and it seems we finally nailed the blank settings page bug.
786
 
4
  Tags: webp, images, performance
5
  Requires at least: 4.0
6
  Tested up to: 5.2
7
+ Stable tag: 0.14.0
8
  Requires PHP: 5.6
9
  License: GPLv3
10
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
38
  - Currently ~73% of all traffic, and ~79% of mobile browsing traffic are done with browsers supporting webp. With Mozilla and Microsoft [finally on board](https://medium.com/@richard_90141/webp-image-support-an-8-year-saga-7aa2bedb8d02), these numbers are bound to increase. Check current numbers on [caniuse.com](https://caniuse.com/webp)).
39
 
40
  ### Recent news
41
+ Jun 2019: Better conversions and more conversion options
42
  Feb 2019: Multisite is now supported (0.12.0)
43
  Jan 2019: Plugin can now alter HTML (0.11.0)
44
 
592
  = When is feature X coming? / Roadmap =
593
  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.
594
 
595
+ Here are my current plans ahead: 0.15 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.16 might be various improvements such as option to choose which folders webp express should process (Just uploads / Just uploads and templates / Whole wp-content / Whole system) and options to exclude certain files and folders. 0.17 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.18 might be a diagnose tool this should release some time spend in the forum. 0.18 might be displaying rules for NGINX. 0.19 might be an effort to allow webp for all browsers using [this javascript library](http://libwebpjs.hohenlimburg.org/v0.6.0/). Unfortunately, the javascript librare 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.20 might be WAMP support. The current milestones, their subtasks and their progress can be viewed here: https://github.com/rosell-dk/webp-express/milestones
596
 
597
  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.
598
 
605
 
606
  == Changelog ==
607
 
608
+ = 0.14.0-beta =
609
+ *(released: 15 jun 2019)*
610
+
611
+ * 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.
612
+ * 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
613
+ * 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
614
+ * Added new conversion methods: Vips and GraphicsMagick
615
+ * Imagick conversion method now supports webp options (finally cracked it!)
616
+ * Using MimeType detection instead of relying on file extensions
617
+ * In "test" converter you now change options and also test PNG files
618
+ * Added conversion logs
619
+ * PNGs are now enabled by default (with the new conversion features especially PNGs are compressed much better)
620
+
621
+ 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
622
+
623
  = 0.13.2 =
624
  *(released: 16 may 2019)*
625
 
797
 
798
  == Upgrade Notice ==
799
 
800
+ = 0.14.0 =
801
+ New awesome conversion options that gets you even smaller webp files without compromising quality.
802
+
803
  = 0.13.2 =
804
  Fixed critical error on upload (can occur in combination with some plugins), and it seems we finally nailed the blank settings page bug.
805
 
composer.json CHANGED
@@ -3,20 +3,9 @@
3
  "description": "WebP for the masses",
4
  "type": "project",
5
  "license": "MIT",
6
- "minimum-stability": "stable",
7
- "repositories": [
8
- {
9
- "type": "vcs",
10
- "url": "https://github.com/rosell-dk/webp-convert"
11
- },
12
- {
13
- "type": "vcs",
14
- "url": "https://github.com/rosell-dk/webp-convert-cloud-service"
15
- }
16
- ],
17
  "require": {
18
- "rosell-dk/webp-convert": "^1.3.8",
19
- "rosell-dk/webp-convert-cloud-service": "^1.0.0",
20
  "rosell-dk/dom-util-for-webp": "^0.3.0"
21
  },
22
  "require-dev": {
3
  "description": "WebP for the masses",
4
  "type": "project",
5
  "license": "MIT",
 
 
 
 
 
 
 
 
 
 
 
6
  "require": {
7
+ "rosell-dk/webp-convert": "^2.0.2",
8
+ "rosell-dk/webp-convert-cloud-service": "^2.0.0",
9
  "rosell-dk/dom-util-for-webp": "^0.3.0"
10
  },
11
  "require-dev": {
composer.lock CHANGED
@@ -4,7 +4,7 @@
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": "b5e5a83c346324652e5f6cb8edf61e11",
8
  "packages": [
9
  {
10
  "name": "rosell-dk/dom-util-for-webp",
@@ -62,22 +62,22 @@
62
  "time": "2019-03-07T09:15:07+00:00"
63
  },
64
  {
65
- "name": "rosell-dk/webp-convert",
66
- "version": "1.3.9",
67
  "source": {
68
  "type": "git",
69
- "url": "https://github.com/rosell-dk/webp-convert.git",
70
- "reference": "3680c278f08748054b4ab3ea7855be8984f008aa"
71
  },
72
  "dist": {
73
  "type": "zip",
74
- "url": "https://api.github.com/repos/rosell-dk/webp-convert/zipball/3680c278f08748054b4ab3ea7855be8984f008aa",
75
- "reference": "3680c278f08748054b4ab3ea7855be8984f008aa",
76
  "shasum": ""
77
  },
78
  "require-dev": {
79
  "friendsofphp/php-cs-fixer": "^2.11",
80
- "phpunit/phpunit": "5.7.27",
81
  "squizlabs/php_codesniffer": "3.*"
82
  },
83
  "type": "library",
@@ -93,53 +93,10 @@
93
  },
94
  "autoload": {
95
  "psr-4": {
96
- "WebPConvert\\": "src/"
97
  }
98
  },
99
- "autoload-dev": {
100
- "psr-4": {
101
- "WebPConvert\\Tests\\": "tests/"
102
- }
103
- },
104
- "scripts": {
105
- "ci": [
106
- "@build",
107
- "@test",
108
- "@phpcs-all",
109
- "@composer validate --no-check-all --strict"
110
- ],
111
- "build": [
112
- "@build-wod",
113
- "@build-require-all"
114
- ],
115
- "cs-fix-all": [
116
- "php-cs-fixer fix src"
117
- ],
118
- "cs-fix": [
119
- "php-cs-fixer fix"
120
- ],
121
- "cs-dry": [
122
- "php-cs-fixer fix --dry-run --diff"
123
- ],
124
- "test": [
125
- "phpunit"
126
- ],
127
- "phpcs": [
128
- "phpcs --standard=PSR2"
129
- ],
130
- "phpcs-all": [
131
- "phpcs --standard=PSR2 src"
132
- ],
133
- "phpcbf": [
134
- "phpcbf --standard=PSR2"
135
- ],
136
- "build-wod": [
137
- "php build-scripts/build-webp-on-demand.php"
138
- ],
139
- "build-require-all": [
140
- "php build-scripts/generate-require-all.php"
141
- ]
142
- },
143
  "license": [
144
  "MIT"
145
  ],
@@ -148,15 +105,81 @@
148
  "name": "Bjørn Rosell",
149
  "homepage": "https://www.bitwise-it.dk/contact",
150
  "role": "Project Author"
151
- },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  {
153
  "name": "Martin Folkers",
154
  "homepage": "https://twobrain.io",
155
  "role": "Collaborator"
 
 
 
 
 
156
  }
157
  ],
158
  "description": "Convert JPEG & PNG to WebP with PHP",
159
  "keywords": [
 
160
  "cwebp",
161
  "gd",
162
  "image conversion",
@@ -165,31 +188,26 @@
165
  "jpg",
166
  "jpg2webp",
167
  "png",
168
- "png2webp",
169
- "webp"
170
  ],
171
- "support": {
172
- "source": "https://github.com/rosell-dk/webp-convert/tree/1.3.9",
173
- "issues": "https://github.com/rosell-dk/webp-convert/issues"
174
- },
175
- "time": "2019-03-21T07:34:21+00:00"
176
  },
177
  {
178
  "name": "rosell-dk/webp-convert-cloud-service",
179
- "version": "1.0.0",
180
  "source": {
181
  "type": "git",
182
  "url": "https://github.com/rosell-dk/webp-convert-cloud-service.git",
183
- "reference": "e250d49f4cb0fd0c07becbfa0abb7a1a31d03e37"
184
  },
185
  "dist": {
186
  "type": "zip",
187
- "url": "https://api.github.com/repos/rosell-dk/webp-convert-cloud-service/zipball/e250d49f4cb0fd0c07becbfa0abb7a1a31d03e37",
188
- "reference": "e250d49f4cb0fd0c07becbfa0abb7a1a31d03e37",
189
  "shasum": ""
190
  },
191
  "require": {
192
- "rosell-dk/webp-convert": "^1.2.1"
193
  },
194
  "require-dev": {
195
  "friendsofphp/php-cs-fixer": "^2.11",
@@ -212,39 +230,7 @@
212
  "WebPConvertCloudService\\": "src/"
213
  }
214
  },
215
- "autoload-dev": {
216
- "psr-4": {
217
- "WebPConvertCloudService\\Tests\\": "tests/"
218
- }
219
- },
220
- "scripts": {
221
- "ci": [
222
- "@test",
223
- "@phpcs-all",
224
- "@composer validate --no-check-all --strict"
225
- ],
226
- "cs-fix-all": [
227
- "php-cs-fixer fix src"
228
- ],
229
- "cs-fix": [
230
- "php-cs-fixer fix"
231
- ],
232
- "cs-dry": [
233
- "php-cs-fixer fix --dry-run --diff"
234
- ],
235
- "test": [
236
- "phpunit tests/"
237
- ],
238
- "phpcs": [
239
- "phpcs --standard=PSR2"
240
- ],
241
- "phpcs-all": [
242
- "phpcs --standard=PSR2 src"
243
- ],
244
- "phpcbf": [
245
- "phpcbf --standard=PSR2"
246
- ]
247
- },
248
  "license": [
249
  "MIT"
250
  ],
@@ -257,6 +243,7 @@
257
  ],
258
  "description": "Cloud service for converting JPEG & PNG to WebP",
259
  "keywords": [
 
260
  "cwebp",
261
  "gd",
262
  "image conversion",
@@ -265,14 +252,9 @@
265
  "jpg",
266
  "jpg2webp",
267
  "png",
268
- "png2webp",
269
- "webp"
270
  ],
271
- "support": {
272
- "source": "https://github.com/rosell-dk/webp-convert-cloud-service/tree/1.0.0",
273
- "issues": "https://github.com/rosell-dk/webp-convert-cloud-service/issues"
274
- },
275
- "time": "2018-11-09T11:17:43+00:00"
276
  }
277
  ],
278
  "packages-dev": [],
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": "d1905bfab23d538f20fae2c74bb476a5",
8
  "packages": [
9
  {
10
  "name": "rosell-dk/dom-util-for-webp",
62
  "time": "2019-03-07T09:15:07+00:00"
63
  },
64
  {
65
+ "name": "rosell-dk/image-mime-type-guesser",
66
+ "version": "0.3",
67
  "source": {
68
  "type": "git",
69
+ "url": "https://github.com/rosell-dk/image-mime-type-guesser.git",
70
+ "reference": "204fd61ca81e3b0ba46c6165dab8f74816b1fe99"
71
  },
72
  "dist": {
73
  "type": "zip",
74
+ "url": "https://api.github.com/repos/rosell-dk/image-mime-type-guesser/zipball/204fd61ca81e3b0ba46c6165dab8f74816b1fe99",
75
+ "reference": "204fd61ca81e3b0ba46c6165dab8f74816b1fe99",
76
  "shasum": ""
77
  },
78
  "require-dev": {
79
  "friendsofphp/php-cs-fixer": "^2.11",
80
+ "phpunit/phpunit": "^5.7.27",
81
  "squizlabs/php_codesniffer": "3.*"
82
  },
83
  "type": "library",
93
  },
94
  "autoload": {
95
  "psr-4": {
96
+ "ImageMimeTypeGuesser\\": "src/"
97
  }
98
  },
99
+ "notification-url": "https://packagist.org/downloads/",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  "license": [
101
  "MIT"
102
  ],
105
  "name": "Bjørn Rosell",
106
  "homepage": "https://www.bitwise-it.dk/contact",
107
  "role": "Project Author"
108
+ }
109
+ ],
110
+ "description": "Guess mime type of images",
111
+ "keywords": [
112
+ "image",
113
+ "images",
114
+ "mime",
115
+ "mime type"
116
+ ],
117
+ "time": "2019-03-29T09:33:28+00:00"
118
+ },
119
+ {
120
+ "name": "rosell-dk/webp-convert",
121
+ "version": "2.0.2",
122
+ "source": {
123
+ "type": "git",
124
+ "url": "https://github.com/rosell-dk/webp-convert.git",
125
+ "reference": "50f59ca429ebe2984a4054696db62bcfcf57d299"
126
+ },
127
+ "dist": {
128
+ "type": "zip",
129
+ "url": "https://api.github.com/repos/rosell-dk/webp-convert/zipball/50f59ca429ebe2984a4054696db62bcfcf57d299",
130
+ "reference": "50f59ca429ebe2984a4054696db62bcfcf57d299",
131
+ "shasum": ""
132
+ },
133
+ "require": {
134
+ "php": "^5.6 | ^7.0",
135
+ "rosell-dk/image-mime-type-guesser": "^0.3"
136
+ },
137
+ "require-dev": {
138
+ "friendsofphp/php-cs-fixer": "^2.11",
139
+ "phpunit/phpunit": "5.7.27",
140
+ "squizlabs/php_codesniffer": "3.*"
141
+ },
142
+ "suggest": {
143
+ "ext-gd": "to use GD extension for converting. Note: Gd must be compiled with webp support",
144
+ "ext-imagick": "to use Imagick extension for converting. Note: Gd must be compiled with webp support",
145
+ "ext-vips": "to use Vips extension for converting.",
146
+ "php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
147
+ },
148
+ "type": "library",
149
+ "extra": {
150
+ "scripts-descriptions": {
151
+ "ci": "Run tests before CI",
152
+ "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
153
+ "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
154
+ "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
155
+ "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
156
+ "test": "Launches the preconfigured PHPUnit"
157
+ }
158
+ },
159
+ "autoload": {
160
+ "psr-4": {
161
+ "WebPConvert\\": "src/"
162
+ }
163
+ },
164
+ "notification-url": "https://packagist.org/downloads/",
165
+ "license": [
166
+ "MIT"
167
+ ],
168
+ "authors": [
169
  {
170
  "name": "Martin Folkers",
171
  "homepage": "https://twobrain.io",
172
  "role": "Collaborator"
173
+ },
174
+ {
175
+ "name": "Bjørn Rosell",
176
+ "homepage": "https://www.bitwise-it.dk/contact",
177
+ "role": "Project Author"
178
  }
179
  ],
180
  "description": "Convert JPEG & PNG to WebP with PHP",
181
  "keywords": [
182
+ "Webp",
183
  "cwebp",
184
  "gd",
185
  "image conversion",
188
  "jpg",
189
  "jpg2webp",
190
  "png",
191
+ "png2webp"
 
192
  ],
193
+ "time": "2019-06-15T11:45:10+00:00"
 
 
 
 
194
  },
195
  {
196
  "name": "rosell-dk/webp-convert-cloud-service",
197
+ "version": "2.0.0",
198
  "source": {
199
  "type": "git",
200
  "url": "https://github.com/rosell-dk/webp-convert-cloud-service.git",
201
+ "reference": "5345c63d0a44d529591c36ebab33c3bcfc080787"
202
  },
203
  "dist": {
204
  "type": "zip",
205
+ "url": "https://api.github.com/repos/rosell-dk/webp-convert-cloud-service/zipball/5345c63d0a44d529591c36ebab33c3bcfc080787",
206
+ "reference": "5345c63d0a44d529591c36ebab33c3bcfc080787",
207
  "shasum": ""
208
  },
209
  "require": {
210
+ "rosell-dk/webp-convert": "^2.0.0"
211
  },
212
  "require-dev": {
213
  "friendsofphp/php-cs-fixer": "^2.11",
230
  "WebPConvertCloudService\\": "src/"
231
  }
232
  },
233
+ "notification-url": "https://packagist.org/downloads/",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  "license": [
235
  "MIT"
236
  ],
243
  ],
244
  "description": "Cloud service for converting JPEG & PNG to WebP",
245
  "keywords": [
246
+ "Webp",
247
  "cwebp",
248
  "gd",
249
  "image conversion",
252
  "jpg",
253
  "jpg2webp",
254
  "png",
255
+ "png2webp"
 
256
  ],
257
+ "time": "2019-06-14T13:05:09+00:00"
 
 
 
 
258
  }
259
  ],
260
  "packages-dev": [],
lib/classes/AdminInit.php CHANGED
@@ -30,7 +30,7 @@ class AdminInit
30
  public static function runMigrationIfNeeded()
31
  {
32
  // When an update requires a migration, the number should be increased
33
- define('WEBPEXPRESS_MIGRATION_VERSION', '8');
34
 
35
  if (WEBPEXPRESS_MIGRATION_VERSION != Option::getOption('webp-express-migration-version', 0)) {
36
  // run migration logic
@@ -38,7 +38,7 @@ class AdminInit
38
  }
39
 
40
  // uncomment next line to test-run a migration
41
- //include WEBPEXPRESS_PLUGIN_DIR . '/lib/migrate/migrate8.php';
42
  }
43
 
44
  public static function adminInitHandler()
@@ -66,6 +66,7 @@ class AdminInit
66
  add_action("admin_post_webpexpress_settings_submit", array('\WebPExpress\OptionsPageHooks', 'submitHandler'));
67
  add_action("admin_init", array('\WebPExpress\AdminInit', 'adminInitHandler'));
68
 
 
69
  // Print pending messages, if any
70
  if (Option::getOption('webp-express-messages-pending')) {
71
  add_action(Multisite::isNetworkActivated() ? 'network_admin_notices' : 'admin_notices', array('\WebPExpress\Messenger', 'printPendingMessages'));
@@ -79,8 +80,10 @@ class AdminInit
79
 
80
  // Ajax actions
81
  add_action('wp_ajax_list_unconverted_files', array('\WebPExpress\BulkConvert', 'processAjaxListUnconvertedFiles'));
82
- add_action('wp_ajax_convert_file', array('\WebPExpress\BulkConvert', 'processAjaxConvertFile'));
 
83
  add_action('wp_ajax_webpexpress_purge_cache', array('\WebPExpress\CachePurge', 'processAjaxPurgeCache'));
 
84
 
85
  // PS:
86
  // Filters for processing upload hooks in order to convert images upon upload (wp_handle_upload / image_make_intermediate_size)
30
  public static function runMigrationIfNeeded()
31
  {
32
  // When an update requires a migration, the number should be increased
33
+ define('WEBPEXPRESS_MIGRATION_VERSION', '9');
34
 
35
  if (WEBPEXPRESS_MIGRATION_VERSION != Option::getOption('webp-express-migration-version', 0)) {
36
  // run migration logic
38
  }
39
 
40
  // uncomment next line to test-run a migration
41
+ //include WEBPEXPRESS_PLUGIN_DIR . '/lib/migrate/migrate9.php';
42
  }
43
 
44
  public static function adminInitHandler()
66
  add_action("admin_post_webpexpress_settings_submit", array('\WebPExpress\OptionsPageHooks', 'submitHandler'));
67
  add_action("admin_init", array('\WebPExpress\AdminInit', 'adminInitHandler'));
68
 
69
+
70
  // Print pending messages, if any
71
  if (Option::getOption('webp-express-messages-pending')) {
72
  add_action(Multisite::isNetworkActivated() ? 'network_admin_notices' : 'admin_notices', array('\WebPExpress\Messenger', 'printPendingMessages'));
80
 
81
  // Ajax actions
82
  add_action('wp_ajax_list_unconverted_files', array('\WebPExpress\BulkConvert', 'processAjaxListUnconvertedFiles'));
83
+ add_action('wp_ajax_convert_file', array('\WebPExpress\Convert', 'processAjaxConvertFile'));
84
+ add_action('wp_ajax_webpexpress_view_log', array('\WebPExpress\ConvertLog', 'processAjaxViewLog'));
85
  add_action('wp_ajax_webpexpress_purge_cache', array('\WebPExpress\CachePurge', 'processAjaxPurgeCache'));
86
+ add_action('wp_ajax_webpexpress_dismiss_message', array('\WebPExpress\DismissableMessages', 'processAjaxDismissMessage'));
87
 
88
  // PS:
89
  // Filters for processing upload hooks in order to convert images upon upload (wp_handle_upload / image_make_intermediate_size)
lib/classes/AlterHtmlHelper.php CHANGED
@@ -198,9 +198,8 @@ class AlterHtmlHelper
198
  * Get url for webp
199
  * returns second argument if no webp
200
  *
201
- * @param $imageUrl (ie http://example.com/wp-content/image.jpg)
202
- * @param $baseUrl (ie http://example.com/wp-content)
203
- * @param $baseDir (ie /var/www/example.com/wp-content)
204
  */
205
  public static function getWebPUrl($sourceUrl, $returnValueOnFail)
206
  {
198
  * Get url for webp
199
  * returns second argument if no webp
200
  *
201
+ * @param $sourceUrl
202
+ * @param $returnValueOnFail
 
203
  */
204
  public static function getWebPUrl($sourceUrl, $returnValueOnFail)
205
  {
lib/classes/BulkConvert.php CHANGED
@@ -204,14 +204,4 @@ class BulkConvert
204
  wp_die();
205
  }
206
 
207
- public static function processAjaxConvertFile()
208
- {
209
- $filename = $_POST['filename'];
210
-
211
- $result = Convert::convertFile($filename);
212
-
213
- echo json_encode($result, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT);
214
- wp_die();
215
- }
216
-
217
  }
204
  wp_die();
205
  }
206
 
 
 
 
 
 
 
 
 
 
 
207
  }
lib/classes/CachePurge.php CHANGED
@@ -4,6 +4,7 @@ namespace WebPExpress;
4
 
5
  use \WebPExpress\Convert;
6
  use \WebPExpress\FileHelper;
 
7
  use \WebPExpress\Paths;
8
 
9
  class CachePurge
@@ -15,6 +16,8 @@ class CachePurge
15
  */
16
  public static function purge($config, $onlyPng)
17
  {
 
 
18
  $filter = [
19
  'only-png' => $onlyPng,
20
  'only-with-corresponding-original' => false
4
 
5
  use \WebPExpress\Convert;
6
  use \WebPExpress\FileHelper;
7
+ use \WebPExpress\DismissableMessages;
8
  use \WebPExpress\Paths;
9
 
10
  class CachePurge
16
  */
17
  public static function purge($config, $onlyPng)
18
  {
19
+ DismissableMessages::dismissMessage('0.14.0/suggest-wipe-because-lossless');
20
+
21
  $filter = [
22
  'only-png' => $onlyPng,
23
  'only-with-corresponding-original' => false
lib/classes/Config.php CHANGED
@@ -58,7 +58,7 @@ class Config
58
  'operation-mode' => 'varied-image-responses',
59
 
60
  // general
61
- 'image-types' => 1,
62
  'destination-folder' => 'separate',
63
  'destination-extension' => 'append',
64
  'cache-control' => 'no-header', /* can be "no-header", "set" or "custom" */
@@ -76,11 +76,22 @@ class Config
76
  'enable-redirection-to-webp-realizer' => true,
77
 
78
  // conversion options
79
- 'converters' => [],
 
 
80
  'quality-auto' => $qualityAuto,
81
  'max-quality' => 80,
82
  'quality-specific' => 70,
 
 
 
 
 
 
 
 
83
  'metadata' => 'none',
 
84
  'convert-on-upload' => true,
85
 
86
  // serve options
@@ -127,20 +138,23 @@ class Config
127
 
128
  if ($config['operation-mode'] == 'varied-image-responses') {
129
  $config = array_merge($config, [
 
130
  'enable-redirection-to-converter' => true,
131
  'only-redirect-to-converter-for-webp-enabled-browsers' => true,
132
  'only-redirect-to-converter-on-cache-miss' => false,
133
  'do-not-pass-source-in-query-string' => true, // Will be removed in 0.13
134
- //'redirect-to-existing-in-htaccess' => true,
135
  'fail' => 'original',
136
  'success-response' => 'converted',
137
  ]);
138
  } elseif ($config['operation-mode'] == 'cdn-friendly') {
139
  $config = array_merge($config, [
 
 
 
140
  'only-redirect-to-converter-for-webp-enabled-browsers' => false,
141
  'only-redirect-to-converter-on-cache-miss' => true,
 
142
  'do-not-pass-source-in-query-string' => true, // Will be removed in 0.13
143
- 'redirect-to-existing-in-htaccess' => false,
144
  'fail' => 'original',
145
  'success-response' => 'original',
146
  // cache-control => 'no-header' (we do not need this, as it is not important what it is set to in cdn-friendly mode, and we dont the value to be lost when switching operation mode)
@@ -181,6 +195,10 @@ class Config
181
  $config['alter-html'] = array_replace_recursive($defaultConfig['alter-html'], $config['alter-html']);
182
  }
183
 
 
 
 
 
184
  $config = self::applyOperationMode($config);
185
 
186
  if (!isset($config['web-service'])) {
@@ -211,7 +229,10 @@ class Config
211
  ConvertersHelper::$defaultConverters
212
  );
213
  } else {
 
 
214
 
 
215
  // This is first time visit!
216
  // We must add converters.
217
  // We want to order them according to which ones that are working,
@@ -225,6 +246,7 @@ class Config
225
 
226
  $defaultConverters = ConvertersHelper::$defaultConverters;
227
 
 
228
  if (count($workingConverters) == 0) {
229
  // No converters are working
230
  // Send ewww converter to top
@@ -255,6 +277,7 @@ class Config
255
  }
256
  $config['converters'] = array_merge($resultPart1, $resultPart2);
257
  }
 
258
  }
259
 
260
 
@@ -274,9 +297,11 @@ class Config
274
  /**
275
  * Loads Config (if available), fills in the rest with defaults
276
  * also applies operation mode.
 
277
  */
278
  public static function loadConfigAndFix($checkQualityDetection = true)
279
  {
 
280
  return self::fix(Config::loadConfig(), $checkQualityDetection);
281
  }
282
 
@@ -466,14 +491,26 @@ class Config
466
 
467
  public static function generateWodOptionsFromConfigObj($config)
468
  {
469
- $options = $config;
470
- $options['converters'] = [];
471
- foreach ($config['converters'] as $converter) {
472
- if (isset($converter['deactivated']) && ($converter['deactivated'])) continue;
473
 
474
- $options['converters'][] = $converter;
 
 
 
 
 
 
 
 
 
 
 
475
  }
476
- foreach ($options['converters'] as &$c) {
 
 
 
 
 
477
  if ($c['converter'] == 'cwebp') {
478
  if (isset($c['options']['set-size']) && $c['options']['set-size']) {
479
  unset($c['options']['set-size']);
@@ -482,6 +519,9 @@ class Config
482
  unset($c['options']['size-in-percentage']);
483
  }
484
  }
 
 
 
485
  unset ($c['id']);
486
  unset($c['working']);
487
  unset($c['error']);
@@ -489,56 +529,109 @@ class Config
489
  if (isset($c['options']['quality']) && ($c['options']['quality'] == 'inherit')) {
490
  unset ($c['options']['quality']);
491
  }
 
492
  if (!isset($c['options'])) {
493
  $c = $c['converter'];
494
- }
495
  }
496
 
497
- if (isset($options['cache-control'])) {
498
- $options['cache-control-header'] = self::getCacheControlHeader($config);
499
- }
500
 
501
- $auto = (isset($options['quality-auto']) && $options['quality-auto']);
502
- $qualitySpecific = (isset($options['quality-specific']) ? $options['quality-specific'] : 70);
 
 
 
503
  if ($auto) {
504
- $options['quality'] = 'auto';
505
- } else {
506
- $options['quality'] = $qualitySpecific;
507
- unset ($options['max-quality']);
 
 
 
 
 
508
  }
509
- unset($options['quality-auto']);
510
- unset($options['quality-specific']);
511
 
512
- unset($options['image-types']);
513
- unset($options['cache-control']);
514
- unset($options['cache-control-custom']);
515
- unset($options['cache-control-public']);
516
- unset($options['cache-control-max-age']);
517
- unset($options['paths-used-in-htaccess']);
518
- unset($options['web-service']);
519
- unset($options['alter-html']);
520
- unset($options['enable-redirection-to-converter']);
521
- unset($options['operation-mode']);
522
- unset($options['only-redirect-to-converter-for-webp-enabled-browsers']);
523
- unset($options['only-redirect-to-converter-on-cache-miss']);
524
- unset($options['enable-redirection-to-webp-realizer']);
525
- unset($options['convert-on-upload']);
 
 
 
526
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
527
 
528
- //unset($options['']);
529
- //unset($options['']);
530
- //unset($options['']);
531
 
 
 
532
 
 
533
 
534
- //unset($options['forward-query-string']); // It is used in webp-on-demand.php, so do not unset!
535
- unset($options['do-not-pass-source-in-query-string']);
536
- unset($options['redirect-to-existing-in-htaccess']);
 
537
 
538
- $options['paths'] = [
539
- 'uploadDirRel' => Paths::getUploadDirRel()
 
 
 
540
  ];
541
 
 
542
  return $options;
543
  }
544
 
@@ -557,6 +650,9 @@ class Config
557
  */
558
  public static function saveConfigurationFileAndWodOptions($config)
559
  {
 
 
 
560
  if (!(self::saveConfigurationFile($config))) {
561
  return false;
562
  }
@@ -580,6 +676,10 @@ class Config
580
  $rewriteRulesNeedsUpdate = HTAccess::doesRewriteRulesNeedUpdate($config);
581
  }
582
 
 
 
 
 
583
  if (self::saveConfigurationFile($config)) {
584
  $options = self::generateWodOptionsFromConfigObj($config);
585
  if (self::saveWodOptionsFile($options)) {
58
  'operation-mode' => 'varied-image-responses',
59
 
60
  // general
61
+ 'image-types' => 3,
62
  'destination-folder' => 'separate',
63
  'destination-extension' => 'append',
64
  'cache-control' => 'no-header', /* can be "no-header", "set" or "custom" */
76
  'enable-redirection-to-webp-realizer' => true,
77
 
78
  // conversion options
79
+ 'jpeg-encoding' => 'auto',
80
+ 'jpeg-enable-near-lossless' => true,
81
+ 'jpeg-near-lossless' => 60,
82
  'quality-auto' => $qualityAuto,
83
  'max-quality' => 80,
84
  'quality-specific' => 70,
85
+
86
+ 'png-encoding' => 'auto',
87
+ 'png-enable-near-lossless' => true,
88
+ 'png-near-lossless' => 60,
89
+ 'png-quality' => 85,
90
+ 'alpha-quality' => 80,
91
+
92
+ 'converters' => [],
93
  'metadata' => 'none',
94
+ //'log-call-arguments' => true,
95
  'convert-on-upload' => true,
96
 
97
  // serve options
138
 
139
  if ($config['operation-mode'] == 'varied-image-responses') {
140
  $config = array_merge($config, [
141
+ //'redirect-to-existing-in-htaccess' => true, // this can now be configured, so do not apply
142
  'enable-redirection-to-converter' => true,
143
  'only-redirect-to-converter-for-webp-enabled-browsers' => true,
144
  'only-redirect-to-converter-on-cache-miss' => false,
145
  'do-not-pass-source-in-query-string' => true, // Will be removed in 0.13
 
146
  'fail' => 'original',
147
  'success-response' => 'converted',
148
  ]);
149
  } elseif ($config['operation-mode'] == 'cdn-friendly') {
150
  $config = array_merge($config, [
151
+ 'redirect-to-existing-in-htaccess' => false,
152
+ 'enable-redirection-to-converter' => false,
153
+ /*
154
  'only-redirect-to-converter-for-webp-enabled-browsers' => false,
155
  'only-redirect-to-converter-on-cache-miss' => true,
156
+ */
157
  'do-not-pass-source-in-query-string' => true, // Will be removed in 0.13
 
158
  'fail' => 'original',
159
  'success-response' => 'original',
160
  // cache-control => 'no-header' (we do not need this, as it is not important what it is set to in cdn-friendly mode, and we dont the value to be lost when switching operation mode)
195
  $config['alter-html'] = array_replace_recursive($defaultConfig['alter-html'], $config['alter-html']);
196
  }
197
 
198
+ if (!isset($config['base-htaccess-on-these-capability-tests'])) {
199
+ self::runAndStoreCapabilityTests($config);
200
+ }
201
+
202
  $config = self::applyOperationMode($config);
203
 
204
  if (!isset($config['web-service'])) {
229
  ConvertersHelper::$defaultConverters
230
  );
231
  } else {
232
+ // This is first time visit!
233
+ $config['converters'] = ConvertersHelper::$defaultConverters;
234
 
235
+ /*
236
  // This is first time visit!
237
  // We must add converters.
238
  // We want to order them according to which ones that are working,
246
 
247
  $defaultConverters = ConvertersHelper::$defaultConverters;
248
 
249
+
250
  if (count($workingConverters) == 0) {
251
  // No converters are working
252
  // Send ewww converter to top
277
  }
278
  $config['converters'] = array_merge($resultPart1, $resultPart2);
279
  }
280
+ */
281
  }
282
 
283
 
297
  /**
298
  * Loads Config (if available), fills in the rest with defaults
299
  * also applies operation mode.
300
+ * If config is not saved yet, the default config will be returned
301
  */
302
  public static function loadConfigAndFix($checkQualityDetection = true)
303
  {
304
+ // PS: Yes, loadConfig may return false. "fix" handles this by returning default config
305
  return self::fix(Config::loadConfig(), $checkQualityDetection);
306
  }
307
 
491
 
492
  public static function generateWodOptionsFromConfigObj($config)
493
  {
 
 
 
 
494
 
495
+ // WebP convert options
496
+ // --------------------
497
+ $wc = [
498
+ 'converters' => []
499
+ ];
500
+
501
+ // Add active converters
502
+ foreach ($config['converters'] as $converter) {
503
+ if (isset($converter['deactivated']) && ($converter['deactivated'])) {
504
+ continue;
505
+ }
506
+ $wc['converters'][] = $converter;
507
  }
508
+
509
+ // Clean the converter options from junk
510
+ foreach ($wc['converters'] as &$c) {
511
+
512
+ // In cwebp converter options (here in webp express), we have a checkbox "set size"
513
+ // - there is no such option in webp-convert - so remove.
514
  if ($c['converter'] == 'cwebp') {
515
  if (isset($c['options']['set-size']) && $c['options']['set-size']) {
516
  unset($c['options']['set-size']);
519
  unset($c['options']['size-in-percentage']);
520
  }
521
  }
522
+
523
+ // 'id', 'working' and 'error' attributes are used internally in webp-express,
524
+ // no need to have it in the wod configuration file.
525
  unset ($c['id']);
526
  unset($c['working']);
527
  unset($c['error']);
529
  if (isset($c['options']['quality']) && ($c['options']['quality'] == 'inherit')) {
530
  unset ($c['options']['quality']);
531
  }
532
+ /*
533
  if (!isset($c['options'])) {
534
  $c = $c['converter'];
535
+ }*/
536
  }
537
 
538
+ // Create jpeg options
539
+ // https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#png-og-jpeg-specific-options
 
540
 
541
+ $auto = (isset($config['quality-auto']) && $config['quality-auto']);
542
+ $wc['jpeg'] = [
543
+ 'encoding' => $config['jpeg-encoding'],
544
+ 'quality' => ($auto ? 'auto' : $config['quality-specific']),
545
+ ];
546
  if ($auto) {
547
+ $wc['jpeg']['default-quality'] = $config['quality-specific'];
548
+ $wc['jpeg']['max-quality'] = $config['max-quality'];
549
+ }
550
+ if ($config['jpeg-encoding'] != 'lossy') {
551
+ if ($config['jpeg-enable-near-lossless']) {
552
+ $wc['jpeg']['near-lossless'] = $config['jpeg-near-lossless'];
553
+ } else {
554
+ $wc['jpeg']['near-lossless'] = 100;
555
+ }
556
  }
 
 
557
 
558
+ // Create png options
559
+ // ---
560
+ $wc['png'] = [
561
+ 'encoding' => $config['png-encoding'],
562
+ 'quality' => $config['png-quality'],
563
+ ];
564
+ if ($config['png-encoding'] != 'lossy') {
565
+ if ($config['png-enable-near-lossless']) {
566
+ $wc['png']['near-lossless'] = $config['png-near-lossless'];
567
+ } else {
568
+ $wc['png']['near-lossless'] = 100;
569
+ }
570
+ }
571
+ if ($config['png-encoding'] != 'lossless') {
572
+ // Only relevant for pngs, and only for "lossy" (and thus also "auto")
573
+ $wc['png']['alpha-quality'] = $config['alpha-quality'];
574
+ }
575
 
576
+ // Other convert options
577
+ $wc['metadata'] = $config['metadata'];
578
+ $wc['log-call-arguments'] = true; // $config['log-call-arguments'];
579
+
580
+ // Serve options
581
+ // -------------
582
+ $serve = [
583
+ 'serve-image' => [
584
+ 'headers' => [
585
+ 'cache-control' => false,
586
+ 'content-length' => true,
587
+ 'content-type' => true,
588
+ 'expires' => false,
589
+ 'last-modified' => true,
590
+ //'vary-accept' => false // This must be different for webp-on-demand and webp-realizer
591
+ ]
592
+ ]
593
+ ];
594
+ if ($config['cache-control'] != 'no-header') {
595
+ $serve['serve-image']['cache-control-header'] = self::getCacheControlHeader($config);
596
+ $serve['serve-image']['headers']['cache-control'] = true;
597
+ $serve['serve-image']['headers']['expires'] = true;
598
+ }
599
+ $serve['fail'] = $config['fail'];
600
+
601
+
602
+ // WOD options
603
+ // -------------
604
+ $wod = [
605
+ 'base-htaccess-on-these-capability-tests' => $config['base-htaccess-on-these-capability-tests'],
606
+ 'destination-extension' => $config['destination-extension'],
607
+ 'destination-folder' => $config['destination-folder'],
608
+ 'forward-query-string' => $config['forward-query-string'],
609
+ //'method-for-passing-source' => $config['method-for-passing-source'],
610
+ 'paths' => [
611
+ 'uploadDirRel' => Paths::getUploadDirRel()
612
+ ],
613
+ 'success-response' => $config['success-response'],
614
+ ];
615
 
 
 
 
616
 
617
+ // Put it all together
618
+ // -------------
619
 
620
+ //$options = array_merge($wc, $serve, $wod);
621
 
622
+ // I'd like to put the webp-convert options in its own key,
623
+ // but it requires some work. Postponing it to another day that I can uncomment the two next lines (and remove the one above)
624
+ //$wc = array_merge($wc, $serve);
625
+ //$options = array_merge($wod, ['webp-convert' => $wc]);
626
 
627
+ //$options = array_merge($wod, array_merge($serve, ['conversion' => $wc]));
628
+
629
+ $options = [
630
+ 'wod' => $wod,
631
+ 'webp-convert' => array_merge($serve, ['convert' => $wc])
632
  ];
633
 
634
+
635
  return $options;
636
  }
637
 
650
  */
651
  public static function saveConfigurationFileAndWodOptions($config)
652
  {
653
+ if (!isset($config['base-htaccess-on-these-capability-tests'])) {
654
+ self::runAndStoreCapabilityTests($config);
655
+ }
656
  if (!(self::saveConfigurationFile($config))) {
657
  return false;
658
  }
676
  $rewriteRulesNeedsUpdate = HTAccess::doesRewriteRulesNeedUpdate($config);
677
  }
678
 
679
+ if (!isset($config['base-htaccess-on-these-capability-tests'])) {
680
+ self::runAndStoreCapabilityTests($config);
681
+ }
682
+
683
  if (self::saveConfigurationFile($config)) {
684
  $options = self::generateWodOptionsFromConfigObj($config);
685
  if (self::saveWodOptionsFile($options)) {
lib/classes/Convert.php CHANGED
@@ -8,6 +8,7 @@ namespace WebPExpress;
8
 
9
  use \WebPExpress\ConvertHelperIndependent;
10
  use \WebPExpress\Config;
 
11
 
12
  class Convert
13
  {
@@ -26,19 +27,27 @@ class Convert
26
  );
27
  }
28
 
29
- public static function convertFile($source, $config = null)
30
  {
31
  if (is_null($config)) {
32
  $config = Config::loadConfigAndFix();
33
  }
34
- $options = Config::generateWodOptionsFromConfigObj($config);
 
 
 
 
 
 
35
 
36
  $destination = self::getDestination($source, $config);
37
 
38
- $result = ConvertHelperIndependent::convert($source, $destination, $options);
 
 
39
 
40
  //$result['destination'] = $destination;
41
- if ($result['success']) {
42
  $result['filesize-original'] = @filesize($source);
43
  $result['filesize-webp'] = @filesize($destination);
44
  }
@@ -58,4 +67,49 @@ class Convert
58
  );
59
  }
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
8
 
9
  use \WebPExpress\ConvertHelperIndependent;
10
  use \WebPExpress\Config;
11
+ use \WebpExpress\ConvertersHelper;
12
 
13
  class Convert
14
  {
27
  );
28
  }
29
 
30
+ public static function convertFile($source, $config = null, $convertOptions = null, $converter = null)
31
  {
32
  if (is_null($config)) {
33
  $config = Config::loadConfigAndFix();
34
  }
35
+ if (is_null($convertOptions)) {
36
+ $convertOptions = Config::generateWodOptionsFromConfigObj($config)['webp-convert']['convert'];
37
+ }
38
+ /*
39
+ if (isset($config['converter'])) {
40
+ $options['convert']['converter'] = $config['converter'];
41
+ }*/
42
 
43
  $destination = self::getDestination($source, $config);
44
 
45
+ $logDir = Paths::getWebPExpressContentDirAbs() . '/log';
46
+
47
+ $result = ConvertHelperIndependent::convert($source, $destination, $convertOptions, $logDir, $converter);
48
 
49
  //$result['destination'] = $destination;
50
+ if ($result['success'] === true) {
51
  $result['filesize-original'] = @filesize($source);
52
  $result['filesize-webp'] = @filesize($destination);
53
  }
67
  );
68
  }
69
 
70
+ public static function processAjaxConvertFile()
71
+ {
72
+ $filename = $_POST['filename'];
73
+
74
+ if (isset($_POST['config-overrides'])) {
75
+ $config = Config::loadConfigAndFix();
76
+
77
+ // overrides
78
+ $overrides = $_POST['config-overrides'];
79
+ $overrides = preg_replace('/\\\\"/', '"', $overrides); // We got crazy encoding, perhaps by jQuery. This cleans it up
80
+ $overrides = json_decode($overrides, true);
81
+
82
+ $config = array_merge($config, $overrides);
83
+
84
+ // single converter
85
+ $converter = null;
86
+ $convertOptions = null;
87
+ if (isset($_POST['converter'])) {
88
+ $converter = $_POST['converter'];
89
+
90
+ // find converter
91
+ $c = ConvertersHelper::getConverterById($config, $converter);
92
+ if ($c !== false) {
93
+
94
+ $convertOptions = Config::generateWodOptionsFromConfigObj($config)['webp-convert']['convert'];
95
+ $convertOptions = array_merge($convertOptions, $c['options']);
96
+ unset($convertOptions['converters']);
97
+
98
+ $config = array_merge($config, $c['options']);
99
+ //echo 'options: <pre>' . print_r($convertOptions, true) . '</pre>'; exit;
100
+ //echo 'options: <pre>' . print_r($c['options'], true) . '</pre>'; exit;
101
+ }
102
+ }
103
+
104
+ $result = self::convertFile($filename, $config, $convertOptions, $converter);
105
+
106
+ } else {
107
+ $result = self::convertFile($filename);
108
+ }
109
+
110
+
111
+ echo json_encode($result, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT);
112
+ wp_die();
113
+ }
114
+
115
  }
lib/classes/ConvertHelperIndependent.php CHANGED
@@ -7,8 +7,10 @@ It is used by webp-on-demand.php, which does not register an auto loader. It is
7
  namespace WebPExpress;
8
 
9
  use \WebPConvert\WebPConvert;
 
10
  use \WebPConvert\Loggers\BufferLogger;
11
  use \WebPExpress\FileHelper;
 
12
 
13
 
14
  class ConvertHelperIndependent
@@ -178,18 +180,106 @@ class ConvertHelperIndependent
178
  }
179
  }
180
 
181
- public static function convert($source, $destination, $options) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  include_once __DIR__ . '/../../vendor/autoload.php';
183
 
184
  $success = false;
185
  $msg = '';
186
  $logger = new BufferLogger();
187
  try {
188
- $success = WebPConvert::convert($source, $destination, $options, $logger);
189
- } catch (\Exception $e) {
 
 
 
 
 
 
 
 
 
 
190
  $msg = $e->getMessage();
 
 
 
 
 
 
191
  }
192
 
 
 
193
  return [
194
  'success' => $success,
195
  'msg' => $msg,
@@ -198,4 +288,22 @@ class ConvertHelperIndependent
198
 
199
  }
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  }
7
  namespace WebPExpress;
8
 
9
  use \WebPConvert\WebPConvert;
10
+ use \WebPConvert\Convert\ConverterFactory;
11
  use \WebPConvert\Loggers\BufferLogger;
12
  use \WebPExpress\FileHelper;
13
+ use WebPConvert\Exceptions\WebPConvertException;
14
 
15
 
16
  class ConvertHelperIndependent
180
  }
181
  }
182
 
183
+ public static function getLogFilename($source, $logDir)
184
+ {
185
+ // Calculate path for log file
186
+ // ---------------------------
187
+ $logDir .= '/conversions';
188
+ $docRoot = rtrim(realpath($_SERVER["DOCUMENT_ROOT"]), '/');
189
+
190
+ // Check if source is residing inside document root.
191
+ // (it is, if path starts with document root + '/')
192
+ if (self::sourceIsInsideDocRoot($source, $docRoot) ) {
193
+
194
+ // We store relative to document root.
195
+ // "Eat" the left part off the source parameter which contains the document root.
196
+ // and also eat the slash (+1)
197
+ $sourceRel = substr($source, strlen($docRoot) + 1);
198
+ return $logDir . '/doc-root/' . $sourceRel . '.md';
199
+ } else {
200
+ // Source file is residing outside document root.
201
+ // we must add complete path to structure
202
+ return $logDir . '/abs' . $source . '.md';
203
+ }
204
+
205
+ }
206
+
207
+ public static function createLogDir($logDir)
208
+ {
209
+ $logDir = Paths::getLogDirAbs();
210
+
211
+ if (!is_dir($logDir)) {
212
+ @mkdir($logDir, 0775, true);
213
+ @chmod($logDir, 0775);
214
+ @file_put_contents(rtrim($logDir . '/') . '/.htaccess', <<<APACHE
215
+ <IfModule mod_authz_core.c>
216
+ Require all denied
217
+ </IfModule>
218
+ <IfModule !mod_authz_core.c>
219
+ Order deny,allow
220
+ Deny from all
221
+ </IfModule>
222
+ APACHE
223
+ );
224
+ @chmod($logDir . '/.htaccess', 0664);
225
+ }
226
+ return is_dir($logDir);
227
+ }
228
+
229
+ public static function saveLog($source, $logDir, $text, $msgTop)
230
+ {
231
+ if (!file_exists($logDir)) {
232
+ self::createLogDir($logDir);
233
+ }
234
+
235
+ $text = preg_replace('#' . preg_quote($_SERVER["DOCUMENT_ROOT"]) . '#', '[doc-root]', $text);
236
+
237
+ $text = 'WebP Express 0.14.0. ' . $msgTop . ', ' . date("Y-m-d H:i:s") . "\n\r\n\r" . $text;
238
+
239
+ $logFile = self::getLogFilename($source, $logDir);
240
+
241
+ $logFolder = @dirname($logFile);
242
+ if (!@file_exists($logFolder)) {
243
+ mkdir($logFolder, 0777, true);
244
+ }
245
+ if (@file_exists($logFolder)) {
246
+ file_put_contents($logFile, $text);
247
+ }
248
+ }
249
+
250
+ /**
251
+ * To convert with a specific converter, set it in the $converter param.
252
+ */
253
+ public static function convert($source, $destination, $convertOptions, $logDir, $converter = null) {
254
  include_once __DIR__ . '/../../vendor/autoload.php';
255
 
256
  $success = false;
257
  $msg = '';
258
  $logger = new BufferLogger();
259
  try {
260
+ if (!is_null($converter)) {
261
+ //if (isset($convertOptions['converter'])) {
262
+ //print_r($convertOptions);exit;
263
+ $logger->logLn('Converter set to: ' . $converter);
264
+ $logger->logLn('');
265
+ $converter = ConverterFactory::makeConverter($converter, $source, $destination, $convertOptions, $logger);
266
+ $converter->doConvert();
267
+ } else {
268
+ WebPConvert::convert($source, $destination, $convertOptions, $logger);
269
+ }
270
+ $success = true;
271
+ } catch (\WebpConvert\Exceptions\WebPConvertException $e) {
272
  $msg = $e->getMessage();
273
+ } catch (\Exception $e) {
274
+ $msg = 'An exception was thrown!';
275
+ } catch (Throwable $e) {
276
+ //Executed only in PHP 7, will not match in PHP 5
277
+ //$msg = $e->getMessage();
278
+ //$msg = 'oh no';
279
  }
280
 
281
+ self::saveLog($source, $logDir, $logger->getMarkDown("\n\r"), 'Conversion triggered using bulk conversion');
282
+
283
  return [
284
  'success' => $success,
285
  'msg' => $msg,
288
 
289
  }
290
 
291
+ public static function serveConverted($source, $destination, $serveOptions, $logDir, $logMsgTop = '')
292
+ {
293
+ include_once __DIR__ . '/../../vendor/autoload.php';
294
+
295
+ // TODO: error_log()
296
+ //ini_set('display_errors', 0);
297
+ //error_reporting(0);
298
+
299
+ //echo '<pre>' . print_r($serveOptions, true) . '</pre>'; exit;
300
+
301
+ $convertLogger = new BufferLogger();
302
+ WebPConvert::serveConverted($source, $destination, $serveOptions, null, $convertLogger);
303
+ $convertLog = $convertLogger->getMarkDown("\n\r");
304
+ if ($convertLog != '') {
305
+ self::saveLog($source, $logDir, $convertLog, $logMsgTop);
306
+ }
307
+
308
+ }
309
  }
lib/classes/ConvertLog.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ use \WebPExpress\ConvertHelperIndependent;
6
+ use \WebPExpress\Paths;
7
+
8
+ class ConvertLog
9
+ {
10
+ public static function processAjaxViewLog()
11
+ {
12
+ $source = $_POST['source'];
13
+
14
+ $logFile = ConvertHelperIndependent::getLogFilename($source, Paths::getLogDirAbs());
15
+ $msg = 'Log file: <i>' . $logFile . '</i><br><br><hr>';
16
+
17
+ if (!file_exists($logFile)) {
18
+ $msg .= '<b>No log file found on that location</b>';
19
+
20
+ } else {
21
+ $log = file_get_contents($logFile);
22
+ if ($log === false) {
23
+ $msg .= '<b>Could not read log file</b>';
24
+ } else {
25
+ $msg .= nl2br($log);
26
+ }
27
+
28
+ }
29
+
30
+ //$log = $source;
31
+ //file_get_contents
32
+
33
+ echo json_encode($msg, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT);
34
+ wp_die();
35
+ }
36
+
37
+ }
lib/classes/ConvertersHelper.php CHANGED
@@ -5,25 +5,32 @@ namespace WebPExpress;
5
  class ConvertersHelper
6
  {
7
  public static $defaultConverters = [
8
- ['converter' => 'gd', 'options' => [
9
- 'skip-pngs' => false
10
- ]],
11
  ['converter' => 'cwebp', 'options' => [
12
  'use-nice' => true,
13
  'try-common-system-paths' => true,
14
  'try-supplied-binary-for-os' => true,
15
  'method' => 6,
16
- 'size-in-percentage' => 45,
17
- 'low-memory' => false,
18
- 'command-line-options' => '-low_memory',
 
 
 
 
 
 
 
19
  ]],
20
- ['converter' => 'imagick'],
21
- ['converter' => 'gmagick'],
22
- ['converter' => 'wpc'], // we should not set api-version default - it is handled in the javascript
23
- ['converter' => 'ewww'],
24
- ['converter' => 'imagickbinary', 'options' => [
25
  'use-nice' => true,
26
  ]],
 
 
 
 
 
 
 
27
  ];
28
 
29
  public static function getDefaultConverterNames()
@@ -58,6 +65,19 @@ class ConvertersHelper
58
  $second = self::normalize($second);
59
 
60
  foreach ($second as $converter) {
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  if (!in_array($converter['converter'], $namesInFirst)) {
62
  $first[] = $converter;
63
  }
@@ -65,4 +85,158 @@ class ConvertersHelper
65
  return $first;
66
  }
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
5
  class ConvertersHelper
6
  {
7
  public static $defaultConverters = [
 
 
 
8
  ['converter' => 'cwebp', 'options' => [
9
  'use-nice' => true,
10
  'try-common-system-paths' => true,
11
  'try-supplied-binary-for-os' => true,
12
  'method' => 6,
13
+ 'size-in-percentage' => null,
14
+ 'low-memory' => true,
15
+ 'command-line-options' => '',
16
+ ]],
17
+ ['converter' => 'vips', 'options' => [
18
+ 'smart-subsample' => false,
19
+ 'preset' => 'none'
20
+ ]],
21
+ ['converter' => 'imagemagick', 'options' => [
22
+ 'use-nice' => true,
23
  ]],
24
+ ['converter' => 'graphicsmagick', 'options' => [
 
 
 
 
25
  'use-nice' => true,
26
  ]],
27
+ ['converter' => 'wpc', 'options' => []], // we should not set api-version default - it is handled in the javascript
28
+ ['converter' => 'ewww', 'options' => []],
29
+ ['converter' => 'imagick', 'options' => []],
30
+ ['converter' => 'gmagick', 'options' => []],
31
+ ['converter' => 'gd', 'options' => [
32
+ 'skip-pngs' => false,
33
+ ]],
34
  ];
35
 
36
  public static function getDefaultConverterNames()
65
  $second = self::normalize($second);
66
 
67
  foreach ($second as $converter) {
68
+ // migrate9 and this functionality could create two converters.
69
+ // so, for a while, skip graphicsmagick and imagemagick
70
+
71
+ if ($converter['converter'] == 'graphicsmagick') {
72
+ if (in_array('gmagickbinary', $namesInFirst)) {
73
+ continue;
74
+ }
75
+ }
76
+ if ($converter['converter'] == 'imagemagick') {
77
+ if (in_array('imagickbinary', $namesInFirst)) {
78
+ continue;
79
+ }
80
+ }
81
  if (!in_array($converter['converter'], $namesInFirst)) {
82
  $first[] = $converter;
83
  }
85
  return $first;
86
  }
87
 
88
+ /**
89
+ * Get converter by id
90
+ *
91
+ * @param object $config
92
+ * @return array|false converter object
93
+ */
94
+ public static function getConverterById($config, $id) {
95
+ if (!isset($config['converters'])) {
96
+ return false;
97
+ }
98
+ $converters = $config['converters'];
99
+
100
+ if (!is_array($converters)) {
101
+ return false;
102
+ }
103
+
104
+ foreach ($converters as $c) {
105
+ if (!isset($c['converter'])) {
106
+ continue;
107
+ }
108
+ if ($c['converter'] == $id) {
109
+ return $c;
110
+ }
111
+ }
112
+ return false;
113
+ }
114
+
115
+ /**
116
+ * Get working converters.
117
+ *
118
+ * @param object $config
119
+ * @return array
120
+ */
121
+ public static function getWorkingConverters($config) {
122
+ if (!isset($config['converters'])) {
123
+ return [];
124
+ }
125
+ $converters = $config['converters'];
126
+
127
+ if (!is_array($converters)) {
128
+ return [];
129
+ }
130
+
131
+ $result = [];
132
+
133
+ foreach ($converters as $c) {
134
+ if (isset($c['working']) && !$c['working']) {
135
+ continue;
136
+ }
137
+ $result[] = $c;
138
+ }
139
+ return $result;
140
+ }
141
+
142
+ /**
143
+ * Get array of working converter ids. Same order as configured.
144
+ */
145
+ public static function getWorkingConverterIds($config)
146
+ {
147
+ $converters = self::getWorkingConverters($config);
148
+ $result = [];
149
+ foreach ($converters as $converter) {
150
+ $result[] = $converter['converter'];
151
+ }
152
+ return $result;
153
+ }
154
+
155
+ /**
156
+ * Get working and active converters.
157
+ *
158
+ * @param object $config
159
+ * @return array
160
+ */
161
+ public static function getWorkingAndActiveConverters($config)
162
+ {
163
+ if (!isset($config['converters'])) {
164
+ return [];
165
+ }
166
+ $converters = $config['converters'];
167
+
168
+ if (!is_array($converters)) {
169
+ return [];
170
+ }
171
+
172
+ $result = [];
173
+
174
+ foreach ($converters as $c) {
175
+ if (isset($c['deactivated']) && $c['deactivated']) {
176
+ continue;
177
+ }
178
+ if (isset($c['working']) && !$c['working']) {
179
+ continue;
180
+ }
181
+ $result[] = $c;
182
+ }
183
+ return $result;
184
+ }
185
+
186
+ public static function getWorkingAndActiveConverterIds($config)
187
+ {
188
+ $converters = self::getWorkingAndActiveConverters($config);
189
+ $result = [];
190
+ foreach ($converters as $converter) {
191
+ $result[] = $converter['converter'];
192
+ }
193
+ return $result;
194
+ }
195
+
196
+ /**
197
+ * Get converter id by converter object
198
+ *
199
+ * @param object $converter
200
+ * @return string converter name, or empty string if not set (it should always be set, however)
201
+ */
202
+ public static function getConverterId($converter) {
203
+ if (!isset($converter['converter'])) {
204
+ return '';
205
+ }
206
+ return $converter['converter'];
207
+ }
208
+
209
+ /**
210
+ * Get first working and active converter.
211
+ *
212
+ * @param object $config
213
+ * @return object|false
214
+ */
215
+ public static function getFirstWorkingAndActiveConverter($config) {
216
+
217
+ $workingConverters = self::getWorkingAndActiveConverters($config);
218
+
219
+ if (count($workingConverters) == 0) {
220
+ return false;
221
+ }
222
+ return $workingConverters[0];
223
+ }
224
+
225
+ /**
226
+ * Get first working and active converter (name)
227
+ *
228
+ * @param object $config
229
+ * @return string|false id of converter, or false if no converter is working and active
230
+ */
231
+ public static function getFirstWorkingAndActiveConverterId($config) {
232
+ $c = self::getFirstWorkingAndActiveConverter($config);
233
+ if ($c === false) {
234
+ return false;
235
+ }
236
+ if (!isset($c['converter'])) {
237
+ return false;
238
+ }
239
+ return $c['converter'];
240
+ }
241
+
242
  }
lib/classes/DismissableMessages.php ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ use \WebPExpress\Option;
6
+ use \WebPExpress\State;
7
+ use \WebPExpress\Messenger;
8
+
9
+ class DismissableMessages
10
+ {
11
+
12
+ /**
13
+ * Add dismissible message.
14
+ *
15
+ * @param string $id An identifier, ie "suggest_enable_pngs"
16
+ */
17
+ public static function addDismissableMessage($id)
18
+ {
19
+ $dismissableMessageIds = State::getState('dismissableMessageIds', []);
20
+
21
+ // Ensure we do not add a message that is already there
22
+ if (in_array($id, $dismissableMessageIds)) {
23
+ return;
24
+ }
25
+ $dismissableMessageIds[] = $id;
26
+ State::setState('dismissableMessageIds', $dismissableMessageIds);
27
+ }
28
+
29
+ public static function printDismissableMessage($level, $msg, $id, $gotItText = '')
30
+ {
31
+ if ($gotItText != '') {
32
+ $javascript = "jQuery(this).closest('div.notice').slideUp();";
33
+ //$javascript = "console.log(jQuery(this).closest('div.notice'));";
34
+ $javascript .= "jQuery.post(ajaxurl, {'action': 'webpexpress_dismiss_message', 'id': '" . $id . "'});";
35
+
36
+ $msg .= '<button type="button" class="button button-primary" onclick="' . $javascript . '" style="display:block; margin-top:20px">' . $gotItText . '</button>';
37
+ }
38
+ Messenger::printMessage($level, $msg);
39
+ }
40
+
41
+ public static function printMessages()
42
+ {
43
+ $ids = State::getState('dismissableMessageIds', []);
44
+ foreach ($ids as $id) {
45
+ include_once __DIR__ . '/../dismissable-messages/' . $id . '.php';
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Dismiss message
51
+ *
52
+ * @param string $id An identifier, ie "suggest_enable_pngs"
53
+ */
54
+ public static function dismissMessage($id) {
55
+ $messages = State::getState('dismissableMessageIds', []);
56
+ $newQueue = [];
57
+ foreach ($messages as $mid) {
58
+ if ($mid == $id) {
59
+
60
+ } else {
61
+ $newQueue[] = $mid;
62
+ }
63
+ }
64
+ State::setState('dismissableMessageIds', $newQueue);
65
+ }
66
+
67
+ /**
68
+ * Dismiss message
69
+ *
70
+ * @param string $id An identifier, ie "suggest_enable_pngs"
71
+ */
72
+ public static function dismissAll() {
73
+ State::setState('dismissableMessageIds', []);
74
+ }
75
+
76
+ public static function processAjaxDismissMessage() {
77
+ $id = $_POST['id'];
78
+ self::dismissMessage($id);
79
+ }
80
+
81
+
82
+ }
lib/classes/HTAccess.php CHANGED
@@ -36,6 +36,8 @@ class HTAccess
36
  return '# WebP Express does not need to write any rules (it has not been set up to redirect to converter, nor to existing webp, and the "convert non-existing webp-files upon request" option has not been enabled)';
37
  }
38
 
 
 
39
  if (isset($config['base-htaccess-on-these-capability-tests'])) {
40
  $capTests = $config['base-htaccess-on-these-capability-tests'];
41
  $modHeaderDefinitelyUnavailable = ($capTests['modHeaderWorking'] === false);
@@ -58,6 +60,11 @@ class HTAccess
58
  $passRelativeFilePathInQSRealizer = $passRelativeFilePathInQS;
59
 
60
 
 
 
 
 
 
61
 
62
  /* Calculate $fileExt */
63
  $imageTypes = $config['image-types'];
@@ -167,6 +174,7 @@ class HTAccess
167
  $uploadsRel = PathHelper::getRelDir(Paths::getIndexDirAbs(), Paths::getUploadDirAbs());
168
 
169
  //$rules .= '# rel: ' . $uploadsRel . "\n";
 
170
  if (strpos($wpContentRel, '.') !== 0) {
171
 
172
  if (strpos($uploadsRel, $wpContentRel) === 0) {
@@ -186,6 +194,10 @@ class HTAccess
186
  //$rules .= "# The following SetEnv allows to diagnose if .htaccess files are turned off\n";
187
  //$rules .= "SetEnv HTACCESS on\n\n";
188
  */
 
 
 
 
189
 
190
  $rules .= "<IfModule mod_rewrite.c>\n" .
191
  " RewriteEngine On\n\n";
@@ -229,10 +241,10 @@ class HTAccess
229
 
230
  if ($config['destination-extension'] == 'append') {
231
  $rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $htaccessDirRel . "/$1.$2.webp -f\n";
232
- $rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . $fileExt . ")$ $1.$2.webp [T=image/webp,E=EXISTING:1,L]\n\n";
233
  } else {
234
  $rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $htaccessDirRel . "/$1.webp -f\n";
235
- $rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . $fileExt . ")$ $1.webp [T=image/webp,E=EXISTING:1,L]\n\n";
236
  //$rules .= " RewriteRule ^(.+)\.(" . $fileExt . ")$ $1.webp [T=image/webp,E=EXISTING:1,L]\n\n";
237
  }
238
  }
@@ -244,6 +256,14 @@ class HTAccess
244
  $rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . $fileExt . ")$ /" . $cacheDirRel . "/" . $htaccessDirRel . "/$1.$2.webp [NC,T=image/webp,E=EXISTING:1,L]\n\n";
245
  //$rules .= " RewriteRule ^\/?(.*)\.(" . $fileExt . ")$ /" . $cacheDirRel . "/" . $htaccessDirRel . "/$1.$2.webp [NC,T=image/webp,E=EXISTING:1,L]\n\n";
246
 
 
 
 
 
 
 
 
 
247
  $rules .= $ccRules;
248
 
249
  }
@@ -389,22 +409,23 @@ class HTAccess
389
  $rules .= "\n";
390
  }
391
 
392
- $addVary = ($config['enable-redirection-to-converter'] && ($config['success-response'] == 'converted')) || ($config['redirect-to-existing-in-htaccess']);
393
 
394
  if ($addVary) {
395
  $rules .= " <IfModule mod_headers.c>\n";
396
  $rules .= " <IfModule mod_setenvif.c>\n";
397
 
 
 
 
 
 
398
  $rules .= " # Set Vary:Accept header for the image types handled by WebP Express.\n" .
399
  " # The purpose is to make proxies and CDNs aware that the response varies with the Accept header. \n" .
400
- " SetEnvIf Request_URI \"\.(" . $fileExt . ")\" ADDVARY\n" .
401
  " Header append \"Vary\" \"Accept\" env=ADDVARY\n\n";
402
 
403
  if ($config['redirect-to-existing-in-htaccess']) {
404
  $rules .= " # Set X-WebP-Express header for diagnose purposes\n" .
405
- " # Apache appends \"REDIRECT_\" in front of the environment variables defined in mod_rewrite, but LiteSpeed does not.\n" .
406
- " # So, the next line is for Apache, in order to set environment variables without \"REDIRECT_\"\n" .
407
- " SetEnvIf REDIRECT_EXISTING 1 EXISTING=1\n" .
408
  //" SetEnvIf REDIRECT_WOD 1 WOD=1\n\n" .
409
  //" # Set the debug header\n" .
410
  " Header set \"X-WebP-Express\" \"Redirected directly to existing webp\" env=EXISTING\n";
36
  return '# WebP Express does not need to write any rules (it has not been set up to redirect to converter, nor to existing webp, and the "convert non-existing webp-files upon request" option has not been enabled)';
37
  }
38
 
39
+
40
+
41
  if (isset($config['base-htaccess-on-these-capability-tests'])) {
42
  $capTests = $config['base-htaccess-on-these-capability-tests'];
43
  $modHeaderDefinitelyUnavailable = ($capTests['modHeaderWorking'] === false);
60
  $passRelativeFilePathInQSRealizer = $passRelativeFilePathInQS;
61
 
62
 
63
+ $addVary = $config['redirect-to-existing-in-htaccess'];
64
+ if ($modHeaderDefinitelyUnavailable) {
65
+ $addVary = false;
66
+ }
67
+
68
 
69
  /* Calculate $fileExt */
70
  $imageTypes = $config['image-types'];
174
  $uploadsRel = PathHelper::getRelDir(Paths::getIndexDirAbs(), Paths::getUploadDirAbs());
175
 
176
  //$rules .= '# rel: ' . $uploadsRel . "\n";
177
+
178
  if (strpos($wpContentRel, '.') !== 0) {
179
 
180
  if (strpos($uploadsRel, $wpContentRel) === 0) {
194
  //$rules .= "# The following SetEnv allows to diagnose if .htaccess files are turned off\n";
195
  //$rules .= "SetEnv HTACCESS on\n\n";
196
  */
197
+ $rules .= "# The rules below are a result of the WebP Express options, Wordpress configuration and the following .htaccess capability tests:\n" .
198
+ "# - mod_header working?: " . ($capTests['modHeaderWorking'] === true ? 'yes' : ($capTests['modHeaderWorking'] === false ? 'no' : 'could not be determined')) . "\n" .
199
+ "# - pass variable from .htaccess to script through header working?: " . ($capTests['passThroughHeaderWorking'] === true ? 'yes' : ($capTests['passThroughHeaderWorking'] === false ? 'no' : 'could not be determined')) . "\n" .
200
+ "# - pass variable from .htaccess to script through environment variable working?: " . ($capTests['passThroughEnvWorking'] === true ? 'yes' : ($capTests['passThroughEnvWorking'] === false ? 'no' : 'could not be determined')) . "\n";
201
 
202
  $rules .= "<IfModule mod_rewrite.c>\n" .
203
  " RewriteEngine On\n\n";
241
 
242
  if ($config['destination-extension'] == 'append') {
243
  $rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $htaccessDirRel . "/$1.$2.webp -f\n";
244
+ $rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . $fileExt . ")$ $1.$2.webp [T=image/webp,E=EXISTING:1," . ($addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
245
  } else {
246
  $rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $htaccessDirRel . "/$1.webp -f\n";
247
+ $rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . $fileExt . ")$ $1.webp [T=image/webp,E=EXISTING:1," . ($addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
248
  //$rules .= " RewriteRule ^(.+)\.(" . $fileExt . ")$ $1.webp [T=image/webp,E=EXISTING:1,L]\n\n";
249
  }
250
  }
256
  $rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . $fileExt . ")$ /" . $cacheDirRel . "/" . $htaccessDirRel . "/$1.$2.webp [NC,T=image/webp,E=EXISTING:1,L]\n\n";
257
  //$rules .= " RewriteRule ^\/?(.*)\.(" . $fileExt . ")$ /" . $cacheDirRel . "/" . $htaccessDirRel . "/$1.$2.webp [NC,T=image/webp,E=EXISTING:1,L]\n\n";
258
 
259
+ if ($addVary) {
260
+ $rules .= " # Make sure that browsers which does not support webp also gets the Vary:Accept header\n" .
261
+ " # when requesting images that would be redirected to existing webp on browsers that does.\n" .
262
+ " <IfModule mod_setenvif.c>\n" .
263
+ " SetEnvIf Request_URI \"\.(" . $fileExt . ")$\" ADDVARY\n" .
264
+ " </IfModule>\n\n";
265
+ }
266
+
267
  $rules .= $ccRules;
268
 
269
  }
409
  $rules .= "\n";
410
  }
411
 
412
+ //$addVary = ($config['enable-redirection-to-converter'] && ($config['success-response'] == 'converted')) || ($config['redirect-to-existing-in-htaccess']);
413
 
414
  if ($addVary) {
415
  $rules .= " <IfModule mod_headers.c>\n";
416
  $rules .= " <IfModule mod_setenvif.c>\n";
417
 
418
+ $rules .= " # Apache appends \"REDIRECT_\" in front of the environment variables defined in mod_rewrite, but LiteSpeed does not.\n" .
419
+ " # So, the next lines are for Apache, in order to set environment variables without \"REDIRECT_\"\n" .
420
+ " SetEnvIf REDIRECT_EXISTING 1 EXISTING=1\n" .
421
+ " SetEnvIf REDIRECT_ADDVARY 1 ADDVARY=1\n\n";
422
+
423
  $rules .= " # Set Vary:Accept header for the image types handled by WebP Express.\n" .
424
  " # The purpose is to make proxies and CDNs aware that the response varies with the Accept header. \n" .
 
425
  " Header append \"Vary\" \"Accept\" env=ADDVARY\n\n";
426
 
427
  if ($config['redirect-to-existing-in-htaccess']) {
428
  $rules .= " # Set X-WebP-Express header for diagnose purposes\n" .
 
 
 
429
  //" SetEnvIf REDIRECT_WOD 1 WOD=1\n\n" .
430
  //" # Set the debug header\n" .
431
  " Header set \"X-WebP-Express\" \"Redirected directly to existing webp\" env=EXISTING\n";
lib/classes/HandleDeleteFileHook.php CHANGED
@@ -21,9 +21,8 @@ class HandleDeleteFileHook
21
  }
22
 
23
  $destination = Convert::getDestination($filename);
24
- error_log('deleting webp:' . $destination);
25
  if (!unlink($destination)) {
26
- error_log('failed deleting webp:' . $destination);
27
  }
28
 
29
  }
21
  }
22
 
23
  $destination = Convert::getDestination($filename);
 
24
  if (!unlink($destination)) {
25
+ error_log('WebP Express failed deleting webp:' . $destination);
26
  }
27
 
28
  }
lib/classes/Messenger.php CHANGED
@@ -10,8 +10,8 @@ class Messenger
10
  private static $printedStyles = false;
11
 
12
  /**
13
- * $level: info | success | warning | error
14
- * $msg: the message (not translated)
15
  *
16
  * Hm... we should add some sprintf-like support
17
  * $msg = sprintf(__( 'You are on a very old version of PHP (%s). WebP Express may not work as intended.', 'webp-express' ), phpversion());
@@ -45,8 +45,9 @@ class Messenger
45
 
46
  //$msg = __( $msg, 'webp-express'); // uncommented. We should add some sprintf-like functionality before making the plugin translatable
47
  printf(
48
- '<div class="%1$s"><p>%2$s</p></div>',
49
- esc_attr('notice notice-' . $level . ' is-dismissible'),
 
50
  $msg
51
  );
52
  }
@@ -66,7 +67,7 @@ class Messenger
66
  .notice-error {
67
  border-left-color: #dc3232;
68
  }
69
- .notice-success {
70
  border-left-color: #46b450;
71
  }
72
  .notice-info {
@@ -88,6 +89,8 @@ class Messenger
88
  }
89
 
90
  State::setState('pendingMessages', []);
 
91
  Option::updateOption('webp-express-messages-pending', false, true);
92
  }
 
93
  }
10
  private static $printedStyles = false;
11
 
12
  /**
13
+ * @param string $level (info | success | warning | error)
14
+ * @param string $msg the message (not translated)
15
  *
16
  * Hm... we should add some sprintf-like support
17
  * $msg = sprintf(__( 'You are on a very old version of PHP (%s). WebP Express may not work as intended.', 'webp-express' ), phpversion());
45
 
46
  //$msg = __( $msg, 'webp-express'); // uncommented. We should add some sprintf-like functionality before making the plugin translatable
47
  printf(
48
+ '<div class="%1$s"><div style="margin:10px 0">%2$s</div></div>',
49
+ //esc_attr('notice notice-' . $level . ' is-dismissible'),
50
+ esc_attr('notice notice-' . $level),
51
  $msg
52
  );
53
  }
67
  .notice-error {
68
  border-left-color: #dc3232;
69
  }
70
+ .notice-success {esc_attr('notice notice-' . $level . ' is-dismissible'),
71
  border-left-color: #46b450;
72
  }
73
  .notice-info {
89
  }
90
 
91
  State::setState('pendingMessages', []);
92
+
93
  Option::updateOption('webp-express-messages-pending', false, true);
94
  }
95
+
96
  }
lib/classes/Paths.php CHANGED
@@ -247,6 +247,13 @@ APACHE
247
  return self::createDirIfMissing(self::getCacheDirAbs());
248
  }
249
 
 
 
 
 
 
 
 
250
  // ------------ Plugin Dir (all plugins) -------------
251
 
252
  public static function getPluginDirAbs()
@@ -382,7 +389,7 @@ APACHE
382
  */
383
  public static function getPluginUrl()
384
  {
385
- return untrailingslashit(plugins_url('', WEBPEXPRESS_PLUGIN));
386
  }
387
 
388
  public static function getPluginUrlPath()
@@ -423,11 +430,14 @@ APACHE
423
  return [
424
  'urls' => [
425
  'webpExpressRoot' => self::getPluginUrlPath(),
 
426
  ],
427
  'filePaths' => [
428
  'webpExpressRoot' => self::getWebPExpressPluginDirAbs(),
429
  'destinationRoot' => self::getCacheDirAbs(),
430
- 'configRelToDocRoot' => self::getConfigDirRel()
 
 
431
  ]
432
  ];
433
  }
247
  return self::createDirIfMissing(self::getCacheDirAbs());
248
  }
249
 
250
+ // ------------ Cache Dir -------------
251
+
252
+ public static function getLogDirAbs()
253
+ {
254
+ return self::getWebPExpressContentDirAbs() . '/log';
255
+ }
256
+
257
  // ------------ Plugin Dir (all plugins) -------------
258
 
259
  public static function getPluginDirAbs()
389
  */
390
  public static function getPluginUrl()
391
  {
392
+ return untrailingslashit(plugins_url(null, WEBPEXPRESS_PLUGIN));
393
  }
394
 
395
  public static function getPluginUrlPath()
430
  return [
431
  'urls' => [
432
  'webpExpressRoot' => self::getPluginUrlPath(),
433
+ 'content' => self::getContentUrlPath(),
434
  ],
435
  'filePaths' => [
436
  'webpExpressRoot' => self::getWebPExpressPluginDirAbs(),
437
  'destinationRoot' => self::getCacheDirAbs(),
438
+ 'configRelToDocRoot' => self::getConfigDirRel(),
439
+ 'pluginRelToDocRoot' => self::getPluginDirRel(),
440
+
441
  ]
442
  ];
443
  }
lib/classes/PlatformInfo.php CHANGED
@@ -23,6 +23,11 @@ class PlatformInfo
23
  return ( strpos( $server, 'litespeed') !== false );
24
  }
25
 
 
 
 
 
 
26
  /**
27
  * It is not always possible to determine if apache has a given module...
28
  * We shall not fool anyone into thinking otherwise by providing a "got" method like Wordpress does...
23
  return ( strpos( $server, 'litespeed') !== false );
24
  }
25
 
26
+ public static function isApacheOrLiteSpeed()
27
+ {
28
+ return self::isApache() || self::isLiteSpeed();
29
+ }
30
+
31
  /**
32
  * It is not always possible to determine if apache has a given module...
33
  * We shall not fool anyone into thinking otherwise by providing a "got" method like Wordpress does...
lib/classes/TestRun.php CHANGED
@@ -7,9 +7,10 @@ use \WebPExpress\ConvertersHelper;
7
  use \WebPExpress\Paths;
8
  use \WebPExpress\FileHelper;
9
 
10
- include_once WEBPEXPRESS_PLUGIN_DIR . '/vendor/autoload.php';
 
11
 
12
- use \WebPConvert\Converters\ConverterHelper;
13
 
14
  /**
15
  *
@@ -22,9 +23,12 @@ class TestRun
22
  public static $converterStatus = null; // to cache the result
23
 
24
  /**
25
- * Get an array of working converters OR false, if tests cannot be made
 
 
26
  */
27
  public static function getConverterStatus() {
 
28
 
29
  // Is result cached?
30
  if (isset(self::$converterStatus)) {
@@ -46,25 +50,15 @@ class TestRun
46
  // But we cannot simply use loadWodOptions - because that would leave out the deactivated
47
  // converters. And we need to test all converters - even the deactivated ones.
48
  // So we load config, set "deactivated" to false, and generate Wod options from the config
49
- $config = Config::loadConfig();
50
- if ((!$config) || (!isset($config['converters'])) || (count($config['converters']) == 0)) {
51
- $config = [
52
- 'converters' => ConvertersHelper::$defaultConverters
53
- ];
54
- } else {
55
- // set deactivated to false on all converters
56
- foreach($config['converters'] as &$converter) {
57
- $converter['deactivated'] = false;
58
- }
59
-
60
- // merge missing converters in
61
- $config['converters'] = ConvertersHelper::mergeConverters($config['converters'], ConvertersHelper::$defaultConverters);
62
- // echo '<pre>' . print_r($config, true) . '</pre>';
63
 
 
 
 
64
  }
65
 
66
  $options = Config::generateWodOptionsFromConfigObj($config);
67
- $options['converters'] = ConvertersHelper::normalize($options['converters']);
68
 
69
  //echo '<pre>' . print_r($options, true) . '</pre>';
70
  foreach ($options['converters'] as $converter) {
@@ -73,7 +67,14 @@ class TestRun
73
  $converterOptions = array_merge($options, $converter['options']);
74
  unset($converterOptions['converters']);
75
 
76
- ConverterHelper::runConverter($converterId, $source, $destination, $converterOptions);
 
 
 
 
 
 
 
77
  $workingConverters[] = $converterId;
78
  } catch (\Exception $e) {
79
  //echo $e->getMessage() . '<br>';
@@ -97,7 +98,7 @@ class TestRun
97
  if (isset(self::$localQualityDetectionWorking)) {
98
  return self::$localQualityDetectionWorking;
99
  } else {
100
- $q = ConverterHelper::detectQualityOfJpg(
101
  Paths::getWebPExpressPluginDirAbs() . '/test/small-q61.jpg'
102
  );
103
  self::$localQualityDetectionWorking = ($q === 61);
7
  use \WebPExpress\Paths;
8
  use \WebPExpress\FileHelper;
9
 
10
+ use \WebPConvert\Convert\ConverterFactory;
11
+ use \WebPConvert\Convert\Helpers\JpegQualityDetector;
12
 
13
+ include_once WEBPEXPRESS_PLUGIN_DIR . '/vendor/autoload.php';
14
 
15
  /**
16
  *
23
  public static $converterStatus = null; // to cache the result
24
 
25
  /**
26
+ * Get a test result object OR false, if tests cannot be made.
27
+ *
28
+ * @return object|false
29
  */
30
  public static function getConverterStatus() {
31
+ //return false;
32
 
33
  // Is result cached?
34
  if (isset(self::$converterStatus)) {
50
  // But we cannot simply use loadWodOptions - because that would leave out the deactivated
51
  // converters. And we need to test all converters - even the deactivated ones.
52
  // So we load config, set "deactivated" to false, and generate Wod options from the config
53
+ $config = Config::loadConfigAndFix();
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
+ // set deactivated to false on all converters
56
+ foreach($config['converters'] as &$converter) {
57
+ $converter['deactivated'] = false;
58
  }
59
 
60
  $options = Config::generateWodOptionsFromConfigObj($config);
61
+ $options['converters'] = ConvertersHelper::normalize($options['webp-convert']['convert']['converters']);
62
 
63
  //echo '<pre>' . print_r($options, true) . '</pre>';
64
  foreach ($options['converters'] as $converter) {
67
  $converterOptions = array_merge($options, $converter['options']);
68
  unset($converterOptions['converters']);
69
 
70
+ //ConverterHelper::runConverter($converterId, $source, $destination, $converterOptions);
71
+ $converterInstance = ConverterFactory::makeConverter(
72
+ $converterId,
73
+ $source,
74
+ $destination,
75
+ $converterOptions
76
+ );
77
+ $converterInstance->doConvert();
78
  $workingConverters[] = $converterId;
79
  } catch (\Exception $e) {
80
  //echo $e->getMessage() . '<br>';
98
  if (isset(self::$localQualityDetectionWorking)) {
99
  return self::$localQualityDetectionWorking;
100
  } else {
101
+ $q = JpegQualityDetector::detectQualityOfJpg(
102
  Paths::getWebPExpressPluginDirAbs() . '/test/small-q61.jpg'
103
  );
104
  self::$localQualityDetectionWorking = ($q === 61);
lib/dismissable-messages/0.14.0/say-hello-to-vips.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ use \WebPExpress\DismissableMessages;
3
+ use \WebPExpress\State;
4
+ use \WebPExpress\TestRun;
5
+
6
+ /*
7
+ $testResult = TestRun::getConverterStatus();
8
+ if ($testResult !== false) {
9
+ $workingConvertersIds = $testResult['workingConverters'];
10
+ } else {
11
+ $workingConvertersIds = [];
12
+ }
13
+ */
14
+
15
+ $workingConvertersIds = State::getState('workingConverterIds', []);
16
+
17
+ if (in_array('vips', $workingConvertersIds)) {
18
+ if (in_array('cwebp', $workingConvertersIds)) {
19
+ DismissableMessages::printDismissableMessage(
20
+ 'info',
21
+ '<p>I have some good news and... more good news! WebP Express now supports Vips and Vips is working on your server. ' .
22
+ 'Vips is one of the best method for converting WebPs, on par with cwebp, which you also have working. ' .
23
+ 'You may want to use Vips instead of cwebp. Your choice.</p>',
24
+ '0.14.0/say-hello-to-vips',
25
+ 'Got it!'
26
+ );
27
+ } else {
28
+ DismissableMessages::printDismissableMessage(
29
+ 'info',
30
+ '<p>I have some good news and... more good news! WebP Express now supports Vips and Vips is working on your server. ' .
31
+ 'Vips is one of the best method for converting WebPs and has therefore been inserted at the top of the list.' .
32
+ '</p>',
33
+ '0.14.0/say-hello-to-vips',
34
+ 'Got it!'
35
+ );
36
+ }
37
+ } else {
38
+ // show message?
39
+ }
lib/dismissable-messages/0.14.0/suggest-enable-pngs.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ use \WebPExpress\DismissableMessages;
3
+
4
+ // introduced in 0.14.0 (migrate 9)
5
+ DismissableMessages::printDismissableMessage(
6
+ 'info',
7
+ 'WebP Express 0.14 handles PNG to WebP conversions quite well. Perhaps it is time to enable PNGs? ',
8
+ '0.14.0/suggest-enable-pngs',
9
+ 'Got it!'
10
+ );
lib/dismissable-messages/0.14.0/suggest-wipe-because-lossless.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ use \WebPExpress\DismissableMessages;
3
+ use \WebPExpress\State;
4
+ use \WebPExpress\TestRun;
5
+
6
+ $convertersSupportingEncodingAuto = ['cwebp', 'vips', 'imagick', 'imagemagick', 'gmagick', 'graphicsmagick'];
7
+
8
+ $workingConvertersIds = State::getState('workingConverterIds', []);
9
+ $workingAndActiveConverterIds = State::getState('workingAndActiveConverterIds', []);
10
+
11
+ $firstActiveAndWorkingConverterId = (isset($workingAndActiveConverterIds[0]) ? $workingAndActiveConverterIds[0] : '');
12
+
13
+ if (in_array($firstActiveAndWorkingConverterId, $convertersSupportingEncodingAuto)) {
14
+ DismissableMessages::printDismissableMessage(
15
+ 'info',
16
+ '<p>WebP Express 0.14 has new options for the conversions. Especially, it can now produce lossless webps, and ' .
17
+ 'it can automatically try both lossy and lossless and select the smallest. You can play around with the ' .
18
+ 'new options when your click "test" next to a converter.</p>' .
19
+ '<p>Once satisfied, dont forget to ' .
20
+ 'wipe your existing converted files (there is a "Delete converted files" button for that here on this page).</p>',
21
+ '0.14.0/suggest-wipe-because-lossless',
22
+ 'Got it!'
23
+ );
24
+ } else {
25
+ if ($firstActiveAndWorkingConverterId == 'gd') {
26
+ foreach ($workingConvertersIds as $workingId) {
27
+ if (in_array($workingId, $convertersSupportingEncodingAuto)) {
28
+ DismissableMessages::printDismissableMessage(
29
+ 'info',
30
+ '<p>WebP Express 0.14 has new options for the conversions. Especially, it can now produce lossless webps, and ' .
31
+ 'it can automatically try both lossy and lossless and select the smallest. You can play around with the ' .
32
+ 'new options when your click "test" next to a converter.</p>' .
33
+ '<p>Once satisfied, dont forget to wipe your existing converted files (there is a "Delete converted files" ' .
34
+ 'button for that here on this page)</p>' .
35
+ '<p>Btw: The "gd" conversion method that you are using does not support lossless encoding ' .
36
+ '(in fact Gd only supports very few conversion options), but fortunately, you have at least one ' .
37
+ 'other conversion method working, so you can simply start using that instead.</p>',
38
+ '0.14.0/suggest-wipe-because-lossless',
39
+ 'Got it!'
40
+ );
41
+ break;
42
+ }
43
+ }
44
+ }
45
+ }
lib/migrate/migrate9.php ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ use \WebPExpress\Config;
6
+ use \WebPExpress\ConvertersHelper;
7
+ use \WebPExpress\DismissableMessages;
8
+ use \WebPExpress\Messenger;
9
+ use \WebPExpress\Option;
10
+ use \WebPExpress\Paths;
11
+
12
+ /**
13
+ * Move a converter to the top
14
+ * @return boolean
15
+ */
16
+ function webpexpress_migrate9_moveConverterToTop(&$config, $converterId) {
17
+
18
+ if (!isset($config['converters'])) {
19
+ return false;
20
+ }
21
+
22
+ if (!is_array($config['converters'])) {
23
+ return false;
24
+ }
25
+
26
+ // find index of vips
27
+ $indexOfVips = -1;
28
+ $vips = null;
29
+ foreach ($config['converters'] as $i => $c) {
30
+ if ($c['converter'] == $converterId) {
31
+ $indexOfVips = $i;
32
+ $vips = $c;
33
+ break;
34
+ }
35
+ }
36
+ if ($indexOfVips > 0) {
37
+ // remove vips found
38
+ array_splice($config['converters'], $indexOfVips, 1);
39
+
40
+ // Insert vips at the top
41
+ array_unshift($config['converters'], $vips);
42
+
43
+ }
44
+ return false;
45
+ }
46
+
47
+ function webpexpress_migrate9() {
48
+
49
+ $config = Config::loadConfigAndFix(false); // false, because we do not need to test if quality detection is working
50
+ $converters = &$config['converters'];
51
+ if (is_array($converters)) {
52
+
53
+
54
+ foreach ($converters as $i => $converter) {
55
+ if (!isset($converter['converter'])) {
56
+ continue;
57
+ }
58
+ if ($converter['converter'] == 'gmagickbinary') {
59
+ $converters[$i]['converter'] = 'graphicsmagick';
60
+ }
61
+ if ($converter['converter'] == 'imagickbinary') {
62
+ $converters[$i]['converter'] = 'imagemagick';
63
+ }
64
+ }
65
+
66
+ // Change specific converter options
67
+ foreach ($converters as &$converter) {
68
+ if (!isset($converter['converter'])) {
69
+ continue;
70
+ }
71
+ if (!isset($converter['options'])) {
72
+ // #273
73
+ $converter['options'] = [];
74
+ continue;
75
+ }
76
+ $options = &$converter['options'];
77
+
78
+ switch ($converter['converter']) {
79
+ case 'gd':
80
+ if (isset($options['skip-pngs'])) {
81
+ $options['png'] = [
82
+ 'skip' => $options['skip-pngs']
83
+ ];
84
+ unset($options['skip-pngs']);
85
+ }
86
+ break;
87
+ case 'wpc':
88
+ if (isset($options['url'])) {
89
+ $options['api-url'] = $options['url'];
90
+ unset($options['url']);
91
+ }
92
+ break;
93
+ case 'ewww':
94
+ if (isset($options['key'])) {
95
+ $options['api-key'] = $options['key'];
96
+ unset($options['key']);
97
+ }
98
+ if (isset($options['key-2'])) {
99
+ $options['api-key-2'] = $options['key-2'];
100
+ unset($options['key-2']);
101
+ }
102
+ break;
103
+ }
104
+ }
105
+
106
+ $firstActiveAndWorkingConverterId = ConvertersHelper::getFirstWorkingAndActiveConverterId($config);
107
+
108
+ // If it aint cwebp, move vips to the top!
109
+ if ($firstActiveAndWorkingConverterId != 'cwebp') {
110
+ $vips = webpexpress_migrate9_moveConverterToTop($config, 'vips');
111
+ }
112
+
113
+ /*
114
+ if ($config['image-types'] == 1) {
115
+ Messenger::addStickyMessage(
116
+ 'info',
117
+ 'WebP Express 0.14 handles PNG to WebP conversions quite well. Perhaps it is time to enable PNGs? ' .
118
+ 'Go to the <a href="' . Paths::getSettingsUrl() . '">options</a> page to change the "Image types to work on" option.',
119
+ 2,
120
+ 'Got it!'
121
+ );
122
+ }*/
123
+
124
+ if ($config['image-types'] == 1) {
125
+ DismissableMessages::addDismissableMessage('0.14.0/suggest-enable-pngs');
126
+ }
127
+ DismissableMessages::addDismissableMessage('0.14.0/suggest-wipe-because-lossless');
128
+ DismissableMessages::addDismissableMessage('0.14.0/say-hello-to-vips');
129
+
130
+ /*
131
+ $convertersSupportingEncodingAuto = ['cwebp', 'vips', 'imagick', 'imagemagick', 'gmagick', 'graphicsmagick'];
132
+
133
+ if (in_array($firstActiveAndWorkingConverterId, $convertersSupportingEncodingAuto)) {
134
+ Messenger::addStickyMessage(
135
+ 'info',
136
+ 'WebP Express 0.14 has new options for the conversions. Especially, it can now produce lossless webps, and ' .
137
+ 'it can automatically try both lossy and lossless and select the smallest. You can play around with the ' .
138
+ 'new options when your click "test" next to a converter. Once satisfied, dont forget to ' .
139
+ 'wipe your existing converted files (there is a "Delete converted files" button for that on the ' .
140
+ '<a href="' . Paths::getSettingsUrl() . '">options page</a>)',
141
+ 1,
142
+ 'Got it!'
143
+ );
144
+ } else {
145
+ //error_log('working converters: ' . print_r(ConvertersHelper::getWorkingConverterIds($config), true));
146
+ $workingIds = ConvertersHelper::getWorkingConverterIds($config);
147
+
148
+ if ($firstActiveAndWorkingConverterId == 'gd') {
149
+ foreach ($workingIds as $workingId) {
150
+ if (in_array($workingId, $convertersSupportingEncodingAuto)) {
151
+ Messenger::addStickyMessage(
152
+ 'info',
153
+ 'WebP Express 0.14 has new options for the conversions. Especially, it can now produce lossless webps, and ' .
154
+ 'it can automatically try both lossy and lossless and select the smallest. You can play around with the ' .
155
+ 'new options when your click "test" next to a converter. Once satisfied, dont forget to ' .
156
+ 'wipe your existing converted files (there is a "Delete converted files" button for that on the ' .
157
+ '<a href="' . Paths::getSettingsUrl() . '">options page</a>). ' .
158
+ '<br><br>Btw: The "gd" conversion method that you are using does not support lossless encoding ' .
159
+ '(in fact Gd only supports very few conversion options), but fortunately, you have the ' .
160
+ '"' . $workingId . '" conversion method working, so you can simply start using that instead.',
161
+ 1,
162
+ 'Got it!'
163
+ );
164
+ break;
165
+ }
166
+ }
167
+
168
+ }
169
+ //
170
+ }
171
+ */
172
+ }
173
+
174
+ // #235
175
+ $config['cache-control-custom'] = preg_replace('#max-age:#', 'max-age=', $config['cache-control-custom']);
176
+
177
+ // #272
178
+ if ($config['fail'] == 'report-as-image') {
179
+ $config['fail'] = 'report';
180
+ }
181
+
182
+ // Force htaccess ?
183
+ $forceHtaccessRegeneration = $config['redirect-to-existing-in-htaccess'];
184
+
185
+ // Save both configs and perhaps also htaccess
186
+ $result = Config::saveConfigurationAndHTAccess($config, $forceHtaccessRegeneration);
187
+
188
+ if ($result['saved-both-config']) {
189
+ Messenger::addMessage(
190
+ 'info',
191
+ 'Successfully migrated <i>WebP Express</i> options for 0.14. '
192
+ );
193
+ Option::updateOption('webp-express-migration-version', '9');
194
+
195
+ } else {
196
+ Messenger::addMessage(
197
+ 'error',
198
+ 'Failed migrating webp express options to 0.14+. Probably you need to grant write permissions in your wp-content folder.'
199
+ );
200
+ }
201
+
202
+ }
203
+
204
+ webpexpress_migrate9();
lib/options/css/images/checker.png ADDED
Binary file
lib/options/css/images/drag-handle.svg ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
5
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
6
+ <polygon fill="#FFFFFF" points="13,21 8,16 13,11 "/>
7
+ <polygon fill="#FFFFFF" points="19,11 24,16 19,21 "/>
8
+ </svg>
lib/options/css/test-convert.css ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #tc_content {
2
+ display: flex;
3
+ }
4
+
5
+ #tc_content > * {
6
+ width: 50%;
7
+ }
8
+
9
+ #tc_conversion_options label {
10
+ font-weight: bold;
11
+ }
12
+
13
+ #tc_conversion_options label + select {
14
+ margin-left: 6px;
15
+ }
16
+
17
+ @media (max-width:600px) {
18
+ #tc_content {
19
+ display: block;
20
+ }
21
+ }
22
+
23
+ /* Comparison slider *
24
+ ------------------- */
25
+ .cd-image-container {
26
+ position: relative;
27
+ width: 100%;
28
+ background: #dc717d url(images/checker.png) repeat center center;
29
+ margin-bottom: 5px;
30
+ }
31
+
32
+ .cd-image-container img {
33
+ display: block;
34
+ }
35
+
36
+ .cd-image-label {
37
+ display: inline-block;
38
+ position: absolute;
39
+ z-index: 10;
40
+ color: #dc717d;
41
+ top: 10px;
42
+ font-weight: bold;
43
+ font-size: 18px;
44
+ /*text-shadow: 2px 2px 0px white;*/
45
+ padding: 2px 4px;
46
+ background-color: #eee;
47
+ border: 1px solid #ccc;
48
+ }
49
+ .cd-image-label.original {
50
+ left: 15px;
51
+ }
52
+ .cd-image-label.webp {
53
+ right: 15px;
54
+ }
55
+
56
+ .cd-resize-img {
57
+ position: absolute;
58
+ top: 0;
59
+ left: 0;
60
+ height: 100%;
61
+ width: 50%;
62
+ overflow: hidden;
63
+ /* Force Hardware Acceleration in WebKit */
64
+ transform: translateZ(0);
65
+ backface-visibility: hidden;
66
+ border-right: 2px dotted black;
67
+ }
68
+ .is-visible .cd-resize-img {
69
+ width: 50%;
70
+ /* bounce in animation of the modified image */
71
+ animation: cd-bounce-in 0.7s;
72
+ }
73
+ .cd-handle.draggable {
74
+ background-color: #445b7c;
75
+ }
76
+ .cd-handle {
77
+ position: absolute;
78
+ height: 44px;
79
+ width: 44px;
80
+ left: 50%;
81
+ top: 50%;
82
+ margin-left: -22px;
83
+ margin-top: -22px;
84
+ border-radius: 50%;
85
+ background: #dc717d url(images/drag-handle.svg) no-repeat center center;
86
+ cursor: move;
87
+ /*box-shadow: 0 0 0 2px rgba(0,0,0,.2), 0 0 4px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.3);*/
88
+ opacity: 100;
89
+ }
90
+
91
+ @keyframes cd-bounce-in {
92
+ 0% {
93
+ width: 0;
94
+ }
95
+ 60% {
96
+ width: 55%;
97
+ }
98
+ 100% {
99
+ width: 50%;
100
+ }
101
+ }
lib/options/css/webp-express-options-page.css CHANGED
@@ -262,6 +262,12 @@
262
  position: relative;
263
  font-style: normal;
264
  }
 
 
 
 
 
 
265
  .help { /* new look! */
266
  background-color: transparent;
267
  border: 1px solid #999;
@@ -285,12 +291,27 @@
285
  margin-bottom: 0px;
286
  }*/
287
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  .converter-options.wpc label {
289
  width: 110px;
290
- display: inline-block;
291
  }
292
- .converter-options.wpc div {
293
- margin-bottom: 5px;
 
 
 
 
294
  }
295
  #wpc_url {
296
  min-width: 400px;
@@ -558,3 +579,12 @@ ul.with-bullets {
558
  padding-left: 20px;
559
  list-style: unset;
560
  }
 
 
 
 
 
 
 
 
 
262
  position: relative;
263
  font-style: normal;
264
  }
265
+
266
+ select + .help,
267
+ input + .help {
268
+ margin-left: 1px; /* bring help icons closer to select and input boxes */
269
+ margin-top: 3px;
270
+ }
271
  .help { /* new look! */
272
  background-color: transparent;
273
  border: 1px solid #999;
291
  margin-bottom: 0px;
292
  }*/
293
 
294
+ .converter-options.wpc div,
295
+ .converter-options.vips div,
296
+ .converter-options.imagemagick div,
297
+ .converter-options.graphicsmagick div {
298
+ margin-bottom: 5px;
299
+ }
300
+ .converter-options.vips label,
301
+ .converter-options.wpc label,
302
+ .converter-options.imagemagick label,
303
+ .converter-options.graphicsmagick label {
304
+ display: inline-block;
305
+ }
306
  .converter-options.wpc label {
307
  width: 110px;
 
308
  }
309
+ .converter-options.vips label {
310
+ width: 143px;
311
+ }
312
+ .converter-options.imagemagick label,
313
+ .converter-options.graphicsmagick label {
314
+ width: 110px;
315
  }
316
  #wpc_url {
317
  min-width: 400px;
579
  padding-left: 20px;
580
  list-style: unset;
581
  }
582
+
583
+ #conversionlog_content {
584
+ overflow-y: scroll;
585
+ top: 20px;
586
+ bottom: 72px;
587
+ position: absolute;
588
+ right: 0;
589
+ left: 3%;
590
+ }
lib/options/enqueue_scripts.php CHANGED
@@ -6,7 +6,7 @@ use \WebPExpress\Paths;
6
  include_once __DIR__ . '/../classes/Config.php';
7
  use \WebPExpress\Config;
8
 
9
- $version = '0.13.1';
10
 
11
 
12
  if (!function_exists('webp_express_add_inline_script')) {
@@ -56,6 +56,26 @@ if (!(isset($config['operation-mode']) && ($config['operation-mode'] == 'no-conv
56
  wp_register_script('bulkconvert', plugins_url('js/bulk-convert.js', __FILE__), [], $version);
57
  wp_enqueue_script('bulkconvert');
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  // purge cache
60
  wp_register_script('purgecache', plugins_url('js/purge-cache.js', __FILE__), [], $version);
61
  wp_enqueue_script('purgecache');
@@ -73,6 +93,9 @@ wp_enqueue_script('page');
73
  wp_register_style('webp-express-options-page-css', plugins_url('css/webp-express-options-page.css', __FILE__), null, $version);
74
  wp_enqueue_style('webp-express-options-page-css');
75
 
 
 
 
76
  wp_register_style('das-popup-css', plugins_url('css/das-popup.css', __FILE__), null, $version);
77
  wp_enqueue_style('das-popup-css');
78
 
6
  include_once __DIR__ . '/../classes/Config.php';
7
  use \WebPExpress\Config;
8
 
9
+ $version = '0.14.0';
10
 
11
 
12
  if (!function_exists('webp_express_add_inline_script')) {
56
  wp_register_script('bulkconvert', plugins_url('js/bulk-convert.js', __FILE__), [], $version);
57
  wp_enqueue_script('bulkconvert');
58
 
59
+ // test convert
60
+ wp_register_script('testconvert', plugins_url('js/test-convert.js', __FILE__), [], $version);
61
+ $canDisplayWebp = (isset($_SERVER['HTTP_ACCEPT']) && (strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false ));
62
+
63
+ /*
64
+ AlterHTMLHelper::getWebPUrlInBase(
65
+ Paths::getPluginUrl() . '/webp-express', // source url
66
+ Paths::getPluginUrl(), // base url
67
+ Paths::getPluginDirAbs() // base dir
68
+ );
69
+
70
+ getRelUrlPath()*/
71
+
72
+ webp_express_add_inline_script('testconvert', 'window.canDisplayWebp = ' . ($canDisplayWebp ? 'true' : 'false') . ';', 'before');
73
+ wp_enqueue_script('testconvert');
74
+
75
+ wp_register_script('image-comparison-slider', plugins_url('js/image-comparison-slider.js', __FILE__), [], $version);
76
+ wp_enqueue_script('image-comparison-slider');
77
+
78
+
79
  // purge cache
80
  wp_register_script('purgecache', plugins_url('js/purge-cache.js', __FILE__), [], $version);
81
  wp_enqueue_script('purgecache');
93
  wp_register_style('webp-express-options-page-css', plugins_url('css/webp-express-options-page.css', __FILE__), null, $version);
94
  wp_enqueue_style('webp-express-options-page-css');
95
 
96
+ wp_register_style('test-convert-css', plugins_url('css/test-convert.css', __FILE__), null, $version);
97
+ wp_enqueue_style('test-convert-css');
98
+
99
  wp_register_style('das-popup-css', plugins_url('css/das-popup.css', __FILE__), null, $version);
100
  wp_enqueue_style('das-popup-css');
101
 
lib/options/js/bulk-convert.js CHANGED
@@ -70,6 +70,7 @@ function startBulkConversion() {
70
  '.reduction {float:right;}\n' +
71
  '</style>';
72
  html += '<button id="bulkPauseResumeBtn" onclick="pauseOrResumeBulkConversion()" class="button button-primary" type="button">Pause</button>';
 
73
  html += '<div id="bulkconvertlog"></div>';
74
  document.getElementById('bulkconvertcontent').innerHTML = html;
75
 
@@ -120,6 +121,42 @@ function logLn() {
120
  //document.getElementById('bulkconvertlog').innerHTML += html;
121
  }
122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  function convertNextInBulkQueue() {
124
  var html;
125
  var bulkInfo = window.webpexpress_bulkconvert;
@@ -166,11 +203,14 @@ function convertNextInBulkQueue() {
166
 
167
  var bulkInfo = window.webpexpress_bulkconvert;
168
  var group = bulkInfo.groups[bulkInfo.groupPointer];
 
169
 
170
  var result = JSON.parse(response);
171
  //console.log(result);
172
 
173
  var html = '';
 
 
174
  if (result['success']) {
175
 
176
  var orgSize = result['filesize-original'];
@@ -181,18 +221,23 @@ function convertNextInBulkQueue() {
181
  bulkInfo['webpTotalFilesize'] += webpSize;
182
 
183
  html += ' <span style="color:green">ok</span></span>' +
 
 
184
  getReductionHtml(orgSize, webpSize, 'Size of original', 'Size of webp')
185
 
186
  //html += ' <span style="color:green" class="has-tip">ok<span class="tip">' + result['log'] + '</span></span>' +
187
  // getReductionHtml(orgSize, webpSize, 'Size of original', 'Size of webp')
188
  } else {
189
- html += ' <span style="color:red">failed</span><br>';
 
190
  if (result['msg'] != '') {
191
  html += ' <span style="">' + result['msg'] + '</span>';
192
  }
193
- if (result['log'] != '') {
 
 
194
  html += ' <span style="font-size:10px">' + result['log'] + '</span>';
195
- }
196
  }
197
  logLn(html);
198
 
70
  '.reduction {float:right;}\n' +
71
  '</style>';
72
  html += '<button id="bulkPauseResumeBtn" onclick="pauseOrResumeBulkConversion()" class="button button-primary" type="button">Pause</button>';
73
+ //html += '<div id="conversionlog" class="das-popup">test</div>';
74
  html += '<div id="bulkconvertlog"></div>';
75
  document.getElementById('bulkconvertcontent').innerHTML = html;
76
 
121
  //document.getElementById('bulkconvertlog').innerHTML += html;
122
  }
123
 
124
+ function webpexpress_viewLog(groupPointer, filePointer) {
125
+
126
+ var bulkInfo = window.webpexpress_bulkconvert;
127
+ var group = bulkInfo.groups[groupPointer];
128
+ var filename = group.files[filePointer];
129
+ var source = group.root + '/' + filename;
130
+
131
+ var w = Math.min(1200, Math.max(200, document.documentElement.clientWidth - 100));
132
+ var h = Math.max(250, document.documentElement.clientHeight - 80);
133
+
134
+ document.getElementById('conversionlog_content').innerHTML = 'loading log...'; // + source;
135
+
136
+ jQuery.ajax({
137
+ method: 'POST',
138
+ url: ajaxurl,
139
+ data: {
140
+ 'action': 'webpexpress_view_log',
141
+ 'source': source
142
+ },
143
+ success: (response) => {
144
+ //alert(response);
145
+ var result = JSON.parse(response);
146
+ var html = '<h1>Conversion log</h1><br>' + result;
147
+ document.getElementById('conversionlog_content').innerHTML = html;
148
+ },
149
+ error: () => {
150
+ //responseCallback({requestError: true});
151
+ },
152
+ });
153
+
154
+ //<h1>Conversion log</h1>
155
+ //tb_show('Conversion log', '#TB_inline?inlineId=conversionlog');
156
+ openDasPopup('conversionlog', w, h);
157
+
158
+ }
159
+
160
  function convertNextInBulkQueue() {
161
  var html;
162
  var bulkInfo = window.webpexpress_bulkconvert;
203
 
204
  var bulkInfo = window.webpexpress_bulkconvert;
205
  var group = bulkInfo.groups[bulkInfo.groupPointer];
206
+ var filename = group.files[bulkInfo.filePointer];
207
 
208
  var result = JSON.parse(response);
209
  //console.log(result);
210
 
211
  var html = '';
212
+ //var htmlViewLog = '&nbsp;&nbsp;<a style="cursor:pointer" onclick="webpexpress_viewLog(\'' + group.root + '/' + filename + '\')">view log</a>';
213
+ var htmlViewLog = '&nbsp;&nbsp;<a style="cursor:pointer" onclick="webpexpress_viewLog(' + bulkInfo.groupPointer + ',' + bulkInfo.filePointer + ')">view log</a>';
214
  if (result['success']) {
215
 
216
  var orgSize = result['filesize-original'];
221
  bulkInfo['webpTotalFilesize'] += webpSize;
222
 
223
  html += ' <span style="color:green">ok</span></span>' +
224
+ htmlViewLog +
225
+
226
  getReductionHtml(orgSize, webpSize, 'Size of original', 'Size of webp')
227
 
228
  //html += ' <span style="color:green" class="has-tip">ok<span class="tip">' + result['log'] + '</span></span>' +
229
  // getReductionHtml(orgSize, webpSize, 'Size of original', 'Size of webp')
230
  } else {
231
+ html += ' <span style="color:red">failed</span>' + htmlViewLog;
232
+ /*
233
  if (result['msg'] != '') {
234
  html += ' <span style="">' + result['msg'] + '</span>';
235
  }
236
+ */
237
+ html += '<br>';
238
+ /*if (result['log'] != '') {
239
  html += ' <span style="font-size:10px">' + result['log'] + '</span>';
240
+ }*/
241
  }
242
  logLn(html);
243
 
lib/options/js/converters.js CHANGED
@@ -7,14 +7,17 @@ window.currentlyEditing = '';
7
 
8
  function getConversionMethodDescription(converterId) {
9
  var descriptions = {
10
- 'cwebp': '<i>cwebp</i> binary',
11
  'wpc': 'Remote WebP Express',
12
  'ewww': 'ewww cloud converter',
13
  'gd': 'Gd extension',
14
- 'imagick': 'Imagick extension',
15
- 'gmagick': 'Gmagick extension',
16
- 'imagickbinary': 'Imagick binary'
 
 
17
  };
 
18
  if (descriptions[converterId]) {
19
  return descriptions[converterId];
20
  }
@@ -206,21 +209,24 @@ function deleteConverterOption(converter, optionName) {
206
  function configureConverter(id) {
207
  var converter = window.convertersMap[id];
208
  window.currentlyEditing = id;
 
 
209
  var q = getConverterOption(converter, 'quality', 'auto');
210
  if (document.getElementById(id + '_quality')) {
211
  document.getElementById(id + '_quality').value = q;
212
  document.getElementById(id + '_max_quality_div').style['display'] = (q == 'auto' ? 'block' : 'none');
213
  document.getElementById(id + '_max_quality').value = getConverterOption(converter, 'max-quality', 85);
214
  }
 
215
 
216
  switch (converter['converter']) {
217
  case 'ewww':
218
- document.getElementById('ewww_key').value = getConverterOption(converter, 'key', '');
219
- document.getElementById('ewww_key_2').value = getConverterOption(converter, 'key-2', '');
220
  break;
221
  case 'wpc':
222
 
223
- document.getElementById('wpc_url').value = getConverterOption(converter, 'url', '');
224
 
225
  /* api key in configuration file can be:
226
  - never set (null)
@@ -255,7 +261,7 @@ function configureConverter(id) {
255
  // - Wpc has never been configured. In that case, URL is not set,
256
  // and we should not mention api 0 (we should set apiVersion to 1)
257
  if (!isConverterOptionSet(converter, 'api-version')) {
258
- if (getConverterOption(converter, 'url', '') == '') {
259
  apiVersion = 1;
260
  }
261
  }
@@ -287,10 +293,10 @@ function configureConverter(id) {
287
 
288
  break;
289
  case 'gd':
290
- document.getElementById('gd_skip_pngs').checked = getConverterOption(converter, 'skip-pngs', '');
291
  break;
292
  case 'cwebp':
293
- document.getElementById('cwebp_use_nice').checked = getConverterOption(converter, 'use-nice', '');
294
  document.getElementById('cwebp_method').value = getConverterOption(converter, 'method', '');
295
  document.getElementById('cwebp_try_common_system_paths').checked = getConverterOption(converter, 'try-common-system-paths', '');
296
  document.getElementById('cwebp_try_supplied_binary').checked = getConverterOption(converter, 'try-supplied-binary-for-os', '');
@@ -298,8 +304,16 @@ function configureConverter(id) {
298
  document.getElementById('cwebp_size_in_percentage').value = getConverterOption(converter, 'size-in-percentage', '');
299
  document.getElementById('cwebp_command_line_options').value = getConverterOption(converter, 'command-line-options', '');
300
  break;
301
- case 'imagickbinary':
302
- document.getElementById('imagickbinary_use_nice').checked = getConverterOption(converter, 'use-nice', '');
 
 
 
 
 
 
 
 
303
  break;
304
  }
305
  tb_show("Configure " + converter['id'] + ' converter', '#TB_inline?inlineId=' + converter['converter']);
@@ -309,6 +323,8 @@ function updateConverterOptions() {
309
  var id = window.currentlyEditing;
310
  var converter = window.convertersMap[id];
311
 
 
 
312
  if (document.getElementById(id + '_quality')) {
313
  var q = document.getElementById(id + '_quality').value;
314
  if (q == 'auto') {
@@ -322,14 +338,15 @@ function updateConverterOptions() {
322
  deleteConverterOption(converter, 'quality');
323
  deleteConverterOption(converter, 'max-quality');
324
  }
 
325
 
326
  switch (converter['converter']) {
327
  case 'ewww':
328
- setConverterOption(converter, 'key', document.getElementById('ewww_key').value);
329
- setConverterOption(converter, 'key-2', document.getElementById('ewww_key_2').value);
330
  break;
331
  case 'wpc':
332
- setConverterOption(converter, 'url', document.getElementById('wpc_url').value);
333
  //setConverterOption(converter, 'secret', document.getElementById('wpc_secret').value);
334
  //setConverterOption(converter, 'url-2', document.getElementById('wpc_url_2').value);
335
  //setConverterOption(converter, 'secret-2', document.getElementById('wpc_secret_2').value);*/
@@ -366,8 +383,21 @@ function updateConverterOptions() {
366
  setConverterOption(converter, 'size-in-percentage', document.getElementById('cwebp_size_in_percentage').value);
367
  setConverterOption(converter, 'command-line-options', document.getElementById('cwebp_command_line_options').value);
368
  break;
369
- case 'imagickbinary':
370
- setConverterOption(converter, 'use-nice', document.getElementById('imagickbinary_use_nice').checked);
 
 
 
 
 
 
 
 
 
 
 
 
 
371
  break;
372
  }
373
 
@@ -391,6 +421,8 @@ function encodePathforQS(path) {
391
 
392
  function testConverter(id) {
393
  //alert('h' + id);
 
 
394
  var converter = window.convertersMap[id];
395
 
396
  // https://stackoverflow.com/questions/4321068/to-invoke-thickbox-using-javascript
@@ -429,7 +461,10 @@ function testConverter(id) {
429
  }
430
  }
431
  }
432
- url += '&TB_iframe=true&width=400&height=300';
 
 
 
433
  //alert(url);
434
  tb_show("Test running converter: " + converter['id'], url);
435
  }
@@ -466,10 +501,11 @@ function activateConverter(id) {
466
  /* WPC */
467
  /* ------------- */
468
 
 
469
  function converterQualityChanged(converterId) {
470
  var q = document.getElementById(converterId + '_quality').value;
471
  document.getElementById(converterId + '_max_quality_div').style['display'] = (q == 'auto' ? 'block' : 'none');
472
- }
473
 
474
  function wpcShowAwaitingApprovalPopup() {
475
  closeDasPopup();
7
 
8
  function getConversionMethodDescription(converterId) {
9
  var descriptions = {
10
+ 'cwebp': '<i>cwebp</i>',
11
  'wpc': 'Remote WebP Express',
12
  'ewww': 'ewww cloud converter',
13
  'gd': 'Gd extension',
14
+ 'imagick': 'Imagick (PHP extension)',
15
+ 'gmagick': 'Gmagick (PHP extension)',
16
+ 'imagemagick': 'ImageMagick',
17
+ 'graphicsmagick': 'GraphicsMagick',
18
+ 'vips': 'Vips'
19
  };
20
+
21
  if (descriptions[converterId]) {
22
  return descriptions[converterId];
23
  }
209
  function configureConverter(id) {
210
  var converter = window.convertersMap[id];
211
  window.currentlyEditing = id;
212
+ /*
213
+ Removed (#243)
214
  var q = getConverterOption(converter, 'quality', 'auto');
215
  if (document.getElementById(id + '_quality')) {
216
  document.getElementById(id + '_quality').value = q;
217
  document.getElementById(id + '_max_quality_div').style['display'] = (q == 'auto' ? 'block' : 'none');
218
  document.getElementById(id + '_max_quality').value = getConverterOption(converter, 'max-quality', 85);
219
  }
220
+ */
221
 
222
  switch (converter['converter']) {
223
  case 'ewww':
224
+ document.getElementById('ewww_api_key').value = getConverterOption(converter, 'api-key', '');
225
+ document.getElementById('ewww_api_key_2').value = getConverterOption(converter, 'api-key-2', '');
226
  break;
227
  case 'wpc':
228
 
229
+ document.getElementById('wpc_api_url').value = getConverterOption(converter, 'api-url', '');
230
 
231
  /* api key in configuration file can be:
232
  - never set (null)
261
  // - Wpc has never been configured. In that case, URL is not set,
262
  // and we should not mention api 0 (we should set apiVersion to 1)
263
  if (!isConverterOptionSet(converter, 'api-version')) {
264
+ if (getConverterOption(converter, 'api-url', '') == '') {
265
  apiVersion = 1;
266
  }
267
  }
293
 
294
  break;
295
  case 'gd':
296
+ document.getElementById('gd_skip_pngs').checked = getConverterOption(converter, 'skip-pngs', false);
297
  break;
298
  case 'cwebp':
299
+ document.getElementById('cwebp_use_nice').checked = getConverterOption(converter, 'use-nice', true);
300
  document.getElementById('cwebp_method').value = getConverterOption(converter, 'method', '');
301
  document.getElementById('cwebp_try_common_system_paths').checked = getConverterOption(converter, 'try-common-system-paths', '');
302
  document.getElementById('cwebp_try_supplied_binary').checked = getConverterOption(converter, 'try-supplied-binary-for-os', '');
304
  document.getElementById('cwebp_size_in_percentage').value = getConverterOption(converter, 'size-in-percentage', '');
305
  document.getElementById('cwebp_command_line_options').value = getConverterOption(converter, 'command-line-options', '');
306
  break;
307
+ case 'imagemagick':
308
+ document.getElementById('imagemagick_use_nice').checked = getConverterOption(converter, 'use-nice', true);
309
+ break;
310
+ case 'graphicsmagick':
311
+ document.getElementById('graphicsmagick_use_nice').checked = getConverterOption(converter, 'use-nice', true);
312
+ break;
313
+ case 'vips':
314
+ document.getElementById('vips_smart_subsample').checked = getConverterOption(converter, 'smart-subsample', false);
315
+ document.getElementById('vips_preset').value = getConverterOption(converter, 'preset', 'disable');
316
+
317
  break;
318
  }
319
  tb_show("Configure " + converter['id'] + ' converter', '#TB_inline?inlineId=' + converter['converter']);
323
  var id = window.currentlyEditing;
324
  var converter = window.convertersMap[id];
325
 
326
+ /*
327
+ Removed (#243)
328
  if (document.getElementById(id + '_quality')) {
329
  var q = document.getElementById(id + '_quality').value;
330
  if (q == 'auto') {
338
  deleteConverterOption(converter, 'quality');
339
  deleteConverterOption(converter, 'max-quality');
340
  }
341
+ */
342
 
343
  switch (converter['converter']) {
344
  case 'ewww':
345
+ setConverterOption(converter, 'api-key', document.getElementById('ewww_api_key').value);
346
+ setConverterOption(converter, 'api-key-2', document.getElementById('ewww_api_key_2').value);
347
  break;
348
  case 'wpc':
349
+ setConverterOption(converter, 'api-url', document.getElementById('wpc_api_url').value);
350
  //setConverterOption(converter, 'secret', document.getElementById('wpc_secret').value);
351
  //setConverterOption(converter, 'url-2', document.getElementById('wpc_url_2').value);
352
  //setConverterOption(converter, 'secret-2', document.getElementById('wpc_secret_2').value);*/
383
  setConverterOption(converter, 'size-in-percentage', document.getElementById('cwebp_size_in_percentage').value);
384
  setConverterOption(converter, 'command-line-options', document.getElementById('cwebp_command_line_options').value);
385
  break;
386
+ case 'imagemagick':
387
+ setConverterOption(converter, 'use-nice', document.getElementById('imagemagick_use_nice').checked);
388
+ break;
389
+ case 'graphicsmagick':
390
+ setConverterOption(converter, 'use-nice', document.getElementById('graphicsmagick_use_nice').checked);
391
+ break;
392
+ case 'vips':
393
+ setConverterOption(converter, 'smart-subsample', document.getElementById('vips_smart_subsample').checked);
394
+
395
+ var vipsPreset = document.getElementById('vips_preset').value;
396
+ if (vipsPreset == 'disable') {
397
+ deleteConverterOption(converter, 'preset');
398
+ } else {
399
+ setConverterOption(converter, 'preset', vipsPreset);
400
+ }
401
  break;
402
  }
403
 
421
 
422
  function testConverter(id) {
423
  //alert('h' + id);
424
+ openTestConvertPopup(id);
425
+ return;
426
  var converter = window.convertersMap[id];
427
 
428
  // https://stackoverflow.com/questions/4321068/to-invoke-thickbox-using-javascript
461
  }
462
  }
463
  }
464
+ var w = Math.min(1000, Math.max(200, document.documentElement.clientWidth - 100));
465
+ var h = Math.max(250, document.documentElement.clientHeight - 80);
466
+
467
+ url += '&TB_iframe=true&width=' + w + '&height=' + h;
468
  //alert(url);
469
  tb_show("Test running converter: " + converter['id'], url);
470
  }
501
  /* WPC */
502
  /* ------------- */
503
 
504
+ /*
505
  function converterQualityChanged(converterId) {
506
  var q = document.getElementById(converterId + '_quality').value;
507
  document.getElementById(converterId + '_max_quality_div').style['display'] = (q == 'auto' ? 'block' : 'none');
508
+ }*/
509
 
510
  function wpcShowAwaitingApprovalPopup() {
511
  closeDasPopup();
lib/options/js/image-comparison-slider.js ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ function initComparisonSlider($) {
3
+ //function to check if the .cd-image-container is in the viewport here
4
+ // ...
5
+
6
+ var w = $('.cd-image-container').width();
7
+
8
+ $('.cd-image-container img').each(function(){
9
+ $(this).css('max-width', w + 'px');
10
+ });
11
+
12
+ $('.cd-image-container img').first().on('load', function() {
13
+ $('.cd-image-container').width($(this).width());
14
+ })
15
+
16
+ //make the .cd-handle element draggable and modify .cd-resize-img width according to its position
17
+ $('.cd-image-container').each(function(){
18
+ var actual = $(this);
19
+ drags(actual.find('.cd-handle'), actual.find('.cd-resize-img'), actual);
20
+ });
21
+
22
+ //draggable funtionality - credits to http://css-tricks.com/snippets/jquery/draggable-without-jquery-ui/
23
+ function drags(dragElement, resizeElement, container) {
24
+ dragElement.on("mousedown vmousedown", function(e) {
25
+ dragElement.addClass('draggable');
26
+ resizeElement.addClass('resizable');
27
+
28
+ var dragWidth = dragElement.outerWidth(),
29
+ xPosition = dragElement.offset().left + dragWidth - e.pageX,
30
+ containerOffset = container.offset().left,
31
+ containerWidth = container.outerWidth(),
32
+ minLeft = containerOffset + 10,
33
+ maxLeft = containerOffset + containerWidth - dragWidth - 10;
34
+
35
+ dragElement.parents().on("mousemove vmousemove", function(e) {
36
+ leftValue = e.pageX + xPosition - dragWidth;
37
+
38
+ //constrain the draggable element to move inside its container
39
+ if(leftValue < minLeft ) {
40
+ leftValue = minLeft;
41
+ } else if ( leftValue > maxLeft) {
42
+ leftValue = maxLeft;
43
+ }
44
+
45
+ widthValue = (leftValue + dragWidth/2 - containerOffset)*100/containerWidth+'%';
46
+
47
+ $('.draggable').css('left', widthValue).on("mouseup vmouseup", function() {
48
+ $(this).removeClass('draggable');
49
+ resizeElement.removeClass('resizable');
50
+ });
51
+
52
+ $('.resizable').css('width', widthValue);
53
+
54
+ //function to upadate images label visibility here
55
+ // ...
56
+
57
+ }).on("mouseup vmouseup", function(e){
58
+ dragElement.removeClass('draggable');
59
+ resizeElement.removeClass('resizable');
60
+ });
61
+ e.preventDefault();
62
+ }).on("mouseup vmouseup", function(e) {
63
+ dragElement.removeClass('draggable');
64
+ resizeElement.removeClass('resizable');
65
+ });
66
+ };
67
+
68
+ };
lib/options/js/page.js CHANGED
@@ -103,16 +103,19 @@ document.addEventListener('DOMContentLoaded', function() {
103
 
104
 
105
  // Toggle Quality (auto / specific)
106
- if (el('quality_auto_select') && el('max_quality_row') && el('quality_specific_row')) {
107
  function updateQualityVisibility() {
108
  var qualityAutoValue = el('quality_auto_select').value;
 
 
 
109
  if (qualityAutoValue == 'auto_on') {
110
- el('max_quality_row').style['display'] = 'table-row';
111
- el('quality_specific_row').style['display'] = 'none';
112
  } else {
113
- el('max_quality_row').style['display'] = 'none';
114
- el('quality_specific_row').style['display'] = 'table-row';
115
- }
116
  }
117
  updateQualityVisibility();
118
  el('quality_auto_select').addEventListener('change', function() {
@@ -120,6 +123,65 @@ document.addEventListener('DOMContentLoaded', function() {
120
  });
121
  }
122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  // Toggle File Extension (only show when "mingled" is selected)
124
  if (el('destination_folder') && el('destination_extension_row') && el('destination_extension')) {
125
  el('destination_extension_row').classList.add('effect-opacity');
103
 
104
 
105
  // Toggle Quality (auto / specific)
106
+ if (el('quality_auto_select') && el('max_quality_div') && el('quality_specific_div')) {
107
  function updateQualityVisibility() {
108
  var qualityAutoValue = el('quality_auto_select').value;
109
+ toggleVisibility('max_quality_div', el('quality_auto_select').value == 'auto_on');
110
+ toggleVisibility('quality_specific_div', el('quality_auto_select').value != 'auto_on');
111
+ /*
112
  if (qualityAutoValue == 'auto_on') {
113
+ el('max_quality_div').style['display'] = 'inline-block';
114
+ el('quality_specific_div').style['display'] = 'none';
115
  } else {
116
+ el('max_quality_div').style['display'] = 'none';
117
+ el('quality_specific_div').style['display'] = 'inline-block';
118
+ }*/
119
  }
120
  updateQualityVisibility();
121
  el('quality_auto_select').addEventListener('change', function() {
123
  });
124
  }
125
 
126
+ // Jpeg encoding changing
127
+ if (el('jpeg_encoding_select') && el('jpeg_quality_lossless_div')) {
128
+ function updateJpgEncoding() {
129
+ toggleVisibility('jpeg_quality_lossless_div', el('jpeg_encoding_select').value != 'lossy');
130
+ }
131
+ updateJpgEncoding();
132
+ el('jpeg_encoding_select').addEventListener('change', function() {
133
+ updateJpgEncoding();
134
+ });
135
+ }
136
+
137
+ // Jpeg near-lossless toggling
138
+ if (el('jpeg_enable_near_lossless') && el('jpeg_near_lossless_div')) {
139
+ function updateNearLosslessVisibilityJpeg() {
140
+ toggleVisibility('jpeg_near_lossless_div', el('jpeg_enable_near_lossless').value == 'on');
141
+ }
142
+ updateNearLosslessVisibilityJpeg();
143
+ el('jpeg_enable_near_lossless').addEventListener('change', function() {
144
+ updateNearLosslessVisibilityJpeg();
145
+ });
146
+ }
147
+
148
+ // PNG encoding changing
149
+ if (el('image_types') && el('png_row')) {
150
+ function updatePngRow() {
151
+ toggleVisibility('png_row', el('image_types').value != '1');
152
+ }
153
+ updatePngRow();
154
+ el('image_types').addEventListener('change', function() {
155
+ updatePngRow();
156
+ });
157
+ }
158
+
159
+
160
+
161
+ // PNG encoding changing
162
+ if (el('png_encoding_select') && el('png_quality_lossy_div')) {
163
+ function updatePngEncoding() {
164
+ toggleVisibility('png_quality_lossy_div', el('png_encoding_select').value != 'lossless');
165
+ }
166
+ updatePngEncoding();
167
+ el('png_encoding_select').addEventListener('change', function() {
168
+ updatePngEncoding();
169
+ });
170
+ }
171
+
172
+ // PNG near-lossless toggling
173
+ if (el('png_enable_near_lossless') && el('png_near_lossless_div')) {
174
+ function updateNearLosslessVisibilityPng() {
175
+ toggleVisibility('png_near_lossless_div', el('png_enable_near_lossless').value == 'on');
176
+ }
177
+ updateNearLosslessVisibilityPng();
178
+ el('png_enable_near_lossless').addEventListener('change', function() {
179
+ updateNearLosslessVisibilityPng();
180
+ });
181
+ }
182
+
183
+
184
+
185
  // Toggle File Extension (only show when "mingled" is selected)
186
  if (el('destination_folder') && el('destination_extension_row') && el('destination_extension')) {
187
  el('destination_extension_row').classList.add('effect-opacity');
lib/options/js/test-convert.js ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ function openTestConvertPopup(converterId) {
3
+ var html = '<div id="tc_conversion_options">options</div><div><div id="tc_conversion_result"><h2>Result</h2>wait...</div></div>'
4
+ document.getElementById('tc_content').innerHTML = html;
5
+
6
+ var w = Math.min(1200, Math.max(200, document.documentElement.clientWidth - 100));
7
+ var h = Math.max(250, document.documentElement.clientHeight - 80);
8
+ tb_show('Testing conversion', '#TB_inline?inlineId=tc_popup&width=' + w + '&height=' + h);
9
+
10
+ webpexpress_createTCOptions(converterId);
11
+ }
12
+
13
+ function webpexpress_createTCOptions(converterId) {
14
+
15
+ var html = '';
16
+ html += '<h2>Options</h2>'
17
+ html += '<form>';
18
+ html += '<div style="display:inline-block; margin-right: 20px;"><label>Converter:</label><select id="converter" name="converter">';
19
+ for (var i=0; i<window.converters.length; i++) {
20
+ var c = window.converters[i];
21
+ var cid = c['converter'];
22
+ html += '<option value="' + cid + '"' + (cid == converterId ? ' selected' : '') + '>' + cid + '</option>';
23
+ }
24
+ html += '</select></div>'
25
+ html += '<div style="display:inline-block;"><label>Test image:</label><select id="test_image" name="image">';
26
+ //html += '<option value="dice.png">dice.png</option>';
27
+ html += '<option value="test-pattern-tv.jpg">test-pattern-tv.jpg</option>';
28
+ html += '<option value="dice.png">dice.png</option>';
29
+ //html += '<option value="alphatest.png">alphatest.png</option>';
30
+ html += '<option value="palette-based-colors.png">palette-based-colors.png</option>';
31
+ //html += '<option value="test.png">test.png</option>';
32
+ html += '<option value="architecture-q85-w600.jpg">architecture-q85-w600.jpg</option>';
33
+ html += '</select></div>';
34
+ // html += '<h3>Conversion options</h3>'
35
+ html += '<div id="tc_png" class="toggler effect-visibility"><h3>Conversion options (PNG)</h3><div id="tc_png_options"></div></div>';
36
+ html += '<div id="tc_jpeg" class="toggler effect-visibility"><h3>Conversion options (JPEG)</h3><div id="tc_jpeg_options"></div></div>';
37
+ // html += '<div id="tc_jpeg_options" class="toggler effect-visibility"></div>';
38
+ html += '<div><label>Metadata:</label><div id="tc_metadata" style="display: inline-block"></div></div>';
39
+ html += '<button onclick="runTestConversion()" class="button button-primary" type="button" style="margin-top:25px">Convert</button>';
40
+ html += '</form>';
41
+ document.getElementById('tc_conversion_options').innerHTML = html;
42
+
43
+ // Append PNG
44
+ document.getElementById('tc_png_options').appendChild(
45
+ document.getElementById('png_td').cloneNode(true)
46
+ );
47
+
48
+ // Append Jpeg
49
+ document.getElementById('tc_jpeg_options').appendChild(
50
+ document.getElementById('jpeg_td').cloneNode(true)
51
+ );
52
+
53
+ // Append Metadata
54
+ document.getElementById('tc_metadata').appendChild(
55
+ document.getElementById('metadata').cloneNode(true)
56
+ );
57
+
58
+ // change ids. All id's will get appended "tc_" - unless they already have
59
+ document.querySelectorAll('#tc_conversion_options [id]').forEach(function(el) {
60
+ el.value = document.getElementById(el.id).value;
61
+ if (el.id.indexOf('tc_') != 0) {
62
+ el.id = 'tc_' + el.id;
63
+ }
64
+ });
65
+
66
+ // listen to all select box changes
67
+ document.querySelectorAll('#tc_conversion_options select').forEach(function(el) {
68
+ el.addEventListener('change', function() {
69
+ webpexpress_updateVisibilities();
70
+ });
71
+ });
72
+
73
+ webpexpress_updateVisibilities();
74
+
75
+ runTestConversion();
76
+ }
77
+
78
+ function webpexpress_updateVisibilities() {
79
+ // toggleVisibility('png_row', el('image_types').value != '1');
80
+ function el(elmId) {
81
+ return document.getElementById(elmId);
82
+ }
83
+
84
+ var testImage = el('tc_test_image').value;
85
+ var isPng = (testImage.toLowerCase().indexOf('.png') != -1);
86
+
87
+ toggleVisibility('tc_png', isPng);
88
+ toggleVisibility('tc_jpeg', !isPng);
89
+
90
+ toggleVisibility('tc_png_quality_lossy_div', el('tc_png_encoding_select').value != 'lossless');
91
+ toggleVisibility('tc_png_near_lossless_div', el('tc_png_enable_near_lossless').value == 'on');
92
+
93
+ console.log('value:' + el('tc_quality_auto_select').value);
94
+ toggleVisibility('tc_max_quality_div', el('tc_quality_auto_select').value == 'auto_on');
95
+ toggleVisibility('tc_quality_specific_div', el('tc_quality_auto_select').value != 'auto_on');
96
+
97
+
98
+ }
99
+
100
+ function runTestConversion() {
101
+ var html = '';
102
+
103
+ function elTxt(elmName) {
104
+ //var el = document.getElementById('tc_' + elmId);
105
+ var el = document.querySelector('#tc_conversion_options [name=' + elmName + ']');
106
+ if (!el) {
107
+ alert('Error: Could not find element with name: "' + elmName + '"');
108
+ }
109
+ return el.value;
110
+ }
111
+ function elInt(elmName) {
112
+ return parseInt(elTxt(elmName), 10);
113
+ }
114
+
115
+ var configOverrides = {
116
+ "jpeg-encoding": elTxt("jpeg-encoding"),
117
+ "jpeg-enable-near-lossless": (elTxt("jpeg-enable-near-lossless") == 'on'),
118
+ "jpeg-near-lossless": elInt('jpeg-near-lossless'),
119
+ "quality-auto": (elTxt("quality-auto") == 'auto_on'),
120
+ "max-quality": elInt('max-quality'),
121
+ "quality-specific": (elTxt("quality-auto") == 'auto_on' ? elInt('quality-fallback') : elInt('quality-specific')),
122
+ "png-encoding": elTxt("png-encoding"),
123
+ "png-enable-near-lossless": true,
124
+ "png-near-lossless": elInt("png-near-lossless"),
125
+ "png-quality": elInt("png-quality"),
126
+ "alpha-quality": elInt("alpha-quality"),
127
+ "metadata": elTxt('metadata'),
128
+ "log-call-arguments": true,
129
+ };
130
+
131
+ var data = {
132
+ 'action': 'convert_file',
133
+ 'filename': window.webpExpressPaths['filePaths']['webpExpressRoot'] + '/test/' + elTxt('image'),
134
+ "converter": elTxt("converter"),
135
+ 'config-overrides': JSON.stringify(configOverrides)
136
+ }
137
+
138
+ //html = JSON.stringify(data);
139
+ //html = 'Converting...';
140
+ document.getElementById('tc_conversion_result').innerHTML = html;
141
+
142
+ jQuery.ajax({
143
+ method: 'POST',
144
+ url: ajaxurl,
145
+ data: data,
146
+ //dataType: 'json',
147
+ success: (response) => {
148
+ convertResponseCallback(response);
149
+ },
150
+ error: () => {
151
+ convertResponseCallback({requestError: true});
152
+ },
153
+ });
154
+ }
155
+
156
+ function processLogMoveOptions(thelog) {
157
+ var pos1 = thelog.indexOf('Options:<br>---');
158
+ if (pos1 >= 0) {
159
+ var pos2 = thelog.indexOf('<br>', pos1 + 12) + 4;
160
+ //pos2+=8;
161
+ /*if (thelog.indexOf('<br>', pos2) < 2) {
162
+ pos2 = thelog.indexOf('<br>', pos2) + 4;
163
+ }*/
164
+ var pos3 = thelog.indexOf('----<br>', pos2) + 8;
165
+
166
+ // Remove empty line after "Conversion log:"
167
+ var pos4 = thelog.indexOf('<br>', pos3);
168
+ if (pos4-pos3 < 2) {
169
+ pos3 = pos4 + 4;
170
+ }
171
+ //pos3+=4;
172
+
173
+ return thelog.substr(0, pos1) +
174
+ thelog.substr(pos3) +
175
+ //'-------------------------------------------<br>' +
176
+ '<h3>Options:</h3>' +
177
+ thelog.substr(pos2, pos3-pos2);
178
+ }
179
+ return thelog;
180
+
181
+
182
+ /*
183
+ return thelog.substr(0, pos1) +
184
+ 'Click to view options' +
185
+ '<div style="display:none">' + thelog.substr(pos1, pos2-pos1) + '</div>' +
186
+ thelog.substr(pos2);
187
+ */
188
+ }
189
+
190
+ function convertResponseCallback(response){
191
+
192
+
193
+ if (typeof response.requestError == 'boolean') {
194
+ document.getElementById('tc_conversion_result').innerHTML = '<h1 style="color:red">An error occured!</h1>';
195
+ //console.log('response', response);
196
+ return;
197
+ }
198
+
199
+ if (response[0] != '{') {
200
+ document.getElementById('tc_conversion_result').innerHTML =
201
+ '<h1 style="color:red">Response was not JSON</h1><p>The following was returned:</p>' + response;
202
+ return;
203
+ }
204
+ var result = JSON.parse(response);
205
+ result['log'] = processLogMoveOptions(result['log']);
206
+
207
+ //var html = document.getElementById('tc_conversion_result').innerHTML;
208
+ var html = '';
209
+
210
+ if (result['success'] === true) {
211
+
212
+ html += '<h2>Result: <span style="color:green;margin-bottom:2px">Success</span></h2>';
213
+
214
+ // sizes
215
+ var orgSize = result['filesize-original'];
216
+ var webpSize = result['filesize-webp'];
217
+ html += '<b>Reduction: ' + Math.round((orgSize - webpSize)/orgSize * 100) + '% ';
218
+
219
+
220
+ if (orgSize < 10000) {
221
+ orgSizeStr = orgSize + ' bytes';
222
+ webpSizeStr = webpSize + ' bytes';
223
+
224
+ } else {
225
+ orgSizeStr = Math.round(orgSize / 1024) + ' K';
226
+ webpSizeStr = Math.round(webpSize / 1024) + ' K';
227
+ }
228
+ html += '(from ' + orgSizeStr.replace('K', 'kb') + ' to ' + webpSizeStr.replace('K', 'kb') + ')';
229
+ html += '</b><br><br>'
230
+
231
+ if (window.canDisplayWebp) {
232
+ var filename = document.querySelector('#tc_conversion_options [name=image]').value;
233
+ var srcUrl = '/' + window.webpExpressPaths['urls']['webpExpressRoot'] + '/test/' + filename;
234
+ //html += '<img src="/' + srcUrl + '" style="width:100%">';
235
+
236
+ var webpUrl = '/' + window.webpExpressPaths['urls']['content'] +
237
+ '/webp-express/webp-images/doc-root/' +
238
+ window.webpExpressPaths['filePaths']['pluginRelToDocRoot'] + '/' +
239
+ 'webp-express/' +
240
+ 'test/' +
241
+ filename + '.webp';
242
+ //html += '<img src="' + webpUrl + '" style="width:100%">';
243
+
244
+ html += '<div class="cd-image-container">';
245
+ html += ' <div class="cd-image-label webp">WebP: ' + webpSizeStr + '</div>';
246
+ html += ' <div class="cd-image-label original">' + (filename.toLowerCase().indexOf('png') > 0 ? 'PNG' : 'JPEG') + ': ' + orgSizeStr + '</div>';
247
+ html += ' <img src="' + webpUrl + '" alt="Converted Image" style="max-width:100%">';
248
+ html += ' <div class="cd-resize-img"> <!-- the resizable image on top -->';
249
+ html += ' <img src="' + srcUrl + '" alt="Original Image">';
250
+ html += ' </div>';
251
+ html += ' <span class="cd-handle"></span> <!-- slider handle -->';
252
+ html += '</div> <!-- cd-image-container -->';
253
+ html += '<i>Drag the slider above to compare original vs webp</i><br><br>'
254
+ }
255
+
256
+ html += '<h3>Conversion log:</h3>'
257
+ html += result['log'];
258
+
259
+ document.getElementById('tc_conversion_result').innerHTML = html;
260
+ initComparisonSlider(jQuery);
261
+
262
+ } else {
263
+ html += '<h2>Result: <span style="color:red;margin-bottom:2px">Failure</span></h2>';
264
+
265
+ if (result['msg'] != '') {
266
+ html += ' <h3>Message: <span style="color:red; font-weight: bold">' + result['msg'] + '</span></h3>';
267
+ }
268
+ if (result['log'] != '') {
269
+ html += '<h3>Conversion log:</h3>';
270
+ html += result['log'];
271
+ }
272
+
273
+ document.getElementById('tc_conversion_result').innerHTML = html;
274
+ }
275
+
276
+ //html = result['log'];
277
+
278
+ }
lib/options/options/conversion-options/bulk-convert.inc CHANGED
@@ -8,6 +8,13 @@
8
  ?>
9
  </th>
10
  <td>
 
 
 
 
 
 
 
11
  <div>
12
  <button onclick="openBulkConvertPopup()" class="button button-secondary" type="button">Bulk Convert</button>
13
  <div id="bulkconvertpopup" style="display:none;">
8
  ?>
9
  </th>
10
  <td>
11
+ <div id="conversionlog" class="das-popup">
12
+ <div id="conversionlog_content">
13
+ </div>
14
+ <button onclick="closeDasPopup()" class="button button-primary" type="button" style="position:absolute; bottom:20px">
15
+ Close
16
+ </button>
17
+ </div>
18
  <div>
19
  <button onclick="openBulkConvertPopup()" class="button button-secondary" type="button">Bulk Convert</button>
20
  <div id="bulkconvertpopup" style="display:none;">
lib/options/options/conversion-options/conversion-options.inc CHANGED
@@ -4,7 +4,9 @@
4
  <table class="form-table">
5
  <tbody>
6
  <?php
7
- include_once 'quality.inc';
 
 
8
  include_once 'metadata.inc';
9
  include_once 'converters.inc';
10
  include_once 'convert-on-upload.inc';
4
  <table class="form-table">
5
  <tbody>
6
  <?php
7
+ // include_once 'quality.inc';
8
+ include_once 'jpeg.inc';
9
+ include_once 'png.inc';
10
  include_once 'metadata.inc';
11
  include_once 'converters.inc';
12
  include_once 'convert-on-upload.inc';
lib/options/options/conversion-options/converter-options/ewww.php CHANGED
@@ -8,14 +8,14 @@
8
  </p>
9
  <h3>Options</h3>
10
  <div>
11
- <label for="ewww_key">Key</label>
12
- <input type="text" id="ewww_key" placeholder="Your API key here">
13
  </div>
14
  <br>
15
  <h4>Fallback (optional)</h4>
16
  <div>
17
- <label for="ewww_key_2">key</label>
18
- <input type="text" id="ewww_key_2" placeholder="In case the first one expires...">
19
  </div>
20
  <br>
21
  <?php webp_express_printUpdateButtons() ?>
8
  </p>
9
  <h3>Options</h3>
10
  <div>
11
+ <label for="ewww_api_key">API Key</label>
12
+ <input type="text" id="ewww_api_key" placeholder="Your API key here">
13
  </div>
14
  <br>
15
  <h4>Fallback (optional)</h4>
16
  <div>
17
+ <label for="ewww_api_key_2">API Key</label>
18
+ <input type="text" id="ewww_api_key_2" placeholder="In case the first one expires...">
19
  </div>
20
  <br>
21
  <?php webp_express_printUpdateButtons() ?>
lib/options/options/conversion-options/converter-options/gd.php CHANGED
@@ -5,8 +5,15 @@
5
  <label for="gd_skip_pngs">Skip PNGs</label>
6
  <input type="checkbox" id="gd_skip_pngs">
7
  <br>
8
- You can choose to skip PNG's for Gd (which means the next working and active converter in the stack will handle it, if there is any).
9
- In our first implementation, Gd had problems with transparency. This is however solved now.
 
 
 
 
 
 
 
10
  </div>
11
  <br>
12
  <?php webp_express_printUpdateButtons() ?>
5
  <label for="gd_skip_pngs">Skip PNGs</label>
6
  <input type="checkbox" id="gd_skip_pngs">
7
  <br>
8
+ <p>
9
+ You can choose to skip PNG's for Gd (which means the next working and active converter in the stack will handle it,
10
+ if there is any). There can be two reasons to do that:
11
+ <ul class="with-bullets">
12
+ <li>In our first implementation, Gd had problems with transparency. This should however be solved now. </li>
13
+ <li>Gd does not compress PNGs very effeciently</li>
14
+ </ul>
15
+ </p>
16
+
17
  </div>
18
  <br>
19
  <?php webp_express_printUpdateButtons() ?>
lib/options/options/conversion-options/converter-options/graphicsmagick.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div id="graphicsmagick" style="display:none;">
2
+ <div class="graphicsmagick converter-options">
3
+
4
+ <h3>Gmagick binary options</h3>
5
+ <p>This conversion method works by executing gmagick binary (the 'gm convert' command).</p>
6
+
7
+ <div>
8
+ <label for="graphicsmagick_use_nice">
9
+ Use nice
10
+ <?php echo helpIcon(
11
+ 'Enabling this option saves system resources at the cost of slightly slower conversion.'
12
+ ); ?>
13
+ </label>
14
+ <input type="checkbox" id="graphicsmagick_use_nice">
15
+ <br>
16
+ </div>
17
+ <br>
18
+
19
+ <?php
20
+ /*
21
+ Removed (#243)
22
+ if (!$canDetectQuality) {
23
+ printAutoQualityOptionForConverter('graphicsmagick');
24
+ }*/
25
+ ?>
26
+ <!--
27
+ <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
28
+ -->
29
+ <!-- <a href="javascript: tb_remove();">close</a> -->
30
+ <?php webp_express_printUpdateButtons() ?>
31
+ </div>
32
+ </div>
lib/options/options/conversion-options/converter-options/imagemagick.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div id="imagemagick" style="display:none;">
2
+ <div class="imagemagick converter-options">
3
+
4
+ <h3>ImageMagick options</h3>
5
+ <p>This conversion method works by executing imagemagick binary (the 'convert' command).</p>
6
+
7
+ <div>
8
+ <label for="imagemagick_use_nice">
9
+ Use nice
10
+ <?php echo helpIcon(
11
+ 'Enabling this option saves system resources at the cost of slightly slower conversion.'
12
+ ); ?>
13
+ </label>
14
+ <input type="checkbox" id="imagemagick_use_nice">
15
+ </div>
16
+ <br>
17
+ <?php
18
+ /*
19
+ Removed (#243)
20
+ if (!$canDetectQuality) {
21
+ printAutoQualityOptionForConverter('imagemagick');
22
+ }*/
23
+ ?>
24
+ <!--
25
+ <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
26
+ -->
27
+ <!-- <a href="javascript: tb_remove();">close</a> -->
28
+ <?php webp_express_printUpdateButtons() ?>
29
+ </div>
30
+ </div>
lib/options/options/conversion-options/converter-options/imagick.php CHANGED
@@ -2,12 +2,15 @@
2
  <div class="imagick converter-options">
3
  <h3>Imagick options</h3>
4
  <?php
 
 
 
5
  if ($canDetectQuality) {
6
  echo '<div class="info">imagick has no special options.</div>';
7
  } else {
8
  echo '<br>';
9
  printAutoQualityOptionForConverter('imagick');
10
- }
11
  ?>
12
  <!--
13
  <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
2
  <div class="imagick converter-options">
3
  <h3>Imagick options</h3>
4
  <?php
5
+ echo '<div class="info">imagick has no special options.</div>';
6
+ /*
7
+ Removed (#243)
8
  if ($canDetectQuality) {
9
  echo '<div class="info">imagick has no special options.</div>';
10
  } else {
11
  echo '<br>';
12
  printAutoQualityOptionForConverter('imagick');
13
+ }*/
14
  ?>
15
  <!--
16
  <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
lib/options/options/conversion-options/converter-options/imagickbinary.php DELETED
@@ -1,24 +0,0 @@
1
- <div id="imagickbinary" style="display:none;">
2
- <div class="imagickbinary converter-options">
3
-
4
- <h3>Imagick binary options</h3>
5
- <p>This conversion method works by executing imagick binary (the 'convert' command).</p>
6
-
7
- <div>
8
- <label for="imagickbinary_use_nice">Use nice</label>
9
- <input type="checkbox" id="imagickbinary_use_nice">
10
- <br>Enabling this option saves system resources at the cost of slightly slower conversion
11
- </div>
12
-
13
- <?php
14
- if (!$canDetectQuality) {
15
- printAutoQualityOptionForConverter('imagickbinary');
16
- }
17
- ?>
18
- <!--
19
- <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
20
- -->
21
- <!-- <a href="javascript: tb_remove();">close</a> -->
22
- <?php webp_express_printUpdateButtons() ?>
23
- </div>
24
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/options/options/conversion-options/converter-options/vips.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div id="vips" style="display:none;">
2
+ <div class="vips converter-options">
3
+ <h3>Vips options</h3>
4
+ <div>
5
+ <label for="vips_smart_subsample">
6
+ Smart subsample
7
+ <?php echo helpIcon(
8
+ 'According to <a target="_blank" href="https://jcupitt.github.io/libvips/API/current/VipsForeignSave.html#vips-webpsave">the docs</a>, ' .
9
+ 'this option "enables high quality chroma subsampling".'
10
+ ); ?>
11
+ </label>
12
+ <input type="checkbox" id="vips_smart_subsample">
13
+ </div>
14
+ <div>
15
+ <label for="vips_preset">
16
+ Preset
17
+ <?php echo helpIcon(
18
+ 'Using a preset will set many of the other options to suit a particular type of source material. ' .
19
+ 'It even overrides them. It does however not override the quality option.'
20
+ ); ?>
21
+
22
+ </label>
23
+ <select id="vips_preset">
24
+ <?php
25
+ webpexpress_selectBoxOptions('default', [
26
+ 'none' => 'Do not use a preset',
27
+ 'default' => 'Default',
28
+ 'photo' => 'Photo',
29
+ 'picture' => 'Picture',
30
+ 'drawing' => 'Drawing',
31
+ 'icon' => 'Icon',
32
+ 'text' => 'Text'
33
+ ]);
34
+ ?>
35
+ </select>
36
+ <br>
37
+ </div>
38
+
39
+ <br>
40
+ <?php webp_express_printUpdateButtons() ?>
41
+ </div>
42
+ </div>
lib/options/options/conversion-options/converter-options/wpc.php CHANGED
@@ -34,11 +34,11 @@
34
  </div>
35
 
36
  <div>
37
- <label for="wpc_url">
38
  URL
39
  <?php echo helpIcon('The endpoint of the web service. Copy it from the remote setup.'); ?>
40
  </label>
41
- <input type="text" id="wpc_url" placeholder="Url to your Remote WebP Express">
42
  </div>
43
 
44
  <div id="wpc_secret_div">
@@ -73,9 +73,11 @@
73
  </div>
74
 
75
  <?php
 
 
76
  if (!$canDetectQuality) {
77
  printAutoQualityOptionForConverter('wpc');
78
- }
79
  ?>
80
 
81
  <p>
34
  </div>
35
 
36
  <div>
37
+ <label for="wpc_api_url">
38
  URL
39
  <?php echo helpIcon('The endpoint of the web service. Copy it from the remote setup.'); ?>
40
  </label>
41
+ <input type="text" id="wpc_api_url" placeholder="Url to your Remote WebP Express">
42
  </div>
43
 
44
  <div id="wpc_secret_div">
73
  </div>
74
 
75
  <?php
76
+ /*
77
+ Removed (#243)
78
  if (!$canDetectQuality) {
79
  printAutoQualityOptionForConverter('wpc');
80
+ }*/
81
  ?>
82
 
83
  <p>
lib/options/options/conversion-options/converters.inc CHANGED
@@ -1,76 +1,37 @@
1
- <?php
2
- // Converters
3
- // --------------------
4
 
5
- echo '<tr><th scope="row">Conversion method';
6
- echo helpIcon('Drag to reorder. The conversion method on top will first be tried. ' .
7
- 'Should it fail, the next will be used, etc. To learn more about the conversion methods, <a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/converters.md">Go here</a>');
8
-
9
- echo '</th><td>';
10
-
11
- //$converters = $config['converters'];
12
- //echo '<script>window.converters = ' . json_encode($converters) . '</script>';
13
- //echo '<script>window.defaultConverters = ' . json_encode($defaultConverters) . '</script>';
14
-
15
- echo "<input type='text' name='converters' value='' style='visibility:hidden; height:0' />";
16
-
17
- // https://premium.wpmudev.org/blog/handling-form-submissions/
18
-
19
-
20
- ?>
21
  <?php
22
- /*
23
- $localConverters = ['cwebp', 'imagick', 'gd'];
24
- $testResult = WebPExpressHelpers::testConverters($localConverters);
25
- //print_r($testResult);
26
-
27
- if ($testResult['numOperationalConverters'] == 0) {
28
- echo 'Unfortunately, your server is currently not able to convert to webp files by itself. You will need to set up a cloud converter.<br><br>';
29
- foreach ($testResult['results'] as $result) {
30
- echo $result['converter'] . ':' . $result['message'] . '<br>';
 
 
 
 
 
 
 
 
 
 
31
  }
32
- } else {
33
- //echo 'Your server is able to convert webp files by itself.';
34
- }
35
- if ($testResult['numOperationalConverters'] == 1) {
36
- //
37
- }
38
- */
39
-
40
-
41
- /*
42
- http://php.net/manual/en/function.set-include-path.php
43
-
44
- //exec('/usr/sbin/getsebool -a', $output6, $returnCode5); // ok
45
- //echo 'All se bools: ' . print_r($output6, true) . '. Return code:' . $returnCode5;
46
- */
47
-
48
- //echo '<h2>Conversion methods to try</h2>';
49
- $dragIcon = '<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="17px" height="17px" viewBox="0 0 100.000000 100.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,100.000000) scale(0.100000,-0.100000)" fill="#444444" stroke="none"><path d="M415 920 l-80 -80 165 0 165 0 -80 80 c-44 44 -82 80 -85 80 -3 0 -41 -36 -85 -80z"/><path d="M0 695 l0 -45 500 0 500 0 0 45 0 45 -500 0 -500 0 0 -45z"/><path d="M0 500 l0 -40 500 0 500 0 0 40 0 40 -500 0 -500 0 0 -40z"/><path d="M0 305 l0 -45 500 0 500 0 0 45 0 45 -500 0 -500 0 0 -45z"/><path d="M418 78 l82 -83 82 83 83 82 -165 0 -165 0 83 -82z"/></g></svg>';
50
-
51
- /*echo '<p><i>Drag to reorder. The conversion method on top will first be tried. ';
52
- echo 'Should it fail, the next will be used, etc.<br>';
53
- echo 'To learn more about the conversion methods, ';
54
- echo '<a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/converters.md">Go here</a></i></p>';
55
- */
56
- // https://github.com/RubaXa/Sortable
57
-
58
- // Empty list of converters. The list will be populated by the javascript
59
-
60
- function webp_express_printUpdateButtons() {
61
- ?>
62
- <button onclick="updateConverterOptionsAndSave()" class="button button-primary" type="button">Update and save settings</button>
63
- <button onclick="updateConverterOptions()" class="button button-secondary" type="button">Update, but do not save yet</button>
64
- <?php
65
- //echo '<a href="javascript: tb_remove();">close</a>';
66
- }
67
- echo '<ul id="converters" style="margin-top: -13px"></ul>';
68
 
69
- include 'converter-options/cwebp.php';
70
- include 'converter-options/gd.php';
71
- include 'converter-options/imagick.php';
72
- include 'converter-options/ewww.php';
73
- include 'converter-options/wpc.php';
74
- include 'converter-options/imagickbinary.php';
75
- ?>
76
- </td></tr>
 
 
 
 
 
 
1
 
2
+ <tr>
3
+ <th scope="row">
4
+ Conversion method
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  <?php
6
+ echo helpIcon(
7
+ 'Drag to reorder. The conversion method on top will first be tried. ' .
8
+ 'Should it fail, the next will be used, etc. To learn more about the conversion methods, ' .
9
+ '<a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/converters.md">Go here</a>'
10
+ ); ?>
11
+ </th>
12
+ <td>
13
+ <input type='text' name='converters' value='' style='visibility:hidden; height:0' />
14
+ <ul id="converters" style="margin-top: -13px"></ul>
15
+ <div id="tc_popup" style="display:none;">
16
+ <div id="tc_content"></div>
17
+ </div>
18
+ <?php
19
+ function webp_express_printUpdateButtons() {
20
+ ?>
21
+ <button onclick="updateConverterOptionsAndSave()" class="button button-primary" type="button">Update and save settings</button>
22
+ <button onclick="updateConverterOptions()" class="button button-secondary" type="button">Update, but do not save yet</button>
23
+ <?php
24
+ //echo '<a href="javascript: tb_remove();">close</a>';
25
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ include 'converter-options/cwebp.php';
28
+ include 'converter-options/vips.php';
29
+ include 'converter-options/gd.php';
30
+ include 'converter-options/imagick.php';
31
+ include 'converter-options/ewww.php';
32
+ include 'converter-options/wpc.php';
33
+ include 'converter-options/imagemagick.php';
34
+ include 'converter-options/graphicsmagick.php';
35
+ ?>
36
+ </td>
37
+ </tr>
lib/options/options/conversion-options/jpeg.inc ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+
4
+ jpg
5
+ Encoding: Lossy / Auto
6
+ Quality for lossy: Auto / specific
7
+ Near-Lossless quality (0-100) (default: 60)
8
+
9
+ png
10
+ Encoding: Always lossless / Auto
11
+ Quality for lossy (0-100)
12
+ Near-Lossless quality (0-100) (default: 70)
13
+ Alhpa quality (0-100)
14
+
15
+ */
16
+ // Quality - jpeg
17
+ // --------------------
18
+
19
+ //$canDetectQuality = false;
20
+ ?>
21
+
22
+ <tr>
23
+ <th scope="row" colspan=1>
24
+ Jpeg options
25
+ <?php echo helpIcon(
26
+ 'The "jpeg" settings applies when the image being converted is a jpeg.'
27
+ );?>
28
+ </th>
29
+ <td id="jpeg_td">
30
+ <div>
31
+ <label>
32
+ WebP encoding:
33
+ </label>
34
+ <select id="jpeg_encoding_select" name="jpeg-encoding">
35
+ <?php
36
+ webpexpress_selectBoxOptions($config['jpeg-encoding'], [
37
+ 'lossy' => 'Lossy',
38
+ 'auto' => 'Auto',
39
+ ]);
40
+ ?>
41
+ </select>
42
+ <?php echo helpIcon(
43
+ '<p>The WebP format supports two types of encoding: lossy and lossless.</p>' .
44
+ '<p>If you select "Auto", WebP Express will try converting to both encodings ' .
45
+ 'and select one that resulted in the smallest file.</p>' .
46
+ '<p>Note that Gd and Ewww does not support the "Auto" feature. ' .
47
+ 'Gd can only produce lossy, and will simply do that. ' .
48
+ 'Ewww can not be configured to use a certain encoding, but automatically chooses lossless encoding for PNGs and lossy for JPEGs.' .
49
+ '</p>' .
50
+ //'<p>Also note that Remote WebP Express (if the WebP Express you are ' .
51
+ //'connecting to are using one of these, and you are using version 0.14+)' .
52
+ '<p>You can read more about the option <a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#auto-selecting-between-losslesslossy-encoding">here</a></p>'
53
+ );?>
54
+ </div>
55
+ <div>
56
+ <label>
57
+ Quality for lossy:
58
+ </label>
59
+ <select id="quality_auto_select" name="quality-auto">
60
+ <option value="auto_on" <?php echo ($config['quality-auto'] ? 'selected' : ''); ?>>Same as the jpeg</option>
61
+ <option value="auto_off" <?php echo (!$config['quality-auto'] ? 'selected' : ''); ?>>Fixed quality</option>
62
+ </select>
63
+ <?php echo helpIcon(
64
+ '<p>If you select the "Same as the jpeg" option, WebP Express will try to determine the quality of ' .
65
+ 'the jpeg and use that for the webp - unless the quality is higher than the limit entered, in which ' .
66
+ 'case the limit is used.</p>' .
67
+ '<p>Ouality detection requires imagick or gmagick (not neccessarily compiled with webp). ' .
68
+ 'In case quality detection is not available, the fallback quality is used.</p>' .
69
+ (
70
+ $canDetectQuality ?
71
+ '<p><i>Quality detection is working, btw :)</i></p>' :
72
+ '<p><b>Quality detection is not currently available on your system.</b> ' .
73
+ ((extension_loaded('imagick') && class_exists('\\Imagick')) ?
74
+ 'You have imagick, but you need a newer version (You need PECL >= 2.2.2). ' : ''
75
+ ) .
76
+ 'However, you can still have it by using the <i>Remote WebP Express</i> conversion method (the request for ' .
77
+ 'using same quality as the jpeg, as well as the <i>limit</i> and <i>fallback</i> settings will be forwarded to the remote)' .
78
+ '</p>'
79
+ )
80
+
81
+ );
82
+ /*
83
+ if ($canDetectQuality) {
84
+ echo helpIcon('All converted images will be encoded with this quality');
85
+ } else {
86
+ echo helpIcon('All converted images will be encoded with this quality. ' .
87
+ 'For Remote WebP Express and Imagick, you however have the option to use override this, and use ' .
88
+ '"auto". With some setup, you can get quality detection working and you will then be able to set ' .
89
+ 'quality to "auto" generally. For that you either need to get the imagick extension running ' .
90
+ '(PECL >= 2.2.2) or exec() rights and either imagick or gmagick installed.'
91
+ );
92
+ }
93
+ */
94
+ ?>
95
+ <div id="max_quality_div" style="margin-left:10px;display:inline-block" class="toggler effect-visibility">
96
+ <label>
97
+ Limit:
98
+ </label>
99
+ <input type="text" size=3 id="max_quality" name="max-quality" value="<?php echo $config['max-quality']; ?>" style="text-align:right; padding-left:0px; padding-right:4px; width:34px">
100
+ <?php echo helpIcon(
101
+ 'Quality is expensive byte-wise. For most websites, more than 80 is a waste of bytes. ' .
102
+ 'This option allows you to limit the quality to whatever is lowest: ' .
103
+ 'the quality of the jpeg or the limit entered here. Recommended value: Somewhere between 50-85'
104
+ );?>
105
+ <label style="display:inline-block; margin-left:10px">
106
+ Fallback:
107
+ </label>
108
+ <input type="text" size=3 name="quality-fallback" value="<?php echo $config['quality-specific'] ?>" style="text-align:right; padding-left:0px; padding-right:4px; width:34px">
109
+ <?php
110
+ echo helpIcon(
111
+ 'Fallback quality in case quality detection is not available or should fail for some reason (which has yet to be seen). ' .
112
+ (
113
+ !$canDetectQuality ?
114
+ 'You have quality detection working, btw, so this setting will probably never be used' :
115
+ '<i>You do not have quality detection working, btw</i> - which means that all conversions will have the fixed quality entered here'
116
+ )
117
+ );
118
+ ?>
119
+ </div>
120
+
121
+ <div id="quality_specific_div" style="display:inline-block" class="toggler effect-visibility">
122
+ <input type="text" size=3 name="quality-specific" value="<?php echo $config['quality-specific'] ?>" style="text-align:right; padding-left:0px; padding-right:4px; width:34px">
123
+ <?php
124
+ echo helpIcon('Enter number (0 - 100)');
125
+ ?>
126
+ </div>
127
+ </div>
128
+ <div id="jpeg_quality_lossless_div" class="toggler effect-visibility">
129
+ <label>
130
+ Quality for lossless:
131
+ </label>
132
+ <select id="jpeg_enable_near_lossless" name="jpeg-enable-near-lossless">
133
+ <?php
134
+ webpexpress_selectBoxOptions($config['jpeg-enable-near-lossless'] ? 'on' : 'off', [
135
+ 'on' => 'Apply preprocessing',
136
+ 'off' => '100% lossless',
137
+ ]);
138
+ ?>
139
+ </select>
140
+ <?php
141
+ echo helpIcon(
142
+ '<p>What? Lossless is lossless, right?. Well, that depends on how you look at it. ' .
143
+ 'The webp conversion library has this nifty option called "near lossless preprocessing". The preproccesing manipulates ' .
144
+ 'the image before encoding in order to help compressibility.</p>' .
145
+ '<p>Note that the near-lossless option only is supported by the <i>Cwebp</i> and <i>Vips</i> conversion methods.</p>' .
146
+ '<p>Read more about the feature <a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#near-lossless">here</a></p>'
147
+ );
148
+ ?>
149
+ <div id="jpeg_near_lossless_div" style="display:inline-block; margin-left:10px" class="toggler effect-visibility">
150
+ <label>
151
+ "Near lossless" quality:
152
+ </label>
153
+ <input type="text" size=3 name="jpeg-near-lossless" value="<?php echo $config['jpeg-near-lossless'] ?>" style="text-align:right; padding-left:0px; padding-right:4px; width:34px">
154
+ <?php
155
+ echo helpIcon(
156
+ 'The level of near-lossless image preprocessing (when trying lossless). ' .
157
+ 'You can think of it as "quality" for lossless. The range is 0 (maximum preprocessing) to 100 (no preprocessing). ' .
158
+ '<a href="https://groups.google.com/a/webmproject.org/forum/#!topic/webp-discuss/0GmxDmlexek">Read this</a> to get an informed opinion about appropriate setting.'
159
+ );
160
+ ?>
161
+ </div>
162
+ </div>
163
+ </td>
164
+ </tr>
lib/options/options/conversion-options/metadata.inc CHANGED
@@ -7,7 +7,7 @@ echo '<tr><th scope="row">Metadata';
7
  echo helpIcon('Decide what to do with image metadata, such as Exif. Note that this setting is not supported by the "Gd" conversion method, as it is not possible to copy the metadata with the Gd extension. Imagickbinary also currently lacks support');
8
  echo '</th><td>';
9
 
10
- echo '<select name="metadata">';
11
  echo '<option value="none"' . ($metadata == 'none' ? ' selected' : '') . '>No metadata in webp</option>';
12
  echo '<option value="all"' . ($metadata == 'all' ? ' selected' : '') . '>Copy all metadata to webp</option>';
13
  echo '</select>';
7
  echo helpIcon('Decide what to do with image metadata, such as Exif. Note that this setting is not supported by the "Gd" conversion method, as it is not possible to copy the metadata with the Gd extension. Imagickbinary also currently lacks support');
8
  echo '</th><td>';
9
 
10
+ echo '<select id="metadata" name="metadata">';
11
  echo '<option value="none"' . ($metadata == 'none' ? ' selected' : '') . '>No metadata in webp</option>';
12
  echo '<option value="all"' . ($metadata == 'all' ? ' selected' : '') . '>Copy all metadata to webp</option>';
13
  echo '</select>';
lib/options/options/conversion-options/png.inc ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <tr id="png_row" class="toggler effect-opacity">
2
+ <th scope="row" colspan=1>
3
+ PNG options
4
+ <?php echo helpIcon(
5
+ 'The "png" settings applies when the image being converted is a png.'
6
+ );?>
7
+ </th>
8
+ <td id="png_td">
9
+ <div>
10
+ <label>
11
+ WebP encoding:
12
+ </label>
13
+ <select id="png_encoding_select" name="png-encoding">
14
+ <?php
15
+ webpexpress_selectBoxOptions($config['png-encoding'], [
16
+ 'lossless' => 'Lossless',
17
+ 'auto' => 'Auto',
18
+ ]);
19
+ ?>
20
+ </select>
21
+ <?php echo helpIcon(
22
+ '<p>The WebP format supports two types of encoding: lossy and lossless. ' .
23
+ 'With WebP you can have both transparency AND lossy encoding, so "lossy" encoding is not out of the question, just ' .
24
+ 'because the converted file is a PNG.</p>' .
25
+ '<p>There is no "lossy" option in the combobox, because converting all PNGs to lossy would probably be a bad idea. ' .
26
+ 'However, in many cases you get better compression with lossy. So this is what the "auto" option is for. ' .
27
+ 'With "Auto", WebP Express will try converting to both encodings ' .
28
+ 'and select one that resulted in the smallest file.</p>' .
29
+ '<p>Note that Gd and Ewww neither supports "Lossless" or "Auto". ' .
30
+ 'Gd can only produce lossy, and will simply do that. ' .
31
+ 'Ewww can not be configured to use a certain encoding, but automatically chooses lossless encoding for PNGs and lossy for JPEGs.' .
32
+ '</p>' .
33
+ '<p>You can read more about the option <a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#auto-selecting-between-losslesslossy-encoding">here</a></p>'
34
+ );?>
35
+ </div>
36
+ <div id="png_quality_lossy_div" class="toggler effect-visibility">
37
+ <label>
38
+ Quality for lossy:
39
+ </label>
40
+
41
+ <input type="text" size=3 name="png-quality" value="<?php echo $config['png-quality'] ?>" style="text-align:right; padding-left:0px; padding-right:4px; width:34px">
42
+ <?php
43
+ echo helpIcon(
44
+ 'You probably want to set this value a bit higher than the quality for JPEGs. PNGs are often used for icons and graphics, ' .
45
+ 'which perhaps demands a bit more quality than photos, which jpegs often are used for.'
46
+ );
47
+ ?>
48
+ <label style="margin-left:10px">
49
+ Alpha quality:
50
+ </label>
51
+ <input type="text" size=3 name="alpha-quality" value="<?php echo $config['alpha-quality'] ?>" style="text-align:right; padding-left:0px; padding-right:4px; width:34px">
52
+ <?php
53
+ echo helpIcon(
54
+ 'The alpha quality is the quality of the alpha channel (the tranparency layer). ' .
55
+ 'The option is only relevant for images with alpha channel, and only relevant in lossy encoding. ' .
56
+ '<p>Note that Gd and Ewww does not support the "Alpha quality" feature. ' .
57
+ 'They simply ignore the option and converts the alpha channel losslessly. ' .
58
+ '</p>'
59
+ );
60
+ ?>
61
+ </div>
62
+ <div>
63
+ <label>
64
+ Quality for lossless:
65
+ </label>
66
+ <select id="png_enable_near_lossless" name="png-enable-near-lossless">
67
+ <?php
68
+ webpexpress_selectBoxOptions($config['png-enable-near-lossless'] ? 'on' : 'off', [
69
+ 'on' => 'Apply preprocessing',
70
+ 'off' => '100% lossless',
71
+ ]);
72
+ ?>
73
+ </select>
74
+ <?php
75
+ echo helpIcon(
76
+ '<p>What? Lossless is lossless, right?. Well, that depends on how you look at it. ' .
77
+ 'The webp conversion library has this nifty option called "near lossless preprocessing". The preproccesing manipulates ' .
78
+ 'the image before encoding in order to help compressibility.</p>' .
79
+ '<p>Note that the near-lossless option only is supported by the <i>Cwebp</i> and <i>Vips</i> conversion methods.</p>' .
80
+ '<p>Read more about the feature <a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md#near-lossless">here</a></p>'
81
+ );
82
+ ?>
83
+ <div id="png_near_lossless_div" style="display:inline-block; margin-left:10px" class="toggler effect-visibility">
84
+ <label>
85
+ "Near lossless" quality:
86
+ </label>
87
+ <input type="text" size=3 name="png-near-lossless" value="<?php echo $config['png-near-lossless'] ?>" style="text-align:right; padding-left:0px; padding-right:4px; width:34px">
88
+ <?php
89
+ echo helpIcon(
90
+ 'The level of near-lossless image preprocessing (when trying lossless). ' .
91
+ 'You can think of it as "quality" for lossless. The range is 0 (maximum preprocessing) to 100 (no preprocessing). ' .
92
+ '<a href="https://groups.google.com/a/webmproject.org/forum/#!topic/webp-discuss/0GmxDmlexek">Read this</a> to get an informed opinion about appropriate setting.'
93
+ );
94
+ ?>
95
+ </div>
96
+ </div>
97
+ </td>
98
+ </tr>
lib/options/options/conversion-options/quality.inc CHANGED
@@ -1,41 +1,69 @@
1
  <?php
2
- // Quality
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  // --------------------
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  if ($canDetectQuality) {
6
- echo '<tr><th scope="row">Quality';
7
- echo helpIcon('If "Auto" is selected, the converted image will get same quality as source. Auto is recommended!');
8
- echo '</th><td>';
9
  $qualityAuto = $config['quality-auto'];;
10
- echo '<select id="quality_auto_select" name="quality-auto">';
11
- echo '<option value="auto_on"' . ($qualityAuto ? ' selected' : '') . '>Auto</option>';
12
- echo '<option value="auto_off"' . (!$qualityAuto ? ' selected' : '') . '>Specific value</option>';
13
  echo '</select>';
14
 
15
- echo '</td></tr>';
16
-
17
-
18
  // Max quality
19
  // --------------------
20
  $maxQuality = $config['max-quality'];
21
 
22
- echo '<tr id="max_quality_row"><th scope="row">Max quality (0-100)';
 
 
 
23
  echo helpIcon('Quality is expensive byte-wise. For most websites, more than 80 is a waste of bytes. ' .
24
  'This option allows you to limit the quality to whatever is lowest: ' .
25
- 'the quality of the source or max quality. Recommended value: Somewhere between 50-85');
26
- echo '</th><td>';
27
-
28
- echo '<input type="text" size=3 id="max_quality" name="max-quality" value="' . $maxQuality . '">';
29
- echo '</td></tr>';
30
- } else {
31
-
32
  }
33
 
34
  // Quality - specific
35
  // --------------------
36
  $qualitySpecific = $config['quality-specific'];
37
 
38
- echo '<tr id="quality_specific_row"><th scope="row">Quality (0-100)';
 
39
  if ($canDetectQuality) {
40
  echo helpIcon('All converted images will be encoded with this quality');
41
  } else {
@@ -46,7 +74,22 @@ if ($canDetectQuality) {
46
  '(PECL >= 2.2.2) or exec() rights and either imagick or gmagick installed.'
47
  );
48
  }
49
- echo '</th><td>';
 
50
 
51
  echo '<input type="text" size=3 id="quality_specific" name="quality-specific" value="' . $qualitySpecific . '">';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  echo '</td></tr>';
1
  <?php
2
+ /*
3
+
4
+ jpg
5
+ Encoding: Always lossy / Auto
6
+ Quality for lossy: Auto / specific
7
+ Near-Lossless quality (0-100) (default: 60)
8
+
9
+ png
10
+ Encoding: Always lossless / Auto
11
+ Quality for lossy (0-100)
12
+ Near-Lossless quality (0-100) (default: 70)
13
+ Alhpa quality (0-100)
14
+
15
+ */
16
+ // Quality - jpeg
17
  // --------------------
18
 
19
+ //$canDetectQuality = false;
20
+ echo '<tr><th scope="row">Quality (jpeg -> webp)';
21
+ if ($canDetectQuality) {
22
+ echo helpIcon(
23
+ 'Quality of webp, when the image being converted is a jpeg. If "Same as the jpeg" is selected, the converted image ' .
24
+ 'will get same quality as source. Auto is recommended!'
25
+ );
26
+ } else {
27
+ echo helpIcon(
28
+ '<p>Quality of webp, when converting jpegs (0-100)</p>' .
29
+ '<p>Note: If your system had the capability to detect the quality ' .
30
+ 'of jpeg images, you would have had the option to choose "Same as jpeg". To get that option, you ' .
31
+ 'either need to get the imagick extension running (PECL >= 2.2.2) or imagick or gmagick installed plus ' .
32
+ 'exec() rights.</p>' .
33
+ '<p>Note: If you use the <i>Remote WebP Express</i> converter, you can configure it to ask the remote ' .
34
+ 'to do the automatic quality detection for jpegs. This will override the value entered here.</p>'
35
+ );
36
+ }
37
+ echo '</th><td>';
38
+
39
  if ($canDetectQuality) {
 
 
 
40
  $qualityAuto = $config['quality-auto'];;
41
+ echo '<select id="quality_auto_select" name="quality-auto" style="margin-right: 15px;">';
42
+ echo '<option value="auto_on"' . ($qualityAuto ? ' selected' : '') . '>Same as the jpeg</option>';
43
+ echo '<option value="auto_off"' . (!$qualityAuto ? ' selected' : '') . '>Fixed quality</option>';
44
  echo '</select>';
45
 
 
 
 
46
  // Max quality
47
  // --------------------
48
  $maxQuality = $config['max-quality'];
49
 
50
+ // echo '<tr id="max_quality_div"><th scope="row">Max quality (0-100)';
51
+ echo '<div id="max_quality_div">Max ';
52
+ //echo '</th><td>';
53
+ echo '<input type="text" size=3 id="max_quality" name="max-quality" value="' . $maxQuality . '">';
54
  echo helpIcon('Quality is expensive byte-wise. For most websites, more than 80 is a waste of bytes. ' .
55
  'This option allows you to limit the quality to whatever is lowest: ' .
56
+ 'the quality of the jpeg or the limit entered here. Recommended value: Somewhere between 50-85');
57
+ //echo '</td></tr>';
58
+ echo '</div>';
 
 
 
 
59
  }
60
 
61
  // Quality - specific
62
  // --------------------
63
  $qualitySpecific = $config['quality-specific'];
64
 
65
+ echo '<div id="quality_specific_div">';
66
+ /*
67
  if ($canDetectQuality) {
68
  echo helpIcon('All converted images will be encoded with this quality');
69
  } else {
74
  '(PECL >= 2.2.2) or exec() rights and either imagick or gmagick installed.'
75
  );
76
  }
77
+ */
78
+ //echo '</th><td>';
79
 
80
  echo '<input type="text" size=3 id="quality_specific" name="quality-specific" value="' . $qualitySpecific . '">';
81
+ echo helpIcon('Enter number (0 - 100)');
82
+ echo '</div>';
83
+ echo '</td></tr>';
84
+
85
+
86
+ // Quality - PNG
87
+ // --------------------
88
+ echo '<tr id="quality_png"><th scope="row">Quality (png -> webp)';
89
+ echo helpIcon(
90
+ 'Quality of webp, when the image that is converted is a png.'
91
+ );
92
+ echo '</th><td>';
93
+ echo '<input type="text" size=3 id="quality_png" name="quality-png" value="' . $config['quality-png'] . '">';
94
+ echo helpIcon('Enter number (0 - 100). Recommended value: Somewhere between 60-90');
95
  echo '</td></tr>';
lib/options/options/general/image-types.inc CHANGED
@@ -32,7 +32,7 @@ echo '</th><td>';
32
  // Converting both jpegs and pngs is (1+2) = 3
33
  $imageTypes = $config['image-types'];
34
 
35
- echo '<select name="image-types">';
36
  echo '<option value="0"' . ($imageTypes == 0 ? ' selected' : '') . '>None! (disable)</option>';
37
  echo '<option value="1"' . ($imageTypes == 1 ? ' selected' : '') . '>Only jpegs</option>';
38
  echo '<option value="3"' . ($imageTypes == 3 ? ' selected' : '') . '>Both jpegs and pngs</option>';
32
  // Converting both jpegs and pngs is (1+2) = 3
33
  $imageTypes = $config['image-types'];
34
 
35
+ echo '<select name="image-types" id="image_types">';
36
  echo '<option value="0"' . ($imageTypes == 0 ? ' selected' : '') . '>None! (disable)</option>';
37
  echo '<option value="1"' . ($imageTypes == 1 ? ' selected' : '') . '>Only jpegs</option>';
38
  echo '<option value="3"' . ($imageTypes == 3 ? ' selected' : '') . '>Both jpegs and pngs</option>';
lib/options/options/redirection-rules/enable-redirection-to-converter.inc CHANGED
@@ -1,24 +1,14 @@
1
  <tr>
2
  <th scope="row">
3
- <?php if ($config['operation-mode'] == 'cdn-friendly'): ?>
4
- Redirect requests for jpg/png to converter, but only when there is no webp, and serve the original<?php
5
- echo helpIcon(
6
- //'<p><em>The auto-conversion works this way: When an image is requested, a rule in the .htaccess detects if that image has been converted. If not, the request is redirected to the converter, which creates the webp and returns <em>the original</em> image</em></p>
7
- '<p>This works the following way:' .
8
- '<ol>' .
9
- '<li>WebP adds rules in the <i>.htaccess</i> that redirects requests for jpg/png images to the converter, <i>when no corresponding webp image is found</i></li>' .
10
- '<li>The converter creates the webp image, the jpg/png is served</li>' .
11
- '<li>The jpg/png file is served</li>' .
12
- '</ol>' .
13
- '<p>This only happens once per image. The next time the jpg/png is requested, ' .
14
- 'the rule will not trigger because it now detects a corresponding webp</p>' .
15
- '<p>Note: After the introduction of the <i>Convert non-existing webp-files upon request?</i> option, ' .
16
- 'you probably will not need this option. There are however rare cases, where it could be useful. ' .
17
- '</p>'
18
- ); ?>
19
- <?php else: ?>
20
- Enable redirection to converter?<?php echo helpIcon('This will add rules in the .htaccess that redirects images (jpg/png) to the script'); ?>
21
- <?php endif; ?>
22
  </th>
23
  <td>
24
  <input
1
  <tr>
2
  <th scope="row">
3
+ Enable redirection to converter?
4
+ <?php echo helpIcon(
5
+ '<p>This will add rules in the <i>.htaccess</i> that redirects images (jpeg/png) to the conversion script ' .
6
+ 'for browsers that supports webp.</p>' .
7
+ '<p>If the script detects that the webp already exists, and it is smaller and newer than the original, the ' .
8
+ 'webp is served directly. Otherwise the original image is converted and served.</p>' .
9
+ '<p>The redirect rule is placed below the rule that redirects directly to existing ' .
10
+ 'webp files.</p>'
11
+ ); ?>
 
 
 
 
 
 
 
 
 
 
12
  </th>
13
  <td>
14
  <input
lib/options/options/redirection-rules/enable-redirection-to-webp-realizer.inc CHANGED
@@ -1,16 +1,18 @@
1
  <tr>
2
  <th scope="row">
3
- Redirect requests for non-existing webp-files to converter</span>
4
  <?php echo helpIcon(
5
- '<p>Have ie. "image.jpg.webp" automatically generated from "image.jpg" the first time the webp is requested. ' .
6
- 'This way you can reference webps before they actually exists.</p>' .
 
7
  '<p>The feature works the following way:' .
8
  '<ol>' .
9
  '<li>WebP adds rules in the <i>.htaccess</i> that redirects requests for non-existing webp files to <i>webp-realizer.php</i></li>' .
10
- '<li><i>webp-realizer.php</i> looks for a corresponding jpg/png. If found, it is converted and saved, so the webp will be directly available the next time it is requested &ndash; and it tells the browser to fetch the same URL again (302 redirect to same location). In case no corresponding jpg/png is found, a 404 is returned</li>' .
 
11
  '</ol>' .
12
  '</p>' .
13
- 'This only happens once per image. The next time the webp is requested, the rule will not trigger because the webp now exists'
14
  // '<p>This feature allows you to reference webp images before they actually exists. You can ie write:' .
15
  // "<pre>&lt;picture&gt;\n &lt;source srcset=\"image.jpg.webp\" type=\"image/webp\" /&gt;\n &lt;img src=\"image.jpg\" /&gt;&lt;/picture&gt;"
16
 
1
  <tr>
2
  <th scope="row">
3
+ Create webp files upon request?</span>
4
  <?php echo helpIcon(
5
+ '<p>Enabling this option will add lines in the .htaccess which redirects requests for non-existing webp-files to ' .
6
+ 'the converter script (webp-realizer.php). ' .
7
+ '<i>This way you can reference webps before they actually exists.</i></p>' .
8
  '<p>The feature works the following way:' .
9
  '<ol>' .
10
  '<li>WebP adds rules in the <i>.htaccess</i> that redirects requests for non-existing webp files to <i>webp-realizer.php</i></li>' .
11
+ '<li><i>webp-realizer.php</i> looks for a corresponding jpg/png. ' .
12
+ 'If found, it is converted, saved and served. In case no corresponding jpg/png is found, a 404 is issued</li>' .
13
  '</ol>' .
14
  '</p>' .
15
+ 'This only happens once per image. The next time the webp is requested, the rule will not trigger because the webp now exists.'
16
  // '<p>This feature allows you to reference webp images before they actually exists. You can ie write:' .
17
  // "<pre>&lt;picture&gt;\n &lt;source srcset=\"image.jpg.webp\" type=\"image/webp\" /&gt;\n &lt;img src=\"image.jpg\" /&gt;&lt;/picture&gt;"
18
 
lib/options/options/redirection-rules/redirect-to-existing.inc CHANGED
@@ -4,14 +4,20 @@
4
 
5
  if ($config['operation-mode'] == 'no-conversion') {
6
  echo 'Activate redirection';
7
- echo helpIcon('This will add rules in the .htaccess that redirects directly to existing converted files (note that it is an internal redirect, which is much faster than a 301 or 302 redirect).');
 
 
 
8
  } else {
9
- echo 'Redirect directly to existing converted images';
10
- echo helpIcon('This will add rules in the .htaccess that redirects directly to existing converted files. ' .
11
- 'If you do not activate this setting, it will be the PHP script that handles the redirection to existing ' .
12
- 'webp files. Best performance is achieved by redirecting in .htaccess');
 
 
13
  }
14
- ?>
 
15
  </th>
16
  <td>
17
  <input type="checkbox" id="redirect_to_existing_in_htaccess" name="redirect-to-existing-in-htaccess" value="true" <?php echo ($config['redirect-to-existing-in-htaccess'] ? 'checked="checked"' : '') ?> >
4
 
5
  if ($config['operation-mode'] == 'no-conversion') {
6
  echo 'Activate redirection';
7
+ echo helpIcon(
8
+ 'This will add rules in the .htaccess that redirects directly to existing converted files ' .
9
+ '(note that it is an internal redirect, which is much faster than a 301 or 302 redirect).'
10
+ );
11
  } else {
12
+ echo 'Enable direct redirection to existing converted images?';
13
+ echo helpIcon(
14
+ '<p>This will add rules in the .htaccess that redirects directly to existing converted files, for ' .
15
+ 'browsers that supports webp.</p>' .
16
+ '<p>The rule is placed above the rule that redirects to the converter.</p>'
17
+ );
18
  }
19
+
20
+ ?>
21
  </th>
22
  <td>
23
  <input type="checkbox" id="redirect_to_existing_in_htaccess" name="redirect-to-existing-in-htaccess" value="true" <?php echo ($config['redirect-to-existing-in-htaccess'] ? 'checked="checked"' : '') ?> >
lib/options/options/redirection-rules/redirection-rules.inc CHANGED
@@ -8,17 +8,6 @@
8
  </p>
9
  <?php elseif ($config['operation-mode'] == 'cdn-friendly') : ?>
10
  <h2><i>.htaccess</i> rules for webp generation</h2>
11
- <p>
12
- The following redirect rules will not produce varied image responses.
13
- Their job is simply to trigger webp conversion. Note that this is achieved <i>without</i> producing varied responses.
14
- On most systems you need only to activate the first option. The second option can be used if the first one fails on your system.
15
- </p>
16
- <?php elseif ($config['operation-mode'] == 'varied-image-responses') : ?>
17
- <h3>Redirection rules</h3>
18
- <div><i>The options here affects the rules created in the .htaccess.<br>
19
- Note: The option for enabling/disabling redirects to the converter is not available in this operation mode, as
20
- that redirection is part of what defines this mode.
21
- <?php echo helpIcon('Note: The general options also affects the rules.'); ?></i></div>
22
  <?php else : ?>
23
  <h3>Redirection rules</h3>
24
  <div><i>The options here affects the rules created in the .htaccess. <?php echo helpIcon('Note: The general options also affects the rules.'); ?></i></div>
@@ -46,10 +35,11 @@
46
  include_once 'enable-redirection-to-webp-realizer.inc';
47
 
48
  // ps: we call it "auto convert", when in this mode
49
- include_once 'enable-redirection-to-converter.inc';
50
  break;
51
  case 'varied-image-responses':
52
  include_once 'redirect-to-existing.inc';
 
53
  include_once 'enable-redirection-to-webp-realizer.inc';
54
  break;
55
  }
8
  </p>
9
  <?php elseif ($config['operation-mode'] == 'cdn-friendly') : ?>
10
  <h2><i>.htaccess</i> rules for webp generation</h2>
 
 
 
 
 
 
 
 
 
 
 
11
  <?php else : ?>
12
  <h3>Redirection rules</h3>
13
  <div><i>The options here affects the rules created in the .htaccess. <?php echo helpIcon('Note: The general options also affects the rules.'); ?></i></div>
35
  include_once 'enable-redirection-to-webp-realizer.inc';
36
 
37
  // ps: we call it "auto convert", when in this mode
38
+ //include_once 'enable-redirection-to-converter.inc';
39
  break;
40
  case 'varied-image-responses':
41
  include_once 'redirect-to-existing.inc';
42
+ include_once 'enable-redirection-to-converter.inc';
43
  include_once 'enable-redirection-to-webp-realizer.inc';
44
  break;
45
  }
lib/options/options/serve-options/response-on-failure.inc CHANGED
@@ -10,7 +10,7 @@ echo '<select name="fail">';
10
  echo '<option value="original"' . ($fail == 'original' ? ' selected' : '') . '>Original image</option>';
11
  echo '<option value="404"' . ($fail == '404' ? ' selected' : '') . '>404</option>';
12
  echo '<option value="report"' . ($fail == 'report' ? ' selected' : '') . '>Error report (in plain text)</option>';
13
- echo '<option value="report-as-image"' . ($fail == 'report-as-image' ? ' selected' : '') . '>Error report as image</option>';
14
  echo '</select>';
15
  echo '</td></tr>';
16
  // echo '<tr><td colspan=2>Determines what the converter should serve, in case the image conversion should fail. For production servers, recommended value is "Original image". For development servers, choose anything you like, but that</td></tr>';
10
  echo '<option value="original"' . ($fail == 'original' ? ' selected' : '') . '>Original image</option>';
11
  echo '<option value="404"' . ($fail == '404' ? ' selected' : '') . '>404</option>';
12
  echo '<option value="report"' . ($fail == 'report' ? ' selected' : '') . '>Error report (in plain text)</option>';
13
+ //echo '<option value="report-as-image"' . ($fail == 'report-as-image' ? ' selected' : '') . '>Error report as image</option>';
14
  echo '</select>';
15
  echo '</td></tr>';
16
  // echo '<tr><td colspan=2>Determines what the converter should serve, in case the image conversion should fail. For production servers, recommended value is "Original image". For development servers, choose anything you like, but that</td></tr>';
lib/options/page-messages.php CHANGED
@@ -1,13 +1,15 @@
1
  <?php
2
 
3
- use \WebPExpress\Paths;
4
- use \WebPExpress\HTAccess;
5
  use \WebPExpress\Config;
6
- use \WebPExpress\State;
 
 
 
7
  use \WebPExpress\Messenger;
 
8
  use \WebPExpress\PlatformInfo;
9
- use \WebPExpress\FileHelper;
10
- use \WebPExpress\CapabilityTest;
11
 
12
  //use \WebPExpress\BulkConvert;
13
  //echo '<pre>' . print_r(BulkConvert::getList($config), true) . "</pre>";
@@ -34,7 +36,122 @@ if (CapabilityTest::copyCapabilityTestsToWpContent()) {
34
  echo 'copy failed!';
35
  }*/
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  $anyRedirectionToConverterEnabled = (($config['enable-redirection-to-converter']) || ($config['enable-redirection-to-webp-realizer']));
40
  $anyRedirectionEnabled = ($anyRedirectionToConverterEnabled || $config['redirect-to-existing-in-htaccess']);
@@ -68,7 +185,6 @@ if ($cacheEnablerActivated && !$webpEnabled) {
68
  if (($config['operation-mode'] == 'cdn-friendly') && !$config['alter-html']['enabled']) {
69
  //echo print_r(get_option('cache-enabler'), true);
70
 
71
-
72
  if ($cacheEnablerActivated) {
73
  if ($webpEnabled) {
74
  Messenger::printMessage(
@@ -114,6 +230,21 @@ if ($config['enable-redirection-to-webp-realizer'] && $config['alter-html']['ena
114
  );
115
  }
116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
  /*
119
  if (Config::isConfigFileThereAndOk() ) { // && PlatformInfo::definitelyGotModEnv()
1
  <?php
2
 
3
+ use \WebPExpress\CapabilityTest;
 
4
  use \WebPExpress\Config;
5
+ use \WebPExpress\ConvertersHelper;
6
+ use \WebPExpress\DismissableMessages;
7
+ use \WebPExpress\FileHelper;
8
+ use \WebPExpress\HTAccess;
9
  use \WebPExpress\Messenger;
10
+ use \WebPExpress\Paths;
11
  use \WebPExpress\PlatformInfo;
12
+ use \WebPExpress\State;
 
13
 
14
  //use \WebPExpress\BulkConvert;
15
  //echo '<pre>' . print_r(BulkConvert::getList($config), true) . "</pre>";
36
  echo 'copy failed!';
37
  }*/
38
 
39
+ // Dissmiss page messages for which the condition no longer applies
40
+ if ($config['image-types'] != 1) {
41
+ DismissableMessages::dismissMessage('0.14.0/suggest-enable-pngs');
42
+ }
43
+
44
+ //DismissableMessages::dismissAll();
45
+ //DismissableMessages::addDismissableMessage('0.14.0/suggest-enable-pngs');
46
+ //DismissableMessages::addDismissableMessage('0.14.0/suggest-wipe-because-lossless');
47
+ //DismissableMessages::addDismissableMessage('0.14.0/say-hello-to-vips');
48
+
49
+
50
+ DismissableMessages::printMessages();
51
+
52
+ //$dismissableMessageIds = ['suggest-enable-pngs'];
53
+
54
+ $firstActiveAndWorkingConverterId = ConvertersHelper::getFirstWorkingAndActiveConverterId($config);
55
+ $workingIds = ConvertersHelper::getWorkingConverterIds($config);
56
+
57
+ /*print_r($dismissableMessageIds);
58
 
59
+ foreach ($dismissableMessageIds as $pageMessageId) {
60
+ switch ($pageMessageId) {
61
+ case 'suggest-enable-pngs':
62
+ break;
63
+ case 'suggest-wipe-because-lossless':
64
+ // introduced in 0.14.0 (migrate 9)
65
+
66
+ $convertersSupportingEncodingAuto = ['cwebp', 'vips', 'imagick', 'imagemagick', 'gmagick', 'graphicsmagick'];
67
+
68
+ if (in_array($firstActiveAndWorkingConverterId, $convertersSupportingEncodingAuto)) {
69
+ DismissableMessages::printDismissableMessage(
70
+ 'info',
71
+ '<p>WebP Express 0.14 has new options for the conversions. Especially, it can now produce lossless webps, and ' .
72
+ 'it can automatically try both lossy and lossless and select the smallest. You can play around with the ' .
73
+ 'new options when your click "test" next to a converter.</p>' .
74
+ '<p>Once satisfied, dont forget to ' .
75
+ 'wipe your existing converted files (there is a "Delete converted files" button for that here on this page).</p>',
76
+ $pageMessageId,
77
+ 'Got it!'
78
+ );
79
+ } else {
80
+
81
+ if ($firstActiveAndWorkingConverterId == 'gd') {
82
+ foreach ($workingIds as $workingId) {
83
+ if (in_array($workingId, $convertersSupportingEncodingAuto)) {
84
+ DismissableMessages::printDismissableMessage(
85
+ 'info',
86
+ '<p>WebP Express 0.14 has new options for the conversions. Especially, it can now produce lossless webps, and ' .
87
+ 'it can automatically try both lossy and lossless and select the smallest. You can play around with the ' .
88
+ 'new options when your click "test" next to a converter.</p>' .
89
+ '<p>Once satisfied, dont forget to wipe your existing converted files (there is a "Delete converted files" ' .
90
+ 'button for that here on this page)</p>' .
91
+ '<p>Btw: The "gd" conversion method that you are using does not support lossless encoding ' .
92
+ '(in fact Gd only supports very few conversion options), but fortunately, you have the ' .
93
+ '"' . $workingId . '" conversion method working, so you can simply start using that instead.</p>',
94
+ $pageMessageId,
95
+ 'Got it!'
96
+ );
97
+ break;
98
+ }
99
+ }
100
+ }
101
+ }
102
+ break;
103
+ case 'say-hello-to-vips':
104
+ if (in_array('vips', $workingIds)) {
105
+ if ($firstActiveAndWorkingConverterId == 'cwebp') {
106
+ DismissableMessages::printDismissableMessage(
107
+ 'info',
108
+ '<p>I have good news and good news. WebP Express now supports Vips and Vips is working on your server. ' .
109
+ 'Vips is one of the best method for converting WebPs, on par with cwebp, which you are currently using. ' .
110
+ 'You may want to use Vips instead of cwebp. Your choice.</p>',
111
+ $pageMessageId,
112
+ 'Got it!'
113
+ );
114
+ } else {
115
+ DismissableMessages::printDismissableMessage(
116
+ 'info',
117
+ '<p>I have good news and good news. WebP Express now supports Vips and Vips is working on your server. ' .
118
+ 'Vips is one of the best method for converting WebPs and has therefore been inserted at the top of the list.' .
119
+ '</p>',
120
+ $pageMessageId,
121
+ 'Got it!'
122
+ );
123
+ }
124
+ } else {
125
+ // show message?
126
+ }
127
+ break;
128
+ }
129
+ }
130
+ */
131
+ /*
132
+ if ($config['image-types'] == 1) {
133
+ if (!in_array('suggest-enable-pngs', $dismissedPageMessageIds)) {
134
+ Messenger::printMessage(
135
+ 'info',
136
+ 'WebP Express 0.14 handles PNG to WebP conversions quite well. Perhaps it is time to enable PNGs? ' .
137
+ 'Go to the <a href="' . Paths::getSettingsUrl() . '">options</a> page to change the "Image types to work on" option.',
138
+ 2,
139
+ 'Got it!'
140
+ );
141
+ }
142
+ }
143
+ */
144
+
145
+ if ($config['redirect-to-existing-in-htaccess']) {
146
+ if (PlatformInfo::isApacheOrLiteSpeed() && isset($config['base-htaccess-on-these-capability-tests']['modHeaderWorking']) && ($config['base-htaccess-on-these-capability-tests']['modHeaderWorking'] == false)) {
147
+ Messenger::printMessage(
148
+ 'warning',
149
+ 'It seems your server setup does not support headers in <i>.htaccess</i>. You should either fix this (install <i>mod_headers</i>) <i>or</i> ' .
150
+ 'deactivate the "Enable direct redirection to existing converted images?" option. Otherwise the <i>Vary:Accept</i> header ' .
151
+ 'will not be added and this can result in problems for users behind proxy servers (ie used in larger companies)'
152
+ );
153
+ }
154
+ }
155
 
156
  $anyRedirectionToConverterEnabled = (($config['enable-redirection-to-converter']) || ($config['enable-redirection-to-webp-realizer']));
157
  $anyRedirectionEnabled = ($anyRedirectionToConverterEnabled || $config['redirect-to-existing-in-htaccess']);
185
  if (($config['operation-mode'] == 'cdn-friendly') && !$config['alter-html']['enabled']) {
186
  //echo print_r(get_option('cache-enabler'), true);
187
 
 
188
  if ($cacheEnablerActivated) {
189
  if ($webpEnabled) {
190
  Messenger::printMessage(
230
  );
231
  }
232
 
233
+ if ($config['image-types'] == 3) {
234
+ $workingConverters = ConvertersHelper::getWorkingAndActiveConverters($config);
235
+ if (count($workingConverters) == 1) {
236
+ if (ConvertersHelper::getConverterId($workingConverters[0]) == 'gd') {
237
+ Messenger::printMessage(
238
+ 'warning',
239
+ 'You have enabled PNGs, but configured Gd to skip PNGs, and Gd is your only active working converter. ' .
240
+ 'This is a bad combination!'
241
+ );
242
+ }
243
+ }
244
+ }
245
+
246
+
247
+
248
 
249
  /*
250
  if (Config::isConfigFileThereAndOk() ) { // && PlatformInfo::definitelyGotModEnv()
lib/options/page.php CHANGED
@@ -28,6 +28,8 @@ function webpexpress_converterName($converterId) {
28
  return $converterId;
29
  }
30
 
 
 
31
  function printAutoQualityOptionForConverter($converterId) {
32
  ?>
33
  <div>
@@ -49,11 +51,16 @@ function printAutoQualityOptionForConverter($converterId) {
49
  </div>
50
  <?php
51
  }
 
52
 
53
  $canDetectQuality = TestRun::isLocalQualityDetectionWorking();
54
  $testResult = TestRun::getConverterStatus();
55
  $config = Config::getConfigForOptionsPage();
56
 
 
 
 
 
57
  //State::setState('last-ewww-optimize-attempt', 0);
58
  //State::setState('last-ewww-optimize', 0);
59
  \WebPExpress\KeepEwwwSubscriptionAlive::keepAliveIfItIsTime($config);
28
  return $converterId;
29
  }
30
 
31
+ /*
32
+ Removed (#243)
33
  function printAutoQualityOptionForConverter($converterId) {
34
  ?>
35
  <div>
51
  </div>
52
  <?php
53
  }
54
+ */
55
 
56
  $canDetectQuality = TestRun::isLocalQualityDetectionWorking();
57
  $testResult = TestRun::getConverterStatus();
58
  $config = Config::getConfigForOptionsPage();
59
 
60
+ State::setState('workingConverterIds', ConvertersHelper::getWorkingConverterIds($config));
61
+ State::setState('workingAndActiveConverterIds', ConvertersHelper::getWorkingAndActiveConverterIds($config));
62
+
63
+
64
  //State::setState('last-ewww-optimize-attempt', 0);
65
  //State::setState('last-ewww-optimize', 0);
66
  \WebPExpress\KeepEwwwSubscriptionAlive::keepAliveIfItIsTime($config);
lib/options/submit.php CHANGED
@@ -1,11 +1,16 @@
1
  <?php
2
 
3
  use \WebPExpress\CacheMover;
 
4
  use \WebPExpress\Config;
 
5
  use \WebPExpress\HTAccess;
6
  use \WebPExpress\Messenger;
7
  use \WebPExpress\Paths;
8
- use \WebPExpress\CapabilityTest;
 
 
 
9
 
10
  // https://premium.wpmudev.org/blog/handling-form-submissions/
11
  // checkout https://codex.wordpress.org/Function_Reference/sanitize_meta
@@ -21,6 +26,8 @@ function webp_express_sanitize_quality_field($text) {
21
  $config = Config::loadConfigAndFix(false); // false, because we do not need to test if quality detection is working
22
  $oldConfig = $config;
23
 
 
 
24
  // Set options that are available in all operation modes
25
  $config = array_merge($config, [
26
  'operation-mode' => $_POST['operation-mode'],
@@ -31,6 +38,9 @@ $config = array_merge($config, [
31
 
32
  ]);
33
 
 
 
 
34
  // Set options that are available in all operation modes, except the "CDN friendly" mode
35
  if ($_POST['operation-mode'] != 'cdn-friendly') {
36
 
@@ -83,19 +93,37 @@ if ($_POST['operation-mode'] != 'no-conversion') {
83
  // --------
84
  $config['metadata'] = sanitize_text_field($_POST['metadata']);
85
 
86
- // Quality
87
  // --------
88
- $auto = (isset($_POST['quality-auto']) && $_POST['quality-auto'] == 'auto_on');
89
- $config['quality-auto'] = $auto;
90
 
 
 
91
  if ($auto) {
92
  $config['max-quality'] = webp_express_sanitize_quality_field($_POST['max-quality']);
93
- $config['quality-specific'] = 70;
94
  } else {
95
  $config['max-quality'] = 80;
96
  $config['quality-specific'] = webp_express_sanitize_quality_field($_POST['quality-specific']);
97
  }
98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  $config['convert-on-upload'] = isset($_POST['convert-on-upload']);
100
 
101
  // Web Service
@@ -199,8 +227,16 @@ switch ($_POST['operation-mode']) {
199
 
200
  //echo '<pre>' . print_r($_POST, true) . '</pre>'; exit;
201
  if ($_POST['operation-mode'] != $_POST['change-operation-mode']) {
 
 
202
  $config['operation-mode'] = $_POST['change-operation-mode'];
203
  $config = Config::applyOperationMode($config);
 
 
 
 
 
 
204
  }
205
 
206
  // If we are going to save .htaccess, run and store capability tests first (we should only store results when .htaccess is updated as well)
@@ -213,7 +249,6 @@ if (isset($_POST['force']) || HTAccess::doesRewriteRulesNeedUpdate($config)) {
213
  // -----
214
  $result = Config::saveConfigurationAndHTAccess($config, isset($_POST['force']));
215
 
216
-
217
  // Handle results
218
  // ---------------
219
 
1
  <?php
2
 
3
  use \WebPExpress\CacheMover;
4
+ use \WebPExpress\CapabilityTest;
5
  use \WebPExpress\Config;
6
+ use \WebPExpress\DismissableMessages;
7
  use \WebPExpress\HTAccess;
8
  use \WebPExpress\Messenger;
9
  use \WebPExpress\Paths;
10
+
11
+
12
+ DismissableMessages::dismissMessage('0.14.0/say-hello-to-vips');
13
+
14
 
15
  // https://premium.wpmudev.org/blog/handling-form-submissions/
16
  // checkout https://codex.wordpress.org/Function_Reference/sanitize_meta
26
  $config = Config::loadConfigAndFix(false); // false, because we do not need to test if quality detection is working
27
  $oldConfig = $config;
28
 
29
+ // Note that "operation-mode" is actually the old mode. The new mode is posted in "change-operation-mode"
30
+
31
  // Set options that are available in all operation modes
32
  $config = array_merge($config, [
33
  'operation-mode' => $_POST['operation-mode'],
38
 
39
  ]);
40
 
41
+
42
+
43
+
44
  // Set options that are available in all operation modes, except the "CDN friendly" mode
45
  if ($_POST['operation-mode'] != 'cdn-friendly') {
46
 
93
  // --------
94
  $config['metadata'] = sanitize_text_field($_POST['metadata']);
95
 
96
+ // Jpeg
97
  // --------
98
+ $config['jpeg-encoding'] = sanitize_text_field($_POST['jpeg-encoding']);
 
99
 
100
+ $auto = (isset($_POST['quality-auto']) && ($_POST['quality-auto'] == 'auto_on'));
101
+ $config['quality-auto'] = $auto;
102
  if ($auto) {
103
  $config['max-quality'] = webp_express_sanitize_quality_field($_POST['max-quality']);
104
+ $config['quality-specific'] = webp_express_sanitize_quality_field($_POST['quality-fallback']);
105
  } else {
106
  $config['max-quality'] = 80;
107
  $config['quality-specific'] = webp_express_sanitize_quality_field($_POST['quality-specific']);
108
  }
109
 
110
+ $jpegEnableNearLossless = (isset($_POST['jpeg-enable-near-lossless']) && ($_POST['jpeg-enable-near-lossless'] == 'on'));
111
+ $config['jpeg-enable-near-lossless'] = $jpegEnableNearLossless;
112
+ $config['jpeg-near-lossless'] = webp_express_sanitize_quality_field($_POST['jpeg-near-lossless']);
113
+
114
+
115
+ // Png
116
+ // --------
117
+ $config['png-encoding'] = sanitize_text_field($_POST['png-encoding']);
118
+
119
+ // TODO: ERRORS!
120
+ $config['png-quality'] = webp_express_sanitize_quality_field($_POST['png-quality']);
121
+ $pngEnableNearLossless = (isset($_POST['png-enable-near-lossless']) && ($_POST['png-enable-near-lossless'] == 'on'));
122
+ $config['png-enable-near-lossless'] = $pngEnableNearLossless;
123
+ $config['png-near-lossless'] = webp_express_sanitize_quality_field($_POST['png-near-lossless']);
124
+ $config['alpha-quality'] = webp_express_sanitize_quality_field($_POST['alpha-quality']);
125
+
126
+
127
  $config['convert-on-upload'] = isset($_POST['convert-on-upload']);
128
 
129
  // Web Service
227
 
228
  //echo '<pre>' . print_r($_POST, true) . '</pre>'; exit;
229
  if ($_POST['operation-mode'] != $_POST['change-operation-mode']) {
230
+
231
+ // Operation mode changed!
232
  $config['operation-mode'] = $_POST['change-operation-mode'];
233
  $config = Config::applyOperationMode($config);
234
+
235
+ if ($config['operation-mode'] == 'varied-image-responses') {
236
+ // changing to "varied image responses" mode should enable
237
+ // the redirect-to-existing-in-htaccess option
238
+ $config['redirect-to-existing-in-htaccess'] = true;
239
+ }
240
  }
241
 
242
  // If we are going to save .htaccess, run and store capability tests first (we should only store results when .htaccess is updated as well)
249
  // -----
250
  $result = Config::saveConfigurationAndHTAccess($config, isset($_POST['force']));
251
 
 
252
  // Handle results
253
  // ---------------
254
 
test/alphatest.png ADDED
Binary file
test/architecture-q85-w600.jpg ADDED
Binary file
test/dice.png ADDED
Binary file
test/palette-based-colors.png ADDED
Binary file
test/{test.jpg → test-pattern-tv.jpg} RENAMED
File without changes
test/test-run.php DELETED
@@ -1,191 +0,0 @@
1
- <?php
2
-
3
- if (isset($_GET['stream-webp-image'])) {
4
- header('Content-type: image/webp');
5
- if (@readfile($_GET['stream-webp-image']) === false) {
6
- // ...
7
- }
8
- exit;
9
- }
10
-
11
- error_reporting(E_ALL);
12
- ini_set("display_errors", 1);
13
-
14
- //require "../wod/webp-convert.inc";
15
- require "../vendor/autoload.php";
16
-
17
- use WebPConvert\WebPConvert;
18
- use WebPConvert\Loggers\EchoLogger;
19
- //use WebPConvertAndServe\WebPConvertAndServe;
20
- //use WebPConvert\Converters\ConverterHelper;
21
- //use WebPConvertAndServe;
22
-
23
- ?>
24
- <html>
25
- <head>
26
- <style>
27
- body {
28
- padding:10px;
29
- font-size: 17px;
30
- }
31
- p {
32
- margin-top: 0;
33
- }
34
- label {
35
- font-style: italic;
36
- }
37
- p.error-msg {
38
- /*font-size: 20px;*/
39
- }
40
- h3 {color: red}
41
- </style>
42
- </head>
43
- <body style="">
44
-
45
- <?php
46
-
47
- /**
48
- * Paths passed in query string were encoded, to avoid triggering LFI warning in Wordfence
49
- * (encoding is done in converters.js)
50
- * see https://github.com/rosell-dk/webp-express/issues/87
51
- */
52
- function decodePathInQS($encodedPath) {
53
- return preg_replace('/\*\*/', '/', $encodedPath);
54
- }
55
-
56
- //WebPConvertAndServe::convertAndReport($source, $destination, $options);use WebPConvert\Loggers\EchoLogger;
57
- $source = decodePathInQS($_GET['source']);
58
- $destination = decodePathInQS($_GET['destination']);
59
- $converter = $_GET['converter'];
60
-
61
- if (isset($_GET['max-quality'])) {
62
- $options['max-quality'] = intval($_GET['max-quality']);
63
- }
64
- if (isset($_GET['quality'])) {
65
- $options['quality'] = intval($_GET['quality']);
66
- }
67
-
68
- /*
69
- if (isset($_GET['method'])) {
70
- $options['method'] = intval($_GET['method']);
71
- }*/
72
-
73
- /**
74
- * Sets the options from the query string
75
- * We make sure only to set those options that are declared by the converter
76
- */
77
- function getConverterOptionsFromQueryString($converter)
78
- {
79
-
80
- // Get meta about the options that the converter supports
81
- $converterClassName = 'WebPConvert\\Converters\\' . ucfirst($converter);
82
- $availOptions = array_column($converterClassName::$extraOptions, 'type', 'name');
83
- //print_r($availOptions);
84
-
85
- // Set options
86
- $options = [];
87
- foreach ($availOptions as $optionName => $optionType) {
88
- //echo $optionName . ':' . $optionType . '<br>';
89
- switch ($optionType) {
90
- case 'string':
91
- if (isset($_GET[$optionName])) {
92
- $options[$optionName] = $_GET[$optionName];
93
- }
94
- break;
95
- case 'number':
96
- if (isset($_GET[$optionName])) {
97
- $options[$optionName] = floatval($_GET[$optionName]);
98
- }
99
- break;
100
- case 'boolean':
101
- if (isset($_GET[$optionName])) {
102
- $options[$optionName] = ($_GET[$optionName] == 'true');
103
- }
104
- break;
105
- }
106
- }
107
-
108
- if ($converter == 'wpc') {
109
-
110
- // Handle api key.
111
- // If it has been modified on the options page, it is passed as 'new-api-key'.
112
- // If it has not been modified, it is not passed at all!
113
- // - in that case, we must load it from the config file.
114
-
115
- if (isset($_GET['new-api-key'])) {
116
- $options['api-key'] = $_GET['new-api-key'];
117
- } elseif (isset($_GET['configDirRel'])) {
118
-
119
- // Fetch api-key from configuration file.
120
- $configFilename = $_SERVER['DOCUMENT_ROOT'] . '/' . decodePathInQS($_GET['configDirRel']) . '/config.json';
121
-
122
- if (file_exists($configFilename)) {
123
-
124
- $handle = @fopen($configFilename, "r");
125
- $json = fread($handle, filesize($configFilename));
126
- fclose($handle);
127
-
128
- $config = json_decode($json, true);
129
- if ($config) {
130
- foreach ($config['converters'] as $converter) {
131
- if ($converter['converter'] == 'wpc') {
132
- //print_r($converter);
133
- if (isset($converter['options']['api-key'])) {
134
- $options['api-key'] = $converter['options']['api-key'];
135
- //echo 'api-key:' . $converter['options']['api-key'] . '<br>';
136
- //print_r($options);
137
- }
138
- }
139
- }
140
- }
141
- }
142
- }
143
- if (!isset($options['api-key'])) {
144
- echo '<p style="color:red">Warning: No Api key is set</p>';
145
- }
146
- }
147
-
148
- return $options;
149
- }
150
- $options['converters'] = [[
151
- 'converter' => $converter,
152
- 'options' => getConverterOptionsFromQueryString($converter)
153
- ]];
154
-
155
-
156
-
157
-
158
-
159
- //echo '<pre>' . print_r($_GET, true) . '</pre>';
160
- //echo '<pre>' . print_r($options, true) . '</pre>';
161
-
162
- function testRun($converter, $source, $destination, $options) {
163
-
164
- $success = false;
165
- try {
166
- $success = WebPConvert::convert($source, $destination, $options, new EchoLogger());
167
- } catch (\Exception $e) {
168
- $msg = $e->getMessage();
169
- }
170
-
171
- if (!$success) {
172
- echo '<h3 class="error">Test conversion failed</h3>';
173
-
174
- if (isset($msg)) {
175
- echo '<label>Problem:</label>';
176
- //echo '<p class="failure">' . $failure . '</p>';
177
- //echo '<label>Details:</label>';
178
- echo '<p class="error-msg">' . $msg . '</p>';
179
- }
180
- } else {
181
- //echo '<p>Successfully converted test image</p>';
182
-
183
- if (isset($_SERVER['HTTP_ACCEPT']) && (strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false )) {
184
- //echo '<img src="' . $_GET['destinationUrl'] . '" width=48%><br><br>';
185
- echo '<img src="?stream-webp-image=' . $destination . '" width=48%><br><br>';
186
-
187
- }
188
- }
189
- }
190
-
191
- testRun($converter, $source, $destination, $options);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test/test.png ADDED
Binary file
vendor/composer/autoload_classmap.php CHANGED
@@ -6,35 +6,4 @@ $vendorDir = dirname(dirname(__FILE__));
6
  $baseDir = dirname($vendorDir);
7
 
8
  return array(
9
- 'DOMUtilForWebP\\ImageUrlReplacer' => $vendorDir . '/rosell-dk/dom-util-for-webp/src/ImageUrlReplacer.php',
10
- 'DOMUtilForWebP\\PictureTags' => $vendorDir . '/rosell-dk/dom-util-for-webp/src/PictureTags.php',
11
- 'WebPConvertCloudService\\AccessCheck' => $vendorDir . '/rosell-dk/webp-convert-cloud-service/src/AccessCheck.php',
12
- 'WebPConvertCloudService\\Serve' => $vendorDir . '/rosell-dk/webp-convert-cloud-service/src/Serve.php',
13
- 'WebPConvertCloudService\\WebPConvertCloudService' => $vendorDir . '/rosell-dk/webp-convert-cloud-service/src/WebPConvertCloudService.php',
14
- 'WebPConvert\\Converters\\ConverterHelper' => $vendorDir . '/rosell-dk/webp-convert/src/Converters/ConverterHelper.php',
15
- 'WebPConvert\\Converters\\Cwebp' => $vendorDir . '/rosell-dk/webp-convert/src/Converters/Cwebp.php',
16
- 'WebPConvert\\Converters\\Ewww' => $vendorDir . '/rosell-dk/webp-convert/src/Converters/Ewww.php',
17
- 'WebPConvert\\Converters\\Exceptions\\ConversionDeclinedException' => $vendorDir . '/rosell-dk/webp-convert/src/Converters/Exceptions/ConversionDeclinedException.php',
18
- 'WebPConvert\\Converters\\Exceptions\\ConverterFailedException' => $vendorDir . '/rosell-dk/webp-convert/src/Converters/Exceptions/ConverterFailedException.php',
19
- 'WebPConvert\\Converters\\Exceptions\\ConverterNotOperationalException' => $vendorDir . '/rosell-dk/webp-convert/src/Converters/Exceptions/ConverterNotOperationalException.php',
20
- 'WebPConvert\\Converters\\Gd' => $vendorDir . '/rosell-dk/webp-convert/src/Converters/Gd.php',
21
- 'WebPConvert\\Converters\\Gmagick' => $vendorDir . '/rosell-dk/webp-convert/src/Converters/Gmagick.php',
22
- 'WebPConvert\\Converters\\Imagick' => $vendorDir . '/rosell-dk/webp-convert/src/Converters/Imagick.php',
23
- 'WebPConvert\\Converters\\ImagickBinary' => $vendorDir . '/rosell-dk/webp-convert/src/Converters/Imagickbinary.php',
24
- 'WebPConvert\\Converters\\Wpc' => $vendorDir . '/rosell-dk/webp-convert/src/Converters/Wpc.php',
25
- 'WebPConvert\\Exceptions\\ConverterNotFoundException' => $vendorDir . '/rosell-dk/webp-convert/src/Exceptions/ConverterNotFoundException.php',
26
- 'WebPConvert\\Exceptions\\CreateDestinationFileException' => $vendorDir . '/rosell-dk/webp-convert/src/Exceptions/CreateDestinationFileException.php',
27
- 'WebPConvert\\Exceptions\\CreateDestinationFolderException' => $vendorDir . '/rosell-dk/webp-convert/src/Exceptions/CreateDestinationFolderException.php',
28
- 'WebPConvert\\Exceptions\\InvalidFileExtensionException' => $vendorDir . '/rosell-dk/webp-convert/src/Exceptions/InvalidFileExtensionException.php',
29
- 'WebPConvert\\Exceptions\\TargetNotFoundException' => $vendorDir . '/rosell-dk/webp-convert/src/Exceptions/TargetNotFoundException.php',
30
- 'WebPConvert\\Exceptions\\WebPConvertBaseException' => $vendorDir . '/rosell-dk/webp-convert/src/Exceptions/WebPConvertBaseException.php',
31
- 'WebPConvert\\Loggers\\BaseLogger' => $vendorDir . '/rosell-dk/webp-convert/src/Loggers/BaseLogger.php',
32
- 'WebPConvert\\Loggers\\BufferLogger' => $vendorDir . '/rosell-dk/webp-convert/src/Loggers/BufferLogger.php',
33
- 'WebPConvert\\Loggers\\EchoLogger' => $vendorDir . '/rosell-dk/webp-convert/src/Loggers/EchoLogger.php',
34
- 'WebPConvert\\Loggers\\VoidLogger' => $vendorDir . '/rosell-dk/webp-convert/src/Loggers/VoidLogger.php',
35
- 'WebPConvert\\Serve\\Report' => $vendorDir . '/rosell-dk/webp-convert/src/Serve/Report.php',
36
- 'WebPConvert\\Serve\\ServeBase' => $vendorDir . '/rosell-dk/webp-convert/src/Serve/ServeBase.php',
37
- 'WebPConvert\\Serve\\ServeConverted' => $vendorDir . '/rosell-dk/webp-convert/src/Serve/ServeConverted.php',
38
- 'WebPConvert\\Serve\\ServeExistingOrHandOver' => $vendorDir . '/rosell-dk/webp-convert/src/Serve/ServeExistingOrHandOver.php',
39
- 'WebPConvert\\WebPConvert' => $vendorDir . '/rosell-dk/webp-convert/src/WebPConvert.php',
40
  );
6
  $baseDir = dirname($vendorDir);
7
 
8
  return array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  );
vendor/composer/autoload_psr4.php CHANGED
@@ -8,5 +8,6 @@ $baseDir = dirname($vendorDir);
8
  return array(
9
  'WebPConvert\\' => array($vendorDir . '/rosell-dk/webp-convert/src'),
10
  'WebPConvertCloudService\\' => array($vendorDir . '/rosell-dk/webp-convert-cloud-service/src'),
 
11
  'DOMUtilForWebP\\' => array($vendorDir . '/rosell-dk/dom-util-for-webp/src'),
12
  );
8
  return array(
9
  'WebPConvert\\' => array($vendorDir . '/rosell-dk/webp-convert/src'),
10
  'WebPConvertCloudService\\' => array($vendorDir . '/rosell-dk/webp-convert-cloud-service/src'),
11
+ 'ImageMimeTypeGuesser\\' => array($vendorDir . '/rosell-dk/image-mime-type-guesser/src'),
12
  'DOMUtilForWebP\\' => array($vendorDir . '/rosell-dk/dom-util-for-webp/src'),
13
  );
vendor/composer/autoload_static.php CHANGED
@@ -12,6 +12,10 @@ class ComposerStaticInit9a5d1d521aac5c1f4f08f3a90858f030
12
  'WebPConvert\\' => 12,
13
  'WebPConvertCloudService\\' => 24,
14
  ),
 
 
 
 
15
  'D' =>
16
  array (
17
  'DOMUtilForWebP\\' => 15,
@@ -27,52 +31,21 @@ class ComposerStaticInit9a5d1d521aac5c1f4f08f3a90858f030
27
  array (
28
  0 => __DIR__ . '/..' . '/rosell-dk/webp-convert-cloud-service/src',
29
  ),
 
 
 
 
30
  'DOMUtilForWebP\\' =>
31
  array (
32
  0 => __DIR__ . '/..' . '/rosell-dk/dom-util-for-webp/src',
33
  ),
34
  );
35
 
36
- public static $classMap = array (
37
- 'DOMUtilForWebP\\ImageUrlReplacer' => __DIR__ . '/..' . '/rosell-dk/dom-util-for-webp/src/ImageUrlReplacer.php',
38
- 'DOMUtilForWebP\\PictureTags' => __DIR__ . '/..' . '/rosell-dk/dom-util-for-webp/src/PictureTags.php',
39
- 'WebPConvertCloudService\\AccessCheck' => __DIR__ . '/..' . '/rosell-dk/webp-convert-cloud-service/src/AccessCheck.php',
40
- 'WebPConvertCloudService\\Serve' => __DIR__ . '/..' . '/rosell-dk/webp-convert-cloud-service/src/Serve.php',
41
- 'WebPConvertCloudService\\WebPConvertCloudService' => __DIR__ . '/..' . '/rosell-dk/webp-convert-cloud-service/src/WebPConvertCloudService.php',
42
- 'WebPConvert\\Converters\\ConverterHelper' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Converters/ConverterHelper.php',
43
- 'WebPConvert\\Converters\\Cwebp' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Converters/Cwebp.php',
44
- 'WebPConvert\\Converters\\Ewww' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Converters/Ewww.php',
45
- 'WebPConvert\\Converters\\Exceptions\\ConversionDeclinedException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Converters/Exceptions/ConversionDeclinedException.php',
46
- 'WebPConvert\\Converters\\Exceptions\\ConverterFailedException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Converters/Exceptions/ConverterFailedException.php',
47
- 'WebPConvert\\Converters\\Exceptions\\ConverterNotOperationalException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Converters/Exceptions/ConverterNotOperationalException.php',
48
- 'WebPConvert\\Converters\\Gd' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Converters/Gd.php',
49
- 'WebPConvert\\Converters\\Gmagick' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Converters/Gmagick.php',
50
- 'WebPConvert\\Converters\\Imagick' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Converters/Imagick.php',
51
- 'WebPConvert\\Converters\\ImagickBinary' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Converters/Imagickbinary.php',
52
- 'WebPConvert\\Converters\\Wpc' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Converters/Wpc.php',
53
- 'WebPConvert\\Exceptions\\ConverterNotFoundException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Exceptions/ConverterNotFoundException.php',
54
- 'WebPConvert\\Exceptions\\CreateDestinationFileException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Exceptions/CreateDestinationFileException.php',
55
- 'WebPConvert\\Exceptions\\CreateDestinationFolderException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Exceptions/CreateDestinationFolderException.php',
56
- 'WebPConvert\\Exceptions\\InvalidFileExtensionException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Exceptions/InvalidFileExtensionException.php',
57
- 'WebPConvert\\Exceptions\\TargetNotFoundException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Exceptions/TargetNotFoundException.php',
58
- 'WebPConvert\\Exceptions\\WebPConvertBaseException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Exceptions/WebPConvertBaseException.php',
59
- 'WebPConvert\\Loggers\\BaseLogger' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Loggers/BaseLogger.php',
60
- 'WebPConvert\\Loggers\\BufferLogger' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Loggers/BufferLogger.php',
61
- 'WebPConvert\\Loggers\\EchoLogger' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Loggers/EchoLogger.php',
62
- 'WebPConvert\\Loggers\\VoidLogger' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Loggers/VoidLogger.php',
63
- 'WebPConvert\\Serve\\Report' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Serve/Report.php',
64
- 'WebPConvert\\Serve\\ServeBase' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Serve/ServeBase.php',
65
- 'WebPConvert\\Serve\\ServeConverted' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Serve/ServeConverted.php',
66
- 'WebPConvert\\Serve\\ServeExistingOrHandOver' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Serve/ServeExistingOrHandOver.php',
67
- 'WebPConvert\\WebPConvert' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/WebPConvert.php',
68
- );
69
-
70
  public static function getInitializer(ClassLoader $loader)
71
  {
72
  return \Closure::bind(function () use ($loader) {
73
  $loader->prefixLengthsPsr4 = ComposerStaticInit9a5d1d521aac5c1f4f08f3a90858f030::$prefixLengthsPsr4;
74
  $loader->prefixDirsPsr4 = ComposerStaticInit9a5d1d521aac5c1f4f08f3a90858f030::$prefixDirsPsr4;
75
- $loader->classMap = ComposerStaticInit9a5d1d521aac5c1f4f08f3a90858f030::$classMap;
76
 
77
  }, null, ClassLoader::class);
78
  }
12
  'WebPConvert\\' => 12,
13
  'WebPConvertCloudService\\' => 24,
14
  ),
15
+ 'I' =>
16
+ array (
17
+ 'ImageMimeTypeGuesser\\' => 21,
18
+ ),
19
  'D' =>
20
  array (
21
  'DOMUtilForWebP\\' => 15,
31
  array (
32
  0 => __DIR__ . '/..' . '/rosell-dk/webp-convert-cloud-service/src',
33
  ),
34
+ 'ImageMimeTypeGuesser\\' =>
35
+ array (
36
+ 0 => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src',
37
+ ),
38
  'DOMUtilForWebP\\' =>
39
  array (
40
  0 => __DIR__ . '/..' . '/rosell-dk/dom-util-for-webp/src',
41
  ),
42
  );
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  public static function getInitializer(ClassLoader $loader)
45
  {
46
  return \Closure::bind(function () use ($loader) {
47
  $loader->prefixLengthsPsr4 = ComposerStaticInit9a5d1d521aac5c1f4f08f3a90858f030::$prefixLengthsPsr4;
48
  $loader->prefixDirsPsr4 = ComposerStaticInit9a5d1d521aac5c1f4f08f3a90858f030::$prefixDirsPsr4;
 
49
 
50
  }, null, ClassLoader::class);
51
  }
vendor/composer/installed.json CHANGED
@@ -56,27 +56,94 @@
56
  "replace"
57
  ]
58
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  {
60
  "name": "rosell-dk/webp-convert",
61
- "version": "1.3.9",
62
- "version_normalized": "1.3.9.0",
63
  "source": {
64
  "type": "git",
65
  "url": "https://github.com/rosell-dk/webp-convert.git",
66
- "reference": "3680c278f08748054b4ab3ea7855be8984f008aa"
67
  },
68
  "dist": {
69
  "type": "zip",
70
- "url": "https://api.github.com/repos/rosell-dk/webp-convert/zipball/3680c278f08748054b4ab3ea7855be8984f008aa",
71
- "reference": "3680c278f08748054b4ab3ea7855be8984f008aa",
72
  "shasum": ""
73
  },
 
 
 
 
74
  "require-dev": {
75
  "friendsofphp/php-cs-fixer": "^2.11",
76
  "phpunit/phpunit": "5.7.27",
77
  "squizlabs/php_codesniffer": "3.*"
78
  },
79
- "time": "2019-03-21T07:34:21+00:00",
 
 
 
 
 
 
80
  "type": "library",
81
  "extra": {
82
  "scripts-descriptions": {
@@ -94,67 +161,25 @@
94
  "WebPConvert\\": "src/"
95
  }
96
  },
97
- "autoload-dev": {
98
- "psr-4": {
99
- "WebPConvert\\Tests\\": "tests/"
100
- }
101
- },
102
- "scripts": {
103
- "ci": [
104
- "@build",
105
- "@test",
106
- "@phpcs-all",
107
- "@composer validate --no-check-all --strict"
108
- ],
109
- "build": [
110
- "@build-wod",
111
- "@build-require-all"
112
- ],
113
- "cs-fix-all": [
114
- "php-cs-fixer fix src"
115
- ],
116
- "cs-fix": [
117
- "php-cs-fixer fix"
118
- ],
119
- "cs-dry": [
120
- "php-cs-fixer fix --dry-run --diff"
121
- ],
122
- "test": [
123
- "phpunit"
124
- ],
125
- "phpcs": [
126
- "phpcs --standard=PSR2"
127
- ],
128
- "phpcs-all": [
129
- "phpcs --standard=PSR2 src"
130
- ],
131
- "phpcbf": [
132
- "phpcbf --standard=PSR2"
133
- ],
134
- "build-wod": [
135
- "php build-scripts/build-webp-on-demand.php"
136
- ],
137
- "build-require-all": [
138
- "php build-scripts/generate-require-all.php"
139
- ]
140
- },
141
  "license": [
142
  "MIT"
143
  ],
144
  "authors": [
145
- {
146
- "name": "Bjørn Rosell",
147
- "homepage": "https://www.bitwise-it.dk/contact",
148
- "role": "Project Author"
149
- },
150
  {
151
  "name": "Martin Folkers",
152
  "homepage": "https://twobrain.io",
153
  "role": "Collaborator"
 
 
 
 
 
154
  }
155
  ],
156
  "description": "Convert JPEG & PNG to WebP with PHP",
157
  "keywords": [
 
158
  "cwebp",
159
  "gd",
160
  "image conversion",
@@ -163,38 +188,33 @@
163
  "jpg",
164
  "jpg2webp",
165
  "png",
166
- "png2webp",
167
- "webp"
168
- ],
169
- "support": {
170
- "source": "https://github.com/rosell-dk/webp-convert/tree/1.3.9",
171
- "issues": "https://github.com/rosell-dk/webp-convert/issues"
172
- }
173
  },
174
  {
175
  "name": "rosell-dk/webp-convert-cloud-service",
176
- "version": "1.0.0",
177
- "version_normalized": "1.0.0.0",
178
  "source": {
179
  "type": "git",
180
  "url": "https://github.com/rosell-dk/webp-convert-cloud-service.git",
181
- "reference": "e250d49f4cb0fd0c07becbfa0abb7a1a31d03e37"
182
  },
183
  "dist": {
184
  "type": "zip",
185
- "url": "https://api.github.com/repos/rosell-dk/webp-convert-cloud-service/zipball/e250d49f4cb0fd0c07becbfa0abb7a1a31d03e37",
186
- "reference": "e250d49f4cb0fd0c07becbfa0abb7a1a31d03e37",
187
  "shasum": ""
188
  },
189
  "require": {
190
- "rosell-dk/webp-convert": "^1.2.1"
191
  },
192
  "require-dev": {
193
  "friendsofphp/php-cs-fixer": "^2.11",
194
  "phpunit/phpunit": "5.7.27",
195
  "squizlabs/php_codesniffer": "3.*"
196
  },
197
- "time": "2018-11-09T11:17:43+00:00",
198
  "type": "library",
199
  "extra": {
200
  "scripts-descriptions": {
@@ -212,39 +232,7 @@
212
  "WebPConvertCloudService\\": "src/"
213
  }
214
  },
215
- "autoload-dev": {
216
- "psr-4": {
217
- "WebPConvertCloudService\\Tests\\": "tests/"
218
- }
219
- },
220
- "scripts": {
221
- "ci": [
222
- "@test",
223
- "@phpcs-all",
224
- "@composer validate --no-check-all --strict"
225
- ],
226
- "cs-fix-all": [
227
- "php-cs-fixer fix src"
228
- ],
229
- "cs-fix": [
230
- "php-cs-fixer fix"
231
- ],
232
- "cs-dry": [
233
- "php-cs-fixer fix --dry-run --diff"
234
- ],
235
- "test": [
236
- "phpunit tests/"
237
- ],
238
- "phpcs": [
239
- "phpcs --standard=PSR2"
240
- ],
241
- "phpcs-all": [
242
- "phpcs --standard=PSR2 src"
243
- ],
244
- "phpcbf": [
245
- "phpcbf --standard=PSR2"
246
- ]
247
- },
248
  "license": [
249
  "MIT"
250
  ],
@@ -257,6 +245,7 @@
257
  ],
258
  "description": "Cloud service for converting JPEG & PNG to WebP",
259
  "keywords": [
 
260
  "cwebp",
261
  "gd",
262
  "image conversion",
@@ -265,12 +254,7 @@
265
  "jpg",
266
  "jpg2webp",
267
  "png",
268
- "png2webp",
269
- "webp"
270
- ],
271
- "support": {
272
- "source": "https://github.com/rosell-dk/webp-convert-cloud-service/tree/1.0.0",
273
- "issues": "https://github.com/rosell-dk/webp-convert-cloud-service/issues"
274
- }
275
  }
276
  ]
56
  "replace"
57
  ]
58
  },
59
+ {
60
+ "name": "rosell-dk/image-mime-type-guesser",
61
+ "version": "0.3",
62
+ "version_normalized": "0.3.0.0",
63
+ "source": {
64
+ "type": "git",
65
+ "url": "https://github.com/rosell-dk/image-mime-type-guesser.git",
66
+ "reference": "204fd61ca81e3b0ba46c6165dab8f74816b1fe99"
67
+ },
68
+ "dist": {
69
+ "type": "zip",
70
+ "url": "https://api.github.com/repos/rosell-dk/image-mime-type-guesser/zipball/204fd61ca81e3b0ba46c6165dab8f74816b1fe99",
71
+ "reference": "204fd61ca81e3b0ba46c6165dab8f74816b1fe99",
72
+ "shasum": ""
73
+ },
74
+ "require-dev": {
75
+ "friendsofphp/php-cs-fixer": "^2.11",
76
+ "phpunit/phpunit": "^5.7.27",
77
+ "squizlabs/php_codesniffer": "3.*"
78
+ },
79
+ "time": "2019-03-29T09:33:28+00:00",
80
+ "type": "library",
81
+ "extra": {
82
+ "scripts-descriptions": {
83
+ "ci": "Run tests before CI",
84
+ "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
85
+ "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
86
+ "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
87
+ "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
88
+ "test": "Launches the preconfigured PHPUnit"
89
+ }
90
+ },
91
+ "installation-source": "dist",
92
+ "autoload": {
93
+ "psr-4": {
94
+ "ImageMimeTypeGuesser\\": "src/"
95
+ }
96
+ },
97
+ "notification-url": "https://packagist.org/downloads/",
98
+ "license": [
99
+ "MIT"
100
+ ],
101
+ "authors": [
102
+ {
103
+ "name": "Bjørn Rosell",
104
+ "homepage": "https://www.bitwise-it.dk/contact",
105
+ "role": "Project Author"
106
+ }
107
+ ],
108
+ "description": "Guess mime type of images",
109
+ "keywords": [
110
+ "image",
111
+ "images",
112
+ "mime",
113
+ "mime type"
114
+ ]
115
+ },
116
  {
117
  "name": "rosell-dk/webp-convert",
118
+ "version": "2.0.2",
119
+ "version_normalized": "2.0.2.0",
120
  "source": {
121
  "type": "git",
122
  "url": "https://github.com/rosell-dk/webp-convert.git",
123
+ "reference": "50f59ca429ebe2984a4054696db62bcfcf57d299"
124
  },
125
  "dist": {
126
  "type": "zip",
127
+ "url": "https://api.github.com/repos/rosell-dk/webp-convert/zipball/50f59ca429ebe2984a4054696db62bcfcf57d299",
128
+ "reference": "50f59ca429ebe2984a4054696db62bcfcf57d299",
129
  "shasum": ""
130
  },
131
+ "require": {
132
+ "php": "^5.6 | ^7.0",
133
+ "rosell-dk/image-mime-type-guesser": "^0.3"
134
+ },
135
  "require-dev": {
136
  "friendsofphp/php-cs-fixer": "^2.11",
137
  "phpunit/phpunit": "5.7.27",
138
  "squizlabs/php_codesniffer": "3.*"
139
  },
140
+ "suggest": {
141
+ "ext-gd": "to use GD extension for converting. Note: Gd must be compiled with webp support",
142
+ "ext-imagick": "to use Imagick extension for converting. Note: Gd must be compiled with webp support",
143
+ "ext-vips": "to use Vips extension for converting.",
144
+ "php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
145
+ },
146
+ "time": "2019-06-15T11:45:10+00:00",
147
  "type": "library",
148
  "extra": {
149
  "scripts-descriptions": {
161
  "WebPConvert\\": "src/"
162
  }
163
  },
164
+ "notification-url": "https://packagist.org/downloads/",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  "license": [
166
  "MIT"
167
  ],
168
  "authors": [
 
 
 
 
 
169
  {
170
  "name": "Martin Folkers",
171
  "homepage": "https://twobrain.io",
172
  "role": "Collaborator"
173
+ },
174
+ {
175
+ "name": "Bjørn Rosell",
176
+ "homepage": "https://www.bitwise-it.dk/contact",
177
+ "role": "Project Author"
178
  }
179
  ],
180
  "description": "Convert JPEG & PNG to WebP with PHP",
181
  "keywords": [
182
+ "Webp",
183
  "cwebp",
184
  "gd",
185
  "image conversion",
188
  "jpg",
189
  "jpg2webp",
190
  "png",
191
+ "png2webp"
192
+ ]
 
 
 
 
 
193
  },
194
  {
195
  "name": "rosell-dk/webp-convert-cloud-service",
196
+ "version": "2.0.0",
197
+ "version_normalized": "2.0.0.0",
198
  "source": {
199
  "type": "git",
200
  "url": "https://github.com/rosell-dk/webp-convert-cloud-service.git",
201
+ "reference": "5345c63d0a44d529591c36ebab33c3bcfc080787"
202
  },
203
  "dist": {
204
  "type": "zip",
205
+ "url": "https://api.github.com/repos/rosell-dk/webp-convert-cloud-service/zipball/5345c63d0a44d529591c36ebab33c3bcfc080787",
206
+ "reference": "5345c63d0a44d529591c36ebab33c3bcfc080787",
207
  "shasum": ""
208
  },
209
  "require": {
210
+ "rosell-dk/webp-convert": "^2.0.0"
211
  },
212
  "require-dev": {
213
  "friendsofphp/php-cs-fixer": "^2.11",
214
  "phpunit/phpunit": "5.7.27",
215
  "squizlabs/php_codesniffer": "3.*"
216
  },
217
+ "time": "2019-06-14T13:05:09+00:00",
218
  "type": "library",
219
  "extra": {
220
  "scripts-descriptions": {
232
  "WebPConvertCloudService\\": "src/"
233
  }
234
  },
235
+ "notification-url": "https://packagist.org/downloads/",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  "license": [
237
  "MIT"
238
  ],
245
  ],
246
  "description": "Cloud service for converting JPEG & PNG to WebP",
247
  "keywords": [
248
+ "Webp",
249
  "cwebp",
250
  "gd",
251
  "image conversion",
254
  "jpg",
255
  "jpg2webp",
256
  "png",
257
+ "png2webp"
258
+ ]
 
 
 
 
 
259
  }
260
  ]
vendor/rosell-dk/image-mime-type-guesser/.php_cs.dist ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $finder = PhpCsFixer\Finder::create()
4
+ ->exclude('tests')
5
+ ->in(__DIR__)
6
+ ;
7
+
8
+ $config = PhpCsFixer\Config::create();
9
+ $config
10
+ ->setRules([
11
+ '@PSR2' => true,
12
+ 'array_syntax' => [
13
+ 'syntax' => 'short',
14
+ ],
15
+ ])
16
+ ->setFinder($finder)
17
+ ;
18
+
19
+ return $config;
vendor/rosell-dk/image-mime-type-guesser/LICENSE ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Bjørn Rosell
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
vendor/rosell-dk/image-mime-type-guesser/README.md ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # image-mime-type-guesser
2
+
3
+ [![Latest Stable Version](https://img.shields.io/packagist/v/rosell-dk/image-mime-type-guesser.svg?style=flat-square)](https://packagist.org/packages/rosell-dk/image-mime-type-guesser)
4
+ [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%205.6-8892BF.svg?style=flat-square)](https://php.net)
5
+ [![Build Status](https://img.shields.io/travis/rosell-dk/image-mime-type-guesser/master.svg?style=flat-square)](https://travis-ci.org/rosell-dk/image-mime-type-guesser)
6
+ [![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/rosell-dk/image-mime-type-guesser.svg?style=flat-square)](https://scrutinizer-ci.com/g/rosell-dk/image-mime-type-guesser/code-structure/master)
7
+ [![Quality Score](https://img.shields.io/scrutinizer/g/rosell-dk/image-mime-type-guesser.svg?style=flat-square)](https://scrutinizer-ci.com/g/rosell-dk/image-mime-type-guesser/)
8
+ [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/rosell-dk/image-mime-type-guesser/blob/master/LICENSE)
9
+
10
+
11
+ *Detect / guess mime type of an image*
12
+
13
+ Do you need to determine if a file is an image?<br>
14
+ And perhaps you also want to know the mime type of the image?<br>
15
+ Do you basically need [exif_imagetype](https://www.php.net/manual/en/function.exif-imagetype.php), but which also works when PHP is compiled without exif?
16
+
17
+ &ndash; You come to the right library.
18
+
19
+ Ok, actually the library cannot offer mime type detection for images which works *on all platforms*, but it can try a whole stack of methods and optionally fall back to guess from the file extension.
20
+
21
+ The stack of detect methods are currently (and in that order):
22
+ - [`exif_imagetype`](https://www.php.net/manual/en/function.exif-imagetype.php) *(PHP 4 >= 4.3.0, PHP 5, PHP 7) - unless PHP is compiled without exif*
23
+ - [`finfo`](https://www.php.net/manual/en/class.finfo.php) *(PHP 5 >= 5.3.0, PHP 7, PECL fileinfo >= 0.1.0) - requires fileinfo extension to be enabled*
24
+ - Our custom 4 byte sniffer (based on [this code](http://phil.lavin.me.uk/2011/12/php-accurately-detecting-the-type-of-a-file/)) *(PHP 4, PHP 5, PHP 7) - only detects png, gif and jpeg*
25
+ - [`getimagesize`](https://www.php.net/getimagesize) *(PHP 4, PHP 5, PHP 7)*
26
+ - [`mime_content_type`](https://www.php.net/manual/en/function.mime-content-type.php) *(PHP 4 >= 4.3.0, PHP 5, PHP 7)*
27
+
28
+ Note that these methods all uses the mime type mapping on the server. Not all servers for example detects `image/webp`.
29
+
30
+
31
+ ## Installation
32
+
33
+ Install with composer
34
+
35
+
36
+ ## Usage
37
+
38
+ Use `ImageMimeTypeGuesser::detect` if you do not want the library to make a wild guess based on file extension, but in return are willing to accept the increased probability of the library not returning a mime type as an answer.
39
+
40
+ Example:
41
+ ```php
42
+ $result = ImageMimeTypeGuesser::detect($filePath);
43
+ if (is_null($result)) {
44
+ // the mime type could not be determined
45
+ } elseif ($result === false) {
46
+ // it is NOT an image (not a mime type that the server knows about anyway)
47
+ } else {
48
+ // it is an image, and we know its mime type!
49
+ $mimeType = $result;
50
+ }
51
+ ```
52
+
53
+ If you are ok with wild guessing from file extension, use `ImageMimeTypeGuesser::guess` or `ImageMimeTypeGuesser::lenientGuess`. Lets start with the first.
54
+
55
+ `ImageMimeTypeGuesser::guess` will first try detection. If detection fails (void is returned), it will fall back to guessing from extension using `GuessFromExtension::guess`.
56
+
57
+ As with the detect method, it also has three possible outcomes: a mime type, false or void.
58
+
59
+ *Warning*: Beware that guessing from file extension is unsuited when your aim is to protect the server from harmful uploads.
60
+
61
+ *Notice*: Only a limited set of image extensions is recognized by the extension to mimetype mapper - namely the following: { bmp, gif, ico, jpg, jpeg, png, tif, tiff, webp, svg }. If you need some other specifically, feel free to add a PR, or ask me to do it by creating an issue.
62
+
63
+
64
+ Example:
65
+ ```php
66
+ $result = ImageMimeTypeGuesser::guess($filePath);
67
+ if ($result !== false) {
68
+ // it is an image, and we know its mime type (well, we don't really know, because we allowed guessing from extension)
69
+ $mimeType = $result;
70
+ } else {
71
+ // not an image
72
+ }
73
+ ```
74
+
75
+ If you do not want your servers limited knowledge about image types to be decisive, you can use lenientGuess. It tries to detect. If detection fails (void *or false* is returned), it will fall back to guessing based on file extension.
76
+
77
+ Say for example that your server does not recognize the image/webp format, and you are examining a file "test.webp". In that case, a detection with *detect* will return false (provided that one of the detection methods are operational). The *guess* method will *also* return false, as it never gets to fall back to file extension mapping. However, *lenientGuess* will nail it, and return 'image/webp'.
78
+
79
+ For those who speaks code, the logic is perhaps best described with the code itself:
80
+
81
+ ```php
82
+ public static function lenientGuess($filePath)
83
+ {
84
+ $detectResult = self::detect($filePath);
85
+ if ($detectResult === false) {
86
+ // The server does not recognize this image type.
87
+ // - but perhaps it is because it does not know about this image type.
88
+ // - so we turn to mapping the file extension
89
+ return GuessFromExtension::guess($filePath);
90
+ } elseif (is_null($detectResult)) {
91
+ // the mime type could not be determined
92
+ // perhaps we also in this case want to turn to mapping the file extension
93
+ return GuessFromExtension::guess($filePath);
94
+ }
95
+ return $detectResult;
96
+ }
97
+ ```
98
+
99
+
100
+ Finally, for convenience, there are three methods for testing if a detection / guess / lenient guess is in a list of mime types. They are called `ImageMimeTypeGuesser::detectIsIn`, `ImageMimeTypeGuesser::guessIsIn` and `ImageMimeTypeGuesser::lenientGuessIsIn`.
101
+
102
+ Example:
103
+
104
+ ```php
105
+ if (ImageMimeTypeGuesser::guessIsIn($filePath, ['image/jpeg','image/png']) {
106
+ // Image is either a jpeg or a png (probably)
107
+ }
108
+ ```
vendor/rosell-dk/image-mime-type-guesser/composer.json ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "rosell-dk/image-mime-type-guesser",
3
+ "description": "Guess mime type of images",
4
+ "type": "library",
5
+ "license": "MIT",
6
+ "keywords": ["mime", "mime type", "image", "images"],
7
+ "scripts": {
8
+ "ci": [
9
+ "@test",
10
+ "@phpcs-all",
11
+ "@composer validate --no-check-all --strict"
12
+ ],
13
+ "cs-fix-all": [
14
+ "php-cs-fixer fix src"
15
+ ],
16
+ "cs-fix": "php-cs-fixer fix",
17
+ "cs-dry": "php-cs-fixer fix --dry-run --diff",
18
+ "test": "phpunit tests --coverage-text --coverage-clover=coverage.clover",
19
+ "test2": "phpunit tests",
20
+ "phpcs": "phpcs --standard=PSR2",
21
+ "phpcs-all": "phpcs --standard=PSR2 src",
22
+ "phpcbf": "phpcbf --standard=PSR2"
23
+ },
24
+ "extra": {
25
+ "scripts-descriptions": {
26
+ "ci": "Run tests before CI",
27
+ "phpcs": "Checks coding styles (PSR2) of file/dir, which you must supply. To check all, supply 'src'",
28
+ "phpcbf": "Fix coding styles (PSR2) of file/dir, which you must supply. To fix all, supply 'src'",
29
+ "cs-fix-all": "Fix the coding style of all the source files, to comply with the PSR-2 coding standard",
30
+ "cs-fix": "Fix the coding style of a PHP file or directory, which you must specify.",
31
+ "test": "Launches the preconfigured PHPUnit"
32
+ }
33
+ },
34
+ "autoload": {
35
+ "psr-4": { "ImageMimeTypeGuesser\\": "src/" }
36
+ },
37
+ "autoload-dev": {
38
+ "psr-4": { "ImageMimeTypeGuesser\\Tests\\": "tests/" }
39
+ },
40
+ "authors": [
41
+ {
42
+ "name": "Bjørn Rosell",
43
+ "homepage": "https://www.bitwise-it.dk/contact",
44
+ "role": "Project Author"
45
+ }
46
+ ],
47
+ "require-dev": {
48
+ "friendsofphp/php-cs-fixer": "^2.11",
49
+ "phpunit/phpunit": "^5.7.27",
50
+ "squizlabs/php_codesniffer": "3.*"
51
+ },
52
+ "config": {
53
+ "sort-packages": true
54
+ }
55
+ }
vendor/rosell-dk/image-mime-type-guesser/phpunit.xml.dist ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <phpunit bootstrap="vendor/autoload.php"
3
+ backupGlobals="false"
4
+ backupStaticAttributes="false"
5
+ colors="true"
6
+ verbose="true"
7
+ convertErrorsToExceptions="true"
8
+ convertNoticesToExceptions="true"
9
+ convertWarningsToExceptions="true"
10
+ processIsolation="false"
11
+ stopOnFailure="false">
12
+ <testsuites>
13
+ <testsuite name=":vendor Test Suite">
14
+ <directory suffix="Test.php">./tests/</directory>
15
+ </testsuite>
16
+ </testsuites>
17
+ <filter>
18
+ <whitelist>
19
+ <directory suffix=".php">src/</directory>
20
+ </whitelist>
21
+ </filter>
22
+ <logging>
23
+ <log type="junit" target="build/report.junit.xml"/>
24
+ <log type="coverage-clover" target="build/logs/clover.xml"/>
25
+ <log type="coverage-text" target="build/coverage.txt"/>
26
+ <log type="coverage-html" target="build/coverage"/>
27
+ </logging>
28
+ </phpunit>
vendor/rosell-dk/image-mime-type-guesser/src/Detectors/AbstractDetector.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ImageMimeTypeGuesser\Detectors;
4
+
5
+ use ImageMimeTypeGuesser\Detectors\AbstractDetector;
6
+
7
+ abstract class AbstractDetector
8
+ {
9
+ /**
10
+ * Try to detect mime type of image
11
+ *
12
+ * Returns:
13
+ * - mime type (string) (if it is in fact an image, and type could be determined)
14
+ * - false (if it is not an image type that the server knowns about)
15
+ * - null (if nothing can be determined)
16
+ *
17
+ * @param string $filePath The path to the file
18
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
19
+ * false (if it is not an image type that the server knowns about)
20
+ * or null (if nothing can be determined)
21
+ */
22
+ abstract protected function doDetect($filePath);
23
+
24
+ /**
25
+ * Create an instance of this class
26
+ *
27
+ * @param string $filePath The path to the file
28
+ * @return static
29
+ */
30
+ public static function createInstance()
31
+ {
32
+ return new static();
33
+ }
34
+
35
+ /**
36
+ * Detect mime type of file (for images only)
37
+ *
38
+ * Returns:
39
+ * - mime type (string) (if it is in fact an image, and type could be determined)
40
+ * - false (if it is not an image type that the server knowns about)
41
+ * - null (if nothing can be determined)
42
+ *
43
+ * @param string $filePath The path to the file
44
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
45
+ * false (if it is not an image type that the server knowns about)
46
+ * or null (if nothing can be determined)
47
+ */
48
+ public static function detect($filePath)
49
+ {
50
+ if (!@file_exists($filePath)) {
51
+ return false;
52
+ }
53
+ return self::createInstance()->doDetect($filePath);
54
+ }
55
+ }
vendor/rosell-dk/image-mime-type-guesser/src/Detectors/ExifImageType.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ImageMimeTypeGuesser\Detectors;
4
+
5
+ use \ImageMimeTypeGuesser\Detectors\AbstractDetector;
6
+
7
+ class ExifImageType extends AbstractDetector
8
+ {
9
+
10
+ /**
11
+ * Try to detect mime type of image using *exif_imagetype*.
12
+ *
13
+ * Returns:
14
+ * - mime type (string) (if it is in fact an image, and type could be determined)
15
+ * - false (if it is not an image type that the server knowns about)
16
+ * - null (if nothing can be determined)
17
+ *
18
+ * @param string $filePath The path to the file
19
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
20
+ * false (if it is not an image type that the server knowns about)
21
+ * or null (if nothing can be determined)
22
+ */
23
+ protected function doDetect($filePath)
24
+ {
25
+ // exif_imagetype is fast, however not available on all systems,
26
+ // It may return false. In that case we can rely on that the file is not an image (and return false)
27
+ if (function_exists('exif_imagetype')) {
28
+ try {
29
+ $imageType = exif_imagetype($filePath);
30
+ return ($imageType ? image_type_to_mime_type($imageType) : false);
31
+ } catch (\Exception $e) {
32
+ // Might for example get "Read error!"
33
+ // well well, don't let this stop us
34
+ //echo $e->getMessage();
35
+ // throw($e);
36
+ }
37
+ }
38
+ return null;
39
+ }
40
+ }
vendor/rosell-dk/image-mime-type-guesser/src/Detectors/FInfo.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ImageMimeTypeGuesser\Detectors;
4
+
5
+ class FInfo extends AbstractDetector
6
+ {
7
+
8
+ /**
9
+ * Try to detect mime type of image using *finfo* class.
10
+ *
11
+ * Returns:
12
+ * - mime type (string) (if it is in fact an image, and type could be determined)
13
+ * - false (if it is not an image type that the server knowns about)
14
+ * - null (if nothing can be determined)
15
+ *
16
+ * @param string $filePath The path to the file
17
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
18
+ * false (if it is not an image type that the server knowns about)
19
+ * or null (if nothing can be determined)
20
+ */
21
+ protected function doDetect($filePath)
22
+ {
23
+
24
+ if (class_exists('finfo')) {
25
+ // phpcs:ignore PHPCompatibility.PHP.NewClasses.finfoFound
26
+ $finfo = new \finfo(FILEINFO_MIME);
27
+ $mime = explode('; ', $finfo->file($filePath));
28
+ $result = $mime[0];
29
+
30
+ if (strpos($result, 'image/') === 0) {
31
+ return $result;
32
+ } else {
33
+ return false;
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+ }
vendor/rosell-dk/image-mime-type-guesser/src/Detectors/GetImageSize.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ImageMimeTypeGuesser\Detectors;
4
+
5
+ class GetImageSize extends AbstractDetector
6
+ {
7
+
8
+ /**
9
+ * Try to detect mime type of image using *getimagesize()*.
10
+ *
11
+ * Returns:
12
+ * - mime type (string) (if it is in fact an image, and type could be determined)
13
+ * - false (if it is not an image type that the server knowns about)
14
+ * - null (if nothing can be determined)
15
+ *
16
+ * @param string $filePath The path to the file
17
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
18
+ * false (if it is not an image type that the server knowns about)
19
+ * or null (if nothing can be determined)
20
+ */
21
+ protected function doDetect($filePath)
22
+ {
23
+ // getimagesize is slower than exif_imagetype
24
+ // It may not return "mime". In that case we can rely on that the file is not an image (and return false)
25
+ if (function_exists('getimagesize')) {
26
+ try {
27
+ $imageSize = getimagesize($filePath);
28
+ return (isset($imageSize['mime']) ? $imageSize['mime'] : false);
29
+ } catch (\Exception $e) {
30
+ // well well, don't let this stop us either
31
+ return null;
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+ }
vendor/rosell-dk/image-mime-type-guesser/src/Detectors/MimeContentType.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ImageMimeTypeGuesser\Detectors;
4
+
5
+ class MimeContentType extends AbstractDetector
6
+ {
7
+
8
+ /**
9
+ * Try to detect mime type of image using *mime_content_type()*.
10
+ *
11
+ * Returns:
12
+ * - mime type (string) (if it is in fact an image, and type could be determined)
13
+ * - false (if it is not an image type that the server knowns about)
14
+ * - null (if nothing can be determined)
15
+ *
16
+ * @param string $filePath The path to the file
17
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
18
+ * false (if it is not an image type that the server knowns about)
19
+ * or null (if nothing can be determined)
20
+ */
21
+ protected function doDetect($filePath)
22
+ {
23
+ // mime_content_type supposedly used to be deprecated, but it seems it isn't anymore
24
+ // it may return false on failure.
25
+ if (function_exists('mime_content_type')) {
26
+ try {
27
+ $result = mime_content_type($filePath);
28
+ if ($result !== false) {
29
+ if (strpos($result, 'image/') === 0) {
30
+ return $result;
31
+ } else {
32
+ return false;
33
+ }
34
+ }
35
+ } catch (\Exception $e) {
36
+ // we are unstoppable!
37
+ }
38
+ }
39
+ return null;
40
+ }
41
+ }
vendor/rosell-dk/image-mime-type-guesser/src/Detectors/SniffFirstFourBytes.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ImageMimeTypeGuesser\Detectors;
4
+
5
+ use \ImageMimeTypeGuesser\Detectors\AbstractDetector;
6
+
7
+ class SniffFirstFourBytes extends AbstractDetector
8
+ {
9
+
10
+ /**
11
+ * Try to detect mime type by sniffing the first four bytes.
12
+ *
13
+ * Credits: Based on the code here: http://phil.lavin.me.uk/2011/12/php-accurately-detecting-the-type-of-a-file/
14
+ *
15
+ * Returns:
16
+ * - mime type (string) (if it is in fact an image, and type could be determined)
17
+ * - false (if it is not an image type that the server knowns about)
18
+ * - null (if nothing can be determined)
19
+ *
20
+ * @param string $filePath The path to the file
21
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
22
+ * false (if it is not an image type that the server knowns about)
23
+ * or null (if nothing can be determined)
24
+ */
25
+ protected function doDetect($filePath)
26
+ {
27
+ // PNG, GIF, JFIF JPEG, EXIF JPEF (respectively)
28
+ $known = [
29
+ '89504E47' => 'image/png',
30
+ '47494638' => 'image/gif',
31
+ 'FFD8FFE0' => 'image/jpeg', // JFIF JPEG
32
+ 'FFD8FFE1' => 'image/jpeg', // EXIF JPEG
33
+ ];
34
+
35
+ $handle = @fopen($filePath, 'r');
36
+ if ($handle === false) {
37
+ return null;
38
+ }
39
+ $firstFour = @fread($handle, 4);
40
+ if ($firstFour === false) {
41
+ return null;
42
+ }
43
+ $key = strtoupper(bin2hex($firstFour));
44
+ if (isset($known[$key])) {
45
+ return $known[$key];
46
+ }
47
+ }
48
+ }
vendor/rosell-dk/image-mime-type-guesser/src/Detectors/Stack.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ImageMimeTypeGuesser\Detectors;
4
+
5
+ class Stack extends AbstractDetector
6
+ {
7
+ /**
8
+ * Try to detect mime type of image using all available detectors.
9
+ *
10
+ * Returns:
11
+ * - mime type (string) (if it is in fact an image, and type could be determined)
12
+ * - false (if it is not an image type that the server knowns about)
13
+ * - null (if nothing can be determined)
14
+ *
15
+ * @param string $filePath The path to the file
16
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
17
+ * false (if it is not an image type that the server knowns about)
18
+ * or null (if nothing can be determined)
19
+ */
20
+ protected function doDetect($filePath)
21
+ {
22
+ $detectors = [
23
+ 'ExifImageType',
24
+ 'FInfo',
25
+ 'SniffFirstFourBytes',
26
+ 'GetImageSize',
27
+ 'MimeContentType',
28
+ ];
29
+
30
+ foreach ($detectors as $className) {
31
+ $result = call_user_func(
32
+ array("\\ImageMimeTypeGuesser\\Detectors\\" . $className, 'detect'),
33
+ $filePath
34
+ );
35
+ if (!is_null($result)) {
36
+ return $result;
37
+ }
38
+ }
39
+
40
+ return null; // undetermined
41
+ }
42
+ }
vendor/rosell-dk/image-mime-type-guesser/src/GuessFromExtension.php ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * ImageMimeTypeGuesser - Detect / guess mime type of an image
5
+ *
6
+ * @link https://github.com/rosell-dk/image-mime-type-guesser
7
+ * @license MIT
8
+ */
9
+
10
+ namespace ImageMimeTypeGuesser;
11
+
12
+ class GuessFromExtension
13
+ {
14
+
15
+
16
+ /**
17
+ * Make a wild guess based on file extension.
18
+ *
19
+ * - and I mean wild!
20
+ *
21
+ * Only most popular image types are recognized.
22
+ * Many are not. See this list: https://www.iana.org/assignments/media-types/media-types.xhtml
23
+ * - and the constants here: https://secure.php.net/manual/en/function.exif-imagetype.php
24
+ *
25
+ * If no mapping found, nothing is returned
26
+ *
27
+ * TODO: jp2, jpx, ...
28
+ * Returns:
29
+ * - mimetype (if file extension could be mapped to an image type),
30
+ * - false (if file extension could be mapped to a type known not to be an image type)
31
+ * - null (if file extension could not be mapped to any mime type, using our little list)
32
+ *
33
+ * @param string $filePath The path to the file
34
+ * @return string|false|null mimetype (if file extension could be mapped to an image type),
35
+ * false (if file extension could be mapped to a type known not to be an image type)
36
+ * or null (if file extension could not be mapped to any mime type, using our little list)
37
+ */
38
+ public static function guess($filePath)
39
+ {
40
+ if (!@file_exists($filePath)) {
41
+ return false;
42
+ }
43
+ /*
44
+ Not using pathinfo, as it is locale aware, and I'm not sure if that could lead to problems
45
+
46
+ if (!function_exists('pathinfo')) {
47
+ // This is really a just in case! - We do not expect this to happen.
48
+ // - in fact we have a test case asserting that this does not happen.
49
+ return null;
50
+ //
51
+ $fileExtension = pathinfo($filePath, PATHINFO_EXTENSION);
52
+ $fileExtension = strtolower($fileExtension);
53
+ }*/
54
+
55
+ $result = preg_match('#\\.([^.]*)$#', $filePath, $matches);
56
+ if ($result !== 1) {
57
+ return null;
58
+ }
59
+ $fileExtension = $matches[1];
60
+
61
+ // Trivial image mime types
62
+ if (in_array($fileExtension, ['bmp', 'gif', 'jpeg', 'png', 'tiff', 'webp'])) {
63
+ return 'image/' . $fileExtension;
64
+ }
65
+
66
+ // Common extensions that are definitely not images
67
+ if (in_array($fileExtension, ['txt', 'doc', 'zip', 'gz', 'exe'])) {
68
+ return false;
69
+ }
70
+
71
+ // Non-trivial image mime types
72
+ switch ($fileExtension) {
73
+ case 'ico':
74
+ return 'image/vnd.microsoft.icon'; // or perhaps 'x-icon' ?
75
+
76
+ case 'jpg':
77
+ return 'image/jpeg';
78
+
79
+ case 'svg':
80
+ return 'image/svg+xml';
81
+
82
+ case 'tif':
83
+ return 'image/tiff';
84
+ }
85
+
86
+ // We do not know this extension, return null
87
+ return null;
88
+ }
89
+
90
+ }
vendor/rosell-dk/image-mime-type-guesser/src/ImageMimeTypeGuesser.php ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * ImageMimeTypeGuesser - Detect / guess mime type of an image
5
+ *
6
+ * The library is born out of a discussion here:
7
+ * https://github.com/rosell-dk/webp-convert/issues/98
8
+ *
9
+ * @link https://github.com/rosell-dk/image-mime-type-guesser
10
+ * @license MIT
11
+ */
12
+
13
+ namespace ImageMimeTypeGuesser;
14
+
15
+ use \ImageMimeTypeGuesser\Detectors\Stack;
16
+
17
+ class ImageMimeTypeGuesser
18
+ {
19
+
20
+
21
+ /**
22
+ * Try to detect mime type of image using all available detectors (the "stack" detector).
23
+ *
24
+ * Returns:
25
+ * - mime type (string) (if it is in fact an image, and type could be determined)
26
+ * - false (if it is not an image type that the server knowns about)
27
+ * - null (if nothing can be determined)
28
+ *
29
+ * @param string $filePath The path to the file
30
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
31
+ * false (if it is not an image type that the server knowns about)
32
+ * or null (if nothing can be determined)
33
+ */
34
+ public static function detect($filePath)
35
+ {
36
+ return Stack::detect($filePath);
37
+ }
38
+
39
+ /**
40
+ * Try to detect mime type of image. If that fails, make a guess based on the file extension.
41
+ *
42
+ * Try to detect mime type of image using "stack" detector (all available methods, until one succeeds)
43
+ * If that fails (null), fall back to wild west guessing based solely on file extension.
44
+ *
45
+ * Returns:
46
+ * - mime type (string) (if it is an image, and type could be determined / mapped from file extension))
47
+ * - false (if it is not an image type that the server knowns about)
48
+ * - null (if nothing can be determined)
49
+ *
50
+ * @param string $filePath The path to the file
51
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
52
+ * false (if it is not an image type that the server knowns about)
53
+ * or null (if nothing can be determined)
54
+ */
55
+ public static function guess($filePath)
56
+ {
57
+ $detectionResult = self::detect($filePath);
58
+ if (!is_null($detectionResult)) {
59
+ return $detectionResult;
60
+ }
61
+
62
+ // fall back to the wild west method
63
+ return GuessFromExtension::guess($filePath);
64
+ }
65
+
66
+ /**
67
+ * Try to detect mime type of image. If that fails, make a guess based on the file extension.
68
+ *
69
+ * Try to detect mime type of image using "stack" detector (all available methods, until one succeeds)
70
+ * If that fails (false or null), fall back to wild west guessing based solely on file extension.
71
+ *
72
+ * Returns:
73
+ * - mime type (string) (if it is an image, and type could be determined / mapped from file extension)
74
+ * - false (if it is not an image type that the server knowns about)
75
+ * - null (if nothing can be determined)
76
+ *
77
+ * @param string $filePath The path to the file
78
+ * @return string|false|null mimetype (if it is an image, and type could be determined / guessed),
79
+ * false (if it is not an image type that the server knowns about)
80
+ * or null (if nothing can be determined)
81
+ */
82
+ public static function lenientGuess($filePath)
83
+ {
84
+ $detectResult = self::detect($filePath);
85
+ if ($detectResult === false) {
86
+ // The server does not recognize this image type.
87
+ // - but perhaps it is because it does not know about this image type.
88
+ // - so we turn to mapping the file extension
89
+ return GuessFromExtension::guess($filePath);
90
+ } elseif (is_null($detectResult)) {
91
+ // the mime type could not be determined
92
+ // perhaps we also in this case want to turn to mapping the file extension
93
+ return GuessFromExtension::guess($filePath);
94
+ }
95
+ return $detectResult;
96
+ }
97
+
98
+
99
+ /**
100
+ * Check if the *detected* mime type is in a list of accepted mime types.
101
+ *
102
+ * @param string $filePath The path to the file
103
+ * @param string[] $mimeTypes Mime types to accept
104
+ * @return bool Whether the detected mime type is in the $mimeTypes array or not
105
+ */
106
+ public static function detectIsIn($filePath, $mimeTypes)
107
+ {
108
+ return in_array(self::detect($filePath), $mimeTypes);
109
+ }
110
+
111
+ /**
112
+ * Check if the *guessed* mime type is in a list of accepted mime types.
113
+ *
114
+ * @param string $filePath The path to the file
115
+ * @param string[] $mimeTypes Mime types to accept
116
+ * @return bool Whether the detected / guessed mime type is in the $mimeTypes array or not
117
+ */
118
+ public static function guessIsIn($filePath, $mimeTypes)
119
+ {
120
+ return in_array(self::guess($filePath), $mimeTypes);
121
+ }
122
+
123
+ /**
124
+ * Check if the *leniently guessed* mime type is in a list of accepted mime types.
125
+ *
126
+ * @param string $filePath The path to the file
127
+ * @param string[] $mimeTypes Mime types to accept
128
+ * @return bool Whether the detected / leniently guessed mime type is in the $mimeTypes array or not
129
+ */
130
+ public static function lenientGuessIsIn($filePath, $mimeTypes)
131
+ {
132
+ return in_array(self::lenientGuess($filePath), $mimeTypes);
133
+ }
134
+ }
vendor/rosell-dk/webp-convert-cloud-service/README.md CHANGED
@@ -18,6 +18,7 @@ composer require rosell-dk/webp-convert-cloud-service
18
  Here is an example to get started with:
19
 
20
  ```php
 
21
  require 'vendor/autoload.php';
22
 
23
  use \WebPConvertCloudService\WebPConvertCloudService;
@@ -53,6 +54,7 @@ $options = [
53
 
54
  $wpc = new WebPConvertCloudService();
55
  $wpc->handleRequest($options);
 
56
 
57
  ```
58
 
@@ -81,3 +83,6 @@ curl --form action="convert" --form api-key="my dog is white" --form file=@test.
81
  If you get a corrupt file, then it is probably because the output contains an error message. To see it, run the above command again, but remove the piping of the output to a file.
82
 
83
  You will probably not need to know more of the API. But in case you do, check out [docs/api.md](https://github.com/rosell-dk/webp-convert-cloud-service/blob/master/docs/api.md)
 
 
 
18
  Here is an example to get started with:
19
 
20
  ```php
21
+ <?php
22
  require 'vendor/autoload.php';
23
 
24
  use \WebPConvertCloudService\WebPConvertCloudService;
54
 
55
  $wpc = new WebPConvertCloudService();
56
  $wpc->handleRequest($options);
57
+ ?>
58
 
59
  ```
60
 
83
  If you get a corrupt file, then it is probably because the output contains an error message. To see it, run the above command again, but remove the piping of the output to a file.
84
 
85
  You will probably not need to know more of the API. But in case you do, check out [docs/api.md](https://github.com/rosell-dk/webp-convert-cloud-service/blob/master/docs/api.md)
86
+
87
+ ## Mad Scientist-ware
88
+ If you enjoy this software, feel free to conduct some secret experiments and go mad. If you like.
vendor/rosell-dk/webp-convert-cloud-service/composer.json CHANGED
@@ -49,7 +49,7 @@
49
  "squizlabs/php_codesniffer": "3.*"
50
  },
51
  "require": {
52
- "rosell-dk/webp-convert": "^1.2.1"
53
  },
54
  "config": {
55
  "sort-packages": true
49
  "squizlabs/php_codesniffer": "3.*"
50
  },
51
  "require": {
52
+ "rosell-dk/webp-convert": "^2.0.0"
53
  },
54
  "config": {
55
  "sort-packages": true
vendor/rosell-dk/webp-convert-cloud-service/src/Serve.php CHANGED
@@ -119,17 +119,14 @@ class Serve
119
  }
120
 
121
  try {
122
- if (WebPConvert::convert($source, $destination, $convertOptions)) {
123
- header('Content-type: application/octet-stream');
124
- echo file_get_contents($destination);
125
-
126
- unlink($source);
127
- unlink($destination);
128
- } else {
129
- echo 'no converters could convert the image';
130
- }
131
  } catch (\Exception $e) {
132
- echo 'failed!';
133
  echo $e->getMessage();
134
  }
135
  } else {
119
  }
120
 
121
  try {
122
+ WebPConvert::convert($source, $destination, $convertOptions);
123
+ header('Content-type: application/octet-stream');
124
+ echo file_get_contents($destination);
125
+
126
+ unlink($source);
127
+ unlink($destination);
 
 
 
128
  } catch (\Exception $e) {
129
+ echo 'Conversion failed!';
130
  echo $e->getMessage();
131
  }
132
  } else {
vendor/rosell-dk/webp-convert-cloud-service/src/WebPConvertCloudService.php CHANGED
@@ -84,7 +84,7 @@ class WebPConvertCloudService
84
  {
85
  //$this->options = static::loadConfig();
86
  if (!isset($options)) {
87
- self::exitWithError(self::ERROR_SERVER_SETUP, 'Could not load configuration file');
88
  }
89
 
90
  $action = (isset($_POST['action']) ? $_POST['action'] : 'convert');
@@ -93,7 +93,7 @@ class WebPConvertCloudService
93
 
94
  switch ($action) {
95
  case 'api-version':
96
- echo '1';
97
  exit;
98
  }
99
 
84
  {
85
  //$this->options = static::loadConfig();
86
  if (!isset($options)) {
87
+ self::exitWithError(self::ERROR_SERVER_SETUP, 'No options was supplied');
88
  }
89
 
90
  $action = (isset($_POST['action']) ? $_POST['action'] : 'convert');
93
 
94
  switch ($action) {
95
  case 'api-version':
96
+ echo '2';
97
  exit;
98
  }
99
 
vendor/rosell-dk/webp-convert/.travis.yml CHANGED
@@ -1,18 +1,134 @@
 
 
 
 
 
1
  language: php
2
 
3
- php:
4
- - 5.6
5
- - 7.0
6
- - 7.1
7
- - 7.2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- dist: trusty
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  sudo: false
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  before_script:
 
14
  - (composer self-update; true)
15
  - composer install
 
16
 
17
  script:
18
- - composer test
 
 
 
 
 
 
 
 
 
1
+
2
+ # Other projects which deals with images in PHP and uses travis:
3
+ # https://github.com/JBZoo/Image
4
+ # https://travis-ci.org/peter279k/php-image-converter/builds
5
+
6
  language: php
7
 
8
+ #php:
9
+ # - 5.6
10
+ # - 7.1
11
+ # - 7.2
12
+ # - 7.3
13
+
14
+ # cache composer?
15
+ # https://blog.wyrihaximus.net/2015/07/composer-cache-on-travis/
16
+ # https://github.com/thephpleague/glide/blob/master/.travis.yml
17
+ cache:
18
+ #apt: true
19
+ directories:
20
+ - "$HOME/opt" # cache our imagick/gmagick install
21
+ - $HOME/vips
22
+
23
+ addons:
24
+ apt:
25
+ packages:
26
+ - libjpeg-dev
27
+ - libpng-dev
28
+ - libwebp-dev
29
 
30
+ # following for vips (taken from https://github.com/libvips/php-vips/blob/master/.travis.yml)
31
+ - gobject-introspection
32
+ - libcfitsio3-dev
33
+ - libfftw3-dev
34
+ - libgif-dev
35
+ - libgs-dev
36
+ - libgsf-1-dev
37
+ - libmatio-dev
38
+ - libopenslide-dev
39
+ - liborc-0.4-dev
40
+ - libpango1.0-dev
41
+ - libpoppler-glib-dev
42
 
43
  sudo: false
44
 
45
+ matrix:
46
+ fast_finish: true
47
+ include:
48
+ - name: "Rather new setup (PHP 7.3, Xenial)"
49
+ php: 7.3
50
+ #dist: trusty
51
+ dist: xenial
52
+ #dist: bionic
53
+ env:
54
+ - UPLOADCOVERAGE=0
55
+ - PHPSTAN=1
56
+ - TESTSRCBUILD=0
57
+ - INSTALLVIPS=1
58
+ #- VIPS_VERSION="8.6.3"
59
+ - VIPS_VERSION="8.7.4"
60
+ - INSTALLIMAGEMAGICK=0 # imagemagick.org is currently down... - so installation cannot download gzip
61
+ #- IMAGEMAGICK_VERSION="7.0.8-43"
62
+
63
+ - name: "Standard setup (PHP 7.1, Xenial) - with coverage upload"
64
+ php: 7.1
65
+ #dist: trusty
66
+ dist: xenial
67
+ #dist: bionic
68
+ env:
69
+ - UPLOADCOVERAGE=1
70
+ - PHPSTAN=1
71
+ - TESTSRCBUILD=1
72
+ - INSTALLVIPS=1
73
+ - VIPS_VERSION="8.6.3"
74
+ #- VIPS_VERSION="8.7.4"
75
+ - INSTALLIMAGICK=1
76
+ - IMAGEMAGICK_VERSION="7.0.8-43"
77
+
78
+ - name: "Old setup (PHP 5.6, Trusty)"
79
+ php: 5.6
80
+ dist: trusty
81
+ #dist: xenial
82
+ #dist: bionic
83
+ env:
84
+ - UPLOADCOVERAGE=0
85
+ - PHPSTAN=0
86
+ - TESTSRCBUILD=0
87
+ - INSTALLVIPS=0
88
+ #- VIPS_VERSION="8.7.4"
89
+ - INSTALLIMAGEMAGICK=0 # imagemagick.org is currently down... - so installation cannot download gzip
90
+ #allow_failures:
91
+
92
+ before_install:
93
+ # VIPS
94
+ - if [[ $INSTALLVIPS == 1 ]]; then bash install-vips.sh; fi
95
+
96
+ # Update PATH so that travis can find our imagemagick / gmagick
97
+ - export PATH=$HOME/opt/bin:$PATH
98
+
99
+ # ImageMagick
100
+ - if [[ $INSTALLIMAGEMAGICK == 1 ]]; then bash install-imagemagick-with-webp.sh; fi
101
+
102
+ # install imagick extension (if not already there)
103
+ # hm, not working
104
+ #- echo '' | pecl install imagick
105
+
106
+ # GMagick
107
+
108
+ - bash install-gmagick-with-webp.sh
109
+ #- export GMAGICK_PATH=$HOME/opt/bin
110
+
111
+ # Hm, extension is not working yet. When I get to install the extension I get the message:
112
+ # "Please provide a path to GraphicsMagick-config program."
113
+ # According to here: https://stackoverflow.com/questions/8626558/please-provide-the-prefix-of-graphicsmagick-installation-autodetect
114
+ # it is because the libgraphicsmagick1-dev package is not installed
115
+ # But is that needed when we are building ?
116
+ #- echo '' | pecl install gmagick-beta
117
+
118
  before_script:
119
+ #- echo $PATH
120
  - (composer self-update; true)
121
  - composer install
122
+ - if [[ $PHPSTAN == 1 ]]; then composer require --dev phpstan/phpstan:"^0.11.5"; fi
123
 
124
  script:
125
+ - if [[ $TESTSRCBUILD == 1 ]]; then composer test-src-build; fi
126
+ - composer test-src
127
+ - if [[ $PHPSTAN == 1 ]]; then vendor/bin/phpstan analyse src --level=4; fi
128
+
129
+ after_script:
130
+ - |
131
+ if [[ $UPLOADCOVERAGE == 1 ]]; then
132
+ wget https://scrutinizer-ci.com/ocular.phar
133
+ php ocular.phar code-coverage:upload --format=php-clover coverage.clover
134
+ fi
vendor/rosell-dk/webp-convert/README.md CHANGED
@@ -1,10 +1,26 @@
1
  # WebP Convert
2
 
 
 
3
  [![Build Status](https://travis-ci.org/rosell-dk/webp-convert.png?branch=master)](https://travis-ci.org/rosell-dk/webp-convert)
 
 
 
4
 
5
  *Convert JPEG & PNG to WebP with PHP*
6
 
7
- This library enables you to do webp conversion with PHP using *cwebp*, *gd*, *imagick*, *ewww* cloud converter or the open source *wpc* cloud converter. It also allows you to try a whole stack &ndash; useful if you do not have control over the environment, and simply want the library to do *everything it can* to convert the image to webp.
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  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.
10
 
@@ -16,9 +32,7 @@ composer require rosell-dk/webp-convert
16
  ```
17
 
18
  ## Converting images
19
- To convert an image, using a stack of converters, use the *WebPConvert::convert* method. It is documented in [docs/api/convert.md](https://github.com/rosell-dk/webp-convert/blob/master/docs/api/convert.md).
20
-
21
- Here is an example:
22
 
23
  ```php
24
  <?php
@@ -29,60 +43,60 @@ require 'vendor/autoload.php';
29
  use WebPConvert\WebPConvert;
30
 
31
  $source = __DIR__ . '/logo.jpg';
32
- $destination = __DIR__ . '/logo.jpg.webp';
33
-
34
- $success = WebPConvert::convert($source, $destination, [
35
- // It is not required that you set any options - all have sensible defaults.
36
- // We set some, for the sake of the example.
37
- 'quality' => 'auto',
38
- 'max-quality' => 80,
39
- 'converters' => ['cwebp', 'gd', 'imagick', 'wpc', 'ewww'], // Specify conversion methods to use, and their order
40
-
41
- 'converter-options' => [
42
- 'ewww' => [
43
- 'key' => 'your-api-key-here'
44
- ],
45
- 'wpc' => [
46
- 'api-version' => 1,
47
- 'url' => 'https://example.com/wpc.php',
48
- 'api-key' => 'my dog is white'
49
- ]
50
- ]
51
-
52
- // more options available! - see the api
53
- ]);
54
  ```
55
 
56
- To convert using a specific conversion method, simply set the *converters* option so it only has that method.
57
-
58
- The conversion methods (aka "converters") are documented here: [docs/converters.md](https://github.com/rosell-dk/webp-convert/blob/master/docs/converters.md).
59
 
 
60
 
61
  ## Serving converted images
62
- The *convertAndServe* method tries to serve a converted image. If there already is an image at the destination, it will take that, unless the original is newer or smaller. If the method cannot serve a converted image, it will serve original image, a 404, or whatever the 'fail' option is set to - and return false. It also adds a *X-WebP-Convert-Status* header, which allows you to inspect what happened.
63
 
64
- Example:
65
  ```php
66
  <?php
67
- $success = WebPConvert::convertAndServe($source, $destination, [
68
- 'fail' => 'original', // If failure, serve the original image (source).
69
- //'fail' => '404', // If failure, respond with 404.
 
 
 
 
 
70
  //'show-report' => true, // Generates a report instead of serving an image
71
 
72
- // Besides the specific options for convertAndServe(), you can also use the options for convert()
 
 
 
 
 
 
 
 
 
 
 
73
  ]);
74
  ```
75
- To see all options, look at the API: [docs/api/convert-and-serve.md](https://github.com/rosell-dk/webp-convert/blob/master/docs/api/convert-and-serve.md)
 
 
 
 
76
 
77
 
78
  ## WebP on demand
79
- The library can be used to create a *WebP On Demand* solution, which automatically serves WebP images instead of jpeg/pngs for browsers that supports WebP. To set this up, follow what's described [in this tutorial](https://github.com/rosell-dk/webp-convert/blob/master/docs/webp-on-demand/webp-on-demand.md).
80
 
81
 
82
  ## WebP Convert in the wild
83
  *WebP Convert* is used in the following projects:
84
 
85
-
86
  #### [webp-express](https://github.com/rosell-dk/webp-express)
87
  Wordpress integration
88
 
1
  # WebP Convert
2
 
3
+ [![Latest Stable Version](https://img.shields.io/packagist/v/rosell-dk/webp-convert.svg?style=flat-square)](https://packagist.org/packages/rosell-dk/webp-convert)
4
+ [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%205.6-8892BF.svg?style=flat-square)](https://php.net)
5
  [![Build Status](https://travis-ci.org/rosell-dk/webp-convert.png?branch=master)](https://travis-ci.org/rosell-dk/webp-convert)
6
+ [![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/rosell-dk/webp-convert.svg?style=flat-square)](https://scrutinizer-ci.com/g/rosell-dk/webp-convert/code-structure/master)
7
+ [![Quality Score](https://img.shields.io/scrutinizer/g/rosell-dk/webp-convert.svg?style=flat-square)](https://scrutinizer-ci.com/g/rosell-dk/webp-convert/)
8
+ [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/rosell-dk/webp-convert/blob/master/LICENSE)
9
 
10
  *Convert JPEG & PNG to WebP with PHP*
11
 
12
+ This library enables you to do webp conversion with PHP. It supports an abundance of methods for converting and automatically selects the most capable of these that is available on the system.
13
+
14
+ The library can convert using the following methods:
15
+ - *cwebp* (executing [cwebp](https://developers.google.com/speed/webp/docs/cwebp) binary using an `exec` call)
16
+ - *vips* (using [Vips PHP extension](https://github.com/libvips/php-vips-ext))
17
+ - *imagick* (using [Imagick PHP extension](https://github.com/Imagick/imagick))
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
+ - *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)
22
+ - *ewwww* (using the [ewww](https://ewww.io/plans/) cloud converter (1 USD startup and then free webp conversion))
23
+ - *gd* (using the [Gd PHP extension](https://www.php.net/manual/en/book.image.php))
24
 
25
  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.
26
 
32
  ```
33
 
34
  ## Converting images
35
+ Here is a minimal example of converting using the *WebPConvert::convert* method:
 
 
36
 
37
  ```php
38
  <?php
43
  use WebPConvert\WebPConvert;
44
 
45
  $source = __DIR__ . '/logo.jpg';
46
+ $destination = $source . '.webp';
47
+ $options = [];
48
+ WebPConvert::convert($source, $destination, $options);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  ```
50
 
51
+ The *WebPConvert::convert* method comes with a bunch of options. The following introduction is a *must-read*:
52
+ [docs/v2.0/converting/introduction-for-converting.md](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/introduction-for-converting.md).
 
53
 
54
+ If you are migrating from 1.3.9, [read this](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/migrating-to-2.0.md)
55
 
56
  ## Serving converted images
57
+ The *WebPConvert::serveConverted* method tries to serve a converted image. If there already is an image at the destination, it will take that, unless the original is newer or smaller. If the method cannot serve a converted image, it will serve original image, a 404, or whatever the 'fail' option is set to. It also adds *X-WebP-Convert-Log* headers, which provides insight into what happened.
58
 
59
+ Example (version 2.0):
60
  ```php
61
  <?php
62
+ require 'vendor/autoload.php';
63
+ use WebPConvert\WebPConvert;
64
+
65
+ $source = __DIR__ . '/logo.jpg';
66
+ $destination = $source . '.webp';
67
+
68
+ WebPConvert::serveConverted($source, $destination, [
69
+ 'fail' => 'original', // If failure, serve the original image (source). Other options include 'throw', '404' and 'report'
70
  //'show-report' => true, // Generates a report instead of serving an image
71
 
72
+ 'serve-image' => [
73
+ 'headers' => [
74
+ 'cache-control' => true,
75
+ 'vary-accept' => true,
76
+ // other headers can be toggled...
77
+ ],
78
+ 'cache-control-header' => 'max-age=2',
79
+ ],
80
+
81
+ 'convert' => [
82
+ // all convert option can be entered here (ie "quality")
83
+ ],
84
  ]);
85
  ```
86
+
87
+ The following introduction is a *must-read* (for 2.0):
88
+ [docs/v2.0/serving/introduction-for-serving.md](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/serving/introduction-for-serving.md).
89
+
90
+ The old introduction (for 1.3.9) is available here: [docs/v1.3/serving/convert-and-serve.md](https://github.com/rosell-dk/webp-convert/blob/master/docs/v1.3/serving/convert-and-serve.md)
91
 
92
 
93
  ## WebP on demand
94
+ The library can be used to create a *WebP On Demand* solution, which automatically serves WebP images instead of jpeg/pngs for browsers that supports WebP. To set this up, follow what's described [in this tutorial (not updated for 2.0 yet)](https://github.com/rosell-dk/webp-convert/blob/master/docs/v1.3/webp-on-demand/webp-on-demand.md).
95
 
96
 
97
  ## WebP Convert in the wild
98
  *WebP Convert* is used in the following projects:
99
 
 
100
  #### [webp-express](https://github.com/rosell-dk/webp-express)
101
  Wordpress integration
102
 
vendor/rosell-dk/webp-convert/build-scripts/PHPMerger.php CHANGED
@@ -15,6 +15,7 @@ class PhpMerger
15
  }
16
  public static function generate($conf)
17
  {
 
18
  self::$required = [];
19
  foreach ($conf['jobs'] as $def) {
20
  // untrail slash
@@ -53,25 +54,36 @@ class PhpMerger
53
  self::$required = array_unique(self::$required);
54
 
55
 
56
- echo "included: \n" . implode("\n", self::$required) . "\n";
57
- //exit;
58
  // generate file content
59
  $data = '';
60
  $data .= "<?php \n";
61
  foreach (self::$required as $path) {
62
- $file = file_get_contents(__DIR__ . '/' . $path);
63
- //$file = str_replace('<' . '?php', '', $file);
64
- //$file = str_replace('<' . '?php', '?' . '><?' . 'php', $file);
65
- // prepend closing php tag before php tag (only if php tag is in beginning of file)
66
- $file = preg_replace('/^\<\?php/', '?><?' . 'php', $file);
67
- $data .= $file . "\n";
68
-
 
 
 
 
69
  }
70
 
71
  // generate file
72
  //$my_file = '../generated.inc';
73
- $handle = fopen(__DIR__ . '/' . $conf['destination'], 'w') or die('Cannot open file: ' . $conf['destination']);
74
- fwrite($handle, $data);
75
- echo "saved to '" . $conf['destination'] . "'\n";
 
 
 
 
 
 
 
76
  }
77
  }
15
  }
16
  public static function generate($conf)
17
  {
18
+ $success = true;
19
  self::$required = [];
20
  foreach ($conf['jobs'] as $def) {
21
  // untrail slash
54
  self::$required = array_unique(self::$required);
55
 
56
 
57
+ //echo "included: \n" . implode("\n", self::$required) . "\n";
58
+
59
  // generate file content
60
  $data = '';
61
  $data .= "<?php \n";
62
  foreach (self::$required as $path) {
63
+ try {
64
+ $file = file_get_contents(__DIR__ . '/' . $path);
65
+ //$file = str_replace('<' . '?php', '', $file);
66
+ //$file = str_replace('<' . '?php', '?' . '><?' . 'php', $file);
67
+ // prepend closing php tag before php tag (only if php tag is in beginning of file)
68
+ $file = preg_replace('/^\<\?php/', '?><?' . 'php', $file);
69
+ $data .= $file . "\n";
70
+ } catch (\Exception $e) {
71
+ $success = false;
72
+ //throw $e;
73
+ }
74
  }
75
 
76
  // generate file
77
  //$my_file = '../generated.inc';
78
+ $handle = fopen(__DIR__ . '/' . $conf['destination'], 'w');
79
+ if ($handle !== false) {
80
+ fwrite($handle, $data);
81
+ echo "saved to '" . $conf['destination'] . "'\n";
82
+ } else {
83
+ echo 'OH NO! - failed saving!!!';
84
+ $success = false;
85
+ }
86
+ return $success;
87
+
88
  }
89
  }
vendor/rosell-dk/webp-convert/build-scripts/build-all CHANGED
@@ -1,2 +1 @@
1
  php build-webp-on-demand.php
2
- php generate-require-all.php
1
  php build-webp-on-demand.php
 
vendor/rosell-dk/webp-convert/build-scripts/build-webp-on-demand.php DELETED
@@ -1,89 +0,0 @@
1
- <?php
2
- error_reporting(E_ALL);
3
- ini_set("display_errors", 1);
4
-
5
- require_once('PHPMerger.php');
6
- //use PHPMerger;
7
-
8
- // Build "webp-on-demand-1.php" (for non-composer projects)
9
- PhpMerger::generate([
10
- 'destination' => '../build/webp-on-demand-1.inc',
11
-
12
- 'jobs' => [
13
- [
14
- 'root' => './',
15
- 'files' => [
16
- // put base classes here
17
- '../src/WebPConvert.php',
18
- '../src/Serve/ServeBase.php',
19
- '../src/Serve/ServeExistingOrHandOver.php',
20
- //'webp-on-demand-script.inc',
21
- ],
22
- 'dirs' => [
23
- // dirs will be required in specified order. There is no recursion, so you need to specify subdirs as well.
24
- //'.',
25
- ]
26
- ]
27
- ]
28
- ]);
29
-
30
- // Build "webp-on-demand-2.inc"
31
- PhpMerger::generate([
32
- 'destination' => '../build/webp-on-demand-2.inc',
33
-
34
- 'jobs' => [
35
- [
36
- 'root' => '../src/',
37
-
38
- 'files' => [
39
- // put base classes here
40
- 'Exceptions/WebPConvertBaseException.php',
41
- 'Loggers/BaseLogger.php'
42
- ],
43
- 'dirs' => [
44
- // dirs will be required in specified order. There is no recursion, so you need to specify subdirs as well.
45
- //'.',
46
- '.',
47
- 'Converters',
48
- 'Exceptions',
49
- 'Converters/Exceptions',
50
- 'Loggers',
51
- 'Serve',
52
- ],
53
- 'exclude' => [
54
- '/Serve/ServeBase.php',
55
- '/Serve/ServeExistingOrHandOver.php',
56
- '/WebPConvert.php'
57
- ]
58
- ],
59
- ]
60
- ]);
61
-
62
- // Build "webp-convert.inc", containing the entire library (for the lazy ones)
63
- PhpMerger::generate([
64
- 'destination' => '../build/webp-convert.inc',
65
-
66
- 'jobs' => [
67
- [
68
- 'root' => '../src/',
69
-
70
- 'files' => [
71
- // put base classes here
72
- 'Exceptions/WebPConvertBaseException.php',
73
- 'Loggers/BaseLogger.php'
74
- ],
75
- 'dirs' => [
76
- // dirs will be required in specified order. There is no recursion, so you need to specify subdirs as well.
77
- //'.',
78
- '.',
79
- 'Converters',
80
- 'Exceptions',
81
- 'Converters/Exceptions',
82
- 'Loggers',
83
- 'Serve',
84
- ],
85
- 'exclude' => [
86
- ]
87
- ],
88
- ]
89
- ]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/build-scripts/build.php ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ error_reporting(E_ALL);
3
+ ini_set("display_errors", 1);
4
+
5
+ require_once('PHPMerger.php');
6
+ //use PHPMerger;
7
+
8
+
9
+ /*$filesInWod1 = [
10
+ '/Serve/ServeBase.php',
11
+ '/Serve/ServeExistingOrHandOver.php',
12
+ '/WebPConvert.php'
13
+ ];*/
14
+
15
+ $filesInWod1 = [
16
+ '/Serve/ServeConvertedWebP.php',
17
+ '/Serve/ServeConvertedWebPWithErrorHandling.php',
18
+ '/Serve/ServeFile.php',
19
+ '/Serve/Header.php',
20
+ '/WebPConvert.php'
21
+ ];
22
+
23
+ // Build "webp-on-demand-1.php" (for non-composer projects)
24
+
25
+ $success = PhpMerger::generate([
26
+ 'destination' => '../src-build/webp-on-demand-1.inc',
27
+
28
+ 'jobs' => [
29
+ [
30
+ 'root' => '../src/',
31
+ 'files' => $filesInWod1,
32
+ 'dirs' => [
33
+ // dirs will be required in specified order. There is no recursion, so you need to specify subdirs as well.
34
+ //'.',
35
+ ]
36
+ ]
37
+ ]
38
+ ]);
39
+ if (!$success) {
40
+ exit(255);
41
+ }
42
+
43
+ $jobsEverything = [
44
+ [
45
+ 'root' => '../src/',
46
+
47
+ 'files' => [
48
+ // put base classes here
49
+ 'Options/Option.php',
50
+ 'Convert/Converters/AbstractConverter.php',
51
+ 'Exceptions/WebPConvertException.php',
52
+ 'Convert/Exceptions/ConversionFailedException.php',
53
+ //'Convert/BaseConverters',
54
+ //'Convert/Converters',
55
+ //'Convert/Exceptions',
56
+ //'Loggers',
57
+ //'Serve',
58
+ ],
59
+ 'dirs' => [
60
+ // dirs will be required in specified order. There is no recursion, so you need to specify subdirs as well.
61
+ // TODO: Implement recursion in PHPMerger.php,
62
+ '.',
63
+ 'Options',
64
+ 'Convert/Converters/BaseTraits',
65
+ 'Convert/Converters/ConverterTraits',
66
+ 'Convert/BaseConverters',
67
+ 'Convert/Converters',
68
+ 'Convert/Exceptions',
69
+ 'Convert/Exceptions/ConversionFailed',
70
+ 'Convert/Exceptions/ConversionFailed/ConverterNotOperational',
71
+ 'Convert/Exceptions/ConversionFailed/FileSystemProblems',
72
+ 'Convert/Exceptions/ConversionFailed/InvalidInput',
73
+ 'Convert/Helpers',
74
+ 'Convert',
75
+ 'Exceptions',
76
+ 'Helpers',
77
+ 'Loggers',
78
+ 'Serve',
79
+ 'Serve/Exceptions',
80
+ ],
81
+ 'exclude' => [
82
+ ]
83
+ ],
84
+ [
85
+ 'root' => '../vendor/rosell-dk/image-mime-type-guesser/src/',
86
+
87
+ 'files' => [
88
+ // put base classes here
89
+ 'Detectors/AbstractDetector.php',
90
+ ],
91
+ 'dirs' => [
92
+ // dirs will be required in specified order. There is no recursion, so you need to specify subdirs as well.
93
+ //'.',
94
+ '.',
95
+ 'Detectors',
96
+ ],
97
+ 'exclude' => [
98
+ ]
99
+ ],
100
+ ];
101
+
102
+ // Build "webp-convert.inc", containing the entire library (for the lazy ones)
103
+ $success = PhpMerger::generate([
104
+ 'destination' => '../src-build/webp-convert.inc',
105
+ 'jobs' => $jobsEverything
106
+ ]);
107
+ if (!$success) {
108
+ exit(255);
109
+ }
110
+
111
+ $jobsWod2 = $jobsEverything;
112
+ $jobsWod2[0]['exclude'] = $filesInWod1;
113
+
114
+ // Build "webp-on-demand-2.inc"
115
+ // It must contain everything EXCEPT those classes that were included in 'webp-on-demand-1.inc'
116
+ $success = PhpMerger::generate([
117
+ 'destination' => '../src-build/webp-on-demand-2.inc',
118
+ 'jobs' => $jobsWod2
119
+ ]);
120
+ if (!$success) {
121
+ exit(255);
122
+ }
vendor/rosell-dk/webp-convert/build-scripts/generate-require-all.php DELETED
@@ -1,83 +0,0 @@
1
- <?php
2
- error_reporting(E_ALL);
3
- ini_set("display_errors", 1);
4
-
5
- //require_once('RequireGenerator.php');
6
-
7
- //use RequireGenerator;
8
-
9
- class RequireGenerator
10
- {
11
- private static $required = [];
12
-
13
- private static function add_require_once($path)
14
- {
15
- if ($path[0] != '/') {
16
- $path = '/' . $path;
17
- }
18
-
19
- self::$required[] = $path;
20
-
21
- //echo 'require_once(__DIR__ . "' . $path . '");' . "\n";
22
- }
23
- public static function generate($def)
24
- {
25
- // load base classes, which are required for other classes
26
- foreach ($def['files'] as $file) {
27
- self::add_require_once('/' . $file);
28
- }
29
-
30
- // load dirs in defined order. No recursion.
31
- foreach ($def['dirs'] as $dir) {
32
- $dirAbs = __DIR__ . '/' . $def['dir'] . '/' . $dir;
33
-
34
- $files = glob($dirAbs . '/*.php');
35
- foreach ($files as $file) {
36
- // echo $file . "\n";
37
- // only require files that begins with uppercase (A-Z)
38
- if (preg_match('/\/[A-Z][a-zA-Z]*\.php/', $file)) {
39
- $file = str_replace(__DIR__ . '/' . $def['dir'], '', $file);
40
- $file = str_replace('./', '', $file);
41
- self::add_require_once($file);
42
- }
43
- }
44
- }
45
-
46
- // remove duplicates
47
- self::$required = array_unique(self::$required);
48
-
49
- // generate file content
50
- $data = '';
51
- $data .= "<?php\n";
52
- foreach (self::$required as $path) {
53
- $data .= 'require_once __DIR__ . "' . $path . '";' . "\n";
54
- }
55
-
56
- // generate file
57
- $my_file = __DIR__ . '/' . $def['destination'];
58
- $handle = fopen($my_file, 'w') or die('Cannot open file: '.$my_file);
59
- fwrite($handle, $data);
60
-
61
- echo "'" . $my_file . "' was generated\n";
62
- }
63
- }
64
-
65
-
66
- RequireGenerator::generate([
67
- 'dir' => '../src',
68
- 'destination' => '../src/require-all.inc',
69
- 'files' => [
70
- // put base classes here
71
- 'Exceptions/WebPConvertBaseException.php',
72
- 'Loggers/BaseLogger.php'
73
- ],
74
- 'dirs' => [
75
- // dirs will be required in specified order. There is no recursion, so you need to specify subdirs as well.
76
- '.',
77
- 'Converters',
78
- 'Exceptions',
79
- 'Converters/Exceptions',
80
- 'Loggers',
81
- 'Serve',
82
- ]
83
- ]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/build-tests-webp-convert/WebPConvertBuildTest.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPConvert\Tests;
4
+
5
+ use WebPConvert\WebPConvert;
6
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
7
+ use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\TargetNotFoundException;
8
+ use WebPConvert\Convert\Exceptions\ConversionFailed\FileSystemProblems\CreateDestinationFolderException;
9
+
10
+ use PHPUnit\Framework\TestCase;
11
+
12
+ /**
13
+ * Test the complete build (webp-convert.inc)
14
+ */
15
+ class WebPConvertBuildTest extends TestCase
16
+ {
17
+
18
+ public function testWebPConvertBuildNotCompletelyBroken()
19
+ {
20
+ require __DIR__ . '/../src-build/webp-convert.inc';
21
+
22
+ $source = __DIR__ . '/../tests/images/png-without-extension';
23
+ $this->assertTrue(file_exists($source));
24
+
25
+ ob_start();
26
+ WebPConvert::serveConverted(
27
+ $source,
28
+ $source . '.webp',
29
+ [
30
+ 'reconvert' => true,
31
+ //'converters' => ['imagick'],
32
+ 'converters' => [
33
+ 'imagick',
34
+ 'gd',
35
+ 'cwebp',
36
+ //'vips',
37
+ '\\WebPConvert\\Tests\\Convert\\TestConverters\\SuccessGuaranteedConverter'
38
+ ],
39
+ ]
40
+ );
41
+ ob_end_clean();
42
+ $this->addToAssertionCount(1);
43
+
44
+ }
45
+ }
46
+ require_once(__DIR__ . '/../tests/Serve/mock-header.inc');
vendor/rosell-dk/webp-convert/build-tests-wod/WodBuildTest.php ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPConvert\Tests;
4
+
5
+ use WebPConvert\WebPConvert;
6
+
7
+ use PHPUnit\Framework\TestCase;
8
+
9
+ /**
10
+ * Test the webp-on-demand builds (webp-on-demand-1.inc and webp-on-demand-2.inc)
11
+ */
12
+ class WodBuildTest extends TestCase
13
+ {
14
+
15
+ private static $buildDir = __DIR__ . '/../src-build';
16
+
17
+ public function autoloadingDisallowed($class) {
18
+ throw new \Exception('no autoloading expected! ' . $class);
19
+ }
20
+
21
+ public function autoloaderLoad($class) {
22
+ if (strpos($class, 'WebPConvert\\') === 0) {
23
+ require_once self::$buildDir . '/webp-on-demand-2.inc';
24
+ }
25
+ }
26
+
27
+ /**
28
+ * @runInSeparateProcess
29
+ */
30
+ public function testWodBuildWithoutAutoload()
31
+ {
32
+ // The following should NOT trigger autoloader, because ALL functionality for
33
+ // serving existing is in webp-on-demand-1.php
34
+
35
+ $wod1 = self::$buildDir . '/webp-on-demand-1.inc';
36
+ $this->assertTrue(file_exists($wod1), 'webp-on-demand-1.inc not found!');
37
+ require_once $wod1;
38
+
39
+ spl_autoload_register([self::class, 'autoloaderLoad'], true, true);
40
+
41
+ $source = __DIR__ . '/../tests/images/png-without-extension';
42
+ $this->assertTrue(file_exists($source));
43
+
44
+ ob_start();
45
+ WebPConvert::serveConverted(
46
+ $source,
47
+ $source . '.webp',
48
+ [
49
+ 'convert' => [
50
+ 'converters' => [
51
+ 'gd',
52
+ 'imagick',
53
+ '\\WebPConvert\\Tests\\Convert\\TestConverters\\SuccessGuaranteedConverter'
54
+ // vips? - causes build error:
55
+ // PHPUnit_Framework_Exception: (banana:9793): VIPS-WARNING **: near_lossless unsupported
56
+
57
+ //'imagickbinary',
58
+ // PHPUnit_Framework_Exception: convert: delegate failed `"cwebp" -quiet -q %Q "%i" -o "%o"' @ error/delegate.c/InvokeDelegate/1310.
59
+ ],
60
+ ]
61
+ //'reconvert' => true,
62
+ /* 'convert' => [
63
+ 'converters' => ['imagick'],
64
+ ]*/
65
+ //
66
+
67
+ ]
68
+ );
69
+ ob_end_clean();
70
+ spl_autoload_unregister([self::class, 'autoloaderLoad']);
71
+
72
+ }
73
+
74
+ /**
75
+ * @runInSeparateProcess
76
+ */
77
+ public function testWodBuildWithAutoload()
78
+ {
79
+ $wod1 = self::$buildDir . '/webp-on-demand-1.inc';
80
+ $wod2 = self::$buildDir . '/webp-on-demand-2.inc';
81
+
82
+ $this->assertTrue(file_exists(self::$buildDir), 'build dir not found!');
83
+ $this->assertTrue(file_exists($wod1), 'webp-on-demand-1.inc not found!');
84
+ $this->assertTrue(file_exists($wod2), 'webp-on-demand-2.inc not found!');
85
+
86
+ /*
87
+ As failed assertions exits the method, it is now safe to require the inc...
88
+ If the code is unparsable, a parse error will be thrown, like this:
89
+
90
+ There was 1 error:
91
+
92
+ 1) WebPConvert\Tests\WodBuildTest::testWodBuildNotCompletelyBroken
93
+ ParseError: syntax error, unexpected 'ServeBase' (T_STRING)
94
+
95
+ /var/www/wc/wc0/webp-convert/build/webp-on-demand-1.inc:7
96
+
97
+ ERRORS!
98
+ Tests: 1, Assertions: 3, Errors: 1.
99
+ Script phpunit handling the test event returned with error code 2
100
+ */
101
+ require_once $wod1;
102
+
103
+
104
+ $source = __DIR__ . '/../tests/images/png-without-extension';
105
+ $this->assertTrue(file_exists($source));
106
+
107
+ /*
108
+ We do not try/catch the following.
109
+ If it errors out (which it should not), or an exception is thrown (which ought not to happpen either),
110
+ - It will be discovered, and cause the tests to fail.
111
+
112
+ For example, if webp-on-demand-2 is unparsable, phpunit will fail like this:
113
+
114
+ There was 1 error:
115
+
116
+ /var/www/wc/wc0/webp-convert/build/webp-on-demand-2.inc:9
117
+ /var/www/wc/wc0/webp-convert/build/webp-on-demand-1.inc:293
118
+ /var/www/wc/wc0/webp-convert/tests/WodBuildTest.php:72
119
+
120
+ ERRORS!
121
+ Tests: 1, Assertions: 3, Errors: 1.
122
+ Script phpunit handling the test event returned with error code 2
123
+ */
124
+
125
+ /*
126
+ WebPConvert::convertAndServe(
127
+ $source,
128
+ $source . '.webp',
129
+ [
130
+ 'reconvert' => true,
131
+ //'converters' => ['imagick'],
132
+ 'aboutToServeImageCallBack' => function() {
133
+ // Return false, in order to cancel serving
134
+ return false;
135
+ },
136
+ 'aboutToPerformFailActionCallback' => function () {
137
+ return false;
138
+ }
139
+ ]
140
+ );
141
+ */
142
+
143
+ spl_autoload_register([self::class, 'autoloaderLoad'], true, true);
144
+
145
+ ob_start();
146
+ WebPConvert::serveConverted(
147
+ $source,
148
+ $source . '.webp',
149
+ [
150
+ 'reconvert' => true,
151
+ 'convert' => [
152
+ 'converters' => [
153
+ 'imagick',
154
+ 'gd',
155
+ 'cwebp',
156
+ '\\WebPConvert\\Tests\\Convert\\TestConverters\\SuccessGuaranteedConverter'
157
+ ],
158
+ ]
159
+ ]
160
+ );
161
+ ob_end_clean();
162
+ spl_autoload_unregister([self::class, 'autoloaderLoad']);
163
+
164
+ $this->addToAssertionCount(1);
165
+ }
166
+ }
167
+ require_once(__DIR__ . '/../tests/Serve/mock-header.inc');
vendor/rosell-dk/webp-convert/build/webp-convert.inc DELETED
@@ -1,3089 +0,0 @@
1
- <?php
2
- ?><?php
3
-
4
- namespace WebPConvert\Exceptions;
5
-
6
- class WebPConvertBaseException extends \Exception
7
- {
8
- }
9
-
10
- ?><?php
11
-
12
- namespace WebPConvert\Loggers;
13
-
14
- abstract class BaseLogger
15
- {
16
- /*
17
- $msg: message to log
18
- $style: null | bold | italic
19
- */
20
- abstract public function log($msg, $style = '');
21
-
22
- abstract public function ln();
23
-
24
- public function logLn($msg, $style = '')
25
- {
26
- $this->log($msg, $style);
27
- $this->ln();
28
- }
29
-
30
- public function logLnLn($msg, $style = '')
31
- {
32
- $this->logLn($msg, $style);
33
- $this->ln();
34
- }
35
- }
36
-
37
- ?><?php
38
-
39
- namespace WebPConvert;
40
-
41
- use WebPConvert\Converters\ConverterHelper;
42
- use WebPConvert\ServeExistingOrConvert;
43
- use WebPConvert\Serve\ServeExistingOrHandOver;
44
-
45
- class WebPConvert
46
- {
47
-
48
- /*
49
- @param (string) $source: Absolute path to image to be converted (no backslashes). Image must be jpeg or png
50
- @param (string) $destination: Absolute path (no backslashes)
51
- @param (object) $options: Array of named options, such as 'quality' and 'metadata'
52
- */
53
- public static function convert($source, $destination, $options = [], $logger = null)
54
- {
55
- return ConverterHelper::runConverterStack($source, $destination, $options, $logger);
56
- }
57
-
58
- public static function convertAndServe($source, $destination, $options = [])
59
- {
60
- //return ServeExistingOrConvert::serveExistingOrConvert($source, $destination, $options);
61
- return ServeExistingOrHandOver::serveConverted($source, $destination, $options);
62
- }
63
- }
64
-
65
- ?><?php
66
-
67
- namespace WebPConvert\Converters;
68
-
69
- //use WebPConvert\Converters\Cwebp;
70
-
71
- use WebPConvert\Exceptions\ConverterNotFoundException;
72
- use WebPConvert\Exceptions\CreateDestinationFileException;
73
- use WebPConvert\Exceptions\CreateDestinationFolderException;
74
- use WebPConvert\Exceptions\InvalidFileExtensionException;
75
- use WebPConvert\Exceptions\TargetNotFoundException;
76
-
77
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
78
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
79
-
80
- class ConverterHelper
81
- {
82
- public static $availableConverters = ['cwebp', 'gd', 'imagick', 'gmagick', 'imagickbinary', 'wpc', 'ewww'];
83
- public static $localConverters = ['cwebp', 'gd', 'imagick', 'gmagick', 'imagickbinary'];
84
-
85
- public static $allowedExtensions = ['jpg', 'jpeg', 'png'];
86
-
87
- public static $defaultOptions = [
88
- 'quality' => 'auto',
89
- 'max-quality' => 85,
90
- 'default-quality' => 75,
91
- 'metadata' => 'none',
92
- 'method' => 6,
93
- 'low-memory' => false,
94
- 'lossless' => false,
95
- 'converters' => ['cwebp', 'gd', 'imagick', 'gmagick'],
96
- 'converter-options' => []
97
- ];
98
-
99
- public static function mergeOptions($options, $extraOptions)
100
- {
101
- return $options;
102
- }
103
-
104
- public static function getClassNameOfConverter($converterId)
105
- {
106
- return 'WebPConvert\\Converters\\' . ucfirst($converterId);
107
- }
108
-
109
- /* Call the "convert" method on a converter, by id.
110
- - but also prepares options (merges in the $extraOptions of the converter),
111
- prepares destination folder, and runs some standard validations
112
- If it fails, it throws an exception. Otherwise it don't (there is no return value)
113
- */
114
- public static function runConverter(
115
- $converterId,
116
- $source,
117
- $destination,
118
- $options = [],
119
- $prepareDestinationFolder = true,
120
- $logger = null
121
- ) {
122
-
123
-
124
- if ($prepareDestinationFolder) {
125
- self::prepareDestinationFolderAndRunCommonValidations($source, $destination);
126
- }
127
-
128
- if (!isset($logger)) {
129
- $logger = new \WebPConvert\Loggers\VoidLogger();
130
- }
131
-
132
- $className = self::getClassNameOfConverter($converterId);
133
- if (!is_callable([$className, 'convert'])) {
134
- throw new ConverterNotFoundException();
135
- }
136
-
137
- // Prepare options.
138
- // - Remove 'converters'
139
- $defaultOptions = self::$defaultOptions;
140
- unset($defaultOptions['converters']);
141
-
142
- // - Merge defaults of the converters extra options into the standard default options.
143
- $defaultOptions = array_merge($defaultOptions, array_column($className::$extraOptions, 'default', 'name'));
144
-
145
- // - Merge $defaultOptions into provided options
146
- $options = array_merge($defaultOptions, $options);
147
-
148
- // Individual converters do not accept quality = auto. They need a number.
149
- // Change $options['quality'] to number, based on quality of source and several settings
150
-
151
- self::processQualityOption($source, $options, $logger);
152
-
153
- call_user_func(
154
- [$className, 'doConvert'],
155
- $source,
156
- $destination,
157
- $options,
158
- $logger
159
- );
160
-
161
- if (!@file_exists($destination)) {
162
- throw new ConverterFailedException('Destination file is not there');
163
- } elseif (@filesize($destination) === 0) {
164
- @unlink($destination);
165
- throw new ConverterFailedException('Destination file was completely empty');
166
- } else {
167
- $sourceSize = @filesize($source);
168
- if ($sourceSize !== false) {
169
- $msg = 'Success. ';
170
- $msg .= 'Reduced file size with ' .
171
- round((filesize($source) - filesize($destination))/filesize($source) * 100) . '% ';
172
-
173
- if ($sourceSize < 10000) {
174
- $msg .= '(went from ' . round(filesize($source)) . ' bytes to ';
175
- $msg .= round(filesize($destination)) . ' bytes)';
176
- } else {
177
- $msg .= '(went from ' . round(filesize($source)/1024) . ' kb to ';
178
- $msg .= round(filesize($destination)/1024) . ' kb)';
179
- }
180
- $logger->logLn($msg);
181
- }
182
- }
183
- }
184
-
185
- public static function runConverterWithTiming(
186
- $converterId,
187
- $source,
188
- $destination,
189
- $options = [],
190
- $prepareDestinationFolder = true,
191
- $logger = null
192
- ) {
193
- $beginTime = microtime(true);
194
- if (!isset($logger)) {
195
- $logger = new \WebPConvert\Loggers\VoidLogger();
196
- }
197
- try {
198
- self::runConverter($converterId, $source, $destination, $options, $prepareDestinationFolder, $logger);
199
- $logger->logLn(
200
- 'Successfully converted image in ' .
201
- round((microtime(true) - $beginTime) * 1000) . ' ms'
202
- );
203
- } catch (\Exception $e) {
204
- $logger->logLn('Failed in ' . round((microtime(true) - $beginTime) * 1000) . ' ms');
205
- throw $e;
206
- }
207
- }
208
-
209
- /*
210
- @param (string) $source: Absolute path to image to be converted (no backslashes). Image must be jpeg or png
211
- @param (string) $destination: Absolute path (no backslashes)
212
- @param (object) $options: Array of named options, such as 'quality' and 'metadata'
213
- */
214
- public static function runConverterStack($source, $destination, $options = [], $logger = null)
215
- {
216
- if (!isset($logger)) {
217
- $logger = new \WebPConvert\Loggers\VoidLogger();
218
- }
219
- self::prepareDestinationFolderAndRunCommonValidations($source, $destination);
220
-
221
- $options = array_merge(self::$defaultOptions, $options);
222
-
223
- self::processQualityOption($source, $options, $logger);
224
-
225
- // Force lossless option to true for PNG images
226
- if (self::getExtension($source) == 'png') {
227
- $options['lossless'] = true;
228
- }
229
-
230
- $defaultConverterOptions = $options;
231
- $defaultConverterOptions['converters'] = null;
232
-
233
- $firstFailException = null;
234
-
235
- // If we have set converter options for a converter, which is not in the converter array,
236
- // then we add it to the array
237
- if (isset($options['converter-options'])) {
238
- foreach ($options['converter-options'] as $converterName => $converterOptions) {
239
- if (!in_array($converterName, $options['converters'])) {
240
- $options['converters'][] = $converterName;
241
- }
242
- }
243
- }
244
-
245
- foreach ($options['converters'] as $converter) {
246
- if (is_array($converter)) {
247
- $converterId = $converter['converter'];
248
- $converterOptions = $converter['options'];
249
- } else {
250
- $converterId = $converter;
251
- $converterOptions = [];
252
- if (isset($options['converter-options'][$converterId])) {
253
- // Note: right now, converter-options are not meant to be used,
254
- // when you have several converters of the same type
255
- $converterOptions = $options['converter-options'][$converterId];
256
- }
257
- }
258
-
259
- $converterOptions = array_merge($defaultConverterOptions, $converterOptions);
260
-
261
- try {
262
- $logger->logLn('Trying:' . $converterId, 'italic');
263
-
264
- // If quality is different, we must recalculate
265
- if ($converterOptions['quality'] != $defaultConverterOptions['quality']) {
266
- unset($converterOptions['_calculated_quality']);
267
- self::processQualityOption($source, $converterOptions, $logger);
268
- }
269
-
270
- self::runConverterWithTiming($converterId, $source, $destination, $converterOptions, false, $logger);
271
-
272
- $logger->logLn('ok', 'bold');
273
- return true;
274
- } catch (\WebPConvert\Converters\Exceptions\ConverterNotOperationalException $e) {
275
- // $logger->logLnLn($e->description . ' : ' . $e->getMessage());
276
- $logger->logLnLn($e->getMessage());
277
-
278
- // The converter is not operational.
279
- // Well, well, we will just have to try the next, then
280
- } catch (\WebPConvert\Converters\Exceptions\ConverterFailedException $e) {
281
- $logger->logLnLn($e->getMessage());
282
-
283
- // Converter failed in an anticipated, yet somewhat surprising fashion.
284
- // The converter seemed operational - requirements was in order - but it failed anyway.
285
- // This is moderately bad.
286
- // If some other converter can handle the conversion, we will let this one go.
287
- // But if not, we shall throw the exception
288
-
289
- if (!$firstFailException) {
290
- $firstFailException = $e;
291
- }
292
- } catch (\WebPConvert\Converters\Exceptions\ConversionDeclinedException $e) {
293
- $logger->logLnLn($e->getMessage());
294
-
295
- // The converter declined.
296
- // Gd is for example throwing this, when asked to convert a PNG, but configured not to
297
- // We also possibly rethrow this, because it may have come as a surprise to the user
298
- // who perhaps only tested jpg
299
- if (!$firstFailException) {
300
- $firstFailException = $e;
301
- }
302
- }
303
- }
304
-
305
- if ($firstFailException) {
306
- // At least one converter failed or declined.
307
- $logger->logLn('Conversion failed. None of the tried converters could convert the image', 'bold');
308
- } else {
309
- // All converters threw a ConverterNotOperationalException
310
- $logger->logLn('Conversion failed. None of the tried converters are operational', 'bold');
311
- }
312
-
313
- // No converters could do the job.
314
- // If one of them failed moderately bad, rethrow that exception.
315
- if ($firstFailException) {
316
- throw $firstFailException;
317
- }
318
-
319
- return false;
320
- }
321
-
322
- /* Try to detect quality of jpeg.
323
- If not possible, nothing is returned (null). Otherwise quality is returned (int)
324
- */
325
- public static function detectQualityOfJpg($filename)
326
- {
327
- // Try Imagick extension
328
- if (extension_loaded('imagick') && class_exists('\\Imagick')) {
329
- // Do not risk uncaught ImagickException when trying to detect quality of jpeg
330
- // (it can happen in the rare case, there is no jpeg delegate)
331
- try {
332
- $img = new \Imagick($filename);
333
-
334
- // The required function is available as from PECL imagick v2.2.2
335
- // (you can see your version like this: phpversion("imagick"))
336
- if (method_exists($img, 'getImageCompressionQuality')) {
337
- return $img->getImageCompressionQuality();
338
- }
339
- } catch (\Exception $e) {
340
- // do nothing.
341
- }
342
- }
343
-
344
- // Gmagick extension doesn't support dectecting image quality (yet):
345
- // https://bugs.php.net/bug.php?id=63939
346
- // It is not supported in 2.0.5RC1. But perhaps there is a new version out now?
347
- // Check here: https://pecl.php.net/package-changelog.php?package=gmagick
348
-
349
- if (function_exists('shell_exec')) {
350
- // Try Imagick
351
- $quality = shell_exec("identify -format '%Q' " . escapeshellarg($filename));
352
- if ($quality) {
353
- return intval($quality);
354
- }
355
-
356
- // Try GraphicsMagick
357
- $quality = shell_exec("gm identify -format '%Q' " . escapeshellarg($filename));
358
- if ($quality) {
359
- return intval($quality);
360
- }
361
- }
362
- }
363
-
364
- public static function processQualityOption($source, &$options, $logger)
365
- {
366
- if (isset($options['_calculated_quality'])) {
367
- return;
368
- }
369
-
370
- if ($options['quality'] == 'auto') {
371
- $q = self::detectQualityOfJpg($source);
372
- //$logger->log('Quality set to auto... Quality of source: ');
373
- if (!$q) {
374
- $q = $options['default-quality'];
375
- $logger->logLn(
376
- 'Quality of source could not be established (Imagick or GraphicsMagick is required)' .
377
- ' - Using default instead (' . $options['default-quality'] . ').'
378
- );
379
-
380
- // this allows the wpc converter to know
381
- $options['_quality_could_not_be_detected'] = true;
382
- } else {
383
- if ($q > $options['max-quality']) {
384
- $logger->log(
385
- 'Quality of source is ' . $q . '. ' .
386
- 'This is higher than max-quality, so using that instead (' . $options['max-quality'] . ')'
387
- );
388
- } else {
389
- $logger->log('Quality set to same as source: ' . $q);
390
- }
391
- }
392
- $logger->ln();
393
- $q = min($q, $options['max-quality']);
394
-
395
- $options['_calculated_quality'] = $q;
396
- //$logger->logLn('Using quality: ' . $options['quality']);
397
- } else {
398
- $logger->logLn(
399
- 'Quality: ' . $options['quality'] . '. ' .
400
- 'Consider setting quality to "auto" instead. It is generally a better idea'
401
- );
402
- $options['_calculated_quality'] = $options['quality'];
403
- }
404
- $logger->ln();
405
- }
406
-
407
-
408
- public static function getExtension($filePath)
409
- {
410
- $fileExtension = pathinfo($filePath, PATHINFO_EXTENSION);
411
- return strtolower($fileExtension);
412
- }
413
-
414
- // Throws an exception if the provided file doesn't exist
415
- public static function isValidTarget($filePath)
416
- {
417
- if (!@file_exists($filePath)) {
418
- throw new TargetNotFoundException('File or directory not found: ' . $filePath);
419
- }
420
-
421
- return true;
422
- }
423
-
424
- // Throws an exception if the provided file's extension is invalid
425
- public static function isAllowedExtension($filePath)
426
- {
427
- $fileExtension = pathinfo($filePath, PATHINFO_EXTENSION);
428
- if (!in_array(strtolower($fileExtension), self::$allowedExtensions)) {
429
- throw new InvalidFileExtensionException('Unsupported file extension: ' . $fileExtension);
430
- }
431
-
432
- return true;
433
- }
434
-
435
- // Creates folder in provided path & sets correct permissions
436
- // also deletes the file at filePath (if it already exists)
437
- public static function createWritableFolder($filePath)
438
- {
439
- $folder = dirname($filePath);
440
- if (!@file_exists($folder)) {
441
- // TODO: what if this is outside open basedir?
442
- // see http://php.net/manual/en/ini.core.php#ini.open-basedir
443
-
444
- // First, we have to figure out which permissions to set.
445
- // We want same permissions as parent folder
446
- // But which parent? - the parent to the first missing folder
447
-
448
- $parentFolders = explode('/', $folder);
449
- $poppedFolders = [];
450
-
451
- while (!(@file_exists(implode('/', $parentFolders))) && count($parentFolders) > 0) {
452
- array_unshift($poppedFolders, array_pop($parentFolders));
453
- }
454
-
455
- // Retrieving permissions of closest existing folder
456
- $closestExistingFolder = implode('/', $parentFolders);
457
- $permissions = @fileperms($closestExistingFolder) & 000777;
458
- $stat = @stat($closestExistingFolder);
459
-
460
- // Trying to create the given folder (recursively)
461
- if (!@mkdir($folder, $permissions, true)) {
462
- throw new CreateDestinationFolderException('Failed creating folder: ' . $folder);
463
- }
464
-
465
- // `mkdir` doesn't always respect permissions, so we have to `chmod` each created subfolder
466
- foreach ($poppedFolders as $subfolder) {
467
- $closestExistingFolder .= '/' . $subfolder;
468
- // Setting directory permissions
469
- if ($permissions !== false) {
470
- @chmod($folder, $permissions);
471
- }
472
- if ($stat !== false) {
473
- if (isset($stat['uid'])) {
474
- @chown($folder, $stat['uid']);
475
- }
476
- if (isset($stat['gid'])) {
477
- @chgrp($folder, $stat['gid']);
478
- }
479
- }
480
- }
481
- }
482
-
483
- if (@file_exists($filePath)) {
484
- // A file already exists in this folder...
485
- // We delete it, to make way for a new webp
486
- if (!@unlink($filePath)) {
487
- throw new CreateDestinationFileException(
488
- 'Existing file cannot be removed: ' . basename($filePath)
489
- );
490
- }
491
- }
492
-
493
- return true;
494
- }
495
-
496
- public static function prepareDestinationFolderAndRunCommonValidations($source, $destination)
497
- {
498
- self::isValidTarget($source);
499
- self::isAllowedExtension($source);
500
- self::createWritableFolder($destination);
501
- }
502
-
503
- public static function initCurlForConverter()
504
- {
505
- if (!extension_loaded('curl')) {
506
- throw new ConverterNotOperationalException('Required cURL extension is not available.');
507
- }
508
-
509
- if (!function_exists('curl_init')) {
510
- throw new ConverterNotOperationalException('Required url_init() function is not available.');
511
- }
512
-
513
- if (!function_exists('curl_file_create')) {
514
- throw new ConverterNotOperationalException(
515
- 'Required curl_file_create() function is not available (requires PHP > 5.5).'
516
- );
517
- }
518
-
519
- $ch = curl_init();
520
- if (!$ch) {
521
- throw new ConverterNotOperationalException('Could not initialise cURL.');
522
- }
523
- return $ch;
524
- }
525
- }
526
-
527
- ?><?php
528
-
529
- namespace WebPConvert\Converters;
530
-
531
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
532
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
533
-
534
- class Cwebp
535
- {
536
- public static $extraOptions = [
537
- [
538
- 'name' => 'use-nice',
539
- 'type' => 'boolean',
540
- 'sensitive' => false,
541
- 'default' => false,
542
- 'required' => false
543
- ],
544
- // low-memory is defined for all, in ConverterHelper
545
- [
546
- 'name' => 'try-common-system-paths',
547
- 'type' => 'boolean',
548
- 'sensitive' => false,
549
- 'default' => true,
550
- 'required' => false
551
- ],
552
- [
553
- 'name' => 'try-supplied-binary-for-os',
554
- 'type' => 'boolean',
555
- 'sensitive' => false,
556
- 'default' => true,
557
- 'required' => false
558
- ],
559
- [
560
- 'name' => 'size-in-percentage',
561
- 'type' => 'number',
562
- 'sensitive' => false,
563
- 'default' => null,
564
- 'required' => false
565
- ],
566
- [
567
- 'name' => 'command-line-options',
568
- 'type' => 'string',
569
- 'sensitive' => false,
570
- 'default' => '',
571
- 'required' => false
572
- ],
573
- [
574
- 'name' => 'rel-path-to-precompiled-binaries',
575
- 'type' => 'string',
576
- 'sensitive' => false,
577
- 'default' => './Binaries',
578
- 'required' => false
579
- ],
580
- ];
581
-
582
- public static function convert($source, $destination, $options = [])
583
- {
584
- ConverterHelper::runConverter('cwebp', $source, $destination, $options, true);
585
- }
586
-
587
- // System paths to look for cwebp binary
588
- private static $cwebpDefaultPaths = [
589
- '/usr/bin/cwebp',
590
- '/usr/local/bin/cwebp',
591
- '/usr/gnu/bin/cwebp',
592
- '/usr/syno/bin/cwebp'
593
- ];
594
-
595
- // OS-specific binaries included in this library, along with hashes
596
- private static $suppliedBinariesInfo = [
597
- 'WinNT' => [ 'cwebp.exe', '49e9cb98db30bfa27936933e6fd94d407e0386802cb192800d9fd824f6476873'],
598
- 'Darwin' => [ 'cwebp-mac12', 'a06a3ee436e375c89dbc1b0b2e8bd7729a55139ae072ed3f7bd2e07de0ebb379'],
599
- 'SunOS' => [ 'cwebp-sol', '1febaffbb18e52dc2c524cda9eefd00c6db95bc388732868999c0f48deb73b4f'],
600
- 'FreeBSD' => [ 'cwebp-fbsd', 'e5cbea11c97fadffe221fdf57c093c19af2737e4bbd2cb3cd5e908de64286573'],
601
- 'Linux' => [ 'cwebp-linux', '916623e5e9183237c851374d969aebdb96e0edc0692ab7937b95ea67dc3b2568']
602
- ];
603
-
604
- private static function escapeFilename($string)
605
- {
606
-
607
- // filter_var() is should normally be available, but it is not always
608
- // - https://stackoverflow.com/questions/11735538/call-to-undefined-function-filter-var
609
- if (function_exists('filter_var')) {
610
- // Sanitize quotes
611
- $string = filter_var($string, FILTER_SANITIZE_MAGIC_QUOTES);
612
-
613
- // Stripping control characters
614
- // see https://stackoverflow.com/questions/12769462/filter-flag-strip-low-vs-filter-flag-strip-high
615
- $string = filter_var($string, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
616
- }
617
-
618
- // Escaping whitespace. Must be done *after* filter_var!
619
- $string = preg_replace('/\s/', '\\ ', $string);
620
-
621
- return $string;
622
- }
623
-
624
- // Checks if 'Nice' is available
625
- private static function hasNiceSupport()
626
- {
627
- exec("nice 2>&1", $niceOutput);
628
-
629
- if (is_array($niceOutput) && isset($niceOutput[0])) {
630
- if (preg_match('/usage/', $niceOutput[0]) || (preg_match('/^\d+$/', $niceOutput[0]))) {
631
- /*
632
- * Nice is available - default niceness (+10)
633
- * https://www.lifewire.com/uses-of-commands-nice-renice-2201087
634
- * https://www.computerhope.com/unix/unice.htm
635
- */
636
-
637
- return true;
638
- }
639
-
640
- return false;
641
- }
642
- }
643
-
644
- private static function executeBinary($binary, $commandOptions, $useNice, $logger)
645
- {
646
- $command = ($useNice ? 'nice ' : '') . $binary . ' ' . $commandOptions;
647
-
648
- //$logger->logLn('command options:' . $commandOptions);
649
- //$logger->logLn('Trying to execute binary:' . $binary);
650
- exec($command, $output, $returnCode);
651
- //$logger->logLn(self::msgForExitCode($returnCode));
652
- return intval($returnCode);
653
- }
654
-
655
- // Although this method is public, do not call directly.
656
- public static function doConvert($source, $destination, $options, $logger)
657
- {
658
- $errorMsg = '';
659
- // Force lossless option to true for PNG images
660
- if (ConverterHelper::getExtension($source) == 'png') {
661
- $options['lossless'] = true;
662
- }
663
-
664
- if (!function_exists('exec')) {
665
- throw new ConverterNotOperationalException('exec() is not enabled.');
666
- }
667
-
668
- /*
669
- * Prepare cwebp options
670
- */
671
-
672
- $commandOptionsArray = [];
673
-
674
- // Metadata (all, exif, icc, xmp or none (default))
675
- // Comma-separated list of existing metadata to copy from input to output
676
- $commandOptionsArray[] = '-metadata ' . $options['metadata'];
677
-
678
- // Size
679
- if (!is_null($options['size-in-percentage'])) {
680
- $sizeSource = @filesize($source);
681
- if ($sizeSource !== false) {
682
- $targetSize = floor($sizeSource * $options['size-in-percentage'] / 100);
683
- }
684
- }
685
- if (isset($targetSize)) {
686
- $commandOptionsArray[] = '-size ' . $targetSize;
687
- } else {
688
- // Image quality
689
- $commandOptionsArray[] = '-q ' . $options['_calculated_quality'];
690
- }
691
-
692
-
693
- // Losless PNG conversion
694
- $commandOptionsArray[] = ($options['lossless'] ? '-lossless' : '');
695
-
696
- // Built-in method option
697
- $commandOptionsArray[] = '-m ' . strval($options['method']);
698
-
699
- // Built-in low memory option
700
- if ($options['low-memory']) {
701
- $commandOptionsArray[] = '-low_memory';
702
- }
703
-
704
- // command-line-options
705
- if ($options['command-line-options']) {
706
- $arr = explode(' -', ' ' . $options['command-line-options']);
707
- foreach ($arr as $cmdOption) {
708
- $pos = strpos($cmdOption, ' ');
709
- $cName = '';
710
- $cValue = '';
711
- if (!$pos) {
712
- $cName = $cmdOption;
713
- if ($cName == '') {
714
- continue;
715
- }
716
- $commandOptionsArray[] = '-' . $cName;
717
- } else {
718
- $cName = substr($cmdOption, 0, $pos);
719
- $cValues = substr($cmdOption, $pos + 1);
720
- $cValuesArr = explode(' ', $cValues);
721
- foreach ($cValuesArr as &$cArg) {
722
- $cArg = escapeshellarg($cArg);
723
- }
724
- $cValues = implode(' ', $cValuesArr);
725
- $commandOptionsArray[] = '-' . $cName . ' ' . $cValues;
726
- }
727
- }
728
- }
729
-
730
- // Source file
731
- //$commandOptionsArray[] = self::escapeFilename($source);
732
- $commandOptionsArray[] = escapeshellarg($source);
733
-
734
- // Output
735
- $commandOptionsArray[] = '-o ' . escapeshellarg($destination);
736
-
737
- // Redirect stderr to same place as stdout
738
- // https://www.brianstorti.com/understanding-shell-script-idiom-redirect/
739
- $commandOptionsArray[] = '2>&1';
740
-
741
-
742
- $useNice = (($options['use-nice']) && self::hasNiceSupport()) ? true : false;
743
-
744
- $commandOptions = implode(' ', $commandOptionsArray);
745
-
746
- $logger->logLn('cwebp options:' . $commandOptions);
747
-
748
- // Init with common system paths
749
- $cwebpPathsToTest = self::$cwebpDefaultPaths;
750
-
751
- // Remove paths that doesn't exist
752
- /*
753
- $cwebpPathsToTest = array_filter($cwebpPathsToTest, function ($binary) {
754
- //return file_exists($binary);
755
- return @is_readable($binary);
756
- });
757
- */
758
-
759
- // Try all common paths that exists
760
- $success = false;
761
- $failures = [];
762
- $failureCodes = [];
763
-
764
- if (!$options['try-supplied-binary-for-os'] && !$options['try-common-system-paths']) {
765
- $errorMsg .= 'Configured to neither look for cweb binaries in common system locations, ' .
766
- 'nor to use one of the supplied precompiled binaries. But these are the only ways ' .
767
- 'this converter can convert images. No conversion can be made!';
768
- }
769
-
770
- if ($options['try-common-system-paths']) {
771
- foreach ($cwebpPathsToTest as $index => $binary) {
772
- $returnCode = self::executeBinary($binary, $commandOptions, $useNice, $logger);
773
- if ($returnCode == 0) {
774
- $logger->logLn('Successfully executed binary: ' . $binary);
775
- $success = true;
776
- break;
777
- } else {
778
- $failures[] = [$binary, $returnCode];
779
- if (!in_array($returnCode, $failureCodes)) {
780
- $failureCodes[] = $returnCode;
781
- }
782
- }
783
- }
784
- $majorFailCode = 0;
785
- if (!$success) {
786
- if (count($failureCodes) == 1) {
787
- $majorFailCode = $failureCodes[0];
788
- switch ($majorFailCode) {
789
- case 126:
790
- $errorMsg = 'Permission denied. The user that the command was run with (' .
791
- shell_exec('whoami') . ') does not have permission to execute any of the ' .
792
- 'cweb binaries found in common system locations. ';
793
- break;
794
- case 127:
795
- $errorMsg .= 'Found no cwebp binaries in any common system locations. ';
796
- break;
797
- default:
798
- $errorMsg .= 'Tried executing cwebp binaries in common system locations. ' .
799
- 'All failed (exit code: ' . $majorFailCode . '). ';
800
- }
801
- } else {
802
- /**
803
- * $failureCodesBesides127 is used to check first position ($failureCodesBesides127[0])
804
- * however position can vary as index can be 1 or something else. array_values() would
805
- * always start from 0.
806
- */
807
- $failureCodesBesides127 = array_values(array_diff($failureCodes, [127]));
808
-
809
- if (count($failureCodesBesides127) == 1) {
810
- $majorFailCode = $failureCodesBesides127[0];
811
- switch ($returnCode) {
812
- case 126:
813
- $errorMsg = 'Permission denied. The user that the command was run with (' .
814
- shell_exec('whoami') . ') does not have permission to execute any of the cweb ' .
815
- 'binaries found in common system locations. ';
816
- break;
817
- default:
818
- $errorMsg .= 'Tried executing cwebp binaries in common system locations. ' .
819
- 'All failed (exit code: ' . $majorFailCode . '). ';
820
- }
821
- } else {
822
- $errorMsg .= 'None of the cwebp binaries in the common system locations could be executed ' .
823
- '(mixed results - got the following exit codes: ' . implode(',', $failureCodes) . '). ';
824
- }
825
- }
826
- }
827
- }
828
-
829
- if (!$success && $options['try-supplied-binary-for-os']) {
830
- // Try supplied binary (if available for OS, and hash is correct)
831
- if (isset(self::$suppliedBinariesInfo[PHP_OS])) {
832
- $info = self::$suppliedBinariesInfo[PHP_OS];
833
-
834
- $file = $info[0];
835
- $hash = $info[1];
836
-
837
- $binaryFile = __DIR__ . '/' . $options['rel-path-to-precompiled-binaries'] . '/' . $file;
838
-
839
- // The file should exist, but may have been removed manually.
840
- if (@file_exists($binaryFile)) {
841
- // File exists, now generate its hash
842
-
843
- // hash_file() is normally available, but it is not always
844
- // - https://stackoverflow.com/questions/17382712/php-5-3-20-undefined-function-hash
845
- // If available, validate that hash is correct.
846
- $proceedAfterHashCheck = true;
847
- if (function_exists('hash_file')) {
848
- $binaryHash = hash_file('sha256', $binaryFile);
849
-
850
- if ($binaryHash != $hash) {
851
- $errorMsg .= 'Binary checksum of supplied binary is invalid! ' .
852
- 'Did you transfer with FTP, but not in binary mode? ' .
853
- 'File:' . $binaryFile . '. ' .
854
- 'Expected checksum: ' . $hash . '. ' .
855
- 'Actual checksum:' . $binaryHash . '.';
856
- $proceedAfterHashCheck = false;
857
- }
858
- }
859
- if ($proceedAfterHashCheck) {
860
- $returnCode = self::executeBinary($binaryFile, $commandOptions, $useNice, $logger);
861
- if ($returnCode == 0) {
862
- $success = true;
863
- } else {
864
- $errorMsg .= 'Tried executing supplied binary for ' . PHP_OS . ', ' .
865
- ($options['try-common-system-paths'] ? 'but that failed too' : 'but failed');
866
- if ($options['try-common-system-paths'] && ($majorFailCode > 0)) {
867
- $errorMsg .= ' (same error)';
868
- } else {
869
- if ($returnCode > 128) {
870
- $errorMsg .= '. The binary did not work (exit code: ' . $returnCode . '). ' .
871
- 'Check out https://github.com/rosell-dk/webp-convert/issues/92';
872
- } else {
873
- switch ($returnCode) {
874
- case 0:
875
- $success = true;
876
- ;
877
- break;
878
- case 126:
879
- $errorMsg .= ': Permission denied. The user that the command was run' .
880
- ' with (' . shell_exec('whoami') . ') does not have permission to ' .
881
- 'execute that binary.';
882
- break;
883
- case 127:
884
- $errorMsg .= '. The binary was not found! ' .
885
- 'It ought to be here: ' . $binaryFile;
886
- break;
887
- default:
888
- $errorMsg .= ' (exit code:' . $returnCode . ').';
889
- }
890
- }
891
- }
892
- }
893
- }
894
- } else {
895
- $errorMsg .= 'Supplied binary not found! It ought to be here:' . $binaryFile;
896
- }
897
- } else {
898
- $errorMsg .= 'No supplied binaries found for OS:' . PHP_OS;
899
- }
900
- }
901
-
902
-
903
-
904
- // cwebp sets file permissions to 664 but instead ..
905
- // .. $destination's parent folder's permissions should be used (except executable bits)
906
- if ($success) {
907
- $destinationParent = dirname($destination);
908
- $fileStatistics = @stat($destinationParent);
909
- if ($fileStatistics !== false) {
910
- // Apply same permissions as parent folder but strip off the executable bits
911
- $permissions = $fileStatistics['mode'] & 0000666;
912
- @chmod($destination, $permissions);
913
- }
914
- }
915
-
916
- if (!$success) {
917
- throw new ConverterNotOperationalException($errorMsg);
918
- }
919
- }
920
- }
921
-
922
- ?><?php
923
-
924
- namespace WebPConvert\Converters;
925
-
926
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
927
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
928
-
929
- class Ewww
930
- {
931
- public static $extraOptions = [
932
- [
933
- 'name' => 'key',
934
- 'type' => 'string',
935
- 'sensitive' => true,
936
- 'default' => '',
937
- 'required' => true
938
- ],
939
- ];
940
-
941
- public static function convert($source, $destination, $options = [])
942
- {
943
- ConverterHelper::runConverter('ewww', $source, $destination, $options, true);
944
- }
945
-
946
- // Took this parser from Drupal
947
- private static function parseSize($size)
948
- {
949
-
950
- $unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
951
- $size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
952
- if ($unit) {
953
- // Find the position of the unit in the ordered string which is the power
954
- // of magnitude to multiply a kilobyte by.
955
- return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
956
- } else {
957
- return round($size);
958
- }
959
- }
960
-
961
- // Although this method is public, do not call directly.
962
- public static function doConvert($source, $destination, $options, $logger)
963
- {
964
- if ($options['key'] == '') {
965
- throw new ConverterNotOperationalException('Missing API key.');
966
- }
967
- if (strlen($options['key']) < 20) {
968
- throw new ConverterNotOperationalException(
969
- 'Key is invalid. Keys are supposed to be 32 characters long - your key is much shorter'
970
- );
971
- }
972
-
973
- $keyStatus = self::getKeyStatus($options['key']);
974
- switch ($keyStatus) {
975
- case 'great':
976
- break;
977
- case 'exceeded':
978
- throw new ConverterNotOperationalException('quota has exceeded');
979
- break;
980
- case 'invalid':
981
- throw new ConverterNotOperationalException('key is invalid');
982
- break;
983
- }
984
-
985
- $fileSize = @filesize($source);
986
- if ($fileSize !== false) {
987
- $uploadMaxSize = self::parseSize(ini_get('upload_max_filesize'));
988
- if (($uploadMaxSize !== false) && ($uploadMaxSize < $fileSize)) {
989
- throw new ConverterFailedException(
990
- 'File is larger than your max upload (set in your php.ini). File size:' .
991
- round($fileSize/1024) . ' kb. ' .
992
- 'upload_max_filesize in php.ini: ' . ini_get('upload_max_filesize') .
993
- ' (parsed as ' . round($uploadMaxSize/1024) . ' kb)'
994
- );
995
- }
996
-
997
- $postMaxSize = self::parseSize(ini_get('post_max_size'));
998
- if (($postMaxSize !== false) && ($postMaxSize < $fileSize)) {
999
- throw new ConverterFailedException(
1000
- 'File is larger than your post_max_size limit (set in your php.ini). File size:' .
1001
- round($fileSize/1024) . ' kb. ' .
1002
- 'post_max_size in php.ini: ' . ini_get('post_max_size') .
1003
- ' (parsed as ' . round($postMaxSize/1024) . ' kb)'
1004
- );
1005
- }
1006
-
1007
- // ini_get('memory_limit')
1008
- }
1009
-
1010
-
1011
- $ch = ConverterHelper::initCurlForConverter();
1012
-
1013
- $curlOptions = [
1014
- 'api_key' => $options['key'],
1015
- 'webp' => '1',
1016
- 'file' => curl_file_create($source),
1017
- 'domain' => $_SERVER['HTTP_HOST'],
1018
- 'quality' => $options['_calculated_quality'],
1019
- 'metadata' => ($options['metadata'] == 'none' ? '0' : '1')
1020
- ];
1021
-
1022
- curl_setopt_array(
1023
- $ch,
1024
- [
1025
- CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
1026
- CURLOPT_HTTPHEADER => [
1027
- 'User-Agent: WebPConvert',
1028
- 'Accept: image/*'
1029
- ],
1030
- CURLOPT_POSTFIELDS => $curlOptions,
1031
- CURLOPT_BINARYTRANSFER => true,
1032
- CURLOPT_RETURNTRANSFER => true,
1033
- CURLOPT_HEADER => false,
1034
- CURLOPT_SSL_VERIFYPEER => false
1035
- ]
1036
- );
1037
-
1038
- $response = curl_exec($ch);
1039
-
1040
- if (curl_errno($ch)) {
1041
- throw new ConverterNotOperationalException(curl_error($ch));
1042
- }
1043
-
1044
- // The API does not always return images.
1045
- // For example, it may return a message such as '{"error":"invalid","t":"exceeded"}
1046
- // Messages has a http content type of ie 'text/html; charset=UTF-8
1047
- // Images has application/octet-stream.
1048
- // So verify that we got an image back.
1049
- if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
1050
- //echo curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
1051
- curl_close($ch);
1052
-
1053
- /* May return this: {"error":"invalid","t":"exceeded"} */
1054
- $responseObj = json_decode($response);
1055
- if (isset($responseObj->error)) {
1056
- //echo 'error:' . $responseObj->error . '<br>';
1057
- //echo $response;
1058
- //self::blacklistKey($key);
1059
- //throw new ConverterNotOperationalException('The key is invalid. Blacklisted it!');
1060
- throw new ConverterNotOperationalException('The key is invalid');
1061
- }
1062
-
1063
- throw new ConverterNotOperationalException(
1064
- 'ewww api did not return an image. It could be that the key is invalid. Response: '
1065
- . $response
1066
- );
1067
- }
1068
-
1069
- // Not sure this can happen. So just in case
1070
- if ($response == '') {
1071
- throw new ConverterNotOperationalException('ewww api did not return anything');
1072
- }
1073
-
1074
- $success = file_put_contents($destination, $response);
1075
-
1076
- if (!$success) {
1077
- throw new ConverterFailedException('Error saving file');
1078
- }
1079
- }
1080
-
1081
- /**
1082
- * Keep subscription alive by optimizing a jpeg
1083
- * (ewww closes accounts after 6 months of inactivity - and webp conversions seems not to be counted? )
1084
- */
1085
- public static function keepSubscriptionAlive($source, $key)
1086
- {
1087
- try {
1088
- $ch = curl_init();
1089
- } catch (\Exception $e) {
1090
- return 'curl is not installed';
1091
- }
1092
- curl_setopt_array(
1093
- $ch,
1094
- [
1095
- CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
1096
- CURLOPT_HTTPHEADER => [
1097
- 'User-Agent: WebPConvert',
1098
- 'Accept: image/*'
1099
- ],
1100
- CURLOPT_POSTFIELDS => [
1101
- 'api_key' => $key,
1102
- 'webp' => '0',
1103
- 'file' => curl_file_create($source),
1104
- 'domain' => $_SERVER['HTTP_HOST'],
1105
- 'quality' => 60,
1106
- 'metadata' => 0
1107
- ],
1108
- CURLOPT_BINARYTRANSFER => true,
1109
- CURLOPT_RETURNTRANSFER => true,
1110
- CURLOPT_HEADER => false,
1111
- CURLOPT_SSL_VERIFYPEER => false
1112
- ]
1113
- );
1114
-
1115
- $response = curl_exec($ch);
1116
- if (curl_errno($ch)) {
1117
- return 'curl error' . curl_error($ch);
1118
- }
1119
- if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
1120
- curl_close($ch);
1121
-
1122
- /* May return this: {"error":"invalid","t":"exceeded"} */
1123
- $responseObj = json_decode($response);
1124
- if (isset($responseObj->error)) {
1125
- return 'The key is invalid';
1126
- }
1127
-
1128
- return 'ewww api did not return an image. It could be that the key is invalid. Response: ' . $response;
1129
- }
1130
-
1131
- // Not sure this can happen. So just in case
1132
- if ($response == '') {
1133
- return 'ewww api did not return anything';
1134
- }
1135
-
1136
- return true;
1137
- }
1138
-
1139
- /*
1140
- public static function blacklistKey($key)
1141
- {
1142
- }
1143
-
1144
- public static function isKeyBlacklisted($key)
1145
- {
1146
- }*/
1147
-
1148
- /**
1149
- * Return "great", "exceeded" or "invalid"
1150
- */
1151
- public static function getKeyStatus($key)
1152
- {
1153
- $ch = ConverterHelper::initCurlForConverter();
1154
-
1155
- curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/verify/");
1156
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1157
- curl_setopt(
1158
- $ch,
1159
- CURLOPT_POSTFIELDS,
1160
- [
1161
- 'api_key' => $key
1162
- ]
1163
- );
1164
-
1165
- // The 403 forbidden is avoided with this line.
1166
- curl_setopt(
1167
- $ch,
1168
- CURLOPT_USERAGENT,
1169
- 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)'
1170
- );
1171
-
1172
- $response = curl_exec($ch);
1173
- // echo $response;
1174
- if (curl_errno($ch)) {
1175
- throw new \Exception(curl_error($ch));
1176
- }
1177
- curl_close($ch);
1178
-
1179
- // Possible responses:
1180
- // “great” = verification successful
1181
- // “exceeded” = indicates a valid key with no remaining image credits.
1182
- // an empty response indicates that the key is not valid
1183
-
1184
- if ($response == '') {
1185
- return 'invalid';
1186
- }
1187
- $responseObj = json_decode($response);
1188
- if (isset($responseObj->error)) {
1189
- if ($responseObj->error == 'invalid') {
1190
- return 'invalid';
1191
- } else {
1192
- throw new \Exception('Ewww returned unexpected error: ' . $response);
1193
- }
1194
- }
1195
- if (!isset($responseObj->status)) {
1196
- throw new \Exception('Ewww returned unexpected response to verify request: ' . $response);
1197
- }
1198
- switch ($responseObj->status) {
1199
- case 'great':
1200
- case 'exceeded':
1201
- return $responseObj->status;
1202
- }
1203
- throw new \Exception('Ewww returned unexpected status to verify request: "' . $responseObj->status . '"');
1204
- }
1205
-
1206
- public static function isWorkingKey($key)
1207
- {
1208
- return (self::getKeyStatus($key) == 'great');
1209
- }
1210
-
1211
- public static function isValidKey($key)
1212
- {
1213
- return (self::getKeyStatus($key) != 'invalid');
1214
- }
1215
-
1216
- public static function getQuota($key)
1217
- {
1218
- $ch = ConverterHelper::initCurlForConverter();
1219
-
1220
- curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/quota/");
1221
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1222
- curl_setopt(
1223
- $ch,
1224
- CURLOPT_POSTFIELDS,
1225
- [
1226
- 'api_key' => $key
1227
- ]
1228
- );
1229
- curl_setopt(
1230
- $ch,
1231
- CURLOPT_USERAGENT,
1232
- 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)'
1233
- );
1234
-
1235
- $response = curl_exec($ch);
1236
- return $response; // ie -830 23. Seems to return empty for invalid keys
1237
- // or empty
1238
- //echo $response;
1239
- }
1240
- }
1241
-
1242
- ?><?php
1243
-
1244
- namespace WebPConvert\Converters;
1245
-
1246
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
1247
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
1248
- use WebPConvert\Converters\Exceptions\ConversionDeclinedException;
1249
-
1250
- use WebPConvert\Converters\ConverterHelper;
1251
-
1252
- class Gd
1253
- {
1254
- public static $extraOptions = [
1255
- [
1256
- 'name' => 'skip-pngs',
1257
- 'type' => 'boolean',
1258
- 'sensitive' => false,
1259
- 'default' => true,
1260
- 'required' => false
1261
- ],
1262
- ];
1263
-
1264
- public static function convert($source, $destination, $options = [])
1265
- {
1266
- ConverterHelper::runConverter('gd', $source, $destination, $options, true);
1267
- }
1268
-
1269
- /**
1270
- *
1271
- * @return Returns TRUE if the convertion was complete, or if the source image already is a true color image,
1272
- * otherwise FALSE is returned.
1273
- */
1274
- public static function makeTrueColor(&$image)
1275
- {
1276
- if (function_exists('imagepalettetotruecolor')) {
1277
- return imagepalettetotruecolor($image);
1278
- } else {
1279
- // Got the workaround here: https://secure.php.net/manual/en/function.imagepalettetotruecolor.php
1280
- if ((function_exists('imageistruecolor') && !imageistruecolor($image))
1281
- || !function_exists('imageistruecolor')
1282
- ) {
1283
- if (self::functionsExist(['imagecreatetruecolor', 'imagealphablending', 'imagecolorallocatealpha',
1284
- 'imagefilledrectangle', 'imagecopy', 'imagedestroy', 'imagesx', 'imagesy'])) {
1285
- $dst = imagecreatetruecolor(imagesx($image), imagesy($image));
1286
-
1287
- //prevent blending with default black
1288
- imagealphablending($dst, false);
1289
-
1290
- //change the RGB values if you need, but leave alpha at 127
1291
- $transparent = imagecolorallocatealpha($dst, 255, 255, 255, 127);
1292
-
1293
- //simpler than flood fill
1294
- imagefilledrectangle($dst, 0, 0, imagesx($image), imagesy($image), $transparent);
1295
-
1296
- imagealphablending($dst, true); //restore default blending
1297
- imagecopy($dst, $image, 0, 0, 0, 0, imagesx($image), imagesy($image));
1298
- imagedestroy($image);
1299
- $image = $dst;
1300
- return true;
1301
- }
1302
- } else {
1303
- return false;
1304
- }
1305
- }
1306
- }
1307
-
1308
- // Although this method is public, do not call directly.
1309
- public static function doConvert($source, $destination, $options, $logger)
1310
- {
1311
- if (!extension_loaded('gd')) {
1312
- throw new ConverterNotOperationalException('Required Gd extension is not available.');
1313
- }
1314
-
1315
- if (!function_exists('imagewebp')) {
1316
- throw new ConverterNotOperationalException(
1317
- 'Required imagewebp() function is not available. It seems Gd has been compiled without webp support.'
1318
- );
1319
- }
1320
-
1321
- switch (ConverterHelper::getExtension($source)) {
1322
- case 'png':
1323
- if (!$options['skip-pngs']) {
1324
- if (!function_exists('imagecreatefrompng')) {
1325
- throw new ConverterNotOperationalException(
1326
- 'Required imagecreatefrompng() function is not available.'
1327
- );
1328
- }
1329
- $image = @imagecreatefrompng($source);
1330
- if (!$image) {
1331
- throw new ConverterFailedException(
1332
- 'imagecreatefrompng("' . $source . '") failed'
1333
- );
1334
- }
1335
- } else {
1336
- throw new ConversionDeclinedException(
1337
- 'PNG file skipped. GD is configured not to convert PNGs'
1338
- );
1339
- }
1340
- break;
1341
- default:
1342
- if (!function_exists('imagecreatefromjpeg')) {
1343
- throw new ConverterNotOperationalException(
1344
- 'Required imagecreatefromjpeg() function is not available.'
1345
- );
1346
- }
1347
- $image = @imagecreatefromjpeg($source);
1348
- if (!$image) {
1349
- throw new ConverterFailedException('imagecreatefromjpeg("' . $source . '") failed');
1350
- }
1351
- }
1352
-
1353
- $mustMakeTrueColor = false;
1354
- if (function_exists('imageistruecolor')) {
1355
- if (imageistruecolor($image)) {
1356
- $logger->logLn('image is true color');
1357
- } else {
1358
- $logger->logLn('image is not true color');
1359
- $mustMakeTrueColor = true;
1360
- }
1361
- } else {
1362
- $logger->logLn('It can not be determined if image is true color');
1363
- $mustMakeTrueColor = true;
1364
- }
1365
- if ($mustMakeTrueColor) {
1366
- $logger->logLn('converting color palette to true color');
1367
- $success = self::makeTrueColor($image);
1368
- if (!$success) {
1369
- $logger->logLn(
1370
- 'Warning: FAILED converting color palette to true color. Continuing, but this does not look good.'
1371
- );
1372
- }
1373
- }
1374
- if (ConverterHelper::getExtension($source) == 'png') {
1375
- if (function_exists('imagealphablending')) {
1376
- if (!imagealphablending($image, true)) {
1377
- $logger->logLn('Warning: imagealphablending() failed');
1378
- }
1379
- } else {
1380
- $logger->logLn(
1381
- 'Warning: imagealphablending() is not available on your system. ' .
1382
- 'Converting PNGs with transparency might fail on some systems'
1383
- );
1384
- }
1385
- if (function_exists('imagesavealpha')) {
1386
- if (!imagesavealpha($image, true)) {
1387
- $logger->logLn('Warning: imagesavealpha() failed');
1388
- }
1389
- } else {
1390
- $logger->logLn(
1391
- 'Warning: imagesavealpha() is not available on your system. ' .
1392
- 'Converting PNGs with transparency might fail on some systems'
1393
- );
1394
- }
1395
- }
1396
-
1397
- $success = @imagewebp($image, $destination, $options['_calculated_quality']);
1398
-
1399
- if (!$success) {
1400
- throw new ConverterFailedException(
1401
- 'Call to imagewebp() failed. Probably failed writing file. Check file permissions!'
1402
- );
1403
- }
1404
-
1405
- /*
1406
- * This hack solves an `imagewebp` bug
1407
- * See https://stackoverflow.com/questions/30078090/imagewebp-php-creates-corrupted-webp-files
1408
- *
1409
- */
1410
- if (@filesize($destination) % 2 == 1) {
1411
- @file_put_contents($destination, "\0", FILE_APPEND);
1412
- }
1413
-
1414
- imagedestroy($image);
1415
- }
1416
- }
1417
-
1418
- ?><?php
1419
-
1420
- namespace WebPConvert\Converters;
1421
-
1422
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
1423
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
1424
-
1425
- //use WebPConvert\Exceptions\TargetNotFoundException;
1426
-
1427
- class Gmagick
1428
- {
1429
- public static $extraOptions = [];
1430
-
1431
- public static function convert($source, $destination, $options = [])
1432
- {
1433
- ConverterHelper::runConverter('gmagick', $source, $destination, $options, true);
1434
- }
1435
-
1436
- // Although this method is public, do not call directly.
1437
- public static function doConvert($source, $destination, $options, $logger)
1438
- {
1439
- if (!extension_loaded('Gmagick')) {
1440
- throw new ConverterNotOperationalException('Required Gmagick extension is not available.');
1441
- }
1442
-
1443
- if (!class_exists('Gmagick')) {
1444
- throw new ConverterNotOperationalException(
1445
- 'Gmagick is installed, but not correctly. The class Gmagick is not available'
1446
- );
1447
- }
1448
-
1449
- // This might throw an exception.
1450
- // We let it...
1451
- $im = new \Gmagick($source);
1452
-
1453
-
1454
- // Throws an exception if Gmagick does not support WebP conversion
1455
- if (!in_array('WEBP', $im->queryformats())) {
1456
- throw new ConverterNotOperationalException('Gmagick was compiled without WebP support.');
1457
- }
1458
-
1459
- $options = array_merge(ConverterHelper::$defaultOptions, $options);
1460
-
1461
- // Force lossless option to true for PNG images
1462
- if (ConverterHelper::getExtension($source) == 'png') {
1463
- $options['lossless'] = true;
1464
- }
1465
-
1466
-
1467
- /*
1468
- Seems there are currently no way to set webp options
1469
- As noted in the following link, it should probably be done with a $im->addDefinition() method
1470
- - but that isn't exposed (yet)
1471
- (TODO: see if anyone has answered...)
1472
- https://stackoverflow.com/questions/47294962/how-to-write-lossless-webp-files-with-perlmagick
1473
- */
1474
- // The following two does not have any effect... How to set WebP options?
1475
- //$im->setimageoption('webp', 'webp:lossless', $options['lossless'] ? 'true' : 'false');
1476
- //$im->setimageoption('WEBP', 'method', strval($options['method']));
1477
-
1478
- // It seems there is no COMPRESSION_WEBP...
1479
- // http://php.net/manual/en/imagick.setimagecompression.php
1480
- //$im->setImageCompression(Imagick::COMPRESSION_JPEG);
1481
- //$im->setImageCompression(Imagick::COMPRESSION_UNDEFINED);
1482
-
1483
-
1484
-
1485
- $im->setimageformat('WEBP');
1486
-
1487
- if ($options['metadata'] == 'none') {
1488
- // Strip metadata and profiles
1489
- $im->stripImage();
1490
- }
1491
-
1492
- // Ps: Imagick automatically uses same quality as source, when no quality is set
1493
- // This feature is however not present in Gmagick
1494
- $im->setcompressionquality($options['_calculated_quality']);
1495
-
1496
- //$success = $im->writeimagefile(fopen($destination, 'wb'));
1497
- $success = @file_put_contents($destination, $im->getImageBlob());
1498
-
1499
- if (!$success) {
1500
- throw new ConverterFailedException('Failed writing file');
1501
- } else {
1502
- //$logger->logLn('sooms we made it!');
1503
- }
1504
- }
1505
- }
1506
-
1507
- ?><?php
1508
-
1509
- namespace WebPConvert\Converters;
1510
-
1511
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
1512
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
1513
-
1514
- //use WebPConvert\Exceptions\TargetNotFoundException;
1515
-
1516
- class Imagick
1517
- {
1518
- public static $extraOptions = [];
1519
-
1520
- public static function convert($source, $destination, $options = [])
1521
- {
1522
- ConverterHelper::runConverter('imagick', $source, $destination, $options, true);
1523
- }
1524
-
1525
- // Although this method is public, do not call directly.
1526
- public static function doConvert($source, $destination, $options, $logger)
1527
- {
1528
- if (!extension_loaded('imagick')) {
1529
- throw new ConverterNotOperationalException('Required iMagick extension is not available.');
1530
- }
1531
-
1532
- if (!class_exists('Imagick')) {
1533
- throw new ConverterNotOperationalException(
1534
- 'iMagick is installed, but not correctly. The class Imagick is not available'
1535
- );
1536
- }
1537
-
1538
- // This might throw an exception.
1539
- // Ie "ImagickException: no decode delegate for this image format `JPEG'"
1540
- // We let it...
1541
- $im = new \Imagick($source);
1542
- //$im = new \Imagick();
1543
- //$im->readImage($source);
1544
-
1545
- // Throws an exception if iMagick does not support WebP conversion
1546
- if (!in_array('WEBP', $im->queryFormats())) {
1547
- throw new ConverterNotOperationalException('iMagick was compiled without WebP support.');
1548
- }
1549
-
1550
- $options = array_merge(ConverterHelper::$defaultOptions, $options);
1551
-
1552
- // Force lossless option to true for PNG images
1553
- if (ConverterHelper::getExtension($source) == 'png') {
1554
- $options['lossless'] = true;
1555
- }
1556
-
1557
- $im->setImageFormat('WEBP');
1558
-
1559
- /*
1560
- * More about iMagick's WebP options:
1561
- * http://www.imagemagick.org/script/webp.php
1562
- * https://developers.google.com/speed/webp/docs/cwebp
1563
- * https://stackoverflow.com/questions/37711492/imagemagick-specific-webp-calls-in-php
1564
- */
1565
-
1566
- // TODO: We could easily support all webp options with a loop
1567
-
1568
- /*
1569
- After using getImageBlob() to write image, the following setOption() calls
1570
- makes settings makes imagick fail. So can't use those. But its a small price
1571
- to get a converter that actually makes great quality conversions.
1572
-
1573
- $im->setOption('webp:method', strval($options['method']));
1574
- $im->setOption('webp:low-memory', strval($options['low-memory']));
1575
- $im->setOption('webp:lossless', strval($options['lossless']));
1576
- */
1577
-
1578
- if ($options['metadata'] == 'none') {
1579
- // Strip metadata and profiles
1580
- $im->stripImage();
1581
- }
1582
-
1583
- if (isset($options['_quality_could_not_be_detected'])) {
1584
- // quality was set to "auto", but we could not meassure the quality of the jpeg locally
1585
- // but luckily imagick is a big boy, and automatically converts with same quality as
1586
- // source, when the quality isn't set.
1587
- // So we simply do not set quality.
1588
- // This actually kills the max-height functionality. But I deem that this is more important
1589
- // because setting image quality to something higher than source generates bigger files,
1590
- // but gets you no extra quality. When failing to limit quality, you at least get something
1591
- // out of it
1592
- $logger->logLn('Converting without setting quality, to achieve auto quality');
1593
- } else {
1594
- // _calculated_quality is always set, actually - also when quality is set to a number
1595
- if (isset($options['_calculated_quality'])) {
1596
- $logger->logLn('Converting with quality:' . $options['_calculated_quality']);
1597
- $im->setImageCompressionQuality($options['_calculated_quality']);
1598
- //$im->setImageCompressionQuality(55);
1599
- }
1600
- }
1601
-
1602
-
1603
-
1604
- // https://stackoverflow.com/questions/29171248/php-imagick-jpeg-optimization
1605
- // setImageFormat
1606
-
1607
- // TODO: Read up on
1608
- // https://www.smashingmagazine.com/2015/06/efficient-image-resizing-with-imagemagick/
1609
- // https://github.com/nwtn/php-respimg
1610
-
1611
- // TODO:
1612
- // Should we set alpha channel for PNG's like suggested here:
1613
- // https://gauntface.com/blog/2014/09/02/webp-support-with-imagemagick-and-php ??
1614
- // It seems that alpha channel works without... (at least I see completely transparerent pixels)
1615
-
1616
- // TODO: Check out other iMagick methods, see http://php.net/manual/de/imagick.writeimage.php#114714
1617
- // 1. file_put_contents($destination, $im)
1618
- // 2. $im->writeImage($destination)
1619
-
1620
- // We used to use writeImageFile() method. But we now use getImageBlob(). See issue #43
1621
- //$success = $im->writeImageFile(fopen($destination, 'wb'));
1622
-
1623
- $success = @file_put_contents($destination, $im->getImageBlob());
1624
-
1625
- if (!$success) {
1626
- throw new ConverterFailedException('Failed writing file');
1627
- }
1628
- }
1629
- }
1630
-
1631
- ?><?php
1632
-
1633
- namespace WebPConvert\Converters;
1634
-
1635
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
1636
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
1637
-
1638
- //use WebPConvert\Exceptions\TargetNotFoundException;
1639
-
1640
- class ImagickBinary
1641
- {
1642
- public static $extraOptions = [
1643
- [
1644
- 'name' => 'use-nice',
1645
- 'type' => 'boolean',
1646
- 'sensitive' => false,
1647
- 'default' => true,
1648
- 'required' => false
1649
- ],
1650
- ];
1651
-
1652
- public static function convert($source, $destination, $options = [])
1653
- {
1654
- ConverterHelper::runConverter('imagickbinary', $source, $destination, $options, true);
1655
- }
1656
-
1657
- public static function imagickInstalled()
1658
- {
1659
- exec('convert -version', $output, $returnCode);
1660
- return ($returnCode == 0);
1661
- }
1662
-
1663
- // Check if webp delegate is installed
1664
- public static function webPDelegateInstalled()
1665
- {
1666
- /* HM. We should not rely on grep being available
1667
- $command = 'convert -list configure | grep -i "delegates" | grep -i webp';
1668
- exec($command, $output, $returnCode);
1669
- return (count($output) > 0);
1670
- */
1671
-
1672
- $command = 'convert -version';
1673
- exec($command, $output, $returnCode);
1674
- $hasDelegate = false;
1675
- foreach ($output as $line) {
1676
- if (preg_match('/Delegate.*webp.*/i', $line)) {
1677
- return true;
1678
- }
1679
- }
1680
- return false;
1681
- }
1682
-
1683
- // Checks if 'Nice' is available
1684
- private static function hasNiceSupport()
1685
- {
1686
- exec("nice 2>&1", $niceOutput);
1687
-
1688
- if (is_array($niceOutput) && isset($niceOutput[0])) {
1689
- if (preg_match('/usage/', $niceOutput[0]) || (preg_match('/^\d+$/', $niceOutput[0]))) {
1690
- /*
1691
- * Nice is available - default niceness (+10)
1692
- * https://www.lifewire.com/uses-of-commands-nice-renice-2201087
1693
- * https://www.computerhope.com/unix/unice.htm
1694
- */
1695
-
1696
- return true;
1697
- }
1698
-
1699
- return false;
1700
- }
1701
- }
1702
-
1703
- public static function escapeFilename($string)
1704
- {
1705
-
1706
- // filter_var() is should normally be available, but it is not always
1707
- // - https://stackoverflow.com/questions/11735538/call-to-undefined-function-filter-var
1708
- if (function_exists('filter_var')) {
1709
- // Sanitize quotes
1710
- $string = filter_var($string, FILTER_SANITIZE_MAGIC_QUOTES);
1711
-
1712
- // Stripping control characters
1713
- // see https://stackoverflow.com/questions/12769462/filter-flag-strip-low-vs-filter-flag-strip-high
1714
- $string = filter_var($string, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
1715
- }
1716
-
1717
- // Escaping whitespace. Must be done *after* filter_var!
1718
- $string = preg_replace('/\s/', '\\ ', $string);
1719
-
1720
- return $string;
1721
- }
1722
-
1723
- // Although this method is public, do not call directly.
1724
- public static function doConvert($source, $destination, $options, $logger)
1725
- {
1726
- if (!function_exists('exec')) {
1727
- throw new ConverterNotOperationalException('exec() is not enabled.');
1728
- }
1729
-
1730
-
1731
- if (!self::imagickInstalled()) {
1732
- throw new ConverterNotOperationalException('imagick is not installed');
1733
- }
1734
-
1735
-
1736
- // TODO:
1737
- // quality. Like this: 'convert -quality 100 small.jpg small.webp'
1738
- $qualityOption = '';
1739
- //$this->logLn('Using quality:' . $this->getCalculatedQuality());
1740
- if (isset($options['_quality_could_not_be_detected'])) {
1741
- // quality was set to "auto", but we could not meassure the quality of the jpeg locally
1742
- // but luckily imagick is a big boy, and automatically converts with same quality as
1743
- // source, when the quality isn't set.
1744
- } else {
1745
- $qualityOption = '-quality ' . $options['_calculated_quality'] . ' ';
1746
- }
1747
-
1748
-
1749
- // Should we use "magick" or "convert" command?
1750
- // It seems they do the same. But which is best supported? Which is mostly available (whitelisted)?
1751
- // Should we perhaps try both?
1752
- // For now, we just go with "convert"
1753
-
1754
- $command = 'convert '
1755
- . $qualityOption
1756
- . escapeshellarg($source)
1757
- . ' ' . escapeshellarg('webp:' . $destination);
1758
-
1759
- // Nice
1760
- $useNice = (($options['use-nice']) && self::hasNiceSupport()) ? true : false;
1761
- if ($useNice) {
1762
- $logger->logLn('using nice');
1763
- $command = 'nice ' . $command;
1764
- }
1765
-
1766
- $logger->logLn('command:' . $command);
1767
- exec($command, $output, $returnCode);
1768
-
1769
- if ($returnCode == 127) {
1770
- throw new ConverterNotOperationalException('imagick is not installed');
1771
- }
1772
-
1773
- if ($returnCode != 0) {
1774
- if (!self::webPDelegateInstalled()) {
1775
- throw new ConverterNotOperationalException('webp delegate missing');
1776
- }
1777
-
1778
- $logger->logLn('command:' . $command);
1779
- $logger->logLn('return code:' . $returnCode);
1780
- $logger->logLn('output:' . print_r(implode("\n", $output), true));
1781
-
1782
- throw new ConverterNotOperationalException('The exec call failed');
1783
- }
1784
- }
1785
- }
1786
-
1787
- ?><?php
1788
-
1789
- namespace WebPConvert\Converters;
1790
-
1791
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
1792
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
1793
-
1794
- class Wpc
1795
- {
1796
- public static $extraOptions = [
1797
- [
1798
- 'name' => 'api-version', /* Can currently be 0 or 1 */
1799
- 'type' => 'number',
1800
- 'sensitive' => false,
1801
- 'default' => 0,
1802
- 'required' => false
1803
- ],
1804
- [
1805
- 'name' => 'secret', /* only in api v.0 */
1806
- 'type' => 'string',
1807
- 'sensitive' => true,
1808
- 'default' => 'my dog is white',
1809
- 'required' => false
1810
- ],
1811
- [
1812
- 'name' => 'api-key', /* new in api v.1 (renamed 'secret' to 'api-key') */
1813
- 'type' => 'string',
1814
- 'sensitive' => true,
1815
- 'default' => 'my dog is white',
1816
- 'required' => false
1817
- ],
1818
- [
1819
- 'name' => 'url',
1820
- 'type' => 'string',
1821
- 'sensitive' => true,
1822
- 'default' => '',
1823
- 'required' => true
1824
- ],
1825
- [
1826
- 'name' => 'crypt-api-key-in-transfer', /* new in api v.1 */
1827
- 'type' => 'boolean',
1828
- 'sensitive' => false,
1829
- 'default' => false,
1830
- 'required' => false
1831
- ],
1832
-
1833
- /*
1834
- [
1835
- 'name' => 'web-services',
1836
- 'type' => 'array',
1837
- 'sensitive' => true,
1838
- 'default' => [
1839
- [
1840
- 'label' => 'test',
1841
- 'api-key' => 'my dog is white',
1842
- 'url' => 'http://we0/wordpress/webp-express-server',
1843
- 'crypt-api-key-in-transfer' => true
1844
- ]
1845
- ],
1846
- 'required' => true
1847
- ],
1848
- */
1849
- ];
1850
-
1851
- public static function convert($source, $destination, $options = [])
1852
- {
1853
- ConverterHelper::runConverter('wpc', $source, $destination, $options, true);
1854
- }
1855
-
1856
- // Took this parser from Drupal
1857
- private static function parseSize($size)
1858
- {
1859
-
1860
- $unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
1861
- $size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
1862
- if ($unit) {
1863
- // Find the position of the unit in the ordered string which is the power
1864
- // of magnitude to multiply a kilobyte by.
1865
- return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
1866
- } else {
1867
- return round($size);
1868
- }
1869
- }
1870
-
1871
- private static function createRandomSaltForBlowfish()
1872
- {
1873
- $salt = '';
1874
- $validCharsForSalt = array_merge(
1875
- range('A', 'Z'),
1876
- range('a', 'z'),
1877
- range('0', '9'),
1878
- ['.', '/']
1879
- );
1880
-
1881
- for ($i=0; $i<22; $i++) {
1882
- $salt .= $validCharsForSalt[array_rand($validCharsForSalt)];
1883
- }
1884
- return $salt;
1885
- }
1886
-
1887
- // Although this method is public, do not call directly.
1888
- public static function doConvert($source, $destination, $options, $logger)
1889
- {
1890
-
1891
- if (!extension_loaded('curl')) {
1892
- throw new ConverterNotOperationalException('Required cURL extension is not available.');
1893
- }
1894
-
1895
- if (!function_exists('curl_init')) {
1896
- throw new ConverterNotOperationalException('Required url_init() function is not available.');
1897
- }
1898
-
1899
- $apiVersion = $options['api-version'];
1900
-
1901
- if (!function_exists('curl_file_create')) {
1902
- throw new ConverterNotOperationalException(
1903
- 'Required curl_file_create() PHP function is not available (requires PHP > 5.5).'
1904
- );
1905
- }
1906
-
1907
- if ($apiVersion == 0) {
1908
- if (!empty($options['secret'])) {
1909
- // if secret is set, we need md5() and md5_file() functions
1910
- if (!function_exists('md5')) {
1911
- throw new ConverterNotOperationalException(
1912
- 'A secret has been set, which requires us to create a md5 hash from the secret and the file ' .
1913
- 'contents. ' .
1914
- 'But the required md5() PHP function is not available.'
1915
- );
1916
- }
1917
- if (!function_exists('md5_file')) {
1918
- throw new ConverterNotOperationalException(
1919
- 'A secret has been set, which requires us to create a md5 hash from the secret and the file ' .
1920
- 'contents. But the required md5_file() PHP function is not available.'
1921
- );
1922
- }
1923
- }
1924
- }
1925
-
1926
- if ($apiVersion == 1) {
1927
- /*
1928
- if (count($options['web-services']) == 0) {
1929
- throw new ConverterNotOperationalException('No remote host has been set up');
1930
- }*/
1931
- }
1932
-
1933
- if ($options['url'] == '') {
1934
- throw new ConverterNotOperationalException(
1935
- 'Missing URL. You must install Webp Convert Cloud Service on a server, ' .
1936
- 'or the WebP Express plugin for Wordpress - and supply the url.'
1937
- );
1938
- }
1939
-
1940
- $fileSize = @filesize($source);
1941
- if ($fileSize !== false) {
1942
- $uploadMaxSize = self::parseSize(ini_get('upload_max_filesize'));
1943
- if (($uploadMaxSize !== false) && ($uploadMaxSize < $fileSize)) {
1944
- throw new ConverterFailedException(
1945
- 'File is larger than your max upload (set in your php.ini). File size:' .
1946
- round($fileSize/1024) . ' kb. ' .
1947
- 'upload_max_filesize in php.ini: ' . ini_get('upload_max_filesize') .
1948
- ' (parsed as ' . round($uploadMaxSize/1024) . ' kb)'
1949
- );
1950
- }
1951
-
1952
- $postMaxSize = self::parseSize(ini_get('post_max_size'));
1953
- if (($postMaxSize !== false) && ($postMaxSize < $fileSize)) {
1954
- throw new ConverterFailedException(
1955
- 'File is larger than your post_max_size limit (set in your php.ini). File size:' .
1956
- round($fileSize/1024) . ' kb. ' .
1957
- 'post_max_size in php.ini: ' . ini_get('post_max_size') .
1958
- ' (parsed as ' . round($postMaxSize/1024) . ' kb)'
1959
- );
1960
- }
1961
-
1962
- // ini_get('memory_limit')
1963
- }
1964
-
1965
- // Got some code here:
1966
- // https://coderwall.com/p/v4ps1a/send-a-file-via-post-with-curl-and-php
1967
-
1968
- $ch = curl_init();
1969
- if (!$ch) {
1970
- throw new ConverterNotOperationalException('Could not initialise cURL.');
1971
- }
1972
-
1973
- $optionsToSend = $options;
1974
-
1975
- if (isset($options['_quality_could_not_be_detected'])) {
1976
- // quality was set to "auto", but we could not meassure the quality of the jpeg locally
1977
- // Ask the cloud service to do it, rather than using what we came up with.
1978
- $optionsToSend['quality'] = 'auto';
1979
- } else {
1980
- $optionsToSend['quality'] = $options['_calculated_quality'];
1981
- }
1982
-
1983
- unset($optionsToSend['converters']);
1984
- unset($optionsToSend['secret']);
1985
- unset($optionsToSend['_quality_could_not_be_detected']);
1986
- unset($optionsToSend['_calculated_quality']);
1987
-
1988
- $postData = [
1989
- 'file' => curl_file_create($source),
1990
- 'options' => json_encode($optionsToSend),
1991
- 'servername' => (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '')
1992
- ];
1993
-
1994
- if ($apiVersion == 0) {
1995
- $postData['hash'] = md5(md5_file($source) . $options['secret']);
1996
- }
1997
-
1998
- if ($apiVersion == 1) {
1999
- $apiKey = $options['api-key'];
2000
-
2001
- if ($options['crypt-api-key-in-transfer']) {
2002
- if (CRYPT_BLOWFISH == 1) {
2003
- $salt = self::createRandomSaltForBlowfish();
2004
- $postData['salt'] = $salt;
2005
-
2006
- // Strip off the first 28 characters (the first 6 are always "$2y$10$". The next 22 is the salt)
2007
- $postData['api-key-crypted'] = substr(crypt($apiKey, '$2y$10$' . $salt . '$'), 28);
2008
- } else {
2009
- if (!function_exists('crypt')) {
2010
- throw new ConverterNotOperationalException(
2011
- 'Configured to crypt the api-key, but crypt() function is not available.'
2012
- );
2013
- } else {
2014
- throw new ConverterNotOperationalException(
2015
- 'Configured to crypt the api-key. ' .
2016
- 'That requires Blowfish encryption, which is not available on your current setup.'
2017
- );
2018
- }
2019
- }
2020
- } else {
2021
- $postData['api-key'] = $apiKey;
2022
- }
2023
- }
2024
-
2025
-
2026
- // Try one host at the time
2027
- // TODO: shuffle the array first
2028
- /*
2029
- foreach ($options['web-services'] as $webService) {
2030
-
2031
- }
2032
- */
2033
-
2034
-
2035
- curl_setopt_array($ch, [
2036
- CURLOPT_URL => $options['url'],
2037
- CURLOPT_POST => 1,
2038
- CURLOPT_POSTFIELDS => $postData,
2039
- CURLOPT_BINARYTRANSFER => true,
2040
- CURLOPT_RETURNTRANSFER => true,
2041
- CURLOPT_HEADER => false,
2042
- CURLOPT_SSL_VERIFYPEER => false
2043
- ]);
2044
-
2045
- $response = curl_exec($ch);
2046
- if (curl_errno($ch)) {
2047
- throw new ConverterNotOperationalException('Curl error:' . curl_error($ch));
2048
- }
2049
-
2050
- // Check if we got a 404
2051
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
2052
- if ($httpCode == 404) {
2053
- curl_close($ch);
2054
- throw new ConverterFailedException(
2055
- 'WPC was not found at the specified URL - we got a 404 response.'
2056
- );
2057
- }
2058
-
2059
- // The WPC cloud service either returns an image or an error message
2060
- // Images has application/octet-stream.
2061
- // Verify that we got an image back.
2062
- if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
2063
- curl_close($ch);
2064
-
2065
- if (substr($response, 0, 1) == '{') {
2066
- $responseObj = json_decode($response, true);
2067
- if (isset($responseObj['errorCode'])) {
2068
- switch ($responseObj['errorCode']) {
2069
- case 0:
2070
- throw new ConverterFailedException(
2071
- 'There are problems with the server setup: "' .
2072
- $responseObj['errorMessage'] . '"'
2073
- );
2074
- case 1:
2075
- throw new ConverterFailedException(
2076
- 'Access denied. ' . $responseObj['errorMessage']
2077
- );
2078
- default:
2079
- throw new ConverterFailedException(
2080
- 'Conversion failed: "' . $responseObj['errorMessage'] . '"'
2081
- );
2082
- }
2083
- }
2084
- }
2085
-
2086
- // WPC 0.1 returns 'failed![error messag]' when conversion fails. Handle that.
2087
- if (substr($response, 0, 7) == 'failed!') {
2088
- throw new ConverterFailedException(
2089
- 'WPC failed converting image: "' . substr($response, 7) . '"'
2090
- );
2091
- }
2092
-
2093
- if (empty($response)) {
2094
- $errorMsg = 'Error: Unexpected result. We got nothing back. HTTP CODE: ' . $httpCode;
2095
- throw new ConverterFailedException($errorMsg);
2096
- } else {
2097
- $errorMsg = 'Error: Unexpected result. We did not receive an image. We received: "';
2098
- $errorMsg .= str_replace("\r", '', str_replace("\n", '', htmlentities(substr($response, 0, 400))));
2099
- throw new ConverterFailedException($errorMsg . '..."');
2100
- }
2101
- //throw new ConverterNotOperationalException($response);
2102
- }
2103
-
2104
- $success = @file_put_contents($destination, $response);
2105
- curl_close($ch);
2106
-
2107
- if (!$success) {
2108
- throw new ConverterFailedException('Error saving file. Check file permissions');
2109
- }
2110
- /*
2111
- $curlOptions = [
2112
- 'api_key' => $options['key'],
2113
- 'webp' => '1',
2114
- 'file' => curl_file_create($source),
2115
- 'domain' => $_SERVER['HTTP_HOST'],
2116
- 'quality' => $options['quality'],
2117
- 'metadata' => ($options['metadata'] == 'none' ? '0' : '1')
2118
- ];
2119
-
2120
- curl_setopt_array($ch, [
2121
- CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
2122
- CURLOPT_HTTPHEADER => [
2123
- 'User-Agent: WebPConvert',
2124
- 'Accept: image/*'
2125
- ],
2126
- CURLOPT_POSTFIELDS => $curlOptions,
2127
- CURLOPT_BINARYTRANSFER => true,
2128
- CURLOPT_RETURNTRANSFER => true,
2129
- CURLOPT_HEADER => false,
2130
- CURLOPT_SSL_VERIFYPEER => false
2131
- ]);*/
2132
- }
2133
- }
2134
-
2135
- ?><?php
2136
-
2137
- namespace WebPConvert\Exceptions;
2138
-
2139
- use WebPConvert\Exceptions\WebPConvertBaseException;
2140
-
2141
- class ConverterNotFoundException extends WebPConvertBaseException
2142
- {
2143
- public $description = 'The converter does not exist.';
2144
- }
2145
-
2146
- ?><?php
2147
-
2148
- namespace WebPConvert\Exceptions;
2149
-
2150
- use WebPConvert\Exceptions\WebPConvertBaseException;
2151
-
2152
- class CreateDestinationFileException extends WebPConvertBaseException
2153
- {
2154
- public $description = 'The converter could not create destination file. Check file permisions!';
2155
- }
2156
-
2157
- ?><?php
2158
-
2159
- namespace WebPConvert\Exceptions;
2160
-
2161
- use WebPConvert\Exceptions\WebPConvertBaseException;
2162
-
2163
- class CreateDestinationFolderException extends WebPConvertBaseException
2164
- {
2165
- public $description = 'The converter could not create destination folder. Check file permisions!';
2166
- }
2167
-
2168
- ?><?php
2169
-
2170
- namespace WebPConvert\Exceptions;
2171
-
2172
- use WebPConvert\Exceptions\WebPConvertBaseException;
2173
-
2174
- class InvalidFileExtensionException extends WebPConvertBaseException
2175
- {
2176
- public $description = 'The converter does not accept the file extension';
2177
- }
2178
-
2179
- ?><?php
2180
-
2181
- namespace WebPConvert\Exceptions;
2182
-
2183
- use WebPConvert\Exceptions\WebPConvertBaseException;
2184
-
2185
- class TargetNotFoundException extends WebPConvertBaseException
2186
- {
2187
- public $description = 'The converter could not locate source file';
2188
- }
2189
-
2190
- ?><?php
2191
-
2192
- namespace WebPConvert\Converters\Exceptions;
2193
-
2194
- use WebPConvert\Exceptions\WebPConvertBaseException;
2195
-
2196
- class ConversionDeclinedException extends WebPConvertBaseException
2197
- {
2198
- public $description = 'The converter declined converting';
2199
- }
2200
-
2201
- ?><?php
2202
-
2203
- namespace WebPConvert\Converters\Exceptions;
2204
-
2205
- use WebPConvert\Exceptions\WebPConvertBaseException;
2206
-
2207
- class ConverterFailedException extends WebPConvertBaseException
2208
- {
2209
- public $description = 'The converter failed converting, although requirements seemed to be met';
2210
- }
2211
-
2212
- ?><?php
2213
-
2214
- namespace WebPConvert\Converters\Exceptions;
2215
-
2216
- use WebPConvert\Exceptions\WebPConvertBaseException;
2217
-
2218
- class ConverterNotOperationalException extends WebPConvertBaseException
2219
- {
2220
- public $description = 'The converter is not operational';
2221
- }
2222
-
2223
- ?><?php
2224
-
2225
- namespace WebPConvert\Loggers;
2226
-
2227
- use WebPConvert\Loggers\BaseLogger;
2228
-
2229
- class BufferLogger extends BaseLogger
2230
- {
2231
- public $entries = array();
2232
-
2233
- public function log($msg, $style = '')
2234
- {
2235
- $this->entries[] = [$msg, $style];
2236
- }
2237
-
2238
- public function ln()
2239
- {
2240
- $this->entries[] = '';
2241
- }
2242
-
2243
- public function getHtml()
2244
- {
2245
- $html = '';
2246
- foreach ($this->entries as $entry) {
2247
- if ($entry == '') {
2248
- $html .= '<br>';
2249
- } else {
2250
- list($msg, $style) = $entry;
2251
- $msg = htmlspecialchars($msg);
2252
- if ($style == 'bold') {
2253
- $html .= '<b>' . $msg . '</b>';
2254
- } elseif ($style == 'italic') {
2255
- $html .= '<i>' . $msg . '</i>';
2256
- } else {
2257
- $html .= $msg;
2258
- }
2259
- }
2260
- }
2261
- return $html;
2262
- }
2263
-
2264
- public function getText($newLineChar = ' ')
2265
- {
2266
- $text = '';
2267
- foreach ($this->entries as $entry) {
2268
- if ($entry == '') {
2269
- if (substr($text, -2) != '. ') {
2270
- $text .= '. ';
2271
- }
2272
- } else {
2273
- list($msg, $style) = $entry;
2274
- $text .= $msg;
2275
- }
2276
- }
2277
-
2278
- return $text;
2279
- }
2280
- }
2281
-
2282
- ?><?php
2283
-
2284
- namespace WebPConvert\Loggers;
2285
-
2286
- class EchoLogger extends BaseLogger
2287
- {
2288
- public function log($msg, $style = '')
2289
- {
2290
- $msg = htmlspecialchars($msg);
2291
- if ($style == 'bold') {
2292
- echo '<b>' . $msg . '</b>';
2293
- } elseif ($style == 'italic') {
2294
- echo '<i>' . $msg . '</i>';
2295
- } else {
2296
- echo $msg;
2297
- }
2298
- }
2299
-
2300
- public function ln()
2301
- {
2302
- echo '<br>';
2303
- }
2304
- }
2305
-
2306
- ?><?php
2307
-
2308
- namespace WebPConvert\Loggers;
2309
-
2310
- class VoidLogger extends BaseLogger
2311
- {
2312
- public function log($msg, $style = '')
2313
- {
2314
- }
2315
-
2316
- public function ln()
2317
- {
2318
- }
2319
- }
2320
-
2321
- ?><?php
2322
- namespace WebPConvert\Serve;
2323
-
2324
- use WebPConvert\WebPConvert;
2325
- use WebPConvert\Converters\ConverterHelper;
2326
- use WebPConvert\Loggers\EchoLogger;
2327
-
2328
- //use WebPConvert\Loggers\EchoLogger;
2329
-
2330
- class Report
2331
- {
2332
-
2333
- /**
2334
- * Input: We have a converter array where the options are defined
2335
- * Output: the converter array is "flattened" to be just names.
2336
- * and the options have been moved to the "converter-options" option.
2337
- */
2338
- public static function flattenConvertersArray($options)
2339
- {
2340
- // TODO: If there are more of the same converters,
2341
- // they should be added as ie 'wpc-2', 'wpc-3', etc
2342
-
2343
- $result = $options;
2344
- $result['converters'] = [];
2345
- foreach ($options['converters'] as $converter) {
2346
- if (is_array($converter)) {
2347
- $converterName = $converter['converter'];
2348
- if (!isset($options['converter-options'][$converterName])) {
2349
- if (isset($converter['options'])) {
2350
- if (!isset($result['converter-options'])) {
2351
- $result['converter-options'] = [];
2352
- }
2353
- $result['converter-options'][$converterName] = $converter['options'];
2354
- }
2355
- }
2356
- $result['converters'][] = $converterName;
2357
- } else {
2358
- $result['converters'][] = $converter;
2359
- }
2360
- }
2361
- return $result;
2362
- }
2363
-
2364
- /* Hides sensitive options */
2365
- public static function getPrintableOptions($options)
2366
- {
2367
- $printable_options = [];
2368
-
2369
- // (psst: the is_callable check is needed in order to work with WebPConvert v1.0)
2370
- if (is_callable('ConverterHelper', 'getClassNameOfConverter')) {
2371
- $printable_options = self::flattenConvertersArray($options);
2372
- if (isset($printable_options['converter-options'])) {
2373
- foreach ($printable_options['converter-options'] as $converterName => &$converterOptions) {
2374
- $className = ConverterHelper::getClassNameOfConverter($converterName);
2375
-
2376
- // (pstt: the isset check is needed in order to work with WebPConvert v1.0)
2377
- if (isset($className::$extraOptions)) {
2378
- foreach ($className::$extraOptions as $extraOption) {
2379
- if ($extraOption['sensitive']) {
2380
- if (isset($converterOptions[$extraOption['name']])) {
2381
- $converterOptions[$extraOption['name']] = '*******';
2382
- }
2383
- }
2384
- }
2385
- }
2386
- }
2387
- }
2388
- }
2389
- return $printable_options;
2390
- }
2391
-
2392
- public static function getPrintableOptionsAsString($options, $glue = '. ')
2393
- {
2394
- $optionsForPrint = [];
2395
- foreach (self::getPrintableOptions($options) as $optionName => $optionValue) {
2396
- $printValue = '';
2397
- if ($optionName == 'converter-options') {
2398
- $converterNames = [];
2399
- $extraConvertOptions = $optionValue;
2400
- //print_r($extraConvertOptions);
2401
- /*
2402
- foreach ($optionValue as $converterName => $converterOptions) {
2403
-
2404
- if (is_array($converter)) {
2405
- $converterName = $converter['converter'];
2406
- if (isset($converter['options'])) {
2407
- $extraConvertOptions[$converter['converter']] = $converter['options'];
2408
- }
2409
- } else {
2410
- $converterName = $converter;
2411
- }
2412
- $converterNames[] = $converterName;
2413
- }*/
2414
- $glueMe = [];
2415
- foreach ($extraConvertOptions as $converter => $extraOptions) {
2416
- $opt = [];
2417
- foreach ($extraOptions as $oName => $oValue) {
2418
- $opt[] = $oName . ':"' . $oValue . '"';
2419
- }
2420
- $glueMe[] = '(' . $converter . ': (' . implode($opt, ', ') . '))';
2421
- }
2422
- $printValue = implode(',', $glueMe);
2423
- } elseif ($optionName == 'web-service') {
2424
- $printValue = 'sensitive, so not displaying here...';
2425
- } else {
2426
- switch (gettype($optionValue)) {
2427
- case 'boolean':
2428
- if ($optionValue === true) {
2429
- $printValue = 'true';
2430
- } elseif ($optionValue === false) {
2431
- $printValue = 'false';
2432
- }
2433
- break;
2434
- case 'string':
2435
- $printValue = '"' . $optionValue . '"';
2436
- break;
2437
- case 'array':
2438
- $printValue = implode(', ', $optionValue);
2439
- break;
2440
- case 'integer':
2441
- $printValue = $optionValue;
2442
- break;
2443
- default:
2444
- $printValue = $optionValue;
2445
- }
2446
- }
2447
- $optionsForPrint[] = $optionName . ': ' . $printValue;
2448
- }
2449
- return implode($glue, $optionsForPrint);
2450
- }
2451
-
2452
- public static function convertAndReport($source, $destination, $options)
2453
- {
2454
- ?>
2455
- <html>
2456
- <head>
2457
- <style>td {vertical-align: top} table {color: #666}</style>
2458
- <script>
2459
- function showOptions(elToHide) {
2460
- document.getElementById('options').style.display='block';
2461
- elToHide.style.display='none';
2462
- }
2463
- </script>
2464
- </head>
2465
- <body>
2466
- <table>
2467
- <tr><td><i>source:</i></td><td><?php echo $source ?></td></tr>
2468
- <tr><td><i>destination:</i></td><td><?php echo $destination ?><td></tr>
2469
- <tr>
2470
- <td><i>options:</i></td>
2471
- <td>
2472
- <i style="text-decoration:underline;cursor:pointer" onclick="showOptions(this)">click to see</i>
2473
- <pre id="options" style="display:none"><?php
2474
- echo print_r(self::getPrintableOptionsAsString($options, '<br>'), true);
2475
- ?></pre>
2476
- <?php //echo json_encode(self::getPrintableOptions($options)); ?>
2477
- <?php //echo print_r(self::getPrintableOptions($options), true); ?>
2478
- </td>
2479
- </tr>
2480
- </table>
2481
- <br>
2482
- <?php
2483
- // TODO:
2484
- // We could display warning if unknown options are set
2485
- // but that requires that WebPConvert also describes its general options
2486
-
2487
- try {
2488
- $echoLogger = new EchoLogger();
2489
- $success = WebPConvert::convert($source, $destination, $options, $echoLogger);
2490
- } catch (\Exception $e) {
2491
- $success = false;
2492
-
2493
- $msg = $e->getMessage();
2494
-
2495
- echo '<b>' . $msg . '</b>';
2496
- exit;
2497
- }
2498
-
2499
- if ($success) {
2500
- //echo 'ok';
2501
- } else {
2502
- echo '<b>Conversion failed. None of the tried converters are operational</b>';
2503
- }
2504
- ?>
2505
- </body>
2506
- </html>
2507
- <?php
2508
- }
2509
- }
2510
-
2511
- ?><?php
2512
- namespace WebPConvert\Serve;
2513
-
2514
- //use WebPConvert\Serve\Report;
2515
-
2516
- class ServeBase
2517
- {
2518
- public $source;
2519
- public $destination;
2520
- public $options;
2521
-
2522
- // These two fellows are first set when decideWhatToServe is called
2523
- // However, if it is decided to serve a fresh conversion, they might get modified.
2524
- // If that for example results in a file larger than source, $whatToServe will change
2525
- // from 'fresh-conversion' to 'original', and $whyServingThis will change to 'source-lighter'
2526
- public $whatToServe = '';
2527
- public $whyServingThis = '';
2528
-
2529
- public function __construct($source, $destination, $options)
2530
- {
2531
-
2532
- $this->source = $source;
2533
- $this->destination = $destination;
2534
- $this->options = array_merge(self::$defaultOptions, $options);
2535
-
2536
- $this->setErrorReporting();
2537
- }
2538
-
2539
- public static $defaultOptions = [
2540
- 'add-content-type-header' => true,
2541
- 'add-last-modified-header' => true,
2542
- 'add-vary-header' => true,
2543
- 'add-x-header-status' => true,
2544
- 'add-x-header-options' => false,
2545
- 'aboutToServeImageCallBack' => null,
2546
- 'aboutToPerformFailAction' => null,
2547
- 'cache-control-header' => 'public, max-age=86400',
2548
- 'converters' => ['cwebp', 'gd', 'imagick'],
2549
- 'error-reporting' => 'auto',
2550
- 'fail' => 'original',
2551
- 'fail-when-original-unavailable' => '404',
2552
- 'reconvert' => false,
2553
- 'serve-original' => false,
2554
- 'show-report' => false,
2555
- ];
2556
-
2557
- protected function setErrorReporting()
2558
- {
2559
- if (($this->options['error-reporting'] === true) ||
2560
- (($this->options['error-reporting'] === 'auto') && ($this->options['show-report'] === true))
2561
- ) {
2562
- error_reporting(E_ALL);
2563
- ini_set('display_errors', 'On');
2564
- } elseif (($this->options['error-reporting'] === false) ||
2565
- (($this->options['error-reporting'] === 'auto') && ($this->options['show-report'] === false))
2566
- ) {
2567
- error_reporting(0);
2568
- ini_set('display_errors', 'Off');
2569
- }
2570
- }
2571
-
2572
- protected function header($header, $replace = true)
2573
- {
2574
- header($header, $replace);
2575
- }
2576
-
2577
- public function addXStatusHeader($text)
2578
- {
2579
- if ($this->options['add-x-header-status']) {
2580
- $this->header('X-WebP-Convert-Status: ' . $text, true);
2581
- }
2582
- }
2583
-
2584
- public function addVaryHeader()
2585
- {
2586
- if ($this->options['add-vary-header']) {
2587
- $this->header('Vary: Accept');
2588
- }
2589
- }
2590
-
2591
- public function addContentTypeHeader($cType)
2592
- {
2593
- if ($this->options['add-content-type-header']) {
2594
- $this->header('Content-type: ' . $cType);
2595
- }
2596
- }
2597
-
2598
- /* $timestamp Unix timestamp */
2599
- public function addLastModifiedHeader($timestamp)
2600
- {
2601
- if ($this->options['add-last-modified-header']) {
2602
- $this->header("Last-Modified: " . gmdate("D, d M Y H:i:s", $timestamp) ." GMT", true);
2603
- }
2604
- }
2605
-
2606
- public function addCacheControlHeader()
2607
- {
2608
- if (!empty($this->options['cache-control-header'])) {
2609
- $this->header('Cache-Control: ' . $this->options['cache-control-header'], true);
2610
- }
2611
- }
2612
-
2613
- public function serveExisting()
2614
- {
2615
- if (!$this->callAboutToServeImageCallBack('destination')) {
2616
- return;
2617
- }
2618
-
2619
- $this->addXStatusHeader('Serving existing converted image');
2620
- $this->addVaryHeader();
2621
- $this->addContentTypeHeader('image/webp');
2622
- $this->addCacheControlHeader();
2623
- $this->addLastModifiedHeader(@filemtime($this->destination));
2624
-
2625
- if (@readfile($this->destination) === false) {
2626
- $this->header('X-WebP-Convert-Error: Could not read file');
2627
- return false;
2628
- }
2629
- return true;
2630
- }
2631
-
2632
- /**
2633
- * Called immidiately before serving image (either original, already converted or fresh)
2634
- * $whatToServe can be 'source' | 'destination' | 'fresh-conversion'
2635
- * $whyServingThis can be:
2636
- * for 'source':
2637
- * - "explicitly-told-to" (when the "original" option is set)
2638
- * - "source-lighter" (when original image is actually smaller than the converted)
2639
- * for 'fresh-conversion':
2640
- * - "explicitly-told-to" (when the "reconvert" option is set)
2641
- * - "source-modified" (when source is newer than existing)
2642
- * - "no-existing" (when there is no existing at the destination)
2643
- * for 'destination':
2644
- * - "no-reason-not-to" (it is lighter than source, its not older,
2645
- * and we were not told to do otherwise)
2646
- */
2647
- protected function callAboutToServeImageCallBack($whatToServe)
2648
- {
2649
- if (!isset($this->options['aboutToServeImageCallBack'])) {
2650
- return true;
2651
- }
2652
- $result = call_user_func(
2653
- $this->options['aboutToServeImageCallBack'],
2654
- $whatToServe,
2655
- $this->whyServingThis,
2656
- $this
2657
- );
2658
- return ($result !== false);
2659
- }
2660
-
2661
- /**
2662
- * Decides what to serve.
2663
- * Returns array. First item is what to do, second is additional info.
2664
- * First item can be one of these:
2665
- * - "destination" (serve existing converted image at the destination path)
2666
- * - "no-reason-not-to"
2667
- * - "source"
2668
- * - "explicitly-told-to"
2669
- * - "source-lighter"
2670
- * - "fresh-conversion" (note: this may still fail)
2671
- * - "explicitly-told-to"
2672
- * - "source-modified"
2673
- * - "no-existing"
2674
- * - "fail"
2675
- * - "Missing destination argument"
2676
- * - "critical-fail" (a failure where the source file cannot be served)
2677
- * - "Missing source argument"
2678
- * - "Source file was not found!"
2679
- * - "report"
2680
- */
2681
- public function decideWhatToServe()
2682
- {
2683
- $decisionArr = $this->doDecideWhatToServe();
2684
- $this->whatToServe = $decisionArr[0];
2685
- $this->whyServingThis = $decisionArr[1];
2686
- }
2687
-
2688
- private function doDecideWhatToServe()
2689
- {
2690
- if (empty($this->source)) {
2691
- return ['critical-fail', 'Missing source argument'];
2692
- }
2693
- if (@!file_exists($this->source)) {
2694
- return ['critical-fail', 'Source file was not found!'];
2695
- }
2696
- if (empty($this->destination)) {
2697
- return ['fail', 'Missing destination argument'];
2698
- }
2699
- if ($this->options['show-report']) {
2700
- return ['report', ''];
2701
- }
2702
- if ($this->options['serve-original']) {
2703
- return ['source', 'explicitly-told-to'];
2704
- }
2705
- if ($this->options['reconvert']) {
2706
- return ['fresh-conversion', 'explicitly-told-to'];
2707
- }
2708
-
2709
- if (@file_exists($this->destination)) {
2710
- // Reconvert if source file is newer than destination
2711
- $timestampSource = @filemtime($this->source);
2712
- $timestampDestination = @filemtime($this->destination);
2713
- if (($timestampSource !== false) &&
2714
- ($timestampDestination !== false) &&
2715
- ($timestampSource > $timestampDestination)) {
2716
- return ['fresh-conversion', 'source-modified'];
2717
- }
2718
-
2719
- // Serve source if it is smaller than destination
2720
- $filesizeDestination = @filesize($this->destination);
2721
- $filesizeSource = @filesize($this->source);
2722
- if (($filesizeSource !== false) &&
2723
- ($filesizeDestination !== false) &&
2724
- ($filesizeDestination > $filesizeSource)) {
2725
- return ['source', 'source-lighter'];
2726
- }
2727
-
2728
- // Destination exists, and there is no reason left not to serve it
2729
- return ['destination', 'no-reason-not-to'];
2730
- } else {
2731
- return ['fresh-conversion', 'no-existing'];
2732
- }
2733
- }
2734
- }
2735
-
2736
- ?><?php
2737
- namespace WebPConvert\Serve;
2738
-
2739
- use WebPConvert\WebPConvert;
2740
- use WebPConvert\Loggers\BufferLogger;
2741
- use WebPConvert\Converters\ConverterHelper;
2742
- use WebPConvert\Serve\Report;
2743
-
2744
- /**
2745
- * This class must serves a converted image (either a fresh convertion, the destionation, or
2746
- * the original). Upon failure, the fail action given in the options will be exectuted
2747
- */
2748
- class ServeConverted extends ServeBase
2749
- {
2750
-
2751
- private function addXOptionsHeader()
2752
- {
2753
- if ($this->options['add-x-header-options']) {
2754
- $this->header('X-WebP-Convert-Options:' . Report::getPrintableOptionsAsString($this->options));
2755
- }
2756
- }
2757
-
2758
- private function addHeadersPreventingCaching()
2759
- {
2760
- $this->header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
2761
- $this->header("Cache-Control: post-check=0, pre-check=0", false);
2762
- $this->header("Pragma: no-cache");
2763
- }
2764
-
2765
- public function serve404()
2766
- {
2767
- $protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : 'HTTP/1.0';
2768
- $this->header($protocol . " 404 Not Found");
2769
- }
2770
-
2771
- public function serveOriginal()
2772
- {
2773
- if (!$this->callAboutToServeImageCallBack('source')) {
2774
- return true; // we shall not trigger the fail callback
2775
- }
2776
-
2777
- if ($this->options['add-content-type-header']) {
2778
- $arr = explode('.', $this->source);
2779
- $ext = array_pop($arr);
2780
- switch (strtolower($ext)) {
2781
- case 'jpg':
2782
- case 'jpeg':
2783
- $this->header('Content-type: image/jpeg');
2784
- break;
2785
- case 'png':
2786
- $this->header('Content-type: image/png');
2787
- break;
2788
- }
2789
- }
2790
-
2791
- $this->addVaryHeader();
2792
-
2793
- switch ($this->whyServingThis) {
2794
- case 'source-lighter':
2795
- case 'explicitly-told-to':
2796
- $this->addCacheControlHeader();
2797
- $this->addLastModifiedHeader(@filemtime($this->source));
2798
- break;
2799
- default:
2800
- $this->addHeadersPreventingCaching();
2801
- }
2802
-
2803
- if (@readfile($this->source) === false) {
2804
- $this->header('X-WebP-Convert: Could not read file');
2805
- return false;
2806
- }
2807
- return true;
2808
- }
2809
-
2810
- public function serveFreshlyConverted()
2811
- {
2812
-
2813
- $criticalFail = false;
2814
- $success = false;
2815
- $bufferLogger = new BufferLogger();
2816
-
2817
- try {
2818
- $success = WebPConvert::convert($this->source, $this->destination, $this->options, $bufferLogger);
2819
-
2820
- if ($success) {
2821
- // Serve source if it is smaller than destination
2822
- $filesizeDestination = @filesize($this->destination);
2823
- $filesizeSource = @filesize($this->source);
2824
- if (($filesizeSource !== false) &&
2825
- ($filesizeDestination !== false) &&
2826
- ($filesizeDestination > $filesizeSource)) {
2827
- $this->whatToServe = 'original';
2828
- $this->whyServingThis = 'source-lighter';
2829
- return $this->serveOriginal();
2830
- }
2831
-
2832
- if (!$this->callAboutToServeImageCallBack('fresh-conversion')) {
2833
- return;
2834
- }
2835
- if ($this->options['add-content-type-header']) {
2836
- $this->header('Content-type: image/webp');
2837
- }
2838
- if ($this->whyServingThis == 'explicitly-told-to') {
2839
- $this->addXStatusHeader(
2840
- 'Serving freshly converted image (was explicitly told to reconvert)'
2841
- );
2842
- } elseif ($this->whyServingThis == 'source-modified') {
2843
- $this->addXStatusHeader(
2844
- 'Serving freshly converted image (the original had changed)'
2845
- );
2846
- } elseif ($this->whyServingThis == 'no-existing') {
2847
- $this->addXStatusHeader(
2848
- 'Serving freshly converted image (there were no existing to serve)'
2849
- );
2850
- } else {
2851
- $this->addXStatusHeader(
2852
- 'Serving freshly converted image (dont know why!)'
2853
- );
2854
- }
2855
-
2856
- if ($this->options['add-vary-header']) {
2857
- $this->header('Vary: Accept');
2858
- }
2859
-
2860
- if ($this->whyServingThis == 'no-existing') {
2861
- $this->addCacheControlHeader();
2862
- } else {
2863
- $this->addHeadersPreventingCaching();
2864
- }
2865
- $this->addLastModifiedHeader(time());
2866
-
2867
- // Should we add Content-Length header?
2868
- // $this->header('Content-Length: ' . filesize($file));
2869
- if (@readfile($this->destination)) {
2870
- return true;
2871
- } else {
2872
- $this->fail('Error', 'could not read the freshly converted file');
2873
- return false;
2874
- }
2875
- } else {
2876
- $description = 'No converters are operational';
2877
- $msg = '';
2878
- }
2879
- } catch (\WebPConvert\Exceptions\InvalidFileExtensionException $e) {
2880
- $criticalFail = true;
2881
- $description = 'Invalid file extension';
2882
- $msg = $e->getMessage();
2883
- } catch (\WebPConvert\Exceptions\TargetNotFoundException $e) {
2884
- $criticalFail = true;
2885
- $description = 'Source file not found';
2886
- $msg = $e->getMessage();
2887
- } catch (\WebPConvert\Converters\Exceptions\ConverterFailedException $e) {
2888
- // No converters could convert the image. At least one converter failed, even though it appears to be
2889
- // operational
2890
- $description = 'No converters could convert the image';
2891
- $msg = $e->getMessage();
2892
- } catch (\WebPConvert\Converters\Exceptions\ConversionDeclinedException $e) {
2893
- // (no converters could convert the image. At least one converter declined
2894
- $description = 'No converters could/wanted to convert the image';
2895
- $msg = $e->getMessage();
2896
- } catch (\WebPConvert\Exceptions\ConverterNotFoundException $e) {
2897
- $description = 'A converter was not found!';
2898
- $msg = $e->getMessage();
2899
- } catch (\WebPConvert\Exceptions\CreateDestinationFileException $e) {
2900
- $description = 'Cannot create destination file';
2901
- $msg = $e->getMessage();
2902
- } catch (\WebPConvert\Exceptions\CreateDestinationFolderException $e) {
2903
- $description = 'Cannot create destination folder';
2904
- $msg = $e->getMessage();
2905
- } catch (\Exception $e) {
2906
- $description = 'An unanticipated exception was thrown';
2907
- $msg = $e->getMessage();
2908
- }
2909
-
2910
- // Next line is commented out, because we need to be absolute sure that the details does not violate syntax
2911
- // We could either try to filter it, or we could change WebPConvert, such that it only provides safe texts.
2912
- // $this->header('X-WebP-Convert-And-Serve-Details: ' . $bufferLogger->getText());
2913
-
2914
- $this->fail('Conversion failed', $description, $criticalFail);
2915
- return false;
2916
- //echo '<p>This is how conversion process went:</p>' . $bufferLogger->getHtml();
2917
- }
2918
-
2919
- protected function serveErrorMessageImage($msg)
2920
- {
2921
- // Generate image containing error message
2922
- if ($this->options['add-content-type-header']) {
2923
- $this->header('Content-type: image/gif');
2924
- }
2925
-
2926
- // TODO: handle if this fails...
2927
- $image = imagecreatetruecolor(620, 200);
2928
- imagestring($image, 1, 5, 5, $msg, imagecolorallocate($image, 233, 214, 291));
2929
- // echo imagewebp($image);
2930
- echo imagegif($image);
2931
- imagedestroy($image);
2932
- }
2933
-
2934
- protected function fail($title, $description, $critical = false)
2935
- {
2936
- $action = $critical ? $this->options['fail-when-original-unavailable'] : $this->options['fail'];
2937
-
2938
- if (isset($this->options['aboutToPerformFailActionCallback'])) {
2939
- if (call_user_func(
2940
- $this->options['aboutToPerformFailActionCallback'],
2941
- $title,
2942
- $description,
2943
- $action,
2944
- $this
2945
- ) === false) {
2946
- return;
2947
- }
2948
- }
2949
-
2950
- $this->addXStatusHeader('Failed (' . $description . ')');
2951
-
2952
- $this->addHeadersPreventingCaching();
2953
-
2954
-
2955
- $title = 'Conversion failed';
2956
- switch ($action) {
2957
- case 'serve-original':
2958
- if (!$this->serveOriginal()) {
2959
- $this->serve404();
2960
- };
2961
- break;
2962
- case '404':
2963
- $this->serve404();
2964
- break;
2965
- case 'report-as-image':
2966
- // todo: handle if this fails
2967
- self::serveErrorMessageImage($title . '. ' . $description);
2968
- break;
2969
- case 'report':
2970
- echo '<h1>' . $title . '</h1>' . $description;
2971
- break;
2972
- }
2973
- }
2974
-
2975
- protected function criticalFail($title, $description)
2976
- {
2977
- return $this->fail($title, $description, true);
2978
- }
2979
-
2980
- /**
2981
- * Serve the thing specified in $whatToServe and $whyServingThis
2982
- * These are first set my the decideWhatToServe() method, but may later change, if a fresh
2983
- * conversion is made
2984
- */
2985
- public function serve()
2986
- {
2987
-
2988
- //$this->addXOptionsHeader();
2989
-
2990
- switch ($this->whatToServe) {
2991
- case 'destination':
2992
- return $this->serveExisting();
2993
- case 'source':
2994
- if ($this->whyServingThis == 'explicitly-told-to') {
2995
- $this->addXStatusHeader(
2996
- 'Serving original image (was explicitly told to)'
2997
- );
2998
- } else {
2999
- $this->addXStatusHeader(
3000
- 'Serving original image (it is smaller than the already converted)'
3001
- );
3002
- }
3003
- if (!$this->serveOriginal()) {
3004
- $this->criticalFail('Error', 'could not serve original');
3005
- return false;
3006
- }
3007
- return true;
3008
- case 'fresh-conversion':
3009
- return $this->serveFreshlyConverted();
3010
- break;
3011
- case 'critical-fail':
3012
- $this->criticalFail('Error', $this->whyServingThis);
3013
- return false;
3014
- case 'fail':
3015
- $this->fail('Error', $this->whyServingThis);
3016
- return false;
3017
- case 'report':
3018
- $this->addXStatusHeader('Reporting...');
3019
- Report::convertAndReport($this->source, $this->destination, $this->options);
3020
- return true; // yeah, lets say that a report is always a success, even if conversion is a failure
3021
- }
3022
- }
3023
-
3024
- public function decideWhatToServeAndServeIt()
3025
- {
3026
- $this->decideWhatToServe();
3027
- return $this->serve();
3028
- }
3029
-
3030
- /**
3031
- * Main method
3032
- */
3033
- public static function serveConverted($source, $destination, $options)
3034
- {
3035
- if (isset($options['fail']) && ($options['fail'] == 'original')) {
3036
- $options['fail'] = 'serve-original';
3037
- }
3038
- // For backward compatability:
3039
- if (isset($options['critical-fail']) && !isset($options['fail-when-original-unavailable'])) {
3040
- $options['fail-when-original-unavailable'] = $options['critical-fail'];
3041
- }
3042
-
3043
- $cs = new static($source, $destination, $options);
3044
-
3045
- return $cs->decideWhatToServeAndServeIt();
3046
- }
3047
- }
3048
-
3049
- ?><?php
3050
- namespace WebPConvert\Serve;
3051
-
3052
- use WebPConvert\Serve\ServeBase;
3053
- use WebPConvert\Serve\ServeConverted;
3054
-
3055
- /**
3056
- * This class must determine if an existing converted image can and should be served.
3057
- * If so, it must serve it.
3058
- * If not, it must hand the task over to ConvertAndServe
3059
- *
3060
- * The reason for doing it like this is that we want existing images to be served as fast as
3061
- * possible, because that is the thing that will happen most of the time.
3062
- *
3063
- * Anything else, such as error handling and creating new conversion is handed off
3064
- * (and only autoloaded when needed)
3065
- */
3066
-
3067
- class ServeExistingOrHandOver extends ServeBase
3068
- {
3069
-
3070
- /**
3071
- * Main method
3072
- */
3073
- public static function serveConverted($source, $destination, $options)
3074
- {
3075
- $server = new ServeExistingOrHandOver($source, $destination, $options);
3076
-
3077
- $server->decideWhatToServe();
3078
- if ($server->whatToServe == 'destination') {
3079
- return $server->serveExisting();
3080
- } else {
3081
- // Load extra php classes, if told to
3082
- if (isset($options['require-for-conversion'])) {
3083
- require($options['require-for-conversion']);
3084
- }
3085
- ServeConverted::serveConverted($source, $destination, $options);
3086
- }
3087
- }
3088
- }
3089
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/build/webp-on-demand-1.inc DELETED
@@ -1,295 +0,0 @@
1
- <?php
2
- ?><?php
3
-
4
- namespace WebPConvert;
5
-
6
- use WebPConvert\Converters\ConverterHelper;
7
- use WebPConvert\ServeExistingOrConvert;
8
- use WebPConvert\Serve\ServeExistingOrHandOver;
9
-
10
- class WebPConvert
11
- {
12
-
13
- /*
14
- @param (string) $source: Absolute path to image to be converted (no backslashes). Image must be jpeg or png
15
- @param (string) $destination: Absolute path (no backslashes)
16
- @param (object) $options: Array of named options, such as 'quality' and 'metadata'
17
- */
18
- public static function convert($source, $destination, $options = [], $logger = null)
19
- {
20
- return ConverterHelper::runConverterStack($source, $destination, $options, $logger);
21
- }
22
-
23
- public static function convertAndServe($source, $destination, $options = [])
24
- {
25
- //return ServeExistingOrConvert::serveExistingOrConvert($source, $destination, $options);
26
- return ServeExistingOrHandOver::serveConverted($source, $destination, $options);
27
- }
28
- }
29
-
30
- ?><?php
31
- namespace WebPConvert\Serve;
32
-
33
- //use WebPConvert\Serve\Report;
34
-
35
- class ServeBase
36
- {
37
- public $source;
38
- public $destination;
39
- public $options;
40
-
41
- // These two fellows are first set when decideWhatToServe is called
42
- // However, if it is decided to serve a fresh conversion, they might get modified.
43
- // If that for example results in a file larger than source, $whatToServe will change
44
- // from 'fresh-conversion' to 'original', and $whyServingThis will change to 'source-lighter'
45
- public $whatToServe = '';
46
- public $whyServingThis = '';
47
-
48
- public function __construct($source, $destination, $options)
49
- {
50
-
51
- $this->source = $source;
52
- $this->destination = $destination;
53
- $this->options = array_merge(self::$defaultOptions, $options);
54
-
55
- $this->setErrorReporting();
56
- }
57
-
58
- public static $defaultOptions = [
59
- 'add-content-type-header' => true,
60
- 'add-last-modified-header' => true,
61
- 'add-vary-header' => true,
62
- 'add-x-header-status' => true,
63
- 'add-x-header-options' => false,
64
- 'aboutToServeImageCallBack' => null,
65
- 'aboutToPerformFailAction' => null,
66
- 'cache-control-header' => 'public, max-age=86400',
67
- 'converters' => ['cwebp', 'gd', 'imagick'],
68
- 'error-reporting' => 'auto',
69
- 'fail' => 'original',
70
- 'fail-when-original-unavailable' => '404',
71
- 'reconvert' => false,
72
- 'serve-original' => false,
73
- 'show-report' => false,
74
- ];
75
-
76
- protected function setErrorReporting()
77
- {
78
- if (($this->options['error-reporting'] === true) ||
79
- (($this->options['error-reporting'] === 'auto') && ($this->options['show-report'] === true))
80
- ) {
81
- error_reporting(E_ALL);
82
- ini_set('display_errors', 'On');
83
- } elseif (($this->options['error-reporting'] === false) ||
84
- (($this->options['error-reporting'] === 'auto') && ($this->options['show-report'] === false))
85
- ) {
86
- error_reporting(0);
87
- ini_set('display_errors', 'Off');
88
- }
89
- }
90
-
91
- protected function header($header, $replace = true)
92
- {
93
- header($header, $replace);
94
- }
95
-
96
- public function addXStatusHeader($text)
97
- {
98
- if ($this->options['add-x-header-status']) {
99
- $this->header('X-WebP-Convert-Status: ' . $text, true);
100
- }
101
- }
102
-
103
- public function addVaryHeader()
104
- {
105
- if ($this->options['add-vary-header']) {
106
- $this->header('Vary: Accept');
107
- }
108
- }
109
-
110
- public function addContentTypeHeader($cType)
111
- {
112
- if ($this->options['add-content-type-header']) {
113
- $this->header('Content-type: ' . $cType);
114
- }
115
- }
116
-
117
- /* $timestamp Unix timestamp */
118
- public function addLastModifiedHeader($timestamp)
119
- {
120
- if ($this->options['add-last-modified-header']) {
121
- $this->header("Last-Modified: " . gmdate("D, d M Y H:i:s", $timestamp) ." GMT", true);
122
- }
123
- }
124
-
125
- public function addCacheControlHeader()
126
- {
127
- if (!empty($this->options['cache-control-header'])) {
128
- $this->header('Cache-Control: ' . $this->options['cache-control-header'], true);
129
- }
130
- }
131
-
132
- public function serveExisting()
133
- {
134
- if (!$this->callAboutToServeImageCallBack('destination')) {
135
- return;
136
- }
137
-
138
- $this->addXStatusHeader('Serving existing converted image');
139
- $this->addVaryHeader();
140
- $this->addContentTypeHeader('image/webp');
141
- $this->addCacheControlHeader();
142
- $this->addLastModifiedHeader(@filemtime($this->destination));
143
-
144
- if (@readfile($this->destination) === false) {
145
- $this->header('X-WebP-Convert-Error: Could not read file');
146
- return false;
147
- }
148
- return true;
149
- }
150
-
151
- /**
152
- * Called immidiately before serving image (either original, already converted or fresh)
153
- * $whatToServe can be 'source' | 'destination' | 'fresh-conversion'
154
- * $whyServingThis can be:
155
- * for 'source':
156
- * - "explicitly-told-to" (when the "original" option is set)
157
- * - "source-lighter" (when original image is actually smaller than the converted)
158
- * for 'fresh-conversion':
159
- * - "explicitly-told-to" (when the "reconvert" option is set)
160
- * - "source-modified" (when source is newer than existing)
161
- * - "no-existing" (when there is no existing at the destination)
162
- * for 'destination':
163
- * - "no-reason-not-to" (it is lighter than source, its not older,
164
- * and we were not told to do otherwise)
165
- */
166
- protected function callAboutToServeImageCallBack($whatToServe)
167
- {
168
- if (!isset($this->options['aboutToServeImageCallBack'])) {
169
- return true;
170
- }
171
- $result = call_user_func(
172
- $this->options['aboutToServeImageCallBack'],
173
- $whatToServe,
174
- $this->whyServingThis,
175
- $this
176
- );
177
- return ($result !== false);
178
- }
179
-
180
- /**
181
- * Decides what to serve.
182
- * Returns array. First item is what to do, second is additional info.
183
- * First item can be one of these:
184
- * - "destination" (serve existing converted image at the destination path)
185
- * - "no-reason-not-to"
186
- * - "source"
187
- * - "explicitly-told-to"
188
- * - "source-lighter"
189
- * - "fresh-conversion" (note: this may still fail)
190
- * - "explicitly-told-to"
191
- * - "source-modified"
192
- * - "no-existing"
193
- * - "fail"
194
- * - "Missing destination argument"
195
- * - "critical-fail" (a failure where the source file cannot be served)
196
- * - "Missing source argument"
197
- * - "Source file was not found!"
198
- * - "report"
199
- */
200
- public function decideWhatToServe()
201
- {
202
- $decisionArr = $this->doDecideWhatToServe();
203
- $this->whatToServe = $decisionArr[0];
204
- $this->whyServingThis = $decisionArr[1];
205
- }
206
-
207
- private function doDecideWhatToServe()
208
- {
209
- if (empty($this->source)) {
210
- return ['critical-fail', 'Missing source argument'];
211
- }
212
- if (@!file_exists($this->source)) {
213
- return ['critical-fail', 'Source file was not found!'];
214
- }
215
- if (empty($this->destination)) {
216
- return ['fail', 'Missing destination argument'];
217
- }
218
- if ($this->options['show-report']) {
219
- return ['report', ''];
220
- }
221
- if ($this->options['serve-original']) {
222
- return ['source', 'explicitly-told-to'];
223
- }
224
- if ($this->options['reconvert']) {
225
- return ['fresh-conversion', 'explicitly-told-to'];
226
- }
227
-
228
- if (@file_exists($this->destination)) {
229
- // Reconvert if source file is newer than destination
230
- $timestampSource = @filemtime($this->source);
231
- $timestampDestination = @filemtime($this->destination);
232
- if (($timestampSource !== false) &&
233
- ($timestampDestination !== false) &&
234
- ($timestampSource > $timestampDestination)) {
235
- return ['fresh-conversion', 'source-modified'];
236
- }
237
-
238
- // Serve source if it is smaller than destination
239
- $filesizeDestination = @filesize($this->destination);
240
- $filesizeSource = @filesize($this->source);
241
- if (($filesizeSource !== false) &&
242
- ($filesizeDestination !== false) &&
243
- ($filesizeDestination > $filesizeSource)) {
244
- return ['source', 'source-lighter'];
245
- }
246
-
247
- // Destination exists, and there is no reason left not to serve it
248
- return ['destination', 'no-reason-not-to'];
249
- } else {
250
- return ['fresh-conversion', 'no-existing'];
251
- }
252
- }
253
- }
254
-
255
- ?><?php
256
- namespace WebPConvert\Serve;
257
-
258
- use WebPConvert\Serve\ServeBase;
259
- use WebPConvert\Serve\ServeConverted;
260
-
261
- /**
262
- * This class must determine if an existing converted image can and should be served.
263
- * If so, it must serve it.
264
- * If not, it must hand the task over to ConvertAndServe
265
- *
266
- * The reason for doing it like this is that we want existing images to be served as fast as
267
- * possible, because that is the thing that will happen most of the time.
268
- *
269
- * Anything else, such as error handling and creating new conversion is handed off
270
- * (and only autoloaded when needed)
271
- */
272
-
273
- class ServeExistingOrHandOver extends ServeBase
274
- {
275
-
276
- /**
277
- * Main method
278
- */
279
- public static function serveConverted($source, $destination, $options)
280
- {
281
- $server = new ServeExistingOrHandOver($source, $destination, $options);
282
-
283
- $server->decideWhatToServe();
284
- if ($server->whatToServe == 'destination') {
285
- return $server->serveExisting();
286
- } else {
287
- // Load extra php classes, if told to
288
- if (isset($options['require-for-conversion'])) {
289
- require($options['require-for-conversion']);
290
- }
291
- ServeConverted::serveConverted($source, $destination, $options);
292
- }
293
- }
294
- }
295
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/build/webp-on-demand-2.inc DELETED
@@ -1,2795 +0,0 @@
1
- <?php
2
- ?><?php
3
-
4
- namespace WebPConvert\Exceptions;
5
-
6
- class WebPConvertBaseException extends \Exception
7
- {
8
- }
9
-
10
- ?><?php
11
-
12
- namespace WebPConvert\Loggers;
13
-
14
- abstract class BaseLogger
15
- {
16
- /*
17
- $msg: message to log
18
- $style: null | bold | italic
19
- */
20
- abstract public function log($msg, $style = '');
21
-
22
- abstract public function ln();
23
-
24
- public function logLn($msg, $style = '')
25
- {
26
- $this->log($msg, $style);
27
- $this->ln();
28
- }
29
-
30
- public function logLnLn($msg, $style = '')
31
- {
32
- $this->logLn($msg, $style);
33
- $this->ln();
34
- }
35
- }
36
-
37
- ?><?php
38
-
39
- namespace WebPConvert\Converters;
40
-
41
- //use WebPConvert\Converters\Cwebp;
42
-
43
- use WebPConvert\Exceptions\ConverterNotFoundException;
44
- use WebPConvert\Exceptions\CreateDestinationFileException;
45
- use WebPConvert\Exceptions\CreateDestinationFolderException;
46
- use WebPConvert\Exceptions\InvalidFileExtensionException;
47
- use WebPConvert\Exceptions\TargetNotFoundException;
48
-
49
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
50
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
51
-
52
- class ConverterHelper
53
- {
54
- public static $availableConverters = ['cwebp', 'gd', 'imagick', 'gmagick', 'imagickbinary', 'wpc', 'ewww'];
55
- public static $localConverters = ['cwebp', 'gd', 'imagick', 'gmagick', 'imagickbinary'];
56
-
57
- public static $allowedExtensions = ['jpg', 'jpeg', 'png'];
58
-
59
- public static $defaultOptions = [
60
- 'quality' => 'auto',
61
- 'max-quality' => 85,
62
- 'default-quality' => 75,
63
- 'metadata' => 'none',
64
- 'method' => 6,
65
- 'low-memory' => false,
66
- 'lossless' => false,
67
- 'converters' => ['cwebp', 'gd', 'imagick', 'gmagick'],
68
- 'converter-options' => []
69
- ];
70
-
71
- public static function mergeOptions($options, $extraOptions)
72
- {
73
- return $options;
74
- }
75
-
76
- public static function getClassNameOfConverter($converterId)
77
- {
78
- return 'WebPConvert\\Converters\\' . ucfirst($converterId);
79
- }
80
-
81
- /* Call the "convert" method on a converter, by id.
82
- - but also prepares options (merges in the $extraOptions of the converter),
83
- prepares destination folder, and runs some standard validations
84
- If it fails, it throws an exception. Otherwise it don't (there is no return value)
85
- */
86
- public static function runConverter(
87
- $converterId,
88
- $source,
89
- $destination,
90
- $options = [],
91
- $prepareDestinationFolder = true,
92
- $logger = null
93
- ) {
94
-
95
-
96
- if ($prepareDestinationFolder) {
97
- self::prepareDestinationFolderAndRunCommonValidations($source, $destination);
98
- }
99
-
100
- if (!isset($logger)) {
101
- $logger = new \WebPConvert\Loggers\VoidLogger();
102
- }
103
-
104
- $className = self::getClassNameOfConverter($converterId);
105
- if (!is_callable([$className, 'convert'])) {
106
- throw new ConverterNotFoundException();
107
- }
108
-
109
- // Prepare options.
110
- // - Remove 'converters'
111
- $defaultOptions = self::$defaultOptions;
112
- unset($defaultOptions['converters']);
113
-
114
- // - Merge defaults of the converters extra options into the standard default options.
115
- $defaultOptions = array_merge($defaultOptions, array_column($className::$extraOptions, 'default', 'name'));
116
-
117
- // - Merge $defaultOptions into provided options
118
- $options = array_merge($defaultOptions, $options);
119
-
120
- // Individual converters do not accept quality = auto. They need a number.
121
- // Change $options['quality'] to number, based on quality of source and several settings
122
-
123
- self::processQualityOption($source, $options, $logger);
124
-
125
- call_user_func(
126
- [$className, 'doConvert'],
127
- $source,
128
- $destination,
129
- $options,
130
- $logger
131
- );
132
-
133
- if (!@file_exists($destination)) {
134
- throw new ConverterFailedException('Destination file is not there');
135
- } elseif (@filesize($destination) === 0) {
136
- @unlink($destination);
137
- throw new ConverterFailedException('Destination file was completely empty');
138
- } else {
139
- $sourceSize = @filesize($source);
140
- if ($sourceSize !== false) {
141
- $msg = 'Success. ';
142
- $msg .= 'Reduced file size with ' .
143
- round((filesize($source) - filesize($destination))/filesize($source) * 100) . '% ';
144
-
145
- if ($sourceSize < 10000) {
146
- $msg .= '(went from ' . round(filesize($source)) . ' bytes to ';
147
- $msg .= round(filesize($destination)) . ' bytes)';
148
- } else {
149
- $msg .= '(went from ' . round(filesize($source)/1024) . ' kb to ';
150
- $msg .= round(filesize($destination)/1024) . ' kb)';
151
- }
152
- $logger->logLn($msg);
153
- }
154
- }
155
- }
156
-
157
- public static function runConverterWithTiming(
158
- $converterId,
159
- $source,
160
- $destination,
161
- $options = [],
162
- $prepareDestinationFolder = true,
163
- $logger = null
164
- ) {
165
- $beginTime = microtime(true);
166
- if (!isset($logger)) {
167
- $logger = new \WebPConvert\Loggers\VoidLogger();
168
- }
169
- try {
170
- self::runConverter($converterId, $source, $destination, $options, $prepareDestinationFolder, $logger);
171
- $logger->logLn(
172
- 'Successfully converted image in ' .
173
- round((microtime(true) - $beginTime) * 1000) . ' ms'
174
- );
175
- } catch (\Exception $e) {
176
- $logger->logLn('Failed in ' . round((microtime(true) - $beginTime) * 1000) . ' ms');
177
- throw $e;
178
- }
179
- }
180
-
181
- /*
182
- @param (string) $source: Absolute path to image to be converted (no backslashes). Image must be jpeg or png
183
- @param (string) $destination: Absolute path (no backslashes)
184
- @param (object) $options: Array of named options, such as 'quality' and 'metadata'
185
- */
186
- public static function runConverterStack($source, $destination, $options = [], $logger = null)
187
- {
188
- if (!isset($logger)) {
189
- $logger = new \WebPConvert\Loggers\VoidLogger();
190
- }
191
- self::prepareDestinationFolderAndRunCommonValidations($source, $destination);
192
-
193
- $options = array_merge(self::$defaultOptions, $options);
194
-
195
- self::processQualityOption($source, $options, $logger);
196
-
197
- // Force lossless option to true for PNG images
198
- if (self::getExtension($source) == 'png') {
199
- $options['lossless'] = true;
200
- }
201
-
202
- $defaultConverterOptions = $options;
203
- $defaultConverterOptions['converters'] = null;
204
-
205
- $firstFailException = null;
206
-
207
- // If we have set converter options for a converter, which is not in the converter array,
208
- // then we add it to the array
209
- if (isset($options['converter-options'])) {
210
- foreach ($options['converter-options'] as $converterName => $converterOptions) {
211
- if (!in_array($converterName, $options['converters'])) {
212
- $options['converters'][] = $converterName;
213
- }
214
- }
215
- }
216
-
217
- foreach ($options['converters'] as $converter) {
218
- if (is_array($converter)) {
219
- $converterId = $converter['converter'];
220
- $converterOptions = $converter['options'];
221
- } else {
222
- $converterId = $converter;
223
- $converterOptions = [];
224
- if (isset($options['converter-options'][$converterId])) {
225
- // Note: right now, converter-options are not meant to be used,
226
- // when you have several converters of the same type
227
- $converterOptions = $options['converter-options'][$converterId];
228
- }
229
- }
230
-
231
- $converterOptions = array_merge($defaultConverterOptions, $converterOptions);
232
-
233
- try {
234
- $logger->logLn('Trying:' . $converterId, 'italic');
235
-
236
- // If quality is different, we must recalculate
237
- if ($converterOptions['quality'] != $defaultConverterOptions['quality']) {
238
- unset($converterOptions['_calculated_quality']);
239
- self::processQualityOption($source, $converterOptions, $logger);
240
- }
241
-
242
- self::runConverterWithTiming($converterId, $source, $destination, $converterOptions, false, $logger);
243
-
244
- $logger->logLn('ok', 'bold');
245
- return true;
246
- } catch (\WebPConvert\Converters\Exceptions\ConverterNotOperationalException $e) {
247
- // $logger->logLnLn($e->description . ' : ' . $e->getMessage());
248
- $logger->logLnLn($e->getMessage());
249
-
250
- // The converter is not operational.
251
- // Well, well, we will just have to try the next, then
252
- } catch (\WebPConvert\Converters\Exceptions\ConverterFailedException $e) {
253
- $logger->logLnLn($e->getMessage());
254
-
255
- // Converter failed in an anticipated, yet somewhat surprising fashion.
256
- // The converter seemed operational - requirements was in order - but it failed anyway.
257
- // This is moderately bad.
258
- // If some other converter can handle the conversion, we will let this one go.
259
- // But if not, we shall throw the exception
260
-
261
- if (!$firstFailException) {
262
- $firstFailException = $e;
263
- }
264
- } catch (\WebPConvert\Converters\Exceptions\ConversionDeclinedException $e) {
265
- $logger->logLnLn($e->getMessage());
266
-
267
- // The converter declined.
268
- // Gd is for example throwing this, when asked to convert a PNG, but configured not to
269
- // We also possibly rethrow this, because it may have come as a surprise to the user
270
- // who perhaps only tested jpg
271
- if (!$firstFailException) {
272
- $firstFailException = $e;
273
- }
274
- }
275
- }
276
-
277
- if ($firstFailException) {
278
- // At least one converter failed or declined.
279
- $logger->logLn('Conversion failed. None of the tried converters could convert the image', 'bold');
280
- } else {
281
- // All converters threw a ConverterNotOperationalException
282
- $logger->logLn('Conversion failed. None of the tried converters are operational', 'bold');
283
- }
284
-
285
- // No converters could do the job.
286
- // If one of them failed moderately bad, rethrow that exception.
287
- if ($firstFailException) {
288
- throw $firstFailException;
289
- }
290
-
291
- return false;
292
- }
293
-
294
- /* Try to detect quality of jpeg.
295
- If not possible, nothing is returned (null). Otherwise quality is returned (int)
296
- */
297
- public static function detectQualityOfJpg($filename)
298
- {
299
- // Try Imagick extension
300
- if (extension_loaded('imagick') && class_exists('\\Imagick')) {
301
- // Do not risk uncaught ImagickException when trying to detect quality of jpeg
302
- // (it can happen in the rare case, there is no jpeg delegate)
303
- try {
304
- $img = new \Imagick($filename);
305
-
306
- // The required function is available as from PECL imagick v2.2.2
307
- // (you can see your version like this: phpversion("imagick"))
308
- if (method_exists($img, 'getImageCompressionQuality')) {
309
- return $img->getImageCompressionQuality();
310
- }
311
- } catch (\Exception $e) {
312
- // do nothing.
313
- }
314
- }
315
-
316
- // Gmagick extension doesn't support dectecting image quality (yet):
317
- // https://bugs.php.net/bug.php?id=63939
318
- // It is not supported in 2.0.5RC1. But perhaps there is a new version out now?
319
- // Check here: https://pecl.php.net/package-changelog.php?package=gmagick
320
-
321
- if (function_exists('shell_exec')) {
322
- // Try Imagick
323
- $quality = shell_exec("identify -format '%Q' " . escapeshellarg($filename));
324
- if ($quality) {
325
- return intval($quality);
326
- }
327
-
328
- // Try GraphicsMagick
329
- $quality = shell_exec("gm identify -format '%Q' " . escapeshellarg($filename));
330
- if ($quality) {
331
- return intval($quality);
332
- }
333
- }
334
- }
335
-
336
- public static function processQualityOption($source, &$options, $logger)
337
- {
338
- if (isset($options['_calculated_quality'])) {
339
- return;
340
- }
341
-
342
- if ($options['quality'] == 'auto') {
343
- $q = self::detectQualityOfJpg($source);
344
- //$logger->log('Quality set to auto... Quality of source: ');
345
- if (!$q) {
346
- $q = $options['default-quality'];
347
- $logger->logLn(
348
- 'Quality of source could not be established (Imagick or GraphicsMagick is required)' .
349
- ' - Using default instead (' . $options['default-quality'] . ').'
350
- );
351
-
352
- // this allows the wpc converter to know
353
- $options['_quality_could_not_be_detected'] = true;
354
- } else {
355
- if ($q > $options['max-quality']) {
356
- $logger->log(
357
- 'Quality of source is ' . $q . '. ' .
358
- 'This is higher than max-quality, so using that instead (' . $options['max-quality'] . ')'
359
- );
360
- } else {
361
- $logger->log('Quality set to same as source: ' . $q);
362
- }
363
- }
364
- $logger->ln();
365
- $q = min($q, $options['max-quality']);
366
-
367
- $options['_calculated_quality'] = $q;
368
- //$logger->logLn('Using quality: ' . $options['quality']);
369
- } else {
370
- $logger->logLn(
371
- 'Quality: ' . $options['quality'] . '. ' .
372
- 'Consider setting quality to "auto" instead. It is generally a better idea'
373
- );
374
- $options['_calculated_quality'] = $options['quality'];
375
- }
376
- $logger->ln();
377
- }
378
-
379
-
380
- public static function getExtension($filePath)
381
- {
382
- $fileExtension = pathinfo($filePath, PATHINFO_EXTENSION);
383
- return strtolower($fileExtension);
384
- }
385
-
386
- // Throws an exception if the provided file doesn't exist
387
- public static function isValidTarget($filePath)
388
- {
389
- if (!@file_exists($filePath)) {
390
- throw new TargetNotFoundException('File or directory not found: ' . $filePath);
391
- }
392
-
393
- return true;
394
- }
395
-
396
- // Throws an exception if the provided file's extension is invalid
397
- public static function isAllowedExtension($filePath)
398
- {
399
- $fileExtension = pathinfo($filePath, PATHINFO_EXTENSION);
400
- if (!in_array(strtolower($fileExtension), self::$allowedExtensions)) {
401
- throw new InvalidFileExtensionException('Unsupported file extension: ' . $fileExtension);
402
- }
403
-
404
- return true;
405
- }
406
-
407
- // Creates folder in provided path & sets correct permissions
408
- // also deletes the file at filePath (if it already exists)
409
- public static function createWritableFolder($filePath)
410
- {
411
- $folder = dirname($filePath);
412
- if (!@file_exists($folder)) {
413
- // TODO: what if this is outside open basedir?
414
- // see http://php.net/manual/en/ini.core.php#ini.open-basedir
415
-
416
- // First, we have to figure out which permissions to set.
417
- // We want same permissions as parent folder
418
- // But which parent? - the parent to the first missing folder
419
-
420
- $parentFolders = explode('/', $folder);
421
- $poppedFolders = [];
422
-
423
- while (!(@file_exists(implode('/', $parentFolders))) && count($parentFolders) > 0) {
424
- array_unshift($poppedFolders, array_pop($parentFolders));
425
- }
426
-
427
- // Retrieving permissions of closest existing folder
428
- $closestExistingFolder = implode('/', $parentFolders);
429
- $permissions = @fileperms($closestExistingFolder) & 000777;
430
- $stat = @stat($closestExistingFolder);
431
-
432
- // Trying to create the given folder (recursively)
433
- if (!@mkdir($folder, $permissions, true)) {
434
- throw new CreateDestinationFolderException('Failed creating folder: ' . $folder);
435
- }
436
-
437
- // `mkdir` doesn't always respect permissions, so we have to `chmod` each created subfolder
438
- foreach ($poppedFolders as $subfolder) {
439
- $closestExistingFolder .= '/' . $subfolder;
440
- // Setting directory permissions
441
- if ($permissions !== false) {
442
- @chmod($folder, $permissions);
443
- }
444
- if ($stat !== false) {
445
- if (isset($stat['uid'])) {
446
- @chown($folder, $stat['uid']);
447
- }
448
- if (isset($stat['gid'])) {
449
- @chgrp($folder, $stat['gid']);
450
- }
451
- }
452
- }
453
- }
454
-
455
- if (@file_exists($filePath)) {
456
- // A file already exists in this folder...
457
- // We delete it, to make way for a new webp
458
- if (!@unlink($filePath)) {
459
- throw new CreateDestinationFileException(
460
- 'Existing file cannot be removed: ' . basename($filePath)
461
- );
462
- }
463
- }
464
-
465
- return true;
466
- }
467
-
468
- public static function prepareDestinationFolderAndRunCommonValidations($source, $destination)
469
- {
470
- self::isValidTarget($source);
471
- self::isAllowedExtension($source);
472
- self::createWritableFolder($destination);
473
- }
474
-
475
- public static function initCurlForConverter()
476
- {
477
- if (!extension_loaded('curl')) {
478
- throw new ConverterNotOperationalException('Required cURL extension is not available.');
479
- }
480
-
481
- if (!function_exists('curl_init')) {
482
- throw new ConverterNotOperationalException('Required url_init() function is not available.');
483
- }
484
-
485
- if (!function_exists('curl_file_create')) {
486
- throw new ConverterNotOperationalException(
487
- 'Required curl_file_create() function is not available (requires PHP > 5.5).'
488
- );
489
- }
490
-
491
- $ch = curl_init();
492
- if (!$ch) {
493
- throw new ConverterNotOperationalException('Could not initialise cURL.');
494
- }
495
- return $ch;
496
- }
497
- }
498
-
499
- ?><?php
500
-
501
- namespace WebPConvert\Converters;
502
-
503
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
504
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
505
-
506
- class Cwebp
507
- {
508
- public static $extraOptions = [
509
- [
510
- 'name' => 'use-nice',
511
- 'type' => 'boolean',
512
- 'sensitive' => false,
513
- 'default' => false,
514
- 'required' => false
515
- ],
516
- // low-memory is defined for all, in ConverterHelper
517
- [
518
- 'name' => 'try-common-system-paths',
519
- 'type' => 'boolean',
520
- 'sensitive' => false,
521
- 'default' => true,
522
- 'required' => false
523
- ],
524
- [
525
- 'name' => 'try-supplied-binary-for-os',
526
- 'type' => 'boolean',
527
- 'sensitive' => false,
528
- 'default' => true,
529
- 'required' => false
530
- ],
531
- [
532
- 'name' => 'size-in-percentage',
533
- 'type' => 'number',
534
- 'sensitive' => false,
535
- 'default' => null,
536
- 'required' => false
537
- ],
538
- [
539
- 'name' => 'command-line-options',
540
- 'type' => 'string',
541
- 'sensitive' => false,
542
- 'default' => '',
543
- 'required' => false
544
- ],
545
- [
546
- 'name' => 'rel-path-to-precompiled-binaries',
547
- 'type' => 'string',
548
- 'sensitive' => false,
549
- 'default' => './Binaries',
550
- 'required' => false
551
- ],
552
- ];
553
-
554
- public static function convert($source, $destination, $options = [])
555
- {
556
- ConverterHelper::runConverter('cwebp', $source, $destination, $options, true);
557
- }
558
-
559
- // System paths to look for cwebp binary
560
- private static $cwebpDefaultPaths = [
561
- '/usr/bin/cwebp',
562
- '/usr/local/bin/cwebp',
563
- '/usr/gnu/bin/cwebp',
564
- '/usr/syno/bin/cwebp'
565
- ];
566
-
567
- // OS-specific binaries included in this library, along with hashes
568
- private static $suppliedBinariesInfo = [
569
- 'WinNT' => [ 'cwebp.exe', '49e9cb98db30bfa27936933e6fd94d407e0386802cb192800d9fd824f6476873'],
570
- 'Darwin' => [ 'cwebp-mac12', 'a06a3ee436e375c89dbc1b0b2e8bd7729a55139ae072ed3f7bd2e07de0ebb379'],
571
- 'SunOS' => [ 'cwebp-sol', '1febaffbb18e52dc2c524cda9eefd00c6db95bc388732868999c0f48deb73b4f'],
572
- 'FreeBSD' => [ 'cwebp-fbsd', 'e5cbea11c97fadffe221fdf57c093c19af2737e4bbd2cb3cd5e908de64286573'],
573
- 'Linux' => [ 'cwebp-linux', '916623e5e9183237c851374d969aebdb96e0edc0692ab7937b95ea67dc3b2568']
574
- ];
575
-
576
- private static function escapeFilename($string)
577
- {
578
-
579
- // filter_var() is should normally be available, but it is not always
580
- // - https://stackoverflow.com/questions/11735538/call-to-undefined-function-filter-var
581
- if (function_exists('filter_var')) {
582
- // Sanitize quotes
583
- $string = filter_var($string, FILTER_SANITIZE_MAGIC_QUOTES);
584
-
585
- // Stripping control characters
586
- // see https://stackoverflow.com/questions/12769462/filter-flag-strip-low-vs-filter-flag-strip-high
587
- $string = filter_var($string, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
588
- }
589
-
590
- // Escaping whitespace. Must be done *after* filter_var!
591
- $string = preg_replace('/\s/', '\\ ', $string);
592
-
593
- return $string;
594
- }
595
-
596
- // Checks if 'Nice' is available
597
- private static function hasNiceSupport()
598
- {
599
- exec("nice 2>&1", $niceOutput);
600
-
601
- if (is_array($niceOutput) && isset($niceOutput[0])) {
602
- if (preg_match('/usage/', $niceOutput[0]) || (preg_match('/^\d+$/', $niceOutput[0]))) {
603
- /*
604
- * Nice is available - default niceness (+10)
605
- * https://www.lifewire.com/uses-of-commands-nice-renice-2201087
606
- * https://www.computerhope.com/unix/unice.htm
607
- */
608
-
609
- return true;
610
- }
611
-
612
- return false;
613
- }
614
- }
615
-
616
- private static function executeBinary($binary, $commandOptions, $useNice, $logger)
617
- {
618
- $command = ($useNice ? 'nice ' : '') . $binary . ' ' . $commandOptions;
619
-
620
- //$logger->logLn('command options:' . $commandOptions);
621
- //$logger->logLn('Trying to execute binary:' . $binary);
622
- exec($command, $output, $returnCode);
623
- //$logger->logLn(self::msgForExitCode($returnCode));
624
- return intval($returnCode);
625
- }
626
-
627
- // Although this method is public, do not call directly.
628
- public static function doConvert($source, $destination, $options, $logger)
629
- {
630
- $errorMsg = '';
631
- // Force lossless option to true for PNG images
632
- if (ConverterHelper::getExtension($source) == 'png') {
633
- $options['lossless'] = true;
634
- }
635
-
636
- if (!function_exists('exec')) {
637
- throw new ConverterNotOperationalException('exec() is not enabled.');
638
- }
639
-
640
- /*
641
- * Prepare cwebp options
642
- */
643
-
644
- $commandOptionsArray = [];
645
-
646
- // Metadata (all, exif, icc, xmp or none (default))
647
- // Comma-separated list of existing metadata to copy from input to output
648
- $commandOptionsArray[] = '-metadata ' . $options['metadata'];
649
-
650
- // Size
651
- if (!is_null($options['size-in-percentage'])) {
652
- $sizeSource = @filesize($source);
653
- if ($sizeSource !== false) {
654
- $targetSize = floor($sizeSource * $options['size-in-percentage'] / 100);
655
- }
656
- }
657
- if (isset($targetSize)) {
658
- $commandOptionsArray[] = '-size ' . $targetSize;
659
- } else {
660
- // Image quality
661
- $commandOptionsArray[] = '-q ' . $options['_calculated_quality'];
662
- }
663
-
664
-
665
- // Losless PNG conversion
666
- $commandOptionsArray[] = ($options['lossless'] ? '-lossless' : '');
667
-
668
- // Built-in method option
669
- $commandOptionsArray[] = '-m ' . strval($options['method']);
670
-
671
- // Built-in low memory option
672
- if ($options['low-memory']) {
673
- $commandOptionsArray[] = '-low_memory';
674
- }
675
-
676
- // command-line-options
677
- if ($options['command-line-options']) {
678
- $arr = explode(' -', ' ' . $options['command-line-options']);
679
- foreach ($arr as $cmdOption) {
680
- $pos = strpos($cmdOption, ' ');
681
- $cName = '';
682
- $cValue = '';
683
- if (!$pos) {
684
- $cName = $cmdOption;
685
- if ($cName == '') {
686
- continue;
687
- }
688
- $commandOptionsArray[] = '-' . $cName;
689
- } else {
690
- $cName = substr($cmdOption, 0, $pos);
691
- $cValues = substr($cmdOption, $pos + 1);
692
- $cValuesArr = explode(' ', $cValues);
693
- foreach ($cValuesArr as &$cArg) {
694
- $cArg = escapeshellarg($cArg);
695
- }
696
- $cValues = implode(' ', $cValuesArr);
697
- $commandOptionsArray[] = '-' . $cName . ' ' . $cValues;
698
- }
699
- }
700
- }
701
-
702
- // Source file
703
- //$commandOptionsArray[] = self::escapeFilename($source);
704
- $commandOptionsArray[] = escapeshellarg($source);
705
-
706
- // Output
707
- $commandOptionsArray[] = '-o ' . escapeshellarg($destination);
708
-
709
- // Redirect stderr to same place as stdout
710
- // https://www.brianstorti.com/understanding-shell-script-idiom-redirect/
711
- $commandOptionsArray[] = '2>&1';
712
-
713
-
714
- $useNice = (($options['use-nice']) && self::hasNiceSupport()) ? true : false;
715
-
716
- $commandOptions = implode(' ', $commandOptionsArray);
717
-
718
- $logger->logLn('cwebp options:' . $commandOptions);
719
-
720
- // Init with common system paths
721
- $cwebpPathsToTest = self::$cwebpDefaultPaths;
722
-
723
- // Remove paths that doesn't exist
724
- /*
725
- $cwebpPathsToTest = array_filter($cwebpPathsToTest, function ($binary) {
726
- //return file_exists($binary);
727
- return @is_readable($binary);
728
- });
729
- */
730
-
731
- // Try all common paths that exists
732
- $success = false;
733
- $failures = [];
734
- $failureCodes = [];
735
-
736
- if (!$options['try-supplied-binary-for-os'] && !$options['try-common-system-paths']) {
737
- $errorMsg .= 'Configured to neither look for cweb binaries in common system locations, ' .
738
- 'nor to use one of the supplied precompiled binaries. But these are the only ways ' .
739
- 'this converter can convert images. No conversion can be made!';
740
- }
741
-
742
- if ($options['try-common-system-paths']) {
743
- foreach ($cwebpPathsToTest as $index => $binary) {
744
- $returnCode = self::executeBinary($binary, $commandOptions, $useNice, $logger);
745
- if ($returnCode == 0) {
746
- $logger->logLn('Successfully executed binary: ' . $binary);
747
- $success = true;
748
- break;
749
- } else {
750
- $failures[] = [$binary, $returnCode];
751
- if (!in_array($returnCode, $failureCodes)) {
752
- $failureCodes[] = $returnCode;
753
- }
754
- }
755
- }
756
- $majorFailCode = 0;
757
- if (!$success) {
758
- if (count($failureCodes) == 1) {
759
- $majorFailCode = $failureCodes[0];
760
- switch ($majorFailCode) {
761
- case 126:
762
- $errorMsg = 'Permission denied. The user that the command was run with (' .
763
- shell_exec('whoami') . ') does not have permission to execute any of the ' .
764
- 'cweb binaries found in common system locations. ';
765
- break;
766
- case 127:
767
- $errorMsg .= 'Found no cwebp binaries in any common system locations. ';
768
- break;
769
- default:
770
- $errorMsg .= 'Tried executing cwebp binaries in common system locations. ' .
771
- 'All failed (exit code: ' . $majorFailCode . '). ';
772
- }
773
- } else {
774
- /**
775
- * $failureCodesBesides127 is used to check first position ($failureCodesBesides127[0])
776
- * however position can vary as index can be 1 or something else. array_values() would
777
- * always start from 0.
778
- */
779
- $failureCodesBesides127 = array_values(array_diff($failureCodes, [127]));
780
-
781
- if (count($failureCodesBesides127) == 1) {
782
- $majorFailCode = $failureCodesBesides127[0];
783
- switch ($returnCode) {
784
- case 126:
785
- $errorMsg = 'Permission denied. The user that the command was run with (' .
786
- shell_exec('whoami') . ') does not have permission to execute any of the cweb ' .
787
- 'binaries found in common system locations. ';
788
- break;
789
- default:
790
- $errorMsg .= 'Tried executing cwebp binaries in common system locations. ' .
791
- 'All failed (exit code: ' . $majorFailCode . '). ';
792
- }
793
- } else {
794
- $errorMsg .= 'None of the cwebp binaries in the common system locations could be executed ' .
795
- '(mixed results - got the following exit codes: ' . implode(',', $failureCodes) . '). ';
796
- }
797
- }
798
- }
799
- }
800
-
801
- if (!$success && $options['try-supplied-binary-for-os']) {
802
- // Try supplied binary (if available for OS, and hash is correct)
803
- if (isset(self::$suppliedBinariesInfo[PHP_OS])) {
804
- $info = self::$suppliedBinariesInfo[PHP_OS];
805
-
806
- $file = $info[0];
807
- $hash = $info[1];
808
-
809
- $binaryFile = __DIR__ . '/' . $options['rel-path-to-precompiled-binaries'] . '/' . $file;
810
-
811
- // The file should exist, but may have been removed manually.
812
- if (@file_exists($binaryFile)) {
813
- // File exists, now generate its hash
814
-
815
- // hash_file() is normally available, but it is not always
816
- // - https://stackoverflow.com/questions/17382712/php-5-3-20-undefined-function-hash
817
- // If available, validate that hash is correct.
818
- $proceedAfterHashCheck = true;
819
- if (function_exists('hash_file')) {
820
- $binaryHash = hash_file('sha256', $binaryFile);
821
-
822
- if ($binaryHash != $hash) {
823
- $errorMsg .= 'Binary checksum of supplied binary is invalid! ' .
824
- 'Did you transfer with FTP, but not in binary mode? ' .
825
- 'File:' . $binaryFile . '. ' .
826
- 'Expected checksum: ' . $hash . '. ' .
827
- 'Actual checksum:' . $binaryHash . '.';
828
- $proceedAfterHashCheck = false;
829
- }
830
- }
831
- if ($proceedAfterHashCheck) {
832
- $returnCode = self::executeBinary($binaryFile, $commandOptions, $useNice, $logger);
833
- if ($returnCode == 0) {
834
- $success = true;
835
- } else {
836
- $errorMsg .= 'Tried executing supplied binary for ' . PHP_OS . ', ' .
837
- ($options['try-common-system-paths'] ? 'but that failed too' : 'but failed');
838
- if ($options['try-common-system-paths'] && ($majorFailCode > 0)) {
839
- $errorMsg .= ' (same error)';
840
- } else {
841
- if ($returnCode > 128) {
842
- $errorMsg .= '. The binary did not work (exit code: ' . $returnCode . '). ' .
843
- 'Check out https://github.com/rosell-dk/webp-convert/issues/92';
844
- } else {
845
- switch ($returnCode) {
846
- case 0:
847
- $success = true;
848
- ;
849
- break;
850
- case 126:
851
- $errorMsg .= ': Permission denied. The user that the command was run' .
852
- ' with (' . shell_exec('whoami') . ') does not have permission to ' .
853
- 'execute that binary.';
854
- break;
855
- case 127:
856
- $errorMsg .= '. The binary was not found! ' .
857
- 'It ought to be here: ' . $binaryFile;
858
- break;
859
- default:
860
- $errorMsg .= ' (exit code:' . $returnCode . ').';
861
- }
862
- }
863
- }
864
- }
865
- }
866
- } else {
867
- $errorMsg .= 'Supplied binary not found! It ought to be here:' . $binaryFile;
868
- }
869
- } else {
870
- $errorMsg .= 'No supplied binaries found for OS:' . PHP_OS;
871
- }
872
- }
873
-
874
-
875
-
876
- // cwebp sets file permissions to 664 but instead ..
877
- // .. $destination's parent folder's permissions should be used (except executable bits)
878
- if ($success) {
879
- $destinationParent = dirname($destination);
880
- $fileStatistics = @stat($destinationParent);
881
- if ($fileStatistics !== false) {
882
- // Apply same permissions as parent folder but strip off the executable bits
883
- $permissions = $fileStatistics['mode'] & 0000666;
884
- @chmod($destination, $permissions);
885
- }
886
- }
887
-
888
- if (!$success) {
889
- throw new ConverterNotOperationalException($errorMsg);
890
- }
891
- }
892
- }
893
-
894
- ?><?php
895
-
896
- namespace WebPConvert\Converters;
897
-
898
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
899
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
900
-
901
- class Ewww
902
- {
903
- public static $extraOptions = [
904
- [
905
- 'name' => 'key',
906
- 'type' => 'string',
907
- 'sensitive' => true,
908
- 'default' => '',
909
- 'required' => true
910
- ],
911
- ];
912
-
913
- public static function convert($source, $destination, $options = [])
914
- {
915
- ConverterHelper::runConverter('ewww', $source, $destination, $options, true);
916
- }
917
-
918
- // Took this parser from Drupal
919
- private static function parseSize($size)
920
- {
921
-
922
- $unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
923
- $size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
924
- if ($unit) {
925
- // Find the position of the unit in the ordered string which is the power
926
- // of magnitude to multiply a kilobyte by.
927
- return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
928
- } else {
929
- return round($size);
930
- }
931
- }
932
-
933
- // Although this method is public, do not call directly.
934
- public static function doConvert($source, $destination, $options, $logger)
935
- {
936
- if ($options['key'] == '') {
937
- throw new ConverterNotOperationalException('Missing API key.');
938
- }
939
- if (strlen($options['key']) < 20) {
940
- throw new ConverterNotOperationalException(
941
- 'Key is invalid. Keys are supposed to be 32 characters long - your key is much shorter'
942
- );
943
- }
944
-
945
- $keyStatus = self::getKeyStatus($options['key']);
946
- switch ($keyStatus) {
947
- case 'great':
948
- break;
949
- case 'exceeded':
950
- throw new ConverterNotOperationalException('quota has exceeded');
951
- break;
952
- case 'invalid':
953
- throw new ConverterNotOperationalException('key is invalid');
954
- break;
955
- }
956
-
957
- $fileSize = @filesize($source);
958
- if ($fileSize !== false) {
959
- $uploadMaxSize = self::parseSize(ini_get('upload_max_filesize'));
960
- if (($uploadMaxSize !== false) && ($uploadMaxSize < $fileSize)) {
961
- throw new ConverterFailedException(
962
- 'File is larger than your max upload (set in your php.ini). File size:' .
963
- round($fileSize/1024) . ' kb. ' .
964
- 'upload_max_filesize in php.ini: ' . ini_get('upload_max_filesize') .
965
- ' (parsed as ' . round($uploadMaxSize/1024) . ' kb)'
966
- );
967
- }
968
-
969
- $postMaxSize = self::parseSize(ini_get('post_max_size'));
970
- if (($postMaxSize !== false) && ($postMaxSize < $fileSize)) {
971
- throw new ConverterFailedException(
972
- 'File is larger than your post_max_size limit (set in your php.ini). File size:' .
973
- round($fileSize/1024) . ' kb. ' .
974
- 'post_max_size in php.ini: ' . ini_get('post_max_size') .
975
- ' (parsed as ' . round($postMaxSize/1024) . ' kb)'
976
- );
977
- }
978
-
979
- // ini_get('memory_limit')
980
- }
981
-
982
-
983
- $ch = ConverterHelper::initCurlForConverter();
984
-
985
- $curlOptions = [
986
- 'api_key' => $options['key'],
987
- 'webp' => '1',
988
- 'file' => curl_file_create($source),
989
- 'domain' => $_SERVER['HTTP_HOST'],
990
- 'quality' => $options['_calculated_quality'],
991
- 'metadata' => ($options['metadata'] == 'none' ? '0' : '1')
992
- ];
993
-
994
- curl_setopt_array(
995
- $ch,
996
- [
997
- CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
998
- CURLOPT_HTTPHEADER => [
999
- 'User-Agent: WebPConvert',
1000
- 'Accept: image/*'
1001
- ],
1002
- CURLOPT_POSTFIELDS => $curlOptions,
1003
- CURLOPT_BINARYTRANSFER => true,
1004
- CURLOPT_RETURNTRANSFER => true,
1005
- CURLOPT_HEADER => false,
1006
- CURLOPT_SSL_VERIFYPEER => false
1007
- ]
1008
- );
1009
-
1010
- $response = curl_exec($ch);
1011
-
1012
- if (curl_errno($ch)) {
1013
- throw new ConverterNotOperationalException(curl_error($ch));
1014
- }
1015
-
1016
- // The API does not always return images.
1017
- // For example, it may return a message such as '{"error":"invalid","t":"exceeded"}
1018
- // Messages has a http content type of ie 'text/html; charset=UTF-8
1019
- // Images has application/octet-stream.
1020
- // So verify that we got an image back.
1021
- if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
1022
- //echo curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
1023
- curl_close($ch);
1024
-
1025
- /* May return this: {"error":"invalid","t":"exceeded"} */
1026
- $responseObj = json_decode($response);
1027
- if (isset($responseObj->error)) {
1028
- //echo 'error:' . $responseObj->error . '<br>';
1029
- //echo $response;
1030
- //self::blacklistKey($key);
1031
- //throw new ConverterNotOperationalException('The key is invalid. Blacklisted it!');
1032
- throw new ConverterNotOperationalException('The key is invalid');
1033
- }
1034
-
1035
- throw new ConverterNotOperationalException(
1036
- 'ewww api did not return an image. It could be that the key is invalid. Response: '
1037
- . $response
1038
- );
1039
- }
1040
-
1041
- // Not sure this can happen. So just in case
1042
- if ($response == '') {
1043
- throw new ConverterNotOperationalException('ewww api did not return anything');
1044
- }
1045
-
1046
- $success = file_put_contents($destination, $response);
1047
-
1048
- if (!$success) {
1049
- throw new ConverterFailedException('Error saving file');
1050
- }
1051
- }
1052
-
1053
- /**
1054
- * Keep subscription alive by optimizing a jpeg
1055
- * (ewww closes accounts after 6 months of inactivity - and webp conversions seems not to be counted? )
1056
- */
1057
- public static function keepSubscriptionAlive($source, $key)
1058
- {
1059
- try {
1060
- $ch = curl_init();
1061
- } catch (\Exception $e) {
1062
- return 'curl is not installed';
1063
- }
1064
- curl_setopt_array(
1065
- $ch,
1066
- [
1067
- CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
1068
- CURLOPT_HTTPHEADER => [
1069
- 'User-Agent: WebPConvert',
1070
- 'Accept: image/*'
1071
- ],
1072
- CURLOPT_POSTFIELDS => [
1073
- 'api_key' => $key,
1074
- 'webp' => '0',
1075
- 'file' => curl_file_create($source),
1076
- 'domain' => $_SERVER['HTTP_HOST'],
1077
- 'quality' => 60,
1078
- 'metadata' => 0
1079
- ],
1080
- CURLOPT_BINARYTRANSFER => true,
1081
- CURLOPT_RETURNTRANSFER => true,
1082
- CURLOPT_HEADER => false,
1083
- CURLOPT_SSL_VERIFYPEER => false
1084
- ]
1085
- );
1086
-
1087
- $response = curl_exec($ch);
1088
- if (curl_errno($ch)) {
1089
- return 'curl error' . curl_error($ch);
1090
- }
1091
- if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
1092
- curl_close($ch);
1093
-
1094
- /* May return this: {"error":"invalid","t":"exceeded"} */
1095
- $responseObj = json_decode($response);
1096
- if (isset($responseObj->error)) {
1097
- return 'The key is invalid';
1098
- }
1099
-
1100
- return 'ewww api did not return an image. It could be that the key is invalid. Response: ' . $response;
1101
- }
1102
-
1103
- // Not sure this can happen. So just in case
1104
- if ($response == '') {
1105
- return 'ewww api did not return anything';
1106
- }
1107
-
1108
- return true;
1109
- }
1110
-
1111
- /*
1112
- public static function blacklistKey($key)
1113
- {
1114
- }
1115
-
1116
- public static function isKeyBlacklisted($key)
1117
- {
1118
- }*/
1119
-
1120
- /**
1121
- * Return "great", "exceeded" or "invalid"
1122
- */
1123
- public static function getKeyStatus($key)
1124
- {
1125
- $ch = ConverterHelper::initCurlForConverter();
1126
-
1127
- curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/verify/");
1128
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1129
- curl_setopt(
1130
- $ch,
1131
- CURLOPT_POSTFIELDS,
1132
- [
1133
- 'api_key' => $key
1134
- ]
1135
- );
1136
-
1137
- // The 403 forbidden is avoided with this line.
1138
- curl_setopt(
1139
- $ch,
1140
- CURLOPT_USERAGENT,
1141
- 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)'
1142
- );
1143
-
1144
- $response = curl_exec($ch);
1145
- // echo $response;
1146
- if (curl_errno($ch)) {
1147
- throw new \Exception(curl_error($ch));
1148
- }
1149
- curl_close($ch);
1150
-
1151
- // Possible responses:
1152
- // “great” = verification successful
1153
- // “exceeded” = indicates a valid key with no remaining image credits.
1154
- // an empty response indicates that the key is not valid
1155
-
1156
- if ($response == '') {
1157
- return 'invalid';
1158
- }
1159
- $responseObj = json_decode($response);
1160
- if (isset($responseObj->error)) {
1161
- if ($responseObj->error == 'invalid') {
1162
- return 'invalid';
1163
- } else {
1164
- throw new \Exception('Ewww returned unexpected error: ' . $response);
1165
- }
1166
- }
1167
- if (!isset($responseObj->status)) {
1168
- throw new \Exception('Ewww returned unexpected response to verify request: ' . $response);
1169
- }
1170
- switch ($responseObj->status) {
1171
- case 'great':
1172
- case 'exceeded':
1173
- return $responseObj->status;
1174
- }
1175
- throw new \Exception('Ewww returned unexpected status to verify request: "' . $responseObj->status . '"');
1176
- }
1177
-
1178
- public static function isWorkingKey($key)
1179
- {
1180
- return (self::getKeyStatus($key) == 'great');
1181
- }
1182
-
1183
- public static function isValidKey($key)
1184
- {
1185
- return (self::getKeyStatus($key) != 'invalid');
1186
- }
1187
-
1188
- public static function getQuota($key)
1189
- {
1190
- $ch = ConverterHelper::initCurlForConverter();
1191
-
1192
- curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/quota/");
1193
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1194
- curl_setopt(
1195
- $ch,
1196
- CURLOPT_POSTFIELDS,
1197
- [
1198
- 'api_key' => $key
1199
- ]
1200
- );
1201
- curl_setopt(
1202
- $ch,
1203
- CURLOPT_USERAGENT,
1204
- 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)'
1205
- );
1206
-
1207
- $response = curl_exec($ch);
1208
- return $response; // ie -830 23. Seems to return empty for invalid keys
1209
- // or empty
1210
- //echo $response;
1211
- }
1212
- }
1213
-
1214
- ?><?php
1215
-
1216
- namespace WebPConvert\Converters;
1217
-
1218
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
1219
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
1220
- use WebPConvert\Converters\Exceptions\ConversionDeclinedException;
1221
-
1222
- use WebPConvert\Converters\ConverterHelper;
1223
-
1224
- class Gd
1225
- {
1226
- public static $extraOptions = [
1227
- [
1228
- 'name' => 'skip-pngs',
1229
- 'type' => 'boolean',
1230
- 'sensitive' => false,
1231
- 'default' => true,
1232
- 'required' => false
1233
- ],
1234
- ];
1235
-
1236
- public static function convert($source, $destination, $options = [])
1237
- {
1238
- ConverterHelper::runConverter('gd', $source, $destination, $options, true);
1239
- }
1240
-
1241
- /**
1242
- *
1243
- * @return Returns TRUE if the convertion was complete, or if the source image already is a true color image,
1244
- * otherwise FALSE is returned.
1245
- */
1246
- public static function makeTrueColor(&$image)
1247
- {
1248
- if (function_exists('imagepalettetotruecolor')) {
1249
- return imagepalettetotruecolor($image);
1250
- } else {
1251
- // Got the workaround here: https://secure.php.net/manual/en/function.imagepalettetotruecolor.php
1252
- if ((function_exists('imageistruecolor') && !imageistruecolor($image))
1253
- || !function_exists('imageistruecolor')
1254
- ) {
1255
- if (self::functionsExist(['imagecreatetruecolor', 'imagealphablending', 'imagecolorallocatealpha',
1256
- 'imagefilledrectangle', 'imagecopy', 'imagedestroy', 'imagesx', 'imagesy'])) {
1257
- $dst = imagecreatetruecolor(imagesx($image), imagesy($image));
1258
-
1259
- //prevent blending with default black
1260
- imagealphablending($dst, false);
1261
-
1262
- //change the RGB values if you need, but leave alpha at 127
1263
- $transparent = imagecolorallocatealpha($dst, 255, 255, 255, 127);
1264
-
1265
- //simpler than flood fill
1266
- imagefilledrectangle($dst, 0, 0, imagesx($image), imagesy($image), $transparent);
1267
-
1268
- imagealphablending($dst, true); //restore default blending
1269
- imagecopy($dst, $image, 0, 0, 0, 0, imagesx($image), imagesy($image));
1270
- imagedestroy($image);
1271
- $image = $dst;
1272
- return true;
1273
- }
1274
- } else {
1275
- return false;
1276
- }
1277
- }
1278
- }
1279
-
1280
- // Although this method is public, do not call directly.
1281
- public static function doConvert($source, $destination, $options, $logger)
1282
- {
1283
- if (!extension_loaded('gd')) {
1284
- throw new ConverterNotOperationalException('Required Gd extension is not available.');
1285
- }
1286
-
1287
- if (!function_exists('imagewebp')) {
1288
- throw new ConverterNotOperationalException(
1289
- 'Required imagewebp() function is not available. It seems Gd has been compiled without webp support.'
1290
- );
1291
- }
1292
-
1293
- switch (ConverterHelper::getExtension($source)) {
1294
- case 'png':
1295
- if (!$options['skip-pngs']) {
1296
- if (!function_exists('imagecreatefrompng')) {
1297
- throw new ConverterNotOperationalException(
1298
- 'Required imagecreatefrompng() function is not available.'
1299
- );
1300
- }
1301
- $image = @imagecreatefrompng($source);
1302
- if (!$image) {
1303
- throw new ConverterFailedException(
1304
- 'imagecreatefrompng("' . $source . '") failed'
1305
- );
1306
- }
1307
- } else {
1308
- throw new ConversionDeclinedException(
1309
- 'PNG file skipped. GD is configured not to convert PNGs'
1310
- );
1311
- }
1312
- break;
1313
- default:
1314
- if (!function_exists('imagecreatefromjpeg')) {
1315
- throw new ConverterNotOperationalException(
1316
- 'Required imagecreatefromjpeg() function is not available.'
1317
- );
1318
- }
1319
- $image = @imagecreatefromjpeg($source);
1320
- if (!$image) {
1321
- throw new ConverterFailedException('imagecreatefromjpeg("' . $source . '") failed');
1322
- }
1323
- }
1324
-
1325
- $mustMakeTrueColor = false;
1326
- if (function_exists('imageistruecolor')) {
1327
- if (imageistruecolor($image)) {
1328
- $logger->logLn('image is true color');
1329
- } else {
1330
- $logger->logLn('image is not true color');
1331
- $mustMakeTrueColor = true;
1332
- }
1333
- } else {
1334
- $logger->logLn('It can not be determined if image is true color');
1335
- $mustMakeTrueColor = true;
1336
- }
1337
- if ($mustMakeTrueColor) {
1338
- $logger->logLn('converting color palette to true color');
1339
- $success = self::makeTrueColor($image);
1340
- if (!$success) {
1341
- $logger->logLn(
1342
- 'Warning: FAILED converting color palette to true color. Continuing, but this does not look good.'
1343
- );
1344
- }
1345
- }
1346
- if (ConverterHelper::getExtension($source) == 'png') {
1347
- if (function_exists('imagealphablending')) {
1348
- if (!imagealphablending($image, true)) {
1349
- $logger->logLn('Warning: imagealphablending() failed');
1350
- }
1351
- } else {
1352
- $logger->logLn(
1353
- 'Warning: imagealphablending() is not available on your system. ' .
1354
- 'Converting PNGs with transparency might fail on some systems'
1355
- );
1356
- }
1357
- if (function_exists('imagesavealpha')) {
1358
- if (!imagesavealpha($image, true)) {
1359
- $logger->logLn('Warning: imagesavealpha() failed');
1360
- }
1361
- } else {
1362
- $logger->logLn(
1363
- 'Warning: imagesavealpha() is not available on your system. ' .
1364
- 'Converting PNGs with transparency might fail on some systems'
1365
- );
1366
- }
1367
- }
1368
-
1369
- $success = @imagewebp($image, $destination, $options['_calculated_quality']);
1370
-
1371
- if (!$success) {
1372
- throw new ConverterFailedException(
1373
- 'Call to imagewebp() failed. Probably failed writing file. Check file permissions!'
1374
- );
1375
- }
1376
-
1377
- /*
1378
- * This hack solves an `imagewebp` bug
1379
- * See https://stackoverflow.com/questions/30078090/imagewebp-php-creates-corrupted-webp-files
1380
- *
1381
- */
1382
- if (@filesize($destination) % 2 == 1) {
1383
- @file_put_contents($destination, "\0", FILE_APPEND);
1384
- }
1385
-
1386
- imagedestroy($image);
1387
- }
1388
- }
1389
-
1390
- ?><?php
1391
-
1392
- namespace WebPConvert\Converters;
1393
-
1394
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
1395
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
1396
-
1397
- //use WebPConvert\Exceptions\TargetNotFoundException;
1398
-
1399
- class Gmagick
1400
- {
1401
- public static $extraOptions = [];
1402
-
1403
- public static function convert($source, $destination, $options = [])
1404
- {
1405
- ConverterHelper::runConverter('gmagick', $source, $destination, $options, true);
1406
- }
1407
-
1408
- // Although this method is public, do not call directly.
1409
- public static function doConvert($source, $destination, $options, $logger)
1410
- {
1411
- if (!extension_loaded('Gmagick')) {
1412
- throw new ConverterNotOperationalException('Required Gmagick extension is not available.');
1413
- }
1414
-
1415
- if (!class_exists('Gmagick')) {
1416
- throw new ConverterNotOperationalException(
1417
- 'Gmagick is installed, but not correctly. The class Gmagick is not available'
1418
- );
1419
- }
1420
-
1421
- // This might throw an exception.
1422
- // We let it...
1423
- $im = new \Gmagick($source);
1424
-
1425
-
1426
- // Throws an exception if Gmagick does not support WebP conversion
1427
- if (!in_array('WEBP', $im->queryformats())) {
1428
- throw new ConverterNotOperationalException('Gmagick was compiled without WebP support.');
1429
- }
1430
-
1431
- $options = array_merge(ConverterHelper::$defaultOptions, $options);
1432
-
1433
- // Force lossless option to true for PNG images
1434
- if (ConverterHelper::getExtension($source) == 'png') {
1435
- $options['lossless'] = true;
1436
- }
1437
-
1438
-
1439
- /*
1440
- Seems there are currently no way to set webp options
1441
- As noted in the following link, it should probably be done with a $im->addDefinition() method
1442
- - but that isn't exposed (yet)
1443
- (TODO: see if anyone has answered...)
1444
- https://stackoverflow.com/questions/47294962/how-to-write-lossless-webp-files-with-perlmagick
1445
- */
1446
- // The following two does not have any effect... How to set WebP options?
1447
- //$im->setimageoption('webp', 'webp:lossless', $options['lossless'] ? 'true' : 'false');
1448
- //$im->setimageoption('WEBP', 'method', strval($options['method']));
1449
-
1450
- // It seems there is no COMPRESSION_WEBP...
1451
- // http://php.net/manual/en/imagick.setimagecompression.php
1452
- //$im->setImageCompression(Imagick::COMPRESSION_JPEG);
1453
- //$im->setImageCompression(Imagick::COMPRESSION_UNDEFINED);
1454
-
1455
-
1456
-
1457
- $im->setimageformat('WEBP');
1458
-
1459
- if ($options['metadata'] == 'none') {
1460
- // Strip metadata and profiles
1461
- $im->stripImage();
1462
- }
1463
-
1464
- // Ps: Imagick automatically uses same quality as source, when no quality is set
1465
- // This feature is however not present in Gmagick
1466
- $im->setcompressionquality($options['_calculated_quality']);
1467
-
1468
- //$success = $im->writeimagefile(fopen($destination, 'wb'));
1469
- $success = @file_put_contents($destination, $im->getImageBlob());
1470
-
1471
- if (!$success) {
1472
- throw new ConverterFailedException('Failed writing file');
1473
- } else {
1474
- //$logger->logLn('sooms we made it!');
1475
- }
1476
- }
1477
- }
1478
-
1479
- ?><?php
1480
-
1481
- namespace WebPConvert\Converters;
1482
-
1483
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
1484
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
1485
-
1486
- //use WebPConvert\Exceptions\TargetNotFoundException;
1487
-
1488
- class Imagick
1489
- {
1490
- public static $extraOptions = [];
1491
-
1492
- public static function convert($source, $destination, $options = [])
1493
- {
1494
- ConverterHelper::runConverter('imagick', $source, $destination, $options, true);
1495
- }
1496
-
1497
- // Although this method is public, do not call directly.
1498
- public static function doConvert($source, $destination, $options, $logger)
1499
- {
1500
- if (!extension_loaded('imagick')) {
1501
- throw new ConverterNotOperationalException('Required iMagick extension is not available.');
1502
- }
1503
-
1504
- if (!class_exists('Imagick')) {
1505
- throw new ConverterNotOperationalException(
1506
- 'iMagick is installed, but not correctly. The class Imagick is not available'
1507
- );
1508
- }
1509
-
1510
- // This might throw an exception.
1511
- // Ie "ImagickException: no decode delegate for this image format `JPEG'"
1512
- // We let it...
1513
- $im = new \Imagick($source);
1514
- //$im = new \Imagick();
1515
- //$im->readImage($source);
1516
-
1517
- // Throws an exception if iMagick does not support WebP conversion
1518
- if (!in_array('WEBP', $im->queryFormats())) {
1519
- throw new ConverterNotOperationalException('iMagick was compiled without WebP support.');
1520
- }
1521
-
1522
- $options = array_merge(ConverterHelper::$defaultOptions, $options);
1523
-
1524
- // Force lossless option to true for PNG images
1525
- if (ConverterHelper::getExtension($source) == 'png') {
1526
- $options['lossless'] = true;
1527
- }
1528
-
1529
- $im->setImageFormat('WEBP');
1530
-
1531
- /*
1532
- * More about iMagick's WebP options:
1533
- * http://www.imagemagick.org/script/webp.php
1534
- * https://developers.google.com/speed/webp/docs/cwebp
1535
- * https://stackoverflow.com/questions/37711492/imagemagick-specific-webp-calls-in-php
1536
- */
1537
-
1538
- // TODO: We could easily support all webp options with a loop
1539
-
1540
- /*
1541
- After using getImageBlob() to write image, the following setOption() calls
1542
- makes settings makes imagick fail. So can't use those. But its a small price
1543
- to get a converter that actually makes great quality conversions.
1544
-
1545
- $im->setOption('webp:method', strval($options['method']));
1546
- $im->setOption('webp:low-memory', strval($options['low-memory']));
1547
- $im->setOption('webp:lossless', strval($options['lossless']));
1548
- */
1549
-
1550
- if ($options['metadata'] == 'none') {
1551
- // Strip metadata and profiles
1552
- $im->stripImage();
1553
- }
1554
-
1555
- if (isset($options['_quality_could_not_be_detected'])) {
1556
- // quality was set to "auto", but we could not meassure the quality of the jpeg locally
1557
- // but luckily imagick is a big boy, and automatically converts with same quality as
1558
- // source, when the quality isn't set.
1559
- // So we simply do not set quality.
1560
- // This actually kills the max-height functionality. But I deem that this is more important
1561
- // because setting image quality to something higher than source generates bigger files,
1562
- // but gets you no extra quality. When failing to limit quality, you at least get something
1563
- // out of it
1564
- $logger->logLn('Converting without setting quality, to achieve auto quality');
1565
- } else {
1566
- // _calculated_quality is always set, actually - also when quality is set to a number
1567
- if (isset($options['_calculated_quality'])) {
1568
- $logger->logLn('Converting with quality:' . $options['_calculated_quality']);
1569
- $im->setImageCompressionQuality($options['_calculated_quality']);
1570
- //$im->setImageCompressionQuality(55);
1571
- }
1572
- }
1573
-
1574
-
1575
-
1576
- // https://stackoverflow.com/questions/29171248/php-imagick-jpeg-optimization
1577
- // setImageFormat
1578
-
1579
- // TODO: Read up on
1580
- // https://www.smashingmagazine.com/2015/06/efficient-image-resizing-with-imagemagick/
1581
- // https://github.com/nwtn/php-respimg
1582
-
1583
- // TODO:
1584
- // Should we set alpha channel for PNG's like suggested here:
1585
- // https://gauntface.com/blog/2014/09/02/webp-support-with-imagemagick-and-php ??
1586
- // It seems that alpha channel works without... (at least I see completely transparerent pixels)
1587
-
1588
- // TODO: Check out other iMagick methods, see http://php.net/manual/de/imagick.writeimage.php#114714
1589
- // 1. file_put_contents($destination, $im)
1590
- // 2. $im->writeImage($destination)
1591
-
1592
- // We used to use writeImageFile() method. But we now use getImageBlob(). See issue #43
1593
- //$success = $im->writeImageFile(fopen($destination, 'wb'));
1594
-
1595
- $success = @file_put_contents($destination, $im->getImageBlob());
1596
-
1597
- if (!$success) {
1598
- throw new ConverterFailedException('Failed writing file');
1599
- }
1600
- }
1601
- }
1602
-
1603
- ?><?php
1604
-
1605
- namespace WebPConvert\Converters;
1606
-
1607
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
1608
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
1609
-
1610
- //use WebPConvert\Exceptions\TargetNotFoundException;
1611
-
1612
- class ImagickBinary
1613
- {
1614
- public static $extraOptions = [
1615
- [
1616
- 'name' => 'use-nice',
1617
- 'type' => 'boolean',
1618
- 'sensitive' => false,
1619
- 'default' => true,
1620
- 'required' => false
1621
- ],
1622
- ];
1623
-
1624
- public static function convert($source, $destination, $options = [])
1625
- {
1626
- ConverterHelper::runConverter('imagickbinary', $source, $destination, $options, true);
1627
- }
1628
-
1629
- public static function imagickInstalled()
1630
- {
1631
- exec('convert -version', $output, $returnCode);
1632
- return ($returnCode == 0);
1633
- }
1634
-
1635
- // Check if webp delegate is installed
1636
- public static function webPDelegateInstalled()
1637
- {
1638
- /* HM. We should not rely on grep being available
1639
- $command = 'convert -list configure | grep -i "delegates" | grep -i webp';
1640
- exec($command, $output, $returnCode);
1641
- return (count($output) > 0);
1642
- */
1643
-
1644
- $command = 'convert -version';
1645
- exec($command, $output, $returnCode);
1646
- $hasDelegate = false;
1647
- foreach ($output as $line) {
1648
- if (preg_match('/Delegate.*webp.*/i', $line)) {
1649
- return true;
1650
- }
1651
- }
1652
- return false;
1653
- }
1654
-
1655
- // Checks if 'Nice' is available
1656
- private static function hasNiceSupport()
1657
- {
1658
- exec("nice 2>&1", $niceOutput);
1659
-
1660
- if (is_array($niceOutput) && isset($niceOutput[0])) {
1661
- if (preg_match('/usage/', $niceOutput[0]) || (preg_match('/^\d+$/', $niceOutput[0]))) {
1662
- /*
1663
- * Nice is available - default niceness (+10)
1664
- * https://www.lifewire.com/uses-of-commands-nice-renice-2201087
1665
- * https://www.computerhope.com/unix/unice.htm
1666
- */
1667
-
1668
- return true;
1669
- }
1670
-
1671
- return false;
1672
- }
1673
- }
1674
-
1675
- public static function escapeFilename($string)
1676
- {
1677
-
1678
- // filter_var() is should normally be available, but it is not always
1679
- // - https://stackoverflow.com/questions/11735538/call-to-undefined-function-filter-var
1680
- if (function_exists('filter_var')) {
1681
- // Sanitize quotes
1682
- $string = filter_var($string, FILTER_SANITIZE_MAGIC_QUOTES);
1683
-
1684
- // Stripping control characters
1685
- // see https://stackoverflow.com/questions/12769462/filter-flag-strip-low-vs-filter-flag-strip-high
1686
- $string = filter_var($string, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
1687
- }
1688
-
1689
- // Escaping whitespace. Must be done *after* filter_var!
1690
- $string = preg_replace('/\s/', '\\ ', $string);
1691
-
1692
- return $string;
1693
- }
1694
-
1695
- // Although this method is public, do not call directly.
1696
- public static function doConvert($source, $destination, $options, $logger)
1697
- {
1698
- if (!function_exists('exec')) {
1699
- throw new ConverterNotOperationalException('exec() is not enabled.');
1700
- }
1701
-
1702
-
1703
- if (!self::imagickInstalled()) {
1704
- throw new ConverterNotOperationalException('imagick is not installed');
1705
- }
1706
-
1707
-
1708
- // TODO:
1709
- // quality. Like this: 'convert -quality 100 small.jpg small.webp'
1710
- $qualityOption = '';
1711
- //$this->logLn('Using quality:' . $this->getCalculatedQuality());
1712
- if (isset($options['_quality_could_not_be_detected'])) {
1713
- // quality was set to "auto", but we could not meassure the quality of the jpeg locally
1714
- // but luckily imagick is a big boy, and automatically converts with same quality as
1715
- // source, when the quality isn't set.
1716
- } else {
1717
- $qualityOption = '-quality ' . $options['_calculated_quality'] . ' ';
1718
- }
1719
-
1720
-
1721
- // Should we use "magick" or "convert" command?
1722
- // It seems they do the same. But which is best supported? Which is mostly available (whitelisted)?
1723
- // Should we perhaps try both?
1724
- // For now, we just go with "convert"
1725
-
1726
- $command = 'convert '
1727
- . $qualityOption
1728
- . escapeshellarg($source)
1729
- . ' ' . escapeshellarg('webp:' . $destination);
1730
-
1731
- // Nice
1732
- $useNice = (($options['use-nice']) && self::hasNiceSupport()) ? true : false;
1733
- if ($useNice) {
1734
- $logger->logLn('using nice');
1735
- $command = 'nice ' . $command;
1736
- }
1737
-
1738
- $logger->logLn('command:' . $command);
1739
- exec($command, $output, $returnCode);
1740
-
1741
- if ($returnCode == 127) {
1742
- throw new ConverterNotOperationalException('imagick is not installed');
1743
- }
1744
-
1745
- if ($returnCode != 0) {
1746
- if (!self::webPDelegateInstalled()) {
1747
- throw new ConverterNotOperationalException('webp delegate missing');
1748
- }
1749
-
1750
- $logger->logLn('command:' . $command);
1751
- $logger->logLn('return code:' . $returnCode);
1752
- $logger->logLn('output:' . print_r(implode("\n", $output), true));
1753
-
1754
- throw new ConverterNotOperationalException('The exec call failed');
1755
- }
1756
- }
1757
- }
1758
-
1759
- ?><?php
1760
-
1761
- namespace WebPConvert\Converters;
1762
-
1763
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
1764
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
1765
-
1766
- class Wpc
1767
- {
1768
- public static $extraOptions = [
1769
- [
1770
- 'name' => 'api-version', /* Can currently be 0 or 1 */
1771
- 'type' => 'number',
1772
- 'sensitive' => false,
1773
- 'default' => 0,
1774
- 'required' => false
1775
- ],
1776
- [
1777
- 'name' => 'secret', /* only in api v.0 */
1778
- 'type' => 'string',
1779
- 'sensitive' => true,
1780
- 'default' => 'my dog is white',
1781
- 'required' => false
1782
- ],
1783
- [
1784
- 'name' => 'api-key', /* new in api v.1 (renamed 'secret' to 'api-key') */
1785
- 'type' => 'string',
1786
- 'sensitive' => true,
1787
- 'default' => 'my dog is white',
1788
- 'required' => false
1789
- ],
1790
- [
1791
- 'name' => 'url',
1792
- 'type' => 'string',
1793
- 'sensitive' => true,
1794
- 'default' => '',
1795
- 'required' => true
1796
- ],
1797
- [
1798
- 'name' => 'crypt-api-key-in-transfer', /* new in api v.1 */
1799
- 'type' => 'boolean',
1800
- 'sensitive' => false,
1801
- 'default' => false,
1802
- 'required' => false
1803
- ],
1804
-
1805
- /*
1806
- [
1807
- 'name' => 'web-services',
1808
- 'type' => 'array',
1809
- 'sensitive' => true,
1810
- 'default' => [
1811
- [
1812
- 'label' => 'test',
1813
- 'api-key' => 'my dog is white',
1814
- 'url' => 'http://we0/wordpress/webp-express-server',
1815
- 'crypt-api-key-in-transfer' => true
1816
- ]
1817
- ],
1818
- 'required' => true
1819
- ],
1820
- */
1821
- ];
1822
-
1823
- public static function convert($source, $destination, $options = [])
1824
- {
1825
- ConverterHelper::runConverter('wpc', $source, $destination, $options, true);
1826
- }
1827
-
1828
- // Took this parser from Drupal
1829
- private static function parseSize($size)
1830
- {
1831
-
1832
- $unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
1833
- $size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
1834
- if ($unit) {
1835
- // Find the position of the unit in the ordered string which is the power
1836
- // of magnitude to multiply a kilobyte by.
1837
- return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
1838
- } else {
1839
- return round($size);
1840
- }
1841
- }
1842
-
1843
- private static function createRandomSaltForBlowfish()
1844
- {
1845
- $salt = '';
1846
- $validCharsForSalt = array_merge(
1847
- range('A', 'Z'),
1848
- range('a', 'z'),
1849
- range('0', '9'),
1850
- ['.', '/']
1851
- );
1852
-
1853
- for ($i=0; $i<22; $i++) {
1854
- $salt .= $validCharsForSalt[array_rand($validCharsForSalt)];
1855
- }
1856
- return $salt;
1857
- }
1858
-
1859
- // Although this method is public, do not call directly.
1860
- public static function doConvert($source, $destination, $options, $logger)
1861
- {
1862
-
1863
- if (!extension_loaded('curl')) {
1864
- throw new ConverterNotOperationalException('Required cURL extension is not available.');
1865
- }
1866
-
1867
- if (!function_exists('curl_init')) {
1868
- throw new ConverterNotOperationalException('Required url_init() function is not available.');
1869
- }
1870
-
1871
- $apiVersion = $options['api-version'];
1872
-
1873
- if (!function_exists('curl_file_create')) {
1874
- throw new ConverterNotOperationalException(
1875
- 'Required curl_file_create() PHP function is not available (requires PHP > 5.5).'
1876
- );
1877
- }
1878
-
1879
- if ($apiVersion == 0) {
1880
- if (!empty($options['secret'])) {
1881
- // if secret is set, we need md5() and md5_file() functions
1882
- if (!function_exists('md5')) {
1883
- throw new ConverterNotOperationalException(
1884
- 'A secret has been set, which requires us to create a md5 hash from the secret and the file ' .
1885
- 'contents. ' .
1886
- 'But the required md5() PHP function is not available.'
1887
- );
1888
- }
1889
- if (!function_exists('md5_file')) {
1890
- throw new ConverterNotOperationalException(
1891
- 'A secret has been set, which requires us to create a md5 hash from the secret and the file ' .
1892
- 'contents. But the required md5_file() PHP function is not available.'
1893
- );
1894
- }
1895
- }
1896
- }
1897
-
1898
- if ($apiVersion == 1) {
1899
- /*
1900
- if (count($options['web-services']) == 0) {
1901
- throw new ConverterNotOperationalException('No remote host has been set up');
1902
- }*/
1903
- }
1904
-
1905
- if ($options['url'] == '') {
1906
- throw new ConverterNotOperationalException(
1907
- 'Missing URL. You must install Webp Convert Cloud Service on a server, ' .
1908
- 'or the WebP Express plugin for Wordpress - and supply the url.'
1909
- );
1910
- }
1911
-
1912
- $fileSize = @filesize($source);
1913
- if ($fileSize !== false) {
1914
- $uploadMaxSize = self::parseSize(ini_get('upload_max_filesize'));
1915
- if (($uploadMaxSize !== false) && ($uploadMaxSize < $fileSize)) {
1916
- throw new ConverterFailedException(
1917
- 'File is larger than your max upload (set in your php.ini). File size:' .
1918
- round($fileSize/1024) . ' kb. ' .
1919
- 'upload_max_filesize in php.ini: ' . ini_get('upload_max_filesize') .
1920
- ' (parsed as ' . round($uploadMaxSize/1024) . ' kb)'
1921
- );
1922
- }
1923
-
1924
- $postMaxSize = self::parseSize(ini_get('post_max_size'));
1925
- if (($postMaxSize !== false) && ($postMaxSize < $fileSize)) {
1926
- throw new ConverterFailedException(
1927
- 'File is larger than your post_max_size limit (set in your php.ini). File size:' .
1928
- round($fileSize/1024) . ' kb. ' .
1929
- 'post_max_size in php.ini: ' . ini_get('post_max_size') .
1930
- ' (parsed as ' . round($postMaxSize/1024) . ' kb)'
1931
- );
1932
- }
1933
-
1934
- // ini_get('memory_limit')
1935
- }
1936
-
1937
- // Got some code here:
1938
- // https://coderwall.com/p/v4ps1a/send-a-file-via-post-with-curl-and-php
1939
-
1940
- $ch = curl_init();
1941
- if (!$ch) {
1942
- throw new ConverterNotOperationalException('Could not initialise cURL.');
1943
- }
1944
-
1945
- $optionsToSend = $options;
1946
-
1947
- if (isset($options['_quality_could_not_be_detected'])) {
1948
- // quality was set to "auto", but we could not meassure the quality of the jpeg locally
1949
- // Ask the cloud service to do it, rather than using what we came up with.
1950
- $optionsToSend['quality'] = 'auto';
1951
- } else {
1952
- $optionsToSend['quality'] = $options['_calculated_quality'];
1953
- }
1954
-
1955
- unset($optionsToSend['converters']);
1956
- unset($optionsToSend['secret']);
1957
- unset($optionsToSend['_quality_could_not_be_detected']);
1958
- unset($optionsToSend['_calculated_quality']);
1959
-
1960
- $postData = [
1961
- 'file' => curl_file_create($source),
1962
- 'options' => json_encode($optionsToSend),
1963
- 'servername' => (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '')
1964
- ];
1965
-
1966
- if ($apiVersion == 0) {
1967
- $postData['hash'] = md5(md5_file($source) . $options['secret']);
1968
- }
1969
-
1970
- if ($apiVersion == 1) {
1971
- $apiKey = $options['api-key'];
1972
-
1973
- if ($options['crypt-api-key-in-transfer']) {
1974
- if (CRYPT_BLOWFISH == 1) {
1975
- $salt = self::createRandomSaltForBlowfish();
1976
- $postData['salt'] = $salt;
1977
-
1978
- // Strip off the first 28 characters (the first 6 are always "$2y$10$". The next 22 is the salt)
1979
- $postData['api-key-crypted'] = substr(crypt($apiKey, '$2y$10$' . $salt . '$'), 28);
1980
- } else {
1981
- if (!function_exists('crypt')) {
1982
- throw new ConverterNotOperationalException(
1983
- 'Configured to crypt the api-key, but crypt() function is not available.'
1984
- );
1985
- } else {
1986
- throw new ConverterNotOperationalException(
1987
- 'Configured to crypt the api-key. ' .
1988
- 'That requires Blowfish encryption, which is not available on your current setup.'
1989
- );
1990
- }
1991
- }
1992
- } else {
1993
- $postData['api-key'] = $apiKey;
1994
- }
1995
- }
1996
-
1997
-
1998
- // Try one host at the time
1999
- // TODO: shuffle the array first
2000
- /*
2001
- foreach ($options['web-services'] as $webService) {
2002
-
2003
- }
2004
- */
2005
-
2006
-
2007
- curl_setopt_array($ch, [
2008
- CURLOPT_URL => $options['url'],
2009
- CURLOPT_POST => 1,
2010
- CURLOPT_POSTFIELDS => $postData,
2011
- CURLOPT_BINARYTRANSFER => true,
2012
- CURLOPT_RETURNTRANSFER => true,
2013
- CURLOPT_HEADER => false,
2014
- CURLOPT_SSL_VERIFYPEER => false
2015
- ]);
2016
-
2017
- $response = curl_exec($ch);
2018
- if (curl_errno($ch)) {
2019
- throw new ConverterNotOperationalException('Curl error:' . curl_error($ch));
2020
- }
2021
-
2022
- // Check if we got a 404
2023
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
2024
- if ($httpCode == 404) {
2025
- curl_close($ch);
2026
- throw new ConverterFailedException(
2027
- 'WPC was not found at the specified URL - we got a 404 response.'
2028
- );
2029
- }
2030
-
2031
- // The WPC cloud service either returns an image or an error message
2032
- // Images has application/octet-stream.
2033
- // Verify that we got an image back.
2034
- if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
2035
- curl_close($ch);
2036
-
2037
- if (substr($response, 0, 1) == '{') {
2038
- $responseObj = json_decode($response, true);
2039
- if (isset($responseObj['errorCode'])) {
2040
- switch ($responseObj['errorCode']) {
2041
- case 0:
2042
- throw new ConverterFailedException(
2043
- 'There are problems with the server setup: "' .
2044
- $responseObj['errorMessage'] . '"'
2045
- );
2046
- case 1:
2047
- throw new ConverterFailedException(
2048
- 'Access denied. ' . $responseObj['errorMessage']
2049
- );
2050
- default:
2051
- throw new ConverterFailedException(
2052
- 'Conversion failed: "' . $responseObj['errorMessage'] . '"'
2053
- );
2054
- }
2055
- }
2056
- }
2057
-
2058
- // WPC 0.1 returns 'failed![error messag]' when conversion fails. Handle that.
2059
- if (substr($response, 0, 7) == 'failed!') {
2060
- throw new ConverterFailedException(
2061
- 'WPC failed converting image: "' . substr($response, 7) . '"'
2062
- );
2063
- }
2064
-
2065
- if (empty($response)) {
2066
- $errorMsg = 'Error: Unexpected result. We got nothing back. HTTP CODE: ' . $httpCode;
2067
- throw new ConverterFailedException($errorMsg);
2068
- } else {
2069
- $errorMsg = 'Error: Unexpected result. We did not receive an image. We received: "';
2070
- $errorMsg .= str_replace("\r", '', str_replace("\n", '', htmlentities(substr($response, 0, 400))));
2071
- throw new ConverterFailedException($errorMsg . '..."');
2072
- }
2073
- //throw new ConverterNotOperationalException($response);
2074
- }
2075
-
2076
- $success = @file_put_contents($destination, $response);
2077
- curl_close($ch);
2078
-
2079
- if (!$success) {
2080
- throw new ConverterFailedException('Error saving file. Check file permissions');
2081
- }
2082
- /*
2083
- $curlOptions = [
2084
- 'api_key' => $options['key'],
2085
- 'webp' => '1',
2086
- 'file' => curl_file_create($source),
2087
- 'domain' => $_SERVER['HTTP_HOST'],
2088
- 'quality' => $options['quality'],
2089
- 'metadata' => ($options['metadata'] == 'none' ? '0' : '1')
2090
- ];
2091
-
2092
- curl_setopt_array($ch, [
2093
- CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
2094
- CURLOPT_HTTPHEADER => [
2095
- 'User-Agent: WebPConvert',
2096
- 'Accept: image/*'
2097
- ],
2098
- CURLOPT_POSTFIELDS => $curlOptions,
2099
- CURLOPT_BINARYTRANSFER => true,
2100
- CURLOPT_RETURNTRANSFER => true,
2101
- CURLOPT_HEADER => false,
2102
- CURLOPT_SSL_VERIFYPEER => false
2103
- ]);*/
2104
- }
2105
- }
2106
-
2107
- ?><?php
2108
-
2109
- namespace WebPConvert\Exceptions;
2110
-
2111
- use WebPConvert\Exceptions\WebPConvertBaseException;
2112
-
2113
- class ConverterNotFoundException extends WebPConvertBaseException
2114
- {
2115
- public $description = 'The converter does not exist.';
2116
- }
2117
-
2118
- ?><?php
2119
-
2120
- namespace WebPConvert\Exceptions;
2121
-
2122
- use WebPConvert\Exceptions\WebPConvertBaseException;
2123
-
2124
- class CreateDestinationFileException extends WebPConvertBaseException
2125
- {
2126
- public $description = 'The converter could not create destination file. Check file permisions!';
2127
- }
2128
-
2129
- ?><?php
2130
-
2131
- namespace WebPConvert\Exceptions;
2132
-
2133
- use WebPConvert\Exceptions\WebPConvertBaseException;
2134
-
2135
- class CreateDestinationFolderException extends WebPConvertBaseException
2136
- {
2137
- public $description = 'The converter could not create destination folder. Check file permisions!';
2138
- }
2139
-
2140
- ?><?php
2141
-
2142
- namespace WebPConvert\Exceptions;
2143
-
2144
- use WebPConvert\Exceptions\WebPConvertBaseException;
2145
-
2146
- class InvalidFileExtensionException extends WebPConvertBaseException
2147
- {
2148
- public $description = 'The converter does not accept the file extension';
2149
- }
2150
-
2151
- ?><?php
2152
-
2153
- namespace WebPConvert\Exceptions;
2154
-
2155
- use WebPConvert\Exceptions\WebPConvertBaseException;
2156
-
2157
- class TargetNotFoundException extends WebPConvertBaseException
2158
- {
2159
- public $description = 'The converter could not locate source file';
2160
- }
2161
-
2162
- ?><?php
2163
-
2164
- namespace WebPConvert\Converters\Exceptions;
2165
-
2166
- use WebPConvert\Exceptions\WebPConvertBaseException;
2167
-
2168
- class ConversionDeclinedException extends WebPConvertBaseException
2169
- {
2170
- public $description = 'The converter declined converting';
2171
- }
2172
-
2173
- ?><?php
2174
-
2175
- namespace WebPConvert\Converters\Exceptions;
2176
-
2177
- use WebPConvert\Exceptions\WebPConvertBaseException;
2178
-
2179
- class ConverterFailedException extends WebPConvertBaseException
2180
- {
2181
- public $description = 'The converter failed converting, although requirements seemed to be met';
2182
- }
2183
-
2184
- ?><?php
2185
-
2186
- namespace WebPConvert\Converters\Exceptions;
2187
-
2188
- use WebPConvert\Exceptions\WebPConvertBaseException;
2189
-
2190
- class ConverterNotOperationalException extends WebPConvertBaseException
2191
- {
2192
- public $description = 'The converter is not operational';
2193
- }
2194
-
2195
- ?><?php
2196
-
2197
- namespace WebPConvert\Loggers;
2198
-
2199
- use WebPConvert\Loggers\BaseLogger;
2200
-
2201
- class BufferLogger extends BaseLogger
2202
- {
2203
- public $entries = array();
2204
-
2205
- public function log($msg, $style = '')
2206
- {
2207
- $this->entries[] = [$msg, $style];
2208
- }
2209
-
2210
- public function ln()
2211
- {
2212
- $this->entries[] = '';
2213
- }
2214
-
2215
- public function getHtml()
2216
- {
2217
- $html = '';
2218
- foreach ($this->entries as $entry) {
2219
- if ($entry == '') {
2220
- $html .= '<br>';
2221
- } else {
2222
- list($msg, $style) = $entry;
2223
- $msg = htmlspecialchars($msg);
2224
- if ($style == 'bold') {
2225
- $html .= '<b>' . $msg . '</b>';
2226
- } elseif ($style == 'italic') {
2227
- $html .= '<i>' . $msg . '</i>';
2228
- } else {
2229
- $html .= $msg;
2230
- }
2231
- }
2232
- }
2233
- return $html;
2234
- }
2235
-
2236
- public function getText($newLineChar = ' ')
2237
- {
2238
- $text = '';
2239
- foreach ($this->entries as $entry) {
2240
- if ($entry == '') {
2241
- if (substr($text, -2) != '. ') {
2242
- $text .= '. ';
2243
- }
2244
- } else {
2245
- list($msg, $style) = $entry;
2246
- $text .= $msg;
2247
- }
2248
- }
2249
-
2250
- return $text;
2251
- }
2252
- }
2253
-
2254
- ?><?php
2255
-
2256
- namespace WebPConvert\Loggers;
2257
-
2258
- class EchoLogger extends BaseLogger
2259
- {
2260
- public function log($msg, $style = '')
2261
- {
2262
- $msg = htmlspecialchars($msg);
2263
- if ($style == 'bold') {
2264
- echo '<b>' . $msg . '</b>';
2265
- } elseif ($style == 'italic') {
2266
- echo '<i>' . $msg . '</i>';
2267
- } else {
2268
- echo $msg;
2269
- }
2270
- }
2271
-
2272
- public function ln()
2273
- {
2274
- echo '<br>';
2275
- }
2276
- }
2277
-
2278
- ?><?php
2279
-
2280
- namespace WebPConvert\Loggers;
2281
-
2282
- class VoidLogger extends BaseLogger
2283
- {
2284
- public function log($msg, $style = '')
2285
- {
2286
- }
2287
-
2288
- public function ln()
2289
- {
2290
- }
2291
- }
2292
-
2293
- ?><?php
2294
- namespace WebPConvert\Serve;
2295
-
2296
- use WebPConvert\WebPConvert;
2297
- use WebPConvert\Converters\ConverterHelper;
2298
- use WebPConvert\Loggers\EchoLogger;
2299
-
2300
- //use WebPConvert\Loggers\EchoLogger;
2301
-
2302
- class Report
2303
- {
2304
-
2305
- /**
2306
- * Input: We have a converter array where the options are defined
2307
- * Output: the converter array is "flattened" to be just names.
2308
- * and the options have been moved to the "converter-options" option.
2309
- */
2310
- public static function flattenConvertersArray($options)
2311
- {
2312
- // TODO: If there are more of the same converters,
2313
- // they should be added as ie 'wpc-2', 'wpc-3', etc
2314
-
2315
- $result = $options;
2316
- $result['converters'] = [];
2317
- foreach ($options['converters'] as $converter) {
2318
- if (is_array($converter)) {
2319
- $converterName = $converter['converter'];
2320
- if (!isset($options['converter-options'][$converterName])) {
2321
- if (isset($converter['options'])) {
2322
- if (!isset($result['converter-options'])) {
2323
- $result['converter-options'] = [];
2324
- }
2325
- $result['converter-options'][$converterName] = $converter['options'];
2326
- }
2327
- }
2328
- $result['converters'][] = $converterName;
2329
- } else {
2330
- $result['converters'][] = $converter;
2331
- }
2332
- }
2333
- return $result;
2334
- }
2335
-
2336
- /* Hides sensitive options */
2337
- public static function getPrintableOptions($options)
2338
- {
2339
- $printable_options = [];
2340
-
2341
- // (psst: the is_callable check is needed in order to work with WebPConvert v1.0)
2342
- if (is_callable('ConverterHelper', 'getClassNameOfConverter')) {
2343
- $printable_options = self::flattenConvertersArray($options);
2344
- if (isset($printable_options['converter-options'])) {
2345
- foreach ($printable_options['converter-options'] as $converterName => &$converterOptions) {
2346
- $className = ConverterHelper::getClassNameOfConverter($converterName);
2347
-
2348
- // (pstt: the isset check is needed in order to work with WebPConvert v1.0)
2349
- if (isset($className::$extraOptions)) {
2350
- foreach ($className::$extraOptions as $extraOption) {
2351
- if ($extraOption['sensitive']) {
2352
- if (isset($converterOptions[$extraOption['name']])) {
2353
- $converterOptions[$extraOption['name']] = '*******';
2354
- }
2355
- }
2356
- }
2357
- }
2358
- }
2359
- }
2360
- }
2361
- return $printable_options;
2362
- }
2363
-
2364
- public static function getPrintableOptionsAsString($options, $glue = '. ')
2365
- {
2366
- $optionsForPrint = [];
2367
- foreach (self::getPrintableOptions($options) as $optionName => $optionValue) {
2368
- $printValue = '';
2369
- if ($optionName == 'converter-options') {
2370
- $converterNames = [];
2371
- $extraConvertOptions = $optionValue;
2372
- //print_r($extraConvertOptions);
2373
- /*
2374
- foreach ($optionValue as $converterName => $converterOptions) {
2375
-
2376
- if (is_array($converter)) {
2377
- $converterName = $converter['converter'];
2378
- if (isset($converter['options'])) {
2379
- $extraConvertOptions[$converter['converter']] = $converter['options'];
2380
- }
2381
- } else {
2382
- $converterName = $converter;
2383
- }
2384
- $converterNames[] = $converterName;
2385
- }*/
2386
- $glueMe = [];
2387
- foreach ($extraConvertOptions as $converter => $extraOptions) {
2388
- $opt = [];
2389
- foreach ($extraOptions as $oName => $oValue) {
2390
- $opt[] = $oName . ':"' . $oValue . '"';
2391
- }
2392
- $glueMe[] = '(' . $converter . ': (' . implode($opt, ', ') . '))';
2393
- }
2394
- $printValue = implode(',', $glueMe);
2395
- } elseif ($optionName == 'web-service') {
2396
- $printValue = 'sensitive, so not displaying here...';
2397
- } else {
2398
- switch (gettype($optionValue)) {
2399
- case 'boolean':
2400
- if ($optionValue === true) {
2401
- $printValue = 'true';
2402
- } elseif ($optionValue === false) {
2403
- $printValue = 'false';
2404
- }
2405
- break;
2406
- case 'string':
2407
- $printValue = '"' . $optionValue . '"';
2408
- break;
2409
- case 'array':
2410
- $printValue = implode(', ', $optionValue);
2411
- break;
2412
- case 'integer':
2413
- $printValue = $optionValue;
2414
- break;
2415
- default:
2416
- $printValue = $optionValue;
2417
- }
2418
- }
2419
- $optionsForPrint[] = $optionName . ': ' . $printValue;
2420
- }
2421
- return implode($glue, $optionsForPrint);
2422
- }
2423
-
2424
- public static function convertAndReport($source, $destination, $options)
2425
- {
2426
- ?>
2427
- <html>
2428
- <head>
2429
- <style>td {vertical-align: top} table {color: #666}</style>
2430
- <script>
2431
- function showOptions(elToHide) {
2432
- document.getElementById('options').style.display='block';
2433
- elToHide.style.display='none';
2434
- }
2435
- </script>
2436
- </head>
2437
- <body>
2438
- <table>
2439
- <tr><td><i>source:</i></td><td><?php echo $source ?></td></tr>
2440
- <tr><td><i>destination:</i></td><td><?php echo $destination ?><td></tr>
2441
- <tr>
2442
- <td><i>options:</i></td>
2443
- <td>
2444
- <i style="text-decoration:underline;cursor:pointer" onclick="showOptions(this)">click to see</i>
2445
- <pre id="options" style="display:none"><?php
2446
- echo print_r(self::getPrintableOptionsAsString($options, '<br>'), true);
2447
- ?></pre>
2448
- <?php //echo json_encode(self::getPrintableOptions($options)); ?>
2449
- <?php //echo print_r(self::getPrintableOptions($options), true); ?>
2450
- </td>
2451
- </tr>
2452
- </table>
2453
- <br>
2454
- <?php
2455
- // TODO:
2456
- // We could display warning if unknown options are set
2457
- // but that requires that WebPConvert also describes its general options
2458
-
2459
- try {
2460
- $echoLogger = new EchoLogger();
2461
- $success = WebPConvert::convert($source, $destination, $options, $echoLogger);
2462
- } catch (\Exception $e) {
2463
- $success = false;
2464
-
2465
- $msg = $e->getMessage();
2466
-
2467
- echo '<b>' . $msg . '</b>';
2468
- exit;
2469
- }
2470
-
2471
- if ($success) {
2472
- //echo 'ok';
2473
- } else {
2474
- echo '<b>Conversion failed. None of the tried converters are operational</b>';
2475
- }
2476
- ?>
2477
- </body>
2478
- </html>
2479
- <?php
2480
- }
2481
- }
2482
-
2483
- ?><?php
2484
- namespace WebPConvert\Serve;
2485
-
2486
- use WebPConvert\WebPConvert;
2487
- use WebPConvert\Loggers\BufferLogger;
2488
- use WebPConvert\Converters\ConverterHelper;
2489
- use WebPConvert\Serve\Report;
2490
-
2491
- /**
2492
- * This class must serves a converted image (either a fresh convertion, the destionation, or
2493
- * the original). Upon failure, the fail action given in the options will be exectuted
2494
- */
2495
- class ServeConverted extends ServeBase
2496
- {
2497
-
2498
- private function addXOptionsHeader()
2499
- {
2500
- if ($this->options['add-x-header-options']) {
2501
- $this->header('X-WebP-Convert-Options:' . Report::getPrintableOptionsAsString($this->options));
2502
- }
2503
- }
2504
-
2505
- private function addHeadersPreventingCaching()
2506
- {
2507
- $this->header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
2508
- $this->header("Cache-Control: post-check=0, pre-check=0", false);
2509
- $this->header("Pragma: no-cache");
2510
- }
2511
-
2512
- public function serve404()
2513
- {
2514
- $protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : 'HTTP/1.0';
2515
- $this->header($protocol . " 404 Not Found");
2516
- }
2517
-
2518
- public function serveOriginal()
2519
- {
2520
- if (!$this->callAboutToServeImageCallBack('source')) {
2521
- return true; // we shall not trigger the fail callback
2522
- }
2523
-
2524
- if ($this->options['add-content-type-header']) {
2525
- $arr = explode('.', $this->source);
2526
- $ext = array_pop($arr);
2527
- switch (strtolower($ext)) {
2528
- case 'jpg':
2529
- case 'jpeg':
2530
- $this->header('Content-type: image/jpeg');
2531
- break;
2532
- case 'png':
2533
- $this->header('Content-type: image/png');
2534
- break;
2535
- }
2536
- }
2537
-
2538
- $this->addVaryHeader();
2539
-
2540
- switch ($this->whyServingThis) {
2541
- case 'source-lighter':
2542
- case 'explicitly-told-to':
2543
- $this->addCacheControlHeader();
2544
- $this->addLastModifiedHeader(@filemtime($this->source));
2545
- break;
2546
- default:
2547
- $this->addHeadersPreventingCaching();
2548
- }
2549
-
2550
- if (@readfile($this->source) === false) {
2551
- $this->header('X-WebP-Convert: Could not read file');
2552
- return false;
2553
- }
2554
- return true;
2555
- }
2556
-
2557
- public function serveFreshlyConverted()
2558
- {
2559
-
2560
- $criticalFail = false;
2561
- $success = false;
2562
- $bufferLogger = new BufferLogger();
2563
-
2564
- try {
2565
- $success = WebPConvert::convert($this->source, $this->destination, $this->options, $bufferLogger);
2566
-
2567
- if ($success) {
2568
- // Serve source if it is smaller than destination
2569
- $filesizeDestination = @filesize($this->destination);
2570
- $filesizeSource = @filesize($this->source);
2571
- if (($filesizeSource !== false) &&
2572
- ($filesizeDestination !== false) &&
2573
- ($filesizeDestination > $filesizeSource)) {
2574
- $this->whatToServe = 'original';
2575
- $this->whyServingThis = 'source-lighter';
2576
- return $this->serveOriginal();
2577
- }
2578
-
2579
- if (!$this->callAboutToServeImageCallBack('fresh-conversion')) {
2580
- return;
2581
- }
2582
- if ($this->options['add-content-type-header']) {
2583
- $this->header('Content-type: image/webp');
2584
- }
2585
- if ($this->whyServingThis == 'explicitly-told-to') {
2586
- $this->addXStatusHeader(
2587
- 'Serving freshly converted image (was explicitly told to reconvert)'
2588
- );
2589
- } elseif ($this->whyServingThis == 'source-modified') {
2590
- $this->addXStatusHeader(
2591
- 'Serving freshly converted image (the original had changed)'
2592
- );
2593
- } elseif ($this->whyServingThis == 'no-existing') {
2594
- $this->addXStatusHeader(
2595
- 'Serving freshly converted image (there were no existing to serve)'
2596
- );
2597
- } else {
2598
- $this->addXStatusHeader(
2599
- 'Serving freshly converted image (dont know why!)'
2600
- );
2601
- }
2602
-
2603
- if ($this->options['add-vary-header']) {
2604
- $this->header('Vary: Accept');
2605
- }
2606
-
2607
- if ($this->whyServingThis == 'no-existing') {
2608
- $this->addCacheControlHeader();
2609
- } else {
2610
- $this->addHeadersPreventingCaching();
2611
- }
2612
- $this->addLastModifiedHeader(time());
2613
-
2614
- // Should we add Content-Length header?
2615
- // $this->header('Content-Length: ' . filesize($file));
2616
- if (@readfile($this->destination)) {
2617
- return true;
2618
- } else {
2619
- $this->fail('Error', 'could not read the freshly converted file');
2620
- return false;
2621
- }
2622
- } else {
2623
- $description = 'No converters are operational';
2624
- $msg = '';
2625
- }
2626
- } catch (\WebPConvert\Exceptions\InvalidFileExtensionException $e) {
2627
- $criticalFail = true;
2628
- $description = 'Invalid file extension';
2629
- $msg = $e->getMessage();
2630
- } catch (\WebPConvert\Exceptions\TargetNotFoundException $e) {
2631
- $criticalFail = true;
2632
- $description = 'Source file not found';
2633
- $msg = $e->getMessage();
2634
- } catch (\WebPConvert\Converters\Exceptions\ConverterFailedException $e) {
2635
- // No converters could convert the image. At least one converter failed, even though it appears to be
2636
- // operational
2637
- $description = 'No converters could convert the image';
2638
- $msg = $e->getMessage();
2639
- } catch (\WebPConvert\Converters\Exceptions\ConversionDeclinedException $e) {
2640
- // (no converters could convert the image. At least one converter declined
2641
- $description = 'No converters could/wanted to convert the image';
2642
- $msg = $e->getMessage();
2643
- } catch (\WebPConvert\Exceptions\ConverterNotFoundException $e) {
2644
- $description = 'A converter was not found!';
2645
- $msg = $e->getMessage();
2646
- } catch (\WebPConvert\Exceptions\CreateDestinationFileException $e) {
2647
- $description = 'Cannot create destination file';
2648
- $msg = $e->getMessage();
2649
- } catch (\WebPConvert\Exceptions\CreateDestinationFolderException $e) {
2650
- $description = 'Cannot create destination folder';
2651
- $msg = $e->getMessage();
2652
- } catch (\Exception $e) {
2653
- $description = 'An unanticipated exception was thrown';
2654
- $msg = $e->getMessage();
2655
- }
2656
-
2657
- // Next line is commented out, because we need to be absolute sure that the details does not violate syntax
2658
- // We could either try to filter it, or we could change WebPConvert, such that it only provides safe texts.
2659
- // $this->header('X-WebP-Convert-And-Serve-Details: ' . $bufferLogger->getText());
2660
-
2661
- $this->fail('Conversion failed', $description, $criticalFail);
2662
- return false;
2663
- //echo '<p>This is how conversion process went:</p>' . $bufferLogger->getHtml();
2664
- }
2665
-
2666
- protected function serveErrorMessageImage($msg)
2667
- {
2668
- // Generate image containing error message
2669
- if ($this->options['add-content-type-header']) {
2670
- $this->header('Content-type: image/gif');
2671
- }
2672
-
2673
- // TODO: handle if this fails...
2674
- $image = imagecreatetruecolor(620, 200);
2675
- imagestring($image, 1, 5, 5, $msg, imagecolorallocate($image, 233, 214, 291));
2676
- // echo imagewebp($image);
2677
- echo imagegif($image);
2678
- imagedestroy($image);
2679
- }
2680
-
2681
- protected function fail($title, $description, $critical = false)
2682
- {
2683
- $action = $critical ? $this->options['fail-when-original-unavailable'] : $this->options['fail'];
2684
-
2685
- if (isset($this->options['aboutToPerformFailActionCallback'])) {
2686
- if (call_user_func(
2687
- $this->options['aboutToPerformFailActionCallback'],
2688
- $title,
2689
- $description,
2690
- $action,
2691
- $this
2692
- ) === false) {
2693
- return;
2694
- }
2695
- }
2696
-
2697
- $this->addXStatusHeader('Failed (' . $description . ')');
2698
-
2699
- $this->addHeadersPreventingCaching();
2700
-
2701
-
2702
- $title = 'Conversion failed';
2703
- switch ($action) {
2704
- case 'serve-original':
2705
- if (!$this->serveOriginal()) {
2706
- $this->serve404();
2707
- };
2708
- break;
2709
- case '404':
2710
- $this->serve404();
2711
- break;
2712
- case 'report-as-image':
2713
- // todo: handle if this fails
2714
- self::serveErrorMessageImage($title . '. ' . $description);
2715
- break;
2716
- case 'report':
2717
- echo '<h1>' . $title . '</h1>' . $description;
2718
- break;
2719
- }
2720
- }
2721
-
2722
- protected function criticalFail($title, $description)
2723
- {
2724
- return $this->fail($title, $description, true);
2725
- }
2726
-
2727
- /**
2728
- * Serve the thing specified in $whatToServe and $whyServingThis
2729
- * These are first set my the decideWhatToServe() method, but may later change, if a fresh
2730
- * conversion is made
2731
- */
2732
- public function serve()
2733
- {
2734
-
2735
- //$this->addXOptionsHeader();
2736
-
2737
- switch ($this->whatToServe) {
2738
- case 'destination':
2739
- return $this->serveExisting();
2740
- case 'source':
2741
- if ($this->whyServingThis == 'explicitly-told-to') {
2742
- $this->addXStatusHeader(
2743
- 'Serving original image (was explicitly told to)'
2744
- );
2745
- } else {
2746
- $this->addXStatusHeader(
2747
- 'Serving original image (it is smaller than the already converted)'
2748
- );
2749
- }
2750
- if (!$this->serveOriginal()) {
2751
- $this->criticalFail('Error', 'could not serve original');
2752
- return false;
2753
- }
2754
- return true;
2755
- case 'fresh-conversion':
2756
- return $this->serveFreshlyConverted();
2757
- break;
2758
- case 'critical-fail':
2759
- $this->criticalFail('Error', $this->whyServingThis);
2760
- return false;
2761
- case 'fail':
2762
- $this->fail('Error', $this->whyServingThis);
2763
- return false;
2764
- case 'report':
2765
- $this->addXStatusHeader('Reporting...');
2766
- Report::convertAndReport($this->source, $this->destination, $this->options);
2767
- return true; // yeah, lets say that a report is always a success, even if conversion is a failure
2768
- }
2769
- }
2770
-
2771
- public function decideWhatToServeAndServeIt()
2772
- {
2773
- $this->decideWhatToServe();
2774
- return $this->serve();
2775
- }
2776
-
2777
- /**
2778
- * Main method
2779
- */
2780
- public static function serveConverted($source, $destination, $options)
2781
- {
2782
- if (isset($options['fail']) && ($options['fail'] == 'original')) {
2783
- $options['fail'] = 'serve-original';
2784
- }
2785
- // For backward compatability:
2786
- if (isset($options['critical-fail']) && !isset($options['fail-when-original-unavailable'])) {
2787
- $options['fail-when-original-unavailable'] = $options['critical-fail'];
2788
- }
2789
-
2790
- $cs = new static($source, $destination, $options);
2791
-
2792
- return $cs->decideWhatToServeAndServeIt();
2793
- }
2794
- }
2795
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/rosell-dk/webp-convert/composer.json CHANGED
@@ -9,23 +9,39 @@
9
  "@build",
10
  "@test",
11
  "@phpcs-all",
12
- "@composer validate --no-check-all --strict"
 
13
  ],
14
- "build": [
15
- "@build-wod",
16
- "@build-require-all"
 
17
  ],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  "cs-fix-all": [
19
  "php-cs-fixer fix src"
20
  ],
21
  "cs-fix": "php-cs-fixer fix",
22
  "cs-dry": "php-cs-fixer fix --dry-run --diff",
23
- "test": "phpunit",
24
  "phpcs": "phpcs --standard=PSR2",
25
  "phpcs-all": "phpcs --standard=PSR2 src",
26
  "phpcbf": "phpcbf --standard=PSR2",
27
- "build-wod": "php build-scripts/build-webp-on-demand.php",
28
- "build-require-all": "php build-scripts/generate-require-all.php"
 
29
  },
30
  "extra": {
31
  "scripts-descriptions": {
@@ -55,6 +71,16 @@
55
  "role": "Collaborator"
56
  }
57
  ],
 
 
 
 
 
 
 
 
 
 
58
  "require-dev": {
59
  "friendsofphp/php-cs-fixer": "^2.11",
60
  "phpunit/phpunit": "5.7.27",
9
  "@build",
10
  "@test",
11
  "@phpcs-all",
12
+ "@composer validate --no-check-all --strict",
13
+ "@phpstan-global"
14
  ],
15
+ "test": [
16
+ "@test-wod-build",
17
+ "@test-webp-convert-build",
18
+ "@test-src"
19
  ],
20
+ "test-src-build": [
21
+ "@test-wod-build",
22
+ "@test-webp-convert-build"
23
+ ],
24
+ "test-wod-build": [
25
+ "phpunit --no-coverage build-tests-wod",
26
+ "phpunit --no-coverage --bootstrap tests/bootstrap-wod-test.php tests"
27
+ ],
28
+ "test-webp-convert-build": [
29
+ "phpunit --no-coverage build-tests-webp-convert",
30
+ "phpunit --no-coverage --bootstrap tests/bootstrap-webp-convert-test.php tests"
31
+ ],
32
+ "test-src": "phpunit --coverage-text",
33
+ "phpunit": "phpunit --coverage-text",
34
  "cs-fix-all": [
35
  "php-cs-fixer fix src"
36
  ],
37
  "cs-fix": "php-cs-fixer fix",
38
  "cs-dry": "php-cs-fixer fix --dry-run --diff",
 
39
  "phpcs": "phpcs --standard=PSR2",
40
  "phpcs-all": "phpcs --standard=PSR2 src",
41
  "phpcbf": "phpcbf --standard=PSR2",
42
+ "build": "php build-scripts/build.php",
43
+ "phpstan": "vendor/bin/phpstan analyse src --level=4",
44
+ "phpstan-global": "~/.composer/vendor/bin/phpstan analyse src --level=4"
45
  },
46
  "extra": {
47
  "scripts-descriptions": {
71
  "role": "Collaborator"
72
  }
73
  ],
74
+ "require": {
75
+ "php": "^5.6 | ^7.0",
76
+ "rosell-dk/image-mime-type-guesser": "^0.3"
77
+ },
78
+ "suggest": {
79
+ "ext-gd": "to use GD extension for converting. Note: Gd must be compiled with webp support",
80
+ "ext-imagick": "to use Imagick extension for converting. Note: Gd must be compiled with webp support",
81
+ "ext-vips": "to use Vips extension for converting.",
82
+ "php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
83
+ },
84
  "require-dev": {
85
  "friendsofphp/php-cs-fixer": "^2.11",
86
  "phpunit/phpunit": "5.7.27",
vendor/rosell-dk/webp-convert/docs/development.md CHANGED
@@ -14,18 +14,38 @@ Then install the dev tools with composer:
14
  composer install
15
  ```
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  ## Unit Testing
18
  To run all the unit tests do this:
19
  ```
20
  composer test
21
  ```
 
 
22
 
23
  Individual test files can be executed like this:
24
  ```
25
- composer test tests/Converters/WPCTest
26
- composer test tests/Serve/ServeConvertedTest
27
  ```
28
 
 
29
  ## Coding styles
30
  WebPConvert complies with the [PSR-2](https://www.php-fig.org/psr/psr-2/) coding standard.
31
 
@@ -45,7 +65,14 @@ composer cs-fix
45
  ```
46
 
47
  ## Running all tests in one command
48
- The following script runs the unit tests, checks the coding styles and validates composer.json. Run this before pushing to github
 
49
  ```
50
  composer ci
51
  ```
 
 
 
 
 
 
14
  composer install
15
  ```
16
 
17
+ ## The builds
18
+ For those old-schoolers that prefers one packaged file containing all the code - easily uploaded via ftp - we are maintaining `build/webp-convert.inc`.
19
+
20
+ It is an aggregation of all the php files needed, with base classes on top. It also includes the files in vendor/rosell-dk/image-mime-type-guesser.
21
+
22
+ We also maintain `build/webp-on-demand-1.inc` (which only consists of a few classes) and `build/webp-on-demand-2.inc` (which is loaded by webp-on-demand-2, when a conversion is needed, and contains the rest of the library).
23
+
24
+ Whenever code is changed in `src` - or at least, whenever a new release is released, we must rebuild these files. This can be done like this:
25
+
26
+ ```
27
+ composer build
28
+ ```
29
+
30
+ This runs `build-scripts/build-webp-on-demand.php`.
31
+ That file needs maintaining when new base classes arrives, new folders, or new dependencies.
32
+
33
+
34
  ## Unit Testing
35
  To run all the unit tests do this:
36
  ```
37
  composer test
38
  ```
39
+ This also runs tests on the builds.
40
+
41
 
42
  Individual test files can be executed like this:
43
  ```
44
+ composer phpunit tests/Convert/Converters/WPCTest
45
+ composer phpunit tests/Serve/ServeConvertedTest
46
  ```
47
 
48
+
49
  ## Coding styles
50
  WebPConvert complies with the [PSR-2](https://www.php-fig.org/psr/psr-2/) coding standard.
51
 
65
  ```
66
 
67
  ## Running all tests in one command
68
+ The following script runs the unit tests, checks the coding styles, validates `composer.json` and runs the builds.
69
+ Run this before pushing anything to github. "ci" btw stands for *continuous integration*.
70
  ```
71
  composer ci
72
  ```
73
+
74
+ ## Generating api docs
75
+ Install phpdox and run it in the project root:
76
+ ```
77
+ phpdox
78
+ ```
vendor/rosell-dk/webp-convert/docs/{converters.md → v1.3/converting/convert-options.md} RENAMED
@@ -9,23 +9,41 @@ Speed-wise, there is too little difference for it to matter, considering that im
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
- [`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.
13
 
14
- [`gmagick`](#gmagick) uses the *gmagick* extension. It is very similar to *imagick*.
15
 
16
- [`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.
17
 
18
- [`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.
 
 
 
 
 
 
19
 
20
- [`ewww`](#ewww) is also a cloud service. Not free, but cheap enough to be considered *practically* free. It produces webp files a bit smalle than the rest. It seems to produce same size as *cwebp*, when method is set to 3. 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.
21
 
22
  **Summary:**
23
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  *WebPConvert* currently supports the following converters:
25
 
26
  | Converter | Method | Requirements |
27
  | ------------------------------------ | ------------------------------------------------ | -------------------------------------------------- |
28
  | [`cwebp`](#cwebp) | Calls `cwebp` binary directly | `exec()` function *and* that the webserver user has permission to run `cwebp` binary |
 
29
  | [`imagick`](#imagick) | Imagick extension (`ImageMagick` wrapper) | Imagick PHP extension compiled with WebP support |
30
  | [`gmagick`](#gmagick) | Gmagick extension (`ImageMagick` wrapper) | Gmagick PHP extension compiled with WebP support |
31
  | [`gd`](#gd) | GD Graphics (Draw) extension (`LibGD` wrapper) | GD PHP extension compiled with WebP support |
@@ -42,8 +60,8 @@ Instructions regarding getting the individual converters to work are [on the wik
42
  <tr><th>Performance</th><td>~40-120ms to convert a 40kb image (depending on *method* option)</td></tr>
43
  <tr><th>Reliability</th><td>No problems detected so far!</td></tr>
44
  <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>
45
- <tr><th>General options supported</th><td>All (`quality`, `metadata`, `method`, `low-memory`, `lossless`)</td></tr>
46
- <tr><th>Extra options</th><td>`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)</td></tr>
47
  </table>
48
 
49
  [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).
@@ -55,26 +73,68 @@ In more detail, the implementation does this:
55
  - 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
56
  - Permissions of the generated file are set to be the same as parent folder
57
 
58
- #### The `method` option
59
- 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.
60
 
61
- #### The `size-in-percentage` option
62
- 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)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
  #### the `autofilter` option
65
  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.
66
 
67
- #### the `low-memory` option
68
- 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)
69
-
70
  #### the `command-line-options` option
71
  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)
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  #### final words on cwebp
74
  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!
75
 
76
  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.
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  ## wpc
79
  *WebPConvert Cloud Service*
80
 
@@ -83,7 +143,7 @@ See [the wiki](https://github.com/rosell-dk/webp-convert/wiki/Installing-cwebp--
83
  <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>
84
  <tr><th>Reliability</th><td>Great (depends on the reliability on the server where it is set up)</td></tr>
85
  <tr><th>Availability</th><td>Should work on <em>almost</em> any webhost</td></tr>
86
- <tr><th>General options supported</th><td>All (`quality`, `metadata`, `method`, `low-memory`, `lossless`)</td></tr>
87
  <tr><th>Extra options (old api)</th><td>`url`, `secret`</td></tr>
88
  <tr><th>Extra options (new api)</th><td>`url`, `api-version`, `api-key`, `crypt-api-key-in-transfer`</td></tr>
89
  </table>
@@ -119,7 +179,7 @@ WebPConvert::convert($source, $destination, [
119
  'converter-options' => [
120
  'wpc' => [
121
  'api-version' => 1,
122
- 'url' => 'http://example.com/wpc.php',
123
  'api-key' => 'my dog is white',
124
  'crypt-api-key-in-transfer' => true
125
  ],
@@ -127,6 +187,16 @@ WebPConvert::convert($source, $destination, [
127
  ));
128
  ```
129
 
 
 
 
 
 
 
 
 
 
 
130
  #### Example, old API:
131
 
132
  ```php
@@ -135,7 +205,7 @@ WebPConvert::convert($source, $destination, [
135
  'converters' => ['cwebp', 'wpc'],
136
  'converter-options' => [
137
  'wpc' => [
138
- 'url' => 'http://example.com/wpc.php',
139
  'secret' => 'my dog is white',
140
  ],
141
  ]
@@ -182,6 +252,13 @@ WebPConvert::convert($source, $destination, [
182
  ]
183
  ));
184
  ```
 
 
 
 
 
 
 
185
 
186
  ## gd
187
 
@@ -210,9 +287,9 @@ Due to a [bug](https://bugs.php.net/bug.php?id=66590), some versions sometimes c
210
  <table>
211
  <tr><th>Requirements</th><td>Imagick PHP extension (compiled with WebP support)</td></tr>
212
  <tr><th>Quality</th><td>Poor. [See this issue]( https://github.com/rosell-dk/webp-convert/issues/43)</td></tr>
213
- <tr><th>General options supported</th><td>`quality`, `method`, `low-memory`, `lossless`</td></tr>
214
  <tr><th>Extra options</th><td>None</td></tr>
215
- <tr><th>Performance</th><td>~20-320ms to convert a 40kb image (depending on `method` option)</td></tr>
216
  <tr><th>Reliability</th><td>No problems detected so far</td></tr>
217
  <tr><th>Availability</th><td>Probably only available on few shared hosts (if any)</td></tr>
218
  </table>
@@ -233,4 +310,13 @@ See [this page](https://github.com/rosell-dk/webp-convert/wiki/Installing-Imagic
233
 
234
  This converter tryes to execute `convert source.jpg webp:destination.jpg.webp`.
235
 
236
- #### The `method` option
 
 
 
 
 
 
 
 
 
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 |
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).
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
 
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>
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
  ],
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
205
  'converters' => ['cwebp', 'wpc'],
206
  'converter-options' => [
207
  'wpc' => [
208
+ 'url' => 'https://example.com/wpc.php',
209
  'secret' => 'my dog is white',
210
  ],
211
  ]
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
 
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>
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/{api → v1.3/converting}/convert.md RENAMED
@@ -11,7 +11,7 @@
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
15
 
16
  Many options correspond to options of *cwebp*. These are documented [here](https://developers.google.com/speed/webp/docs/cwebp)
17
 
@@ -22,11 +22,13 @@ Many options correspond to options of *cwebp*. These are documented [here](https
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
- | method | Integer | 6 | Specify the compression method to use (0-6). When higher values are used, the encoder will spend more time inspecting additional encoding possibilities and decide on the quality gain. Lower value can result in faster processing time at the expense of larger file size and lower compression quality. |
26
- | low-memory | Boolean | false | Reduce memory usage of lossy encoding by saving four times the compressed size (typically) |
27
- | lossless | Boolean | false | Encode the image without any loss. The option is ignored for PNG's (forced true) |
28
  | converters | Array | ['cwebp', 'gd', 'imagick'] | Specify conversion methods to use, and their order. Also optionally set converter options (see below) |
29
- | converter-options | Array | [] | <b>Upcoming in v1.2.0</b>. Set options of the individual converters (see below) |
 
 
 
 
30
 
31
  #### More on quality=auto
32
  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*.
@@ -74,7 +76,7 @@ WebPConvert::convert($source, $destination, [
74
  ];
75
  )
76
  ```
77
-
78
 
79
  ### More on the `$logger` parameter
80
  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.
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
 
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*.
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.
vendor/rosell-dk/webp-convert/docs/v1.3/converting/converters.md ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/{api → v1.3/serving}/convert-and-serve.md RENAMED
@@ -1,5 +1,7 @@
1
  # API: The WebPConvert::convertAndServe() method
2
 
 
 
3
  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.
4
 
5
  **WebPConvert::convertAndServe($source, $destination, $options)**
@@ -13,6 +15,9 @@ The method tries to serve a converted image. If destination already exists, the
13
  ## The *$options* argument
14
  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)
15
 
 
 
 
16
  ### *fail*
17
  Indicate what to do, in case of normal conversion failure.
18
  Default value: *"original"*
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)**
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"*
vendor/rosell-dk/webp-convert/docs/{webp-on-demand → v1.3/webp-on-demand}/tweaks.md RENAMED
File without changes
vendor/rosell-dk/webp-convert/docs/{webp-on-demand → v1.3/webp-on-demand}/webp-on-demand.md RENAMED
@@ -18,7 +18,7 @@ A setup consists of a PHP script that serves converted images and some *redirect
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/webp-on-demand/without-composer.md)
22
 
23
  ### 1. Require the webp-convert library with composer
24
  ```
@@ -107,13 +107,13 @@ There are some benefits of not passing in query string:
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/api/convert.md) and the [docs on convertAndServe()](https://github.com/rosell-dk/webp-convert/blob/master/docs/api/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/webp-on-demand/tweaks.md#store-converted-images-in-separate-folder)
114
- - [CDN](https://github.com/rosell-dk/webp-convert/blob/master/docs/webp-on-demand/tweaks.md#cdn)
115
- - [Make .htaccess route directly to existing images](https://github.com/rosell-dk/webp-convert/blob/master/docs/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/webp-on-demand/tweaks.md#forward-the-querystring)
117
 
118
 
119
  ## Troubleshooting
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
  ```
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
vendor/rosell-dk/webp-convert/docs/{webp-on-demand → v1.3/webp-on-demand}/without-composer.md RENAMED
@@ -5,13 +5,13 @@ For your convenience, the library has been cooked down to two files: *webp-on-de
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. 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
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
vendor/rosell-dk/webp-convert/docs/v2.0/converting/architecture-q50-w600.jpg ADDED
Binary file
vendor/rosell-dk/webp-convert/docs/v2.0/converting/converters/stack.md ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
Binary file
vendor/rosell-dk/webp-convert/docs/v2.0/converting/introduction-for-converting.md ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 environment variables ("EWWW_API_KEY", "WPC_API_KEY", "WPC_API_URL"). Paths to binaries can also be set with environment variables (it is rarely needed to do this): "CWEBP_PATH", "GRAPHICSMAGICK_PATH" and 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 EWWW_API_KEY yourVerySecretApiKeyGoesHere
89
+
90
+ # Set custom path to imagick for WebP Convert
91
+ SetEnv IMAGEMAGICK_PATH /usr/local/bin/magick
92
+ ```
93
+
94
+
95
+ ## Configuring the options
96
+
97
+ ### Auto quality
98
+ **Q:** What do you get if you convert a low quality jpeg (ie q=50) into a high quality webp (ie q=90) ?\
99
+ **A:** You maintain the low quality, but you get a large file`
100
+
101
+ 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*.
102
+
103
+ 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.
104
+
105
+ How much can be gained? A lot!
106
+ 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.
107
+
108
+ ![A low quality jpeg](https://raw.githubusercontent.com/rosell-dk/webp-convert/master/docs/v2.0/converting/architecture-q50-w600.jpg)
109
+
110
+ **Q:** What do you get if you convert an excessively high quality jpeg into an excessively high quality webp?\
111
+ **A:** An excessively big file
112
+
113
+ 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).
114
+
115
+ 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).
116
+
117
+ So, how much can be gained? A lot!
118
+ 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.
119
+
120
+ ![A (too) high quality jpeg](https://raw.githubusercontent.com/rosell-dk/webp-convert/master/docs/v2.0/converting/mouse-q100.jpg)
121
+
122
+
123
+ ### Auto selecting between lossless/lossy encoding
124
+ 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.
125
+
126
+ 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".
127
+
128
+ 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).
129
+
130
+ ![Dice](https://raw.githubusercontent.com/rosell-dk/webp-convert/master/docs/v2.0/converting/dice.png)
131
+
132
+ Unless you changed the `near-lossless` option described below, the choice is actually between lossy and *near-lossless*.
133
+
134
+ 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.
135
+
136
+ ### Near-lossless
137
+ *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.
138
+
139
+ As unnecessary large conversions are rarely desirable, this library per default sets *near-lossless* to 60. To disable near-lossless, set it to 100.
140
+
141
+ 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.
142
+
143
+ You can read more about the near-lossless mode [here](https://groups.google.com/a/webmproject.org/forum/#!topic/webp-discuss/0GmxDmlexek)
144
+
145
+ ### Alpha-quality
146
+ All converters, except *gd* and *ewww* supports "alpha-quality" option. This allows lossy compressing of the alpha channel.
147
+
148
+ 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.
149
+
150
+ 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.
151
+
152
+ You can read more about the alpha-quality option [here](https://developers.google.com/speed/webp/docs/cwebp)
153
+
154
+
155
+ ### PNG og JPEG-specific options.
156
+
157
+ To have options depending on the image type of the source, you can use the `png` and `jpeg` keys.
158
+
159
+ The following options mimics the default behaviour:
160
+
161
+ ```php
162
+ $options = [
163
+ 'png' => [
164
+ 'encoding' => 'auto', /* Try both lossy and lossless and pick smallest */
165
+ 'near-lossless' => 60, /* The level of near-lossless image preprocessing (when trying lossless) */
166
+ 'quality' => 85, /* Quality when trying lossy. It is set high because pngs is often selected to ensure high quality */
167
+ ],
168
+ 'jpeg' => [
169
+ '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) */
170
+ 'quality' => 'auto', /* Set to same as jpeg (requires imagick or gmagick extension, not necessarily compiled with webp) */
171
+ 'max-quality' => 80, /* Only relevant if quality is set to "auto" */
172
+ 'default-quality' => 75, /* Fallback quality if quality detection isnt working */
173
+ ]
174
+ ];
175
+ ```
176
+
177
+ You can use it for any option, also the converter specific options.
178
+ A use case could for example be to use different converters for png and jpeg:
179
+
180
+ ```php
181
+ $options = [
182
+ 'png' => [
183
+ 'converters' => ['ewww'],
184
+ ],
185
+ 'jpeg' => [
186
+ 'converters' => ['gd'],
187
+ ]
188
+ ];
189
+ ```
190
+
191
+ ## Available options
192
+
193
+ **All** available options are documented [here](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md).
194
+
195
+ Here is a quick overview of the few ones discussed here.
196
+
197
+ | Option | Default (jpeg) | Default (png) | Description |
198
+ | ----------------- | ------------------ | ------------------- | ---------------------------------------------------------------------------------- |
199
+ | quality | "auto" | 85 | See the "Auto quality" section above. |
200
+ | max-quality | 85 | 85 | Only relevant for jpegs and when quality is set to "auto". |
201
+ | default-quality | 75 | 85 | |
202
+ | 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*) |
203
+ | encoding | "auto" | "auto" | See the "Auto selecting between lossless/lossy encoding" section above |
204
+ | jpeg | - | - | Array of options which will be merged into the other options when source is a JPEG |
205
+ | png | - | - | Array of options which will be merged into the other options when source is a PNG |
206
+ | skip | false | false | If true, conversion will be skipped (ie for skipping png conversion for some converters) |
207
+
208
+
209
+ ## More info
210
+
211
+ - The complete api is available [here](https://www.bitwise-it.dk/webp-convert/api/2.0/html/index.xhtml)
212
+ - 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).
213
+ - On the github wiki you can find installation instructions for imagick with webp, gd with webp, etc.
214
+ - 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 ADDED
Binary file
vendor/rosell-dk/webp-convert/docs/v2.0/converting/options.md ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ ### `default-quality`
32
+ ```
33
+ Type: integer (0-100)
34
+ Default: 75 for jpegs and 85 for pngs
35
+ Supported by: all (cwebp, ewww, gd, gmagick, graphicsmagick, imagick, imagemagick, vips)
36
+ ```
37
+ 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>
38
+
39
+ ### `encoding`
40
+ ```
41
+ Type: string ("lossy" | "lossless" | "auto")
42
+ Default: "auto"
43
+ Supported by: cwebp, vips, imagick, gmagick, imagemagick and graphicsmagick (gd always uses lossy encoding, ewww uses lossless for pngs and lossy for jpegs)
44
+ ```
45
+ 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>
46
+
47
+ ### `ewww-api-key`
48
+ ```
49
+ Type: string
50
+ Default: ''
51
+ Supported by: ewww
52
+ ```
53
+ 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.
54
+
55
+ Note: This option can alternatively be set through the *EWWW_API_KEY* environment variable.<br><br>
56
+
57
+ ### `jpeg`
58
+ ```
59
+ Type: array
60
+ Default: []
61
+ Supported by: all
62
+ ```
63
+ 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.
64
+ 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>
65
+
66
+ ### `log-call-arguments`
67
+ ```
68
+ Type: boolean
69
+ Default: false
70
+ Supported by: all
71
+ ```
72
+ Enabling this simply puts some more in the log - namely the arguments that was supplied to the call. Sensitive information is starred out.
73
+
74
+ ### `low-memory`
75
+ ```
76
+ Type: boolean
77
+ Default: false
78
+ Supported by: cwebp, imagick, imagemagick and graphicsmagick
79
+ ```
80
+ 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>
81
+
82
+ ### `max-quality`
83
+ ```
84
+ Type: integer (0-100)
85
+ Default: 85
86
+ Supported by: all (cwebp, ewww, gd, gmagick, graphicsmagick, imagick, imagemagick, vips)
87
+ ```
88
+ 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>
89
+
90
+ ### `metadata`
91
+ ```
92
+ Type: string ("all" | "none" | "exif" | "icc" | "xmp")
93
+ Default: 'none'
94
+ Supported by: 'none' is supported by all. 'all' is supported by all, except *gd*. The rest is only supported by *cwebp*
95
+ ```
96
+ 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>
97
+
98
+ ### `method`
99
+ ```
100
+ Type: integer (0-6)
101
+ Default: 6
102
+ Supported by: cwebp, imagick, gmagick, imagemagick and graphicsmagick
103
+ ```
104
+ 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.<br><br>
105
+
106
+ ### `near-lossless`
107
+ ```
108
+ Type: integer (0-100)
109
+ Default: 60
110
+ Supported by: cwebp, vips
111
+ ```
112
+ 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>
113
+
114
+ ### `png`
115
+ ```
116
+ Type: array
117
+ Default: []
118
+ Supported by: all
119
+ ```
120
+ 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.
121
+ 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>
122
+
123
+ ### `preset`
124
+ ```
125
+ Type: string ('none', 'default', 'photo', 'picture', 'drawing', 'icon' or 'text')
126
+ Default: "none"
127
+ Supported by: cwebp, vips
128
+ ```
129
+ 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>
130
+
131
+ ### `quality`
132
+ ```
133
+ Type: integer (0-100) | "auto"
134
+ Default: "auto" for jpegs and 85 for pngs
135
+ Supported by: all (cwebp, ewww, gd, gmagick, graphicsmagick, imagick, imagemagick, vips)
136
+ ```
137
+ 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>
138
+
139
+ ### `size-in-percentage`
140
+ ```
141
+ Type: integer (0-100) | null
142
+ Default: null
143
+ Supported by: cwebp
144
+ ```
145
+ 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>
146
+
147
+ ### `skip`
148
+ ```
149
+ Type: boolean
150
+ Default: false
151
+ Supported by: all
152
+ ```
153
+ Simply skips conversion. For example this can be used to skip png conversion for a specific converter like this:
154
+ ```php
155
+ $options = [
156
+ 'png' => [
157
+ 'gd-skip' => true,
158
+ ]
159
+ ];
160
+ ```
161
+
162
+ Or it can be used to skip unwanted converters from the default stack, like this:
163
+ ```php
164
+ $options = [
165
+ 'ewww-skip' => true,
166
+ 'wpc-skip' => true,
167
+ 'gd-skip' => true,
168
+ 'imagick-skip' => true,
169
+ 'gmagick-skip' => true,
170
+ ];
171
+ ```
172
+
173
+
174
+
175
+ <br>
176
+
177
+ ### `stack-converters`
178
+ ```
179
+ Type: array
180
+ Default: ['cwebp', 'vips', 'imagick', 'gmagick', 'imagemagick', 'graphicsmagick', 'wpc', 'ewww', 'gd']
181
+ Supported by: stack
182
+ ```
183
+
184
+ Specify the converters to try and their order.
185
+
186
+ 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.
187
+
188
+ The array specifies the converters to try and their order. Each item can be:
189
+
190
+ - An id (ie "cwebp")
191
+ - A fully qualified class name (in case you have programmed your own custom converter)
192
+ - An array with two keys: "converter" and "options".
193
+
194
+ `
195
+ Alternatively, converter options can be set using the *converter-options* option.
196
+
197
+ 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>
198
+
199
+ ### `stack-converter-options`
200
+ ```
201
+ Type: array
202
+ Default: []
203
+ Supported by: stack
204
+ ```
205
+ Extra options for specific converters. Example:
206
+
207
+ ```php
208
+ $options = [
209
+ 'converter-options' => [
210
+ 'vips' => [
211
+ 'quality' => 72
212
+ ],
213
+ ]
214
+ ]
215
+ ```
216
+ <br>
217
+
218
+ ### `stack-extra-converters`
219
+ ```
220
+ Type: array
221
+ Default: []
222
+ Supported by: stack
223
+ ```
224
+ Add extra converters to the bottom of the stack. The items are similar to those in the `stack-converters` option.<br><br>
225
+
226
+ ### `stack-preferred-converters`
227
+ ```
228
+ Type: array
229
+ Default: []
230
+ Supported by: stack
231
+ ```
232
+ 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>
233
+
234
+ ### `stack-shuffle`
235
+ ```
236
+ Type: boolean
237
+ Default: false
238
+ Supported by: stack
239
+ ```
240
+ 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>
241
+
242
+ ### `use-nice`
243
+ ```
244
+ Type: boolean
245
+ Default: false
246
+ Supported by: cwebp, graphicsmagick, imagemagick
247
+ ```
248
+ 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>
249
+
250
+ ### `vips-smart-subsample`
251
+ ```
252
+ Type: boolean
253
+ Default: false
254
+ Supported by: vips
255
+ ```
256
+ 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>
257
+
258
+ ### `wpc-api-key`
259
+ ```
260
+ Type: string
261
+ Default: ''
262
+ Supported by: wpc
263
+ ```
264
+ 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.
265
+
266
+ Note: You can alternatively set the api key through the *WPC_API_KEY* environment variable.<br><br>
267
+
268
+ ### `wpc-api-url`
269
+ ```
270
+ Type: string
271
+ Default: ''
272
+ Supported by: wpc
273
+ ```
274
+ Note: You can alternatively set the api url through the *WPC_API_URL* environment variable.<br><br>
275
+
276
+ ### `wpc-api-version`
277
+ ```
278
+ Type: integer (0 - 1)
279
+ Default: 0
280
+ Supported by: wpc
281
+ ```
282
+ <br>
283
+
284
+ ### `wpc-crypt-api-key-in-transfer`
285
+ ```
286
+ Type: boolean
287
+ Default: false
288
+ Supported by: wpc
289
+ ```
290
+ <br>
291
+
292
+ ### `wpc-secret`
293
+ ```
294
+ Type: string
295
+ Default: ''
296
+ Supported by: wpc
297
+ ```
298
+ Note: This option is only relevant for api version 0.
vendor/rosell-dk/webp-convert/docs/v2.0/migrating-to-2.0.md ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ 'convert' => [
41
+ // options for converting goes here
42
+ 'quality' => 'auto',
43
+ ]
44
+ ]);
45
+ ```
46
+
47
+ ## Failure handling
48
+ 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.
49
+
50
+ 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.
51
+
52
+ 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'.
53
+
54
+ ## Options influencing the decision process
55
+ The default process is like this:
56
+
57
+ 1. Is there a file at the destination? If not, trigger conversion
58
+ 2. Is the destination older than the source? If yes, delete destination and trigger conversion
59
+ 3. Serve the smallest file (destination or source)
60
+
61
+ You can influence the process with the following options:
62
+
63
+ *reconvert*
64
+ If you set *reconvert* to true, the destination and conversion is triggered (between step 1 and 2)
65
+
66
+ *serve-original*
67
+ If you set *serve-original* to true, process will take its cause from (1) to (2) and then end with source being served.
68
+
69
+ *show-report*
70
+ 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.
71
+
72
+ ## Headers
73
+ 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.
74
+
75
+ #### *Cache-Control* and *Expires* headers
76
+ 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:
77
+
78
+ ```
79
+ 'serve-image' => [
80
+ 'headers' => [
81
+ 'cache-control' => true,
82
+ 'expires' => false,
83
+ ],
84
+ 'cache-control-header' => 'public, max-age=31536000',
85
+ ],
86
+ ```
87
+
88
+ 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).
89
+
90
+ #### *Vary: Accept* header
91
+ 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.
92
+
93
+ #### *Last-Modified* header
94
+ 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.
95
+
96
+ #### *Content-Type* header
97
+ 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.
98
+
99
+ 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.
100
+
101
+ #### *Content-Length* header
102
+ 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).
103
+
104
+ #### *X-WebP-Convert-Log* headers
105
+ The serve method adds *X-WebP-Convert-Log* headers in order to let you know what went on.
106
+ For example, if there is no converted image and conversion was successful, the following headers will be sent:
107
+
108
+ ```
109
+ X-WebP-Convert-Log: Converting (there were no file at destination)
110
+ X-WebP-Convert-Log: Serving converted file
111
+ ```
112
+
113
+ On the next call (presuming the webp has not been deleted), no conversion is needed and you should simply see:
114
+ ```
115
+ X-WebP-Convert-Log: Serving converted file
116
+ ```
117
+
118
+ But say that the first conversion actually failed. In case you have permission problems, the output could be:
119
+ ```
120
+ X-WebP-Convert-Log: Converting (there were no file at destination)
121
+ X-WebP-Convert-Log: Failed creating folder. Check the permissions!
122
+ X-WebP-Convert-Log: Performing fail action: original
123
+ ```
124
+
125
+ In case the problem is that the conversion failed, you could see the following:
126
+ ```
127
+ X-WebP-Convert-Log: Converting (there were no file at destination)
128
+ X-WebP-Convert-Log: None of the converters in the stack are operational
129
+ X-WebP-Convert-Log: Performing fail action: original
130
+ ```
131
+
132
+ If you need more info about the conversion process in order to learn why the converters aren't working, enable the *show-report* option.
133
+
134
+ 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:
135
+ ```
136
+ X-WebP-Convert-Log: Source file was not found
137
+ X-WebP-Convert-Log: Performing fail action: original
138
+ X-WebP-Convert-Log: Performing fail action: throw
139
+ ```
140
+
141
+ ## More info
142
+
143
+ - 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/webp-on-demand/tweaks.md ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/v2.0/webp-on-demand/webp-on-demand.md ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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:
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 autoloader($class) {
34
+ if (strpos($class, 'WebPConvert\\') === 0) {
35
+ require_once __DIR__ . '/webp-on-demand-2.inc';
36
+ }
37
+ }
38
+ spl_autoload_register('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 main 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/install-gmagick-with-webp.sh ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # https://duntuk.com/how-install-graphicsmagick-gmagick-php-extension
2
+ # https://gist.github.com/basimhennawi/21c39f9758b0b1cb5e0bd5ee08b5be58
3
+ # https://github.com/rosell-dk/webp-convert/wiki/Installing-gmagick-extension
4
+
5
+ #if [ -d "$HOME/vips/bin" ]; then
6
+ #fi;
7
+
8
+
9
+ $HOME/opt/bin/gm -version | grep -i 'WebP.*yes' && {
10
+ gmagick_installed_with_webp=1
11
+ }
12
+
13
+ if [[ $gmagick_installed_with_webp == 1 ]]; then
14
+ echo "Gmagick is already compiled with webp. Nothing to do :)"
15
+ echo ":)"
16
+ else
17
+ echo "Gmagick is is not installed or not compiled with webp."
18
+ compile_libwebp=1
19
+ compile_gmagick=1
20
+ fi;
21
+ #ls $HOME/opt/bin
22
+
23
+
24
+ cores=$(nproc)
25
+ LIBWEBP_VERSION=1.0.2
26
+
27
+ if [[ $compile_libwebp == 2 ]]; then
28
+ echo "We are going to be compiling libwebp..."
29
+ echo "Using $cores cores."
30
+ echo "Downloading libwebp version $LIBWEBP_VERSION"
31
+ cd /tmp
32
+ curl -O https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-$LIBWEBP_VERSION.tar.gz
33
+ tar xzf libwebp-$LIBWEBP_VERSION.tar.gz
34
+ cd libwebp-*
35
+
36
+ echo "./configure --prefix=$HOME/opt"
37
+ ./configure --prefix=$HOME/opt
38
+
39
+ echo "make -j$CORES"
40
+ make -j$CORES
41
+
42
+ echo "make install -j$CORES"
43
+ make install -j$CORES
44
+ fi;
45
+
46
+ if [[ $compile_gmagick == 2 ]]; then
47
+ echo "Compiling Gmagick"
48
+ echo "Using $cores cores."
49
+ cd /tmp
50
+ echo "Downloading GraphicsMagick-LATEST.tar.gz"
51
+ wget http://78.108.103.11/MIRROR/ftp/GraphicsMagick/GraphicsMagick-LATEST.tar.gz
52
+ tar xfz GraphicsMagick-LATEST.tar.gz
53
+ cd GraphicsMagick-*
54
+
55
+ echo "Configuring"
56
+ ./configure --prefix=$HOME/opt --enable-shared --with-webp=yes
57
+
58
+ echo "make -j$CORES"
59
+ make -j$CORES
60
+
61
+ echo "make install -j$CORES"
62
+ make install -j$CORES
63
+ fi;
64
+
65
+
66
+ #./configure --prefix=$HOME/opt --with-webp=yes &&
67
+
68
+ #$HOME/opt/bin/gm -version
69
+
70
+ #convert -version | grep 'webp' || {
71
+
72
+ #convert -list delegate | grep 'webp =>' || {
73
+ #}
74
+ ##libgraphicsmagick1-dev
vendor/rosell-dk/webp-convert/install-imagemagick-with-webp.sh ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # Install imagick with webp support (if not already there) and update library paths
3
+ # Got the script from here:
4
+ # https://stackoverflow.com/questions/41138404/how-to-install-newer-imagemagick-with-webp-support-in-travis-ci-container
5
+
6
+ if ! [[ $IMAGEMAGICK_VERSION ]]; then
7
+ export IMAGEMAGICK_VERSION="7.0.8-43"
8
+ fi;
9
+
10
+ convert -list delegate | grep 'webp =>' && {
11
+ echo "Imagick is already compiled with webp. Nothing to do :)" &&
12
+ echo ":)"
13
+ }
14
+
15
+ #convert -version | grep 'webp' || {
16
+
17
+ convert -list delegate | grep 'webp =>' || {
18
+ export CORES=$(nproc) &&
19
+ export LIBWEBP_VERSION=1.0.2 &&
20
+ echo "Using $CORES cores for compiling..." &&
21
+ cd /tmp &&
22
+ curl -O https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-$LIBWEBP_VERSION.tar.gz &&
23
+ tar xzf libwebp-$LIBWEBP_VERSION.tar.gz &&
24
+ cd libwebp-* &&
25
+ ./configure --prefix=$HOME/opt &&
26
+ make -j$CORES &&
27
+ make install -j$CORES &&
28
+ cd /tmp &&
29
+ curl -O https://www.imagemagick.org/download/ImageMagick-$IMAGEMAGICK_VERSION.tar.gz &&
30
+ tar xzf ImageMagick-$IMAGEMAGICK_VERSION.tar.gz &&
31
+ cd ImageMagick-* &&
32
+ ./configure --prefix=$HOME/opt --with-webp=yes &&
33
+ make -j$CORES &&
34
+ make install -j$CORES &&
35
+ $HOME/opt/bin/magick -version | grep $IMAGEMAGICK_VERSION
36
+ }
37
+
38
+ export LD_FLAGS=-L$HOME/opt/lib
39
+ export LD_LIBRARY_PATH=/lib:/usr/lib:/usr/local/lib:$HOME/opt/lib
40
+ export CPATH=$CPATH:$HOME/opt/include
vendor/rosell-dk/webp-convert/install-vips.sh ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ if ! [[ $VIPS_VERSION ]]; then
4
+ export VIPS_VERSION="8.7.4"
5
+ fi;
6
+
7
+ export PATH=$HOME/vips/bin:$PATH
8
+ export LD_LIBRARY_PATH=$HOME/vips/lib:$LD_LIBRARY_PATH
9
+ export PKG_CONFIG_PATH=$HOME/vips/lib/pkgconfig:$PKG_CONFIG_PATH
10
+ export PYTHONPATH=$HOME/vips/lib/python2.7/site-packages:$PYTHONPATH
11
+ export GI_TYPELIB_PATH=$HOME/vips/lib/girepository-1.0:$GI_TYPELIB_PATH
12
+
13
+ vips_site=https://github.com/libvips/libvips/releases/download
14
+
15
+ set -e
16
+
17
+ # do we already have the correct vips built? early exit if yes
18
+ # we could check the configure params as well I guess
19
+ if [ -d "$HOME/vips/bin" ]; then
20
+ installed_version=$($HOME/vips/bin/vips --version)
21
+ escaped_version="${VIPS_VERSION//\./\\.}"
22
+ echo "Need vips-$version"
23
+ echo "Found $installed_version"
24
+ if [[ "$installed_version" =~ ^vips-$escaped_version ]]; then
25
+ echo "Using cached directory"
26
+ exit 0
27
+ fi
28
+ fi
29
+
30
+ rm -rf $HOME/vips
31
+ echo "wget: $vips_site/v$VIPS_VERSION/vips-$VIPS_VERSION.tar.gz"
32
+ wget $vips_site/v$VIPS_VERSION/vips-$VIPS_VERSION.tar.gz
33
+ tar xf vips-$VIPS_VERSION.tar.gz
34
+ cd vips-$VIPS_VERSION
35
+ CXXFLAGS=-D_GLIBCXX_USE_CXX11_ABI=0 ./configure --prefix=$HOME/vips --disable-debug --disable-dependency-tracking --disable-introspection --disable-static --enable-gtk-doc-html=no --enable-gtk-doc=no --enable-pyvips8=no --without-orc --without-python
36
+ make && make install
37
+
38
+ # Install PHP extension
39
+ # ----------------------
40
+ yes '' | pecl install vips
vendor/rosell-dk/webp-convert/phpdox.xml ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8" ?>
2
+ <phpdox xmlns="http://xml.phpdox.net/config">
3
+ <project name="WebP Convert" source="${basedir}/src" workdir="${basedir}/build/api/xml">
4
+ <collector backend="parser" />
5
+ <generator output="${basedir}/build/api">
6
+ <build engine="html" output="html"/>
7
+ </generator>
8
+ </project>
9
+ </phpdox>
vendor/rosell-dk/webp-convert/phpstan.neon ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ parameters:
2
+ reportUnmatchedIgnoredErrors: false
3
+ ignoreErrors:
4
+ - '#Instantiated class Imagick not found.#'
5
+ - '#Instantiated class Gmagick not found.#'
6
+ - '#Caught class ImagickException not found.#'
7
+ - '# on an unknown class Imagick.#'
8
+ - '# on an unknown class Gmagick.#'
9
+ -
10
+ message: '#Strict comparison using === between int and false will always evaluate to false.#'
11
+ path: %currentWorkingDirectory%/src/Convert/Converters/Gd.php
12
+ -
13
+ message: '#Strict comparison using === between resource and false will always evaluate to false.#'
14
+ path: %currentWorkingDirectory%/src/Convert/Converters/Ewww.php
15
+ -
16
+ message: '#Strict comparison using === between resource and false will always evaluate to false.#'
17
+ path: %currentWorkingDirectory%/src/Convert/Converters/ConverterTraits/CurlTrait.php
18
+ -
19
+ message: '#Strict comparison using === between resource and false will always evaluate to false.#'
20
+ path: %currentWorkingDirectory%/src/Convert/BaseConverters/AbstractCloudCurlConverter.php
21
+ - '#Function vips_call invoked with 4 parameters, 2 required.#'
22
+ - '#Function vips_.* not found.#'
23
+ -
24
+ message: '#SystemRequirementsNotMet#'
25
+ path: %currentWorkingDirectory%/src/Convert/Converters/ConverterTraits/ExecTrait.php
vendor/rosell-dk/webp-convert/phpunit.xml.dist CHANGED
@@ -11,6 +11,7 @@
11
  processIsolation="false"
12
  stopOnFailure="false"
13
  bootstrap="vendor/autoload.php"
 
14
  >
15
  <testsuites>
16
  <testsuite name="WebPConvert Test Suite">
@@ -20,11 +21,21 @@
20
 
21
  <filter>
22
  <whitelist>
23
- <directory>./</directory>
24
  <exclude>
25
  <directory>./vendor</directory>
26
  <directory>./tests</directory>
 
 
27
  </exclude>
28
  </whitelist>
29
  </filter>
 
 
 
 
 
 
 
 
30
  </phpunit>
11
  processIsolation="false"
12
  stopOnFailure="false"
13
  bootstrap="vendor/autoload.php"
14
+
15
  >
16
  <testsuites>
17
  <testsuite name="WebPConvert Test Suite">
21
 
22
  <filter>
23
  <whitelist>
24
+ <directory suffix=".php">src/</directory>
25
  <exclude>
26
  <directory>./vendor</directory>
27
  <directory>./tests</directory>
28
+ <directory>./build-tests-wod</directory>
29
+ <directory>./build-tests-complete</directory>
30
  </exclude>
31
  </whitelist>
32
  </filter>
33
+
34
+ <logging>
35
+ <log type="junit" target="build/report.junit.xml"/>
36
+ <log type="coverage-clover" target="coverage.clover"/>
37
+ <log type="coverage-text" target="build/coverage.txt"/>
38
+ <log type="coverage-html" target="build/coverage"/>
39
+ </logging>
40
+
41
  </phpunit>
vendor/rosell-dk/webp-convert/src-build/webp-convert.inc ADDED
@@ -0,0 +1,6907 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ ?><?php
3
+
4
+ namespace WebPConvert\Options;
5
+
6
+ use WebPConvert\Options\Exceptions\InvalidOptionTypeException;
7
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
8
+
9
+ /**
10
+ * (base) option class.
11
+ *
12
+ * @package WebPConvert
13
+ * @author Bjørn Rosell <it@rosell.dk>
14
+ * @since Class available since Release 2.0.0
15
+ */
16
+ class Option
17
+ {
18
+ /** @var string The id of the option */
19
+ protected $id;
20
+
21
+ /** @var mixed The default value of the option */
22
+ protected $defaultValue;
23
+
24
+ /** @var mixed The value of the option */
25
+ protected $value;
26
+
27
+ /** @var boolean Whether the value has been explicitly set */
28
+ protected $isExplicitlySet = false;
29
+
30
+ /**
31
+ * Constructor.
32
+ *
33
+ * @param string $id id of the option
34
+ * @param mixed $defaultValue default value for the option
35
+ * @throws InvalidOptionValueException if the default value cannot pass the check
36
+ * @throws InvalidOptionTypeException if the default value is wrong type
37
+ * @return void
38
+ */
39
+ public function __construct($id, $defaultValue)
40
+ {
41
+ $this->id = $id;
42
+ $this->defaultValue = $defaultValue;
43
+
44
+ // Check that default value is ok
45
+ $this->check();
46
+ }
47
+
48
+ /**
49
+ * Get Id.
50
+ *
51
+ * @return string The id of the option
52
+ */
53
+ public function getId()
54
+ {
55
+ return $this->id;
56
+ }
57
+
58
+ /**
59
+ * Get default value.
60
+ *
61
+ * @return mixed The default value for the option
62
+ */
63
+ public function getDefaultValue()
64
+ {
65
+ return $this->defaultValue;
66
+ }
67
+
68
+
69
+ /**
70
+ * Get value, or default value if value has not been explicitly set.
71
+ *
72
+ * @return mixed The value/default value
73
+ */
74
+ public function getValue()
75
+ {
76
+ if (!$this->isExplicitlySet) {
77
+ return $this->defaultValue;
78
+ } else {
79
+ return $this->value;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Get to know if value has been explicitly set.
85
+ *
86
+ * @return boolean Whether or not the value has been set explicitly
87
+ */
88
+ public function isValueExplicitlySet()
89
+ {
90
+ return $this->isExplicitlySet;
91
+ }
92
+
93
+ /**
94
+ * Set value
95
+ *
96
+ * @param mixed $value The value
97
+ * @return void
98
+ */
99
+ public function setValue($value)
100
+ {
101
+ $this->isExplicitlySet = true;
102
+ $this->value = $value;
103
+ }
104
+
105
+ /**
106
+ * Check if the value is valid.
107
+ *
108
+ * This base class does no checking, but this method is overridden by most other options.
109
+ * @return void
110
+ */
111
+ public function check()
112
+ {
113
+ }
114
+
115
+ /**
116
+ * Helpful function for checking type - used by subclasses.
117
+ *
118
+ * @param string $expectedType The expected type, ie 'string'
119
+ * @throws InvalidOptionTypeException If the type is invalid
120
+ * @return void
121
+ */
122
+ protected function checkType($expectedType)
123
+ {
124
+ if (gettype($this->getValue()) != $expectedType) {
125
+ throw new InvalidOptionTypeException(
126
+ 'The "' . $this->id . '" option must be a ' . $expectedType .
127
+ ' (you provided a ' . gettype($this->getValue()) . ')'
128
+ );
129
+ }
130
+ }
131
+
132
+ public function getValueForPrint()
133
+ {
134
+ return print_r($this->getValue(), true);
135
+ }
136
+ }
137
+
138
+ ?><?php
139
+
140
+ // TODO:
141
+ // Read this: https://sourcemaking.com/design_patterns/strategy
142
+
143
+ namespace WebPConvert\Convert\Converters;
144
+
145
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
146
+ use WebPConvert\Exceptions\WebPConvertException;
147
+ use WebPConvert\Convert\Converters\BaseTraits\AutoQualityTrait;
148
+ use WebPConvert\Convert\Converters\BaseTraits\DestinationPreparationTrait;
149
+ use WebPConvert\Convert\Converters\BaseTraits\LoggerTrait;
150
+ use WebPConvert\Convert\Converters\BaseTraits\OptionsTrait;
151
+ use WebPConvert\Convert\Converters\BaseTraits\SourceValidationTrait;
152
+ use WebPConvert\Convert\Converters\BaseTraits\WarningLoggerTrait;
153
+ use WebPConvert\Loggers\BaseLogger;
154
+
155
+ use ImageMimeTypeGuesser\ImageMimeTypeGuesser;
156
+
157
+ /**
158
+ * Base for all converter classes.
159
+ *
160
+ * @package WebPConvert
161
+ * @author Bjørn Rosell <it@rosell.dk>
162
+ * @since Class available since Release 2.0.0
163
+ */
164
+ abstract class AbstractConverter
165
+ {
166
+ use AutoQualityTrait;
167
+ use OptionsTrait;
168
+ use WarningLoggerTrait;
169
+ use DestinationPreparationTrait;
170
+ use SourceValidationTrait;
171
+ use LoggerTrait;
172
+
173
+ /**
174
+ * The actual conversion is be done by a concrete converter extending this class.
175
+ *
176
+ * At the stage this method is called, the abstract converter has taken preparational steps.
177
+ * - It has created the destination folder (if neccesary)
178
+ * - It has checked the input (valid mime type)
179
+ * - It has set up an error handler, mostly in order to catch and log warnings during the doConvert fase
180
+ *
181
+ * Note: This method is not meant to be called from the outside. Use the static *convert* method for converting
182
+ * or, if you wish, create an instance with ::createInstance() and then call ::doConvert()
183
+ *
184
+ * @throws ConversionFailedException in case conversion failed in an antipiciated way (or subclass)
185
+ * @throws \Exception in case conversion failed in an unantipiciated way
186
+ */
187
+ abstract protected function doActualConvert();
188
+
189
+ /**
190
+ * Whether or not the converter supports lossless encoding (even for jpegs)
191
+ *
192
+ * PS: Converters that supports lossless encoding all use the EncodingAutoTrait, which
193
+ * overrides this function.
194
+ *
195
+ * @return boolean Whether the converter supports lossless encoding (even for jpegs).
196
+ */
197
+ public function supportsLossless()
198
+ {
199
+ return false;
200
+ }
201
+
202
+ /** @var string The filename of the image to convert (complete path) */
203
+ protected $source;
204
+
205
+ /** @var string Where to save the webp (complete path) */
206
+ protected $destination;
207
+
208
+ /** @var string|false|null Where to save the webp (complete path) */
209
+ private $sourceMimeType;
210
+
211
+ /**
212
+ * Check basis operationality
213
+ *
214
+ * Converters may override this method for the purpose of performing basic operationaly checks. It is for
215
+ * running general operation checks for a conversion method.
216
+ * If some requirement is not met, it should throw a ConverterNotOperationalException (or subtype)
217
+ *
218
+ * The method is called internally right before calling doActualConvert() method.
219
+ * - It SHOULD take options into account when relevant. For example, a missing api key for a
220
+ * cloud converter should be detected here
221
+ * - It should NOT take the actual filename into consideration, as the purpose is *general*
222
+ * For that pupose, converters should override checkConvertability
223
+ * Also note that doConvert method is allowed to throw ConverterNotOperationalException too.
224
+ *
225
+ * @return void
226
+ */
227
+ public function checkOperationality()
228
+ {
229
+ }
230
+
231
+ /**
232
+ * Converters may override this for the purpose of performing checks on the concrete file.
233
+ *
234
+ * This can for example be used for rejecting big uploads in cloud converters or rejecting unsupported
235
+ * image types.
236
+ *
237
+ * @return void
238
+ */
239
+ public function checkConvertability()
240
+ {
241
+ }
242
+
243
+ /**
244
+ * Constructor.
245
+ *
246
+ * @param string $source path to source file
247
+ * @param string $destination path to destination
248
+ * @param array $options (optional) options for conversion
249
+ * @param BaseLogger $logger (optional)
250
+ */
251
+ public function __construct($source, $destination, $options = [], $logger = null)
252
+ {
253
+ $this->source = $source;
254
+ $this->destination = $destination;
255
+
256
+ $this->setLogger($logger);
257
+ $this->setProvidedOptions($options);
258
+
259
+ if (!isset($this->options['_skip_input_check'])) {
260
+ $this->log('WebP Convert 2.0.0', 'italic');
261
+ $this->logLn(' ignited.');
262
+ $this->logLn('- PHP version: ' . phpversion());
263
+ if (isset($_SERVER['SERVER_SOFTWARE'])) {
264
+ $this->logLn('- Server software: ' . $_SERVER['SERVER_SOFTWARE']);
265
+ }
266
+ $this->logLn('');
267
+ $this->logLn(self::getConverterDisplayName() . ' converter ignited');
268
+ }
269
+
270
+ $this->checkSourceExists();
271
+ $this->checkSourceMimeType();
272
+ }
273
+
274
+ /**
275
+ * Get source.
276
+ *
277
+ * @return string The source.
278
+ */
279
+ public function getSource()
280
+ {
281
+ return $this->source;
282
+ }
283
+
284
+ /**
285
+ * Get destination.
286
+ *
287
+ * @return string The destination.
288
+ */
289
+ public function getDestination()
290
+ {
291
+ return $this->destination;
292
+ }
293
+
294
+ /**
295
+ * Set destination.
296
+ *
297
+ * @param string $destination path to destination
298
+ * @return string The destination.
299
+ */
300
+ public function setDestination($destination)
301
+ {
302
+ $this->destination = $destination;
303
+ }
304
+
305
+
306
+ /**
307
+ * Get converter name for display (defaults to the class name (short)).
308
+ *
309
+ * Converters can override this.
310
+ *
311
+ * @return string A display name, ie "Gd"
312
+ */
313
+ protected static function getConverterDisplayName()
314
+ {
315
+ // https://stackoverflow.com/questions/19901850/how-do-i-get-an-objects-unqualified-short-class-name/25308464
316
+ return substr(strrchr('\\' . static::class, '\\'), 1);
317
+ }
318
+
319
+
320
+ /**
321
+ * Get converter id (defaults to the class name lowercased)
322
+ *
323
+ * Converters can override this.
324
+ *
325
+ * @return string A display name, ie "Gd"
326
+ */
327
+ protected static function getConverterId()
328
+ {
329
+ return strtolower(self::getConverterDisplayName());
330
+ }
331
+
332
+
333
+ /**
334
+ * Create an instance of this class
335
+ *
336
+ * @param string $source The path to the file to convert
337
+ * @param string $destination The path to save the converted file to
338
+ * @param array $options (optional)
339
+ * @param \WebPConvert\Loggers\BaseLogger $logger (optional)
340
+ *
341
+ * @return static
342
+ */
343
+ public static function createInstance($source, $destination, $options = [], $logger = null)
344
+ {
345
+
346
+ return new static($source, $destination, $options, $logger);
347
+ }
348
+
349
+ protected function logReduction($source, $destination)
350
+ {
351
+ $sourceSize = filesize($source);
352
+ $destSize = filesize($destination);
353
+ $this->log(round(($sourceSize - $destSize)/$sourceSize * 100) . '% ');
354
+ if ($sourceSize < 10000) {
355
+ $this->logLn('(went from ' . strval($sourceSize) . ' bytes to '. strval($destSize) . ' bytes)');
356
+ } else {
357
+ $this->logLn('(went from ' . round($sourceSize/1024) . ' kb to ' . round($destSize/1024) . ' kb)');
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Run conversion.
363
+ *
364
+ * @return void
365
+ */
366
+ private function doConvertImplementation()
367
+ {
368
+ $beginTime = microtime(true);
369
+
370
+ $this->activateWarningLogger();
371
+
372
+ $this->checkOptions();
373
+
374
+ // Prepare destination folder
375
+ $this->createWritableDestinationFolder();
376
+ $this->removeExistingDestinationIfExists();
377
+
378
+ if (!isset($this->options['_skip_input_check'])) {
379
+ // Run basic input validations (if source exists and if file extension is valid)
380
+ $this->checkSourceExists();
381
+ $this->checkSourceMimeType();
382
+
383
+ // Check that a file can be written to destination
384
+ $this->checkDestinationWritable();
385
+ }
386
+
387
+ $this->checkOperationality();
388
+ $this->checkConvertability();
389
+
390
+ if ($this->options['log-call-arguments']) {
391
+ $this->logOptions();
392
+ $this->logLn('');
393
+ }
394
+
395
+ $this->runActualConvert();
396
+
397
+ $source = $this->source;
398
+ $destination = $this->destination;
399
+
400
+ if (!@file_exists($destination)) {
401
+ throw new ConversionFailedException('Destination file is not there: ' . $destination);
402
+ } elseif (@filesize($destination) === 0) {
403
+ unlink($destination);
404
+ throw new ConversionFailedException('Destination file was completely empty');
405
+ } else {
406
+ if (!isset($this->options['_suppress_success_message'])) {
407
+ $this->ln();
408
+ $this->log('Converted image in ' . round((microtime(true) - $beginTime) * 1000) . ' ms');
409
+
410
+ $sourceSize = @filesize($source);
411
+ if ($sourceSize !== false) {
412
+ $this->log(', reducing file size with ');
413
+ $this->logReduction($source, $destination);
414
+ }
415
+ }
416
+ }
417
+
418
+ $this->deactivateWarningLogger();
419
+ }
420
+
421
+ //private function logEx
422
+ /**
423
+ * Start conversion.
424
+ *
425
+ * Usually you would rather call the static convert method, but alternatively you can call
426
+ * call ::createInstance to get an instance and then ::doConvert().
427
+ *
428
+ * @return void
429
+ */
430
+ public function doConvert()
431
+ {
432
+ try {
433
+ //trigger_error('hello', E_USER_ERROR);
434
+ $this->doConvertImplementation();
435
+ } catch (WebPConvertException $e) {
436
+ $this->logLn('');
437
+ /*
438
+ if (isset($e->description) && ($e->description != '')) {
439
+ $this->log('Error: ' . $e->description . '. ', 'bold');
440
+ } else {
441
+ $this->log('Error: ', 'bold');
442
+ }
443
+ */
444
+ $this->log('Error: ', 'bold');
445
+ $this->logLn($e->getMessage(), 'bold');
446
+ throw $e;
447
+ } catch (\Exception $e) {
448
+ $className = get_class($e);
449
+
450
+ $classNameParts = explode("\\", $className);
451
+ $shortClassName = array_pop($classNameParts);
452
+
453
+ $this->logLn('');
454
+ $this->logLn($shortClassName . ' thrown in ' . $e->getFile() . ':' . $e->getLine(), 'bold');
455
+ $this->logLn('Message: "' . $e->getMessage() . '"', 'bold');
456
+ //$this->logLn('Exception class: ' . $className);
457
+
458
+ $this->logLn('Trace:');
459
+ foreach ($e->getTrace() as $trace) {
460
+ //$this->logLn(print_r($trace, true));
461
+ if (isset($trace['file']) && isset($trace['line'])) {
462
+ $this->logLn(
463
+ $trace['file'] . ':' . $trace['line']
464
+ );
465
+ }
466
+ }
467
+ throw $e;
468
+ } /*catch (\Error $e) {
469
+ $this->logLn('ERROR');
470
+ }*/
471
+ }
472
+
473
+ /**
474
+ * Runs the actual conversion (after setup and checks)
475
+ * Simply calls the doActualConvert() of the actual converter.
476
+ * However, in the EncodingAutoTrait, this method is overridden to make two conversions
477
+ * and select the smallest.
478
+ *
479
+ * @return void
480
+ */
481
+ protected function runActualConvert()
482
+ {
483
+ $this->doActualConvert();
484
+ }
485
+
486
+ /**
487
+ * Convert an image to webp.
488
+ *
489
+ * @param string $source path to source file
490
+ * @param string $destination path to destination
491
+ * @param array $options (optional) options for conversion
492
+ * @param BaseLogger $logger (optional)
493
+ *
494
+ * @throws ConversionFailedException in case conversion fails in an antipiciated way
495
+ * @throws \Exception in case conversion fails in an unantipiciated way
496
+ * @return void
497
+ */
498
+ public static function convert($source, $destination, $options = [], $logger = null)
499
+ {
500
+ $c = self::createInstance($source, $destination, $options, $logger);
501
+ $c->doConvert();
502
+ //echo $instance->id;
503
+ }
504
+
505
+ /**
506
+ * Get mime type for image (best guess).
507
+ *
508
+ * It falls back to using file extension. If that fails too, false is returned
509
+ *
510
+ * PS: Is it a security risk to fall back on file extension?
511
+ * - By setting file extension to "jpg", one can lure our library into trying to convert a file, which isn't a jpg.
512
+ * hmm, seems very unlikely, though not unthinkable that one of the converters could be exploited
513
+ *
514
+ * @return string|false|null mimetype (if it is an image, and type could be determined / guessed),
515
+ * false (if it is not an image type that the server knowns about)
516
+ * or null (if nothing can be determined)
517
+ */
518
+ public function getMimeTypeOfSource()
519
+ {
520
+ if (!isset($this->sourceMimeType)) {
521
+ $this->sourceMimeType = ImageMimeTypeGuesser::lenientGuess($this->source);
522
+ }
523
+ return $this->sourceMimeType;
524
+ }
525
+ }
526
+
527
+ ?><?php
528
+
529
+ namespace WebPConvert\Exceptions;
530
+
531
+ /**
532
+ * WebPConvertException is the base exception for all exceptions in this library.
533
+ *
534
+ * Note that the parameters for the constructor differs from that of the Exception class.
535
+ * We do not use exception code here, but are instead allowing two version of the error message:
536
+ * a short version and a long version.
537
+ * The short version may not contain special characters or dynamic content.
538
+ * The detailed version may.
539
+ * If the detailed version isn't provided, getDetailedMessage will return the short version.
540
+ *
541
+ */
542
+ class WebPConvertException extends \Exception
543
+ {
544
+ public $description = '';
545
+ protected $detailedMessage;
546
+ protected $shortMessage;
547
+
548
+ public function getDetailedMessage()
549
+ {
550
+ return $this->detailedMessage;
551
+ }
552
+
553
+ public function getShortMessage()
554
+ {
555
+ return $this->shortMessage;
556
+ }
557
+
558
+ public function __construct($shortMessage = "", $detailedMessage = "", $previous = null)
559
+ {
560
+ $detailedMessage = ($detailedMessage != '') ? $detailedMessage : $shortMessage;
561
+ $this->detailedMessage = $detailedMessage;
562
+ $this->shortMessage = $shortMessage;
563
+
564
+ parent::__construct(
565
+ $detailedMessage,
566
+ 0,
567
+ $previous
568
+ );
569
+ }
570
+ }
571
+
572
+ ?><?php
573
+
574
+ namespace WebPConvert\Convert\Exceptions;
575
+
576
+ use WebPConvert\Exceptions\WebPConvertException;
577
+
578
+ /**
579
+ * ConversionFailedException is the base exception in the hierarchy for conversion errors.
580
+ *
581
+ * Exception hierarchy from here:
582
+ *
583
+ * WebpConvertException
584
+ * ConversionFailedException
585
+ * ConversionSkippedException
586
+ * ConverterNotOperationalException
587
+ * InvalidApiKeyException
588
+ * SystemRequirementsNotMetException
589
+ * FileSystemProblemsException
590
+ * CreateDestinationFileException
591
+ * CreateDestinationFolderException
592
+ * InvalidInputException
593
+ * ConverterNotFoundException
594
+ * InvalidImageTypeException
595
+ * InvalidOptionValueException
596
+ * TargetNotFoundException
597
+ */
598
+ class ConversionFailedException extends WebPConvertException
599
+ {
600
+ //public $description = 'Conversion failed';
601
+ public $description = '';
602
+ }
603
+
604
+ ?><?php
605
+
606
+ namespace WebPConvert;
607
+
608
+ //use WebPConvert\Convert\Converters\ConverterHelper;
609
+ use WebPConvert\Convert\Converters\Stack;
610
+ //use WebPConvert\Serve\ServeExistingOrHandOver;
611
+ use WebPConvert\Serve\ServeConvertedWebP;
612
+ use WebPConvert\Serve\ServeConvertedWebPWithErrorHandling;
613
+
614
+ /**
615
+ * Convert images to webp and/or serve them.
616
+ *
617
+ * This class is just a couple of convenience methods for doing conversion and/or
618
+ * serving.
619
+ *
620
+ * @package WebPConvert
621
+ * @author Bjørn Rosell <it@rosell.dk>
622
+ * @since Class available since Release 2.0.0
623
+ */
624
+ class WebPConvert
625
+ {
626
+
627
+ /**
628
+ * Convert jpeg or png into webp
629
+ *
630
+ * Convenience method for calling Stack::convert.
631
+ *
632
+ * @param string $source The image to convert (absolute,no backslashes)
633
+ * Image must be jpeg or png.
634
+ * @param string $destination Where to store the converted file (absolute path, no backslashes).
635
+ * @param array $options (optional) Array of named options
636
+ * The options are documented here:
637
+ * https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md
638
+ * @param \WebPConvert\Loggers\BaseLogger $logger (optional)
639
+ *
640
+ * @throws \WebPConvert\Convert\Exceptions\ConversionFailedException in case conversion fails
641
+ * @return void
642
+ */
643
+ public static function convert($source, $destination, $options = [], $logger = null)
644
+ {
645
+ Stack::convert($source, $destination, $options, $logger);
646
+ }
647
+
648
+ /**
649
+ * Serve webp image, converting first if neccessary.
650
+ *
651
+ * If an image already exists, it will be served, unless it is older or larger than the source. (If it is larger,
652
+ * the original is served, if it is older, the existing webp will be deleted and a fresh conversion will be made
653
+ * and served). In case of error, the action indicated in the 'fail' option will be triggered (default is to serve
654
+ * the original). Look up the ServeConvertedWebP:serve() and the ServeConvertedWebPWithErrorHandling::serve()
655
+ * methods to learn more.
656
+ *
657
+ * @param string $source path to source file
658
+ * @param string $destination path to destination
659
+ * @param array $options (optional) options for serving/converting. The options are documented in the
660
+ * ServeConvertedWebPWithErrorHandling::serve() method
661
+ * @param \WebPConvert\Loggers\BaseLogger $serveLogger (optional)
662
+ * @param \WebPConvert\Loggers\BaseLogger $convertLogger (optional)
663
+ * @return void
664
+ */
665
+ public static function serveConverted(
666
+ $source,
667
+ $destination,
668
+ $options = [],
669
+ $serveLogger = null,
670
+ $convertLogger = null
671
+ ) {
672
+ //return ServeExistingOrHandOver::serveConverted($source, $destination, $options);
673
+ //if (isset($options['handle-errors']) && $options['handle-errors'] === true) {
674
+ if (isset($options['fail']) && ($options['fail'] != 'throw')) {
675
+ ServeConvertedWebPWithErrorHandling::serve($source, $destination, $options, $serveLogger, $convertLogger);
676
+ } else {
677
+ ServeConvertedWebP::serve($source, $destination, $options, $serveLogger, $convertLogger);
678
+ }
679
+ }
680
+ }
681
+
682
+ ?><?php
683
+
684
+ namespace WebPConvert\Options;
685
+
686
+ use WebPConvert\Options\Option;
687
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
688
+
689
+ /**
690
+ * Abstract option class
691
+ *
692
+ * @package WebPConvert
693
+ * @author Bjørn Rosell <it@rosell.dk>
694
+ * @since Class available since Release 2.0.0
695
+ */
696
+ class ArrayOption extends Option
697
+ {
698
+
699
+ public function check()
700
+ {
701
+ $this->checkType('array');
702
+ }
703
+
704
+ public function getValueForPrint()
705
+ {
706
+ if (count($this->getValue()) == 0) {
707
+ return '(empty array)';
708
+ } else {
709
+ return parent::getValueForPrint();
710
+ }
711
+ }
712
+ }
713
+
714
+ ?><?php
715
+
716
+ namespace WebPConvert\Options;
717
+
718
+ use WebPConvert\Options\Option;
719
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
720
+
721
+ /**
722
+ * Boolean option
723
+ *
724
+ * @package WebPConvert
725
+ * @author Bjørn Rosell <it@rosell.dk>
726
+ * @since Class available since Release 2.0.0
727
+ */
728
+ class BooleanOption extends Option
729
+ {
730
+
731
+ public function check()
732
+ {
733
+ $this->checkType('boolean');
734
+ }
735
+
736
+ public function getValueForPrint()
737
+ {
738
+ return ($this->getValue() === true ? 'true' : 'false');
739
+ }
740
+ }
741
+
742
+ ?><?php
743
+
744
+ namespace WebPConvert\Options;
745
+
746
+ use WebPConvert\Options\Option;
747
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
748
+
749
+ /**
750
+ * Ghost option
751
+ *
752
+ * @package WebPConvert
753
+ * @author Bjørn Rosell <it@rosell.dk>
754
+ * @since Class available since Release 2.0.0
755
+ */
756
+ class GhostOption extends Option
757
+ {
758
+
759
+ public function getValueForPrint()
760
+ {
761
+ return '(not defined for this converter)';
762
+ }
763
+ }
764
+
765
+ ?><?php
766
+
767
+ namespace WebPConvert\Options;
768
+
769
+ use WebPConvert\Options\Option;
770
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
771
+
772
+ /**
773
+ * Abstract option class
774
+ *
775
+ * @package WebPConvert
776
+ * @author Bjørn Rosell <it@rosell.dk>
777
+ * @since Class available since Release 2.0.0
778
+ */
779
+ class IntegerOption extends Option
780
+ {
781
+
782
+ protected $minValue;
783
+ protected $maxValue;
784
+
785
+ /**
786
+ * Constructor.
787
+ *
788
+ * @param string $id id of the option
789
+ * @param integer $defaultValue default value for the option
790
+ * @throws InvalidOptionValueException if the default value cannot pass the check
791
+ * @return void
792
+ */
793
+ public function __construct($id, $defaultValue, $minValue = null, $maxValue = null)
794
+ {
795
+ $this->minValue = $minValue;
796
+ $this->maxValue = $maxValue;
797
+ parent::__construct($id, $defaultValue);
798
+ }
799
+
800
+ protected function checkMin()
801
+ {
802
+ if (!is_null($this->minValue) && $this->getValue() < $this->minValue) {
803
+ throw new InvalidOptionValueException(
804
+ '"' . $this->id . '" option must be set to minimum ' . $this->minValue . '. ' .
805
+ 'It was however set to: ' . $this->getValue()
806
+ );
807
+ }
808
+ }
809
+
810
+ protected function checkMax()
811
+ {
812
+ if (!is_null($this->maxValue) && $this->getValue() > $this->maxValue) {
813
+ throw new InvalidOptionValueException(
814
+ '"' . $this->id . '" option must be set to max ' . $this->maxValue . '. ' .
815
+ 'It was however set to: ' . $this->getValue()
816
+ );
817
+ }
818
+ }
819
+
820
+ protected function checkMinMax()
821
+ {
822
+ $this->checkMin();
823
+ $this->checkMax();
824
+ }
825
+
826
+ public function check()
827
+ {
828
+ $this->checkType('integer');
829
+ $this->checkMinMax();
830
+ }
831
+ }
832
+
833
+ ?><?php
834
+
835
+ namespace WebPConvert\Options;
836
+
837
+ use WebPConvert\Options\IntegerOption;
838
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
839
+
840
+ /**
841
+ * Abstract option class
842
+ *
843
+ * @package WebPConvert
844
+ * @author Bjørn Rosell <it@rosell.dk>
845
+ * @since Class available since Release 2.0.0
846
+ */
847
+ class IntegerOrNullOption extends IntegerOption
848
+ {
849
+
850
+ public function __construct($id, $defaultValue, $minValue = null, $maxValue = null)
851
+ {
852
+ parent::__construct($id, $defaultValue, $minValue, $maxValue);
853
+ }
854
+
855
+ public function check()
856
+ {
857
+ $this->checkMinMax();
858
+
859
+ $valueType = gettype($this->getValue());
860
+ if (!in_array($valueType, ['integer', 'NULL'])) {
861
+ throw new InvalidOptionValueException(
862
+ 'The "' . $this->id . '" option must be either integer or NULL. ' .
863
+ 'You however provided a value of type: ' . $valueType
864
+ );
865
+ }
866
+ }
867
+
868
+ public function getValueForPrint()
869
+ {
870
+ if (gettype($this->getValue() == 'NULL')) {
871
+ return 'null (not set)';
872
+ }
873
+ return parent::getValueForPrint();
874
+ }
875
+ }
876
+
877
+ ?><?php
878
+
879
+ namespace WebPConvert\Options;
880
+
881
+ use WebPConvert\Options\StringOption;
882
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
883
+
884
+ /**
885
+ * Metadata option. A Comma-separated list ('all', 'none', 'exif', 'icc', 'xmp')
886
+ *
887
+ * @package WebPConvert
888
+ * @author Bjørn Rosell <it@rosell.dk>
889
+ * @since Class available since Release 2.0.0
890
+ */
891
+ class MetadataOption extends StringOption
892
+ {
893
+
894
+ public function __construct($id, $defaultValue)
895
+ {
896
+ parent::__construct($id, $defaultValue);
897
+ }
898
+
899
+ public function check()
900
+ {
901
+ parent::check();
902
+
903
+ $value = $this->getValue();
904
+
905
+ if (($value == 'all') || ($value == 'none')) {
906
+ return;
907
+ }
908
+
909
+ foreach (explode(',', $value) as $item) {
910
+ if (!in_array($value, ['exif', 'icc', 'xmp'])) {
911
+ throw new InvalidOptionValueException(
912
+ '"metadata" option must be "all", "none" or a comma-separated list of "exif", "icc" or "xmp". ' .
913
+ 'It was however set to: "' . $value . '"'
914
+ );
915
+ }
916
+ }
917
+
918
+ //$this->checkType('string');
919
+ }
920
+ }
921
+
922
+ ?><?php
923
+
924
+ namespace WebPConvert\Options;
925
+
926
+ use WebPConvert\Options\Option;
927
+ use WebPConvert\Options\Exceptions\OptionNotFoundException;
928
+
929
+ /**
930
+ * Handles a collection of options.
931
+ *
932
+ * @package WebPConvert
933
+ * @author Bjørn Rosell <it@rosell.dk>
934
+ * @since Class available since Release 2.0.0
935
+ */
936
+ class Options
937
+ {
938
+
939
+ /** @var array A map of options, keyed by their id */
940
+ private $options = [];
941
+
942
+ /**
943
+ * Add option.
944
+ *
945
+ * @param Option $option The option object to add to collection.
946
+ * @return void
947
+ */
948
+ public function addOption($option)
949
+ {
950
+ $this->options[$option->getId()] = $option;
951
+ }
952
+
953
+ /**
954
+ * Add options.
955
+ *
956
+ * Conveniently add several options in one call.
957
+ *
958
+ * @param Option[] ...$options Array of options objects to add
959
+ * @return void
960
+ */
961
+ public function addOptions(...$options)
962
+ {
963
+ foreach ($options as $option) {
964
+ $this->addOption($option);
965
+ }
966
+ }
967
+
968
+ /**
969
+ * Set the value of an option.
970
+ *
971
+ * @param string $id Id of the option
972
+ * @param mixed $value Value of the option
973
+ * @return void
974
+ */
975
+ public function setOption($id, $value)
976
+ {
977
+ if (!isset($this->options[$id])) {
978
+ throw new OptionNotFoundException(
979
+ 'Could not set option. There is no option called "' . $id . '" in the collection.'
980
+ );
981
+ }
982
+ $option = $this->options[$id];
983
+ $option->setValue($value);
984
+ }
985
+
986
+ /**
987
+ * Set option, or create a new, if no such option exists.
988
+ *
989
+ * @param string $id Id of option to set/create
990
+ * @param mixed $value Value of option
991
+ * @return void
992
+ */
993
+ public function setOrCreateOption($id, $value)
994
+ {
995
+ if (!isset($this->options[$id])) {
996
+ $newOption = new GhostOption($id, null);
997
+ $newOption->setValue($value);
998
+ //$newOption = new Option($id, $value);
999
+ $this->addOption($newOption);
1000
+ } else {
1001
+ $this->setOption($id, $value);
1002
+ }
1003
+ }
1004
+
1005
+ /**
1006
+ * Get the value of an option in the collection - by id.
1007
+ *
1008
+ * @param string $id Id of the option to get
1009
+ * @throws OptionNotFoundException if the option is not in the collection
1010
+ * @return mixed The value of the option
1011
+ */
1012
+ public function getOption($id)
1013
+ {
1014
+ if (!isset($this->options[$id])) {
1015
+ throw new OptionNotFoundException(
1016
+ 'There is no option called "' . $id . '" in the collection.'
1017
+ );
1018
+ }
1019
+ $option = $this->options[$id];
1020
+ return $option->getValue();
1021
+ }
1022
+
1023
+ /**
1024
+ * Return map of option objects.
1025
+ *
1026
+ * @return array map of option objects
1027
+ */
1028
+ public function getOptionsMap()
1029
+ {
1030
+ return $this->options;
1031
+ }
1032
+
1033
+ /**
1034
+ * Return flat associative array of options.
1035
+ *
1036
+ * @return array associative array of options
1037
+ */
1038
+ public function getOptions()
1039
+ {
1040
+ $values = [];
1041
+ foreach ($this->options as $id => $option) {
1042
+ $values[$id] = $option->getValue();
1043
+ }
1044
+ return $values;
1045
+ }
1046
+
1047
+ /**
1048
+ * Check all options in the collection.
1049
+ */
1050
+ public function check()
1051
+ {
1052
+ foreach ($this->options as $id => $option) {
1053
+ $option->check();
1054
+ }
1055
+ }
1056
+ }
1057
+
1058
+ ?><?php
1059
+
1060
+ namespace WebPConvert\Options;
1061
+
1062
+ use WebPConvert\Options\Option;
1063
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
1064
+
1065
+ /**
1066
+ * Quality option.
1067
+ *
1068
+ * Quality can be a number between 0-100 or "auto"
1069
+ *
1070
+ * @package WebPConvert
1071
+ * @author Bjørn Rosell <it@rosell.dk>
1072
+ * @since Class available since Release 2.0.0
1073
+ */
1074
+ class QualityOption extends Option
1075
+ {
1076
+
1077
+ public function __construct($id, $defaultValue)
1078
+ {
1079
+ parent::__construct($id, $defaultValue);
1080
+ }
1081
+
1082
+ public function check()
1083
+ {
1084
+ $value = $this->getValue();
1085
+ if (gettype($value) == 'string') {
1086
+ if ($value != 'auto') {
1087
+ throw new InvalidOptionValueException(
1088
+ 'The "quality" option must be either "auto" or a number between 0-100. ' .
1089
+ 'A string, different from "auto" was given'
1090
+ );
1091
+ }
1092
+ } elseif (gettype($value) == 'integer') {
1093
+ if (($value < 0) || ($value > 100)) {
1094
+ throw new InvalidOptionValueException(
1095
+ 'The "quality" option must be either "auto" or a number between 0-100. ' .
1096
+ 'The number you provided (' . strval($value) . ') is out of range.'
1097
+ );
1098
+ }
1099
+ } else {
1100
+ throw new InvalidOptionValueException(
1101
+ 'The "quality" option must be either "auto" or an integer. ' .
1102
+ 'You however provided a value of type: ' . gettype($value)
1103
+ );
1104
+ }
1105
+ }
1106
+
1107
+ public function getValueForPrint()
1108
+ {
1109
+ if (gettype($this->getValue()) == 'string') {
1110
+ return '"' . $this->getValue() . '"';
1111
+ }
1112
+ return $this->getValue();
1113
+ }
1114
+ }
1115
+
1116
+ ?><?php
1117
+
1118
+ namespace WebPConvert\Options;
1119
+
1120
+ use WebPConvert\Options\StringOption;
1121
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
1122
+
1123
+ /**
1124
+ * Abstract option class
1125
+ *
1126
+ * @package WebPConvert
1127
+ * @author Bjørn Rosell <it@rosell.dk>
1128
+ * @since Class available since Release 2.0.0
1129
+ */
1130
+ class SensitiveArrayOption extends ArrayOption
1131
+ {
1132
+
1133
+ public function check()
1134
+ {
1135
+ parent::check();
1136
+ }
1137
+
1138
+ public function getValueForPrint()
1139
+ {
1140
+ if (count($this->getValue()) == 0) {
1141
+ return '(empty array)';
1142
+ } else {
1143
+ return '(array of ' . count($this->getValue()) . ' items)';
1144
+ }
1145
+ //return '*****';
1146
+ }
1147
+ }
1148
+
1149
+ ?><?php
1150
+
1151
+ namespace WebPConvert\Options;
1152
+
1153
+ use WebPConvert\Options\StringOption;
1154
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
1155
+
1156
+ /**
1157
+ * Abstract option class
1158
+ *
1159
+ * @package WebPConvert
1160
+ * @author Bjørn Rosell <it@rosell.dk>
1161
+ * @since Class available since Release 2.0.0
1162
+ */
1163
+ class SensitiveStringOption extends StringOption
1164
+ {
1165
+
1166
+ public function __construct($id, $defaultValue, $allowedValues = null)
1167
+ {
1168
+ parent::__construct($id, $defaultValue, $allowedValues);
1169
+ }
1170
+
1171
+ public function check()
1172
+ {
1173
+ parent::check();
1174
+ }
1175
+
1176
+ public function getValueForPrint()
1177
+ {
1178
+ if (strlen($this->getValue()) == 0) {
1179
+ return '""';
1180
+ }
1181
+ return '*****';
1182
+ }
1183
+ }
1184
+
1185
+ ?><?php
1186
+
1187
+ namespace WebPConvert\Options;
1188
+
1189
+ use WebPConvert\Options\Option;
1190
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
1191
+
1192
+ /**
1193
+ * Abstract option class
1194
+ *
1195
+ * @package WebPConvert
1196
+ * @author Bjørn Rosell <it@rosell.dk>
1197
+ * @since Class available since Release 2.0.0
1198
+ */
1199
+ class StringOption extends Option
1200
+ {
1201
+
1202
+ public $allowedValues;
1203
+
1204
+ public function __construct($id, $defaultValue, $allowedValues = null)
1205
+ {
1206
+ $this->allowedValues = $allowedValues;
1207
+ parent::__construct($id, $defaultValue);
1208
+ }
1209
+
1210
+ public function check()
1211
+ {
1212
+ $this->checkType('string');
1213
+
1214
+ if (!is_null($this->allowedValues) && (!in_array($this->getValue(), $this->allowedValues))) {
1215
+ throw new InvalidOptionValueException(
1216
+ '"' . $this->id . '" option must be on of these values: ' .
1217
+ '[' . implode(', ', $this->allowedValues) . ']. ' .
1218
+ 'It was however set to: "' . $this->getValue() . '"'
1219
+ );
1220
+ }
1221
+ }
1222
+
1223
+ public function getValueForPrint()
1224
+ {
1225
+ return '"' . $this->getValue() . '"';
1226
+ }
1227
+ }
1228
+
1229
+ ?><?php
1230
+
1231
+ namespace WebPConvert\Convert\Converters\BaseTraits;
1232
+
1233
+ use WebPConvert\Convert\Helpers\JpegQualityDetector;
1234
+
1235
+ /**
1236
+ * Trait for handling the "quality:auto" option.
1237
+ *
1238
+ * This trait is only used in the AbstractConverter class. It has been extracted into a
1239
+ * trait in order to bundle the methods concerning auto quality.
1240
+ *
1241
+ * @package WebPConvert
1242
+ * @author Bjørn Rosell <it@rosell.dk>
1243
+ * @since Class available since Release 2.0.0
1244
+ */
1245
+ trait AutoQualityTrait
1246
+ {
1247
+
1248
+ abstract public function logLn($msg, $style = '');
1249
+ abstract public function getMimeTypeOfSource();
1250
+
1251
+ /** @var boolean Whether the quality option has been processed or not */
1252
+ private $processed = false;
1253
+
1254
+ /** @var boolean Whether the quality of the source could be detected or not (set upon processing) */
1255
+ private $qualityCouldNotBeDetected = false;
1256
+
1257
+ /** @var integer The calculated quality (set upon processing - on successful detection) */
1258
+ private $calculatedQuality;
1259
+
1260
+
1261
+ /**
1262
+ * Determine if quality detection is required but failing.
1263
+ *
1264
+ * It is considered "required" when:
1265
+ * - Mime type is "image/jpeg"
1266
+ * - Quality is set to "auto"
1267
+ *
1268
+ * If quality option hasn't been proccessed yet, it is triggered.
1269
+ *
1270
+ * @return boolean
1271
+ */
1272
+ public function isQualityDetectionRequiredButFailing()
1273
+ {
1274
+ $this->processQualityOptionIfNotAlready();
1275
+ return $this->qualityCouldNotBeDetected;
1276
+ }
1277
+
1278
+ /**
1279
+ * Get calculated quality.
1280
+ *
1281
+ * If the "quality" option is a number, that number is returned.
1282
+ * If mime type of source is something else than "image/jpeg", the "default-quality" option is returned
1283
+ * If quality is "auto" and source is a jpeg image, it will be attempted to detect jpeg quality.
1284
+ * In case of failure, the value of the "default-quality" option is returned.
1285
+ * In case of success, the detected quality is returned, or the value of the "max-quality" if that is lower.
1286
+ *
1287
+ * @return int
1288
+ */
1289
+ public function getCalculatedQuality()
1290
+ {
1291
+ $this->processQualityOptionIfNotAlready();
1292
+ return $this->calculatedQuality;
1293
+ }
1294
+
1295
+ /**
1296
+ * Process the quality option if it is not already processed.
1297
+ *
1298
+ * @return void
1299
+ */
1300
+ private function processQualityOptionIfNotAlready()
1301
+ {
1302
+ if (!$this->processed) {
1303
+ $this->processed = true;
1304
+ $this->processQualityOption();
1305
+ }
1306
+ }
1307
+
1308
+ /**
1309
+ * Process the quality option.
1310
+ *
1311
+ * Sets the private property "calculatedQuality" according to the description for the getCalculatedQuality
1312
+ * function.
1313
+ * In case quality detection was attempted and failed, the private property "qualityCouldNotBeDetected" is set
1314
+ * to true. This is used by the "isQualityDetectionRequiredButFailing" (and documented there too).
1315
+ *
1316
+ * @return void
1317
+ */
1318
+ private function processQualityOption()
1319
+ {
1320
+ $options = $this->options;
1321
+ $source = $this->source;
1322
+
1323
+ $q = $options['quality'];
1324
+ if ($q == 'auto') {
1325
+ if (($this->/** @scrutinizer ignore-call */getMimeTypeOfSource() == 'image/jpeg')) {
1326
+ $q = JpegQualityDetector::detectQualityOfJpg($source);
1327
+ if (is_null($q)) {
1328
+ $q = $options['default-quality'];
1329
+ $this->/** @scrutinizer ignore-call */logLn(
1330
+ 'Quality of source could not be established (Imagick or GraphicsMagick is required)' .
1331
+ ' - Using default instead (' . $options['default-quality'] . ').'
1332
+ );
1333
+
1334
+ $this->qualityCouldNotBeDetected = true;
1335
+ } else {
1336
+ if ($q > $options['max-quality']) {
1337
+ $this->logLn(
1338
+ 'Quality of source is ' . $q . '. ' .
1339
+ 'This is higher than max-quality, so using max-quality instead (' .
1340
+ $options['max-quality'] . ')'
1341
+ );
1342
+ } else {
1343
+ $this->logLn('Quality set to same as source: ' . $q);
1344
+ }
1345
+ }
1346
+ $q = min($q, $options['max-quality']);
1347
+ } else {
1348
+ //$q = $options['default-quality'];
1349
+ $q = min($options['default-quality'], $options['max-quality']);
1350
+ $this->logLn('Quality: ' . $q . '. ');
1351
+ }
1352
+ } else {
1353
+ $this->logLn(
1354
+ 'Quality: ' . $q . '. '
1355
+ );
1356
+ if (($this->getMimeTypeOfSource() == 'image/jpeg')) {
1357
+ $this->logLn(
1358
+ 'Consider setting quality to "auto" instead. It is generally a better idea'
1359
+ );
1360
+ }
1361
+ }
1362
+ $this->calculatedQuality = $q;
1363
+ }
1364
+ }
1365
+
1366
+ ?><?php
1367
+
1368
+ namespace WebPConvert\Convert\Converters\BaseTraits;
1369
+
1370
+ use WebPConvert\Convert\Exceptions\ConversionFailed\FileSystemProblems\CreateDestinationFileException;
1371
+ use WebPConvert\Convert\Exceptions\ConversionFailed\FileSystemProblems\CreateDestinationFolderException;
1372
+
1373
+ /**
1374
+ * Trait for handling options
1375
+ *
1376
+ * This trait is currently only used in the AbstractConverter class. It has been extracted into a
1377
+ * trait in order to bundle the methods concerning options.
1378
+ *
1379
+ * @package WebPConvert
1380
+ * @author Bjørn Rosell <it@rosell.dk>
1381
+ * @since Class available since Release 2.0.0
1382
+ */
1383
+ trait DestinationPreparationTrait
1384
+ {
1385
+
1386
+ abstract public function getDestination();
1387
+ abstract public function logLn($msg, $style = '');
1388
+
1389
+ /**
1390
+ * Create writable folder in provided path (if it does not exist already)
1391
+ *
1392
+ * @throws CreateDestinationFolderException if folder cannot be removed
1393
+ * @return void
1394
+ */
1395
+ private function createWritableDestinationFolder()
1396
+ {
1397
+ $destination = $this->getDestination();
1398
+
1399
+ $folder = dirname($destination);
1400
+ if (!file_exists($folder)) {
1401
+ $this->logLn('Destination folder does not exist. Creating folder: ' . $folder);
1402
+ // TODO: what if this is outside open basedir?
1403
+ // see http://php.net/manual/en/ini.core.php#ini.open-basedir
1404
+
1405
+ // Trying to create the given folder (recursively)
1406
+ if (!mkdir($folder, 0777, true)) {
1407
+ throw new CreateDestinationFolderException(
1408
+ 'Failed creating folder. Check the permissions!',
1409
+ 'Failed creating folder: ' . $folder . '. Check permissions!'
1410
+ );
1411
+ }
1412
+ }
1413
+ }
1414
+
1415
+ /**
1416
+ * Check that we can write file at destination.
1417
+ *
1418
+ * It is assumed that the folder already exists (that ::createWritableDestinationFolder() was called first)
1419
+ *
1420
+ * @throws CreateDestinationFileException if file cannot be created at destination
1421
+ * @return void
1422
+ */
1423
+ private function checkDestinationWritable()
1424
+ {
1425
+ $destination = $this->getDestination();
1426
+ $dirName = dirname($destination);
1427
+
1428
+ if (@is_writable($dirName) && @is_executable($dirName)) {
1429
+ // all is well
1430
+ return;
1431
+ }
1432
+
1433
+ // The above might fail on Windows, even though dir is writable
1434
+ // So, to be absolute sure that we cannot write, we make an actual write test (writing a dummy file)
1435
+ // No harm in doing that for non-Windows systems either.
1436
+ if (file_put_contents($destination, 'dummy') !== false) {
1437
+ // all is well, after all
1438
+ unlink($destination);
1439
+ return;
1440
+ }
1441
+
1442
+ throw new CreateDestinationFileException(
1443
+ 'Cannot create file: ' . basename($destination) . ' in dir:' . dirname($destination)
1444
+ );
1445
+ }
1446
+
1447
+ /**
1448
+ * Remove existing destination.
1449
+ *
1450
+ * @throws CreateDestinationFileException if file cannot be removed
1451
+ * @return void
1452
+ */
1453
+ private function removeExistingDestinationIfExists()
1454
+ {
1455
+ $destination = $this->getDestination();
1456
+ if (file_exists($destination)) {
1457
+ // A file already exists in this folder...
1458
+ // We delete it, to make way for a new webp
1459
+ if (!unlink($destination)) {
1460
+ throw new CreateDestinationFileException(
1461
+ 'Existing file cannot be removed: ' . basename($destination)
1462
+ );
1463
+ }
1464
+ }
1465
+ }
1466
+ }
1467
+
1468
+ ?><?php
1469
+
1470
+ namespace WebPConvert\Convert\Converters\BaseTraits;
1471
+
1472
+ /**
1473
+ * Trait for providing logging capabilities.
1474
+ *
1475
+ * This trait is currently only used in the AbstractConverter class. It has been extracted into a
1476
+ * trait in order to bundle the methods concerning logging.
1477
+ *
1478
+ * @package WebPConvert
1479
+ * @author Bjørn Rosell <it@rosell.dk>
1480
+ * @since Class available since Release 2.0.0
1481
+ */
1482
+ trait LoggerTrait
1483
+ {
1484
+
1485
+ /** @var \WebPConvert\Loggers\BaseLogger The logger (or null if not set) */
1486
+ protected $logger;
1487
+
1488
+ /**
1489
+ * Set logger
1490
+ *
1491
+ * @param \WebPConvert\Loggers\BaseLogger $logger (optional) $logger
1492
+ * @return void
1493
+ */
1494
+ public function setLogger($logger = null)
1495
+ {
1496
+ $this->logger = $logger;
1497
+ }
1498
+
1499
+ /**
1500
+ * Write a line to the logger.
1501
+ *
1502
+ * @param string $msg The line to write.
1503
+ * @param string $style (optional) Ie "italic" or "bold"
1504
+ * @return void
1505
+ */
1506
+ protected function logLn($msg, $style = '')
1507
+ {
1508
+ if (isset($this->logger)) {
1509
+ $this->logger->logLn($msg, $style);
1510
+ }
1511
+ }
1512
+
1513
+ /**
1514
+ * New line
1515
+ *
1516
+ * @return void
1517
+ */
1518
+ protected function ln()
1519
+ {
1520
+ if (isset($this->logger)) {
1521
+ $this->logger->ln();
1522
+ }
1523
+ }
1524
+
1525
+ /**
1526
+ * Write to the logger, without newline
1527
+ *
1528
+ * @param string $msg What to write.
1529
+ * @param string $style (optional) Ie "italic" or "bold"
1530
+ * @return void
1531
+ */
1532
+ protected function log($msg, $style = '')
1533
+ {
1534
+ if (isset($this->logger)) {
1535
+ $this->logger->log($msg, $style);
1536
+ }
1537
+ }
1538
+ }
1539
+
1540
+ ?><?php
1541
+
1542
+ namespace WebPConvert\Convert\Converters\BaseTraits;
1543
+
1544
+ use WebPConvert\Convert\Converters\Stack;
1545
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConversionSkippedException;
1546
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
1547
+ use WebPConvert\Options\Exceptions\InvalidOptionTypeException;
1548
+
1549
+ use WebPConvert\Options\ArrayOption;
1550
+ use WebPConvert\Options\BooleanOption;
1551
+ use WebPConvert\Options\GhostOption;
1552
+ use WebPConvert\Options\IntegerOption;
1553
+ use WebPConvert\Options\IntegerOrNullOption;
1554
+ use WebPConvert\Options\MetadataOption;
1555
+ use WebPConvert\Options\Options;
1556
+ use WebPConvert\Options\StringOption;
1557
+ use WebPConvert\Options\QualityOption;
1558
+
1559
+ /**
1560
+ * Trait for handling options
1561
+ *
1562
+ * This trait is currently only used in the AbstractConverter class. It has been extracted into a
1563
+ * trait in order to bundle the methods concerning options.
1564
+ *
1565
+ * @package WebPConvert
1566
+ * @author Bjørn Rosell <it@rosell.dk>
1567
+ * @since Class available since Release 2.0.0
1568
+ */
1569
+ trait OptionsTrait
1570
+ {
1571
+
1572
+ abstract public function log($msg, $style = '');
1573
+ abstract public function logLn($msg, $style = '');
1574
+ abstract protected function getMimeTypeOfSource();
1575
+
1576
+ /** @var array Provided conversion options */
1577
+ public $providedOptions;
1578
+
1579
+ /** @var array Calculated conversion options (merge of default options and provided options)*/
1580
+ protected $options;
1581
+
1582
+ /** @var Options */
1583
+ protected $options2;
1584
+
1585
+
1586
+ /**
1587
+ * Create options.
1588
+ *
1589
+ * The options created here will be available to all converters.
1590
+ * Individual converters may add options by overriding this method.
1591
+ *
1592
+ * @return void
1593
+ */
1594
+ protected function createOptions()
1595
+ {
1596
+ $isPng = ($this->getMimeTypeOfSource() == 'image/png');
1597
+
1598
+ $this->options2 = new Options();
1599
+ $this->options2->addOptions(
1600
+ new IntegerOption('alpha-quality', 85, 0, 100),
1601
+ new BooleanOption('auto-filter', false),
1602
+ new IntegerOption('default-quality', ($isPng ? 85 : 75), 0, 100),
1603
+ new StringOption('encoding', 'auto', ['lossy', 'lossless', 'auto']),
1604
+ new BooleanOption('low-memory', false),
1605
+ new BooleanOption('log-call-arguments', false),
1606
+ new IntegerOption('max-quality', 85, 0, 100),
1607
+ new MetadataOption('metadata', 'none'),
1608
+ new IntegerOption('method', 6, 0, 6),
1609
+ new IntegerOption('near-lossless', 60, 0, 100),
1610
+ new StringOption('preset', 'none', ['none', 'default', 'photo', 'picture', 'drawing', 'icon', 'text']),
1611
+ new QualityOption('quality', ($isPng ? 85 : 'auto')),
1612
+ new IntegerOrNullOption('size-in-percentage', null, 0, 100),
1613
+ new BooleanOption('skip', false),
1614
+ new BooleanOption('use-nice', false),
1615
+ new ArrayOption('jpeg', []),
1616
+ new ArrayOption('png', [])
1617
+ );
1618
+ }
1619
+
1620
+ /**
1621
+ * Set "provided options" (options provided by the user when calling convert().
1622
+ *
1623
+ * This also calculates the protected options array, by merging in the default options, merging
1624
+ * jpeg and png options and merging prefixed options (such as 'vips-quality').
1625
+ * The resulting options array are set in the protected property $this->options and can be
1626
+ * retrieved using the public ::getOptions() function.
1627
+ *
1628
+ * @param array $providedOptions (optional)
1629
+ * @return void
1630
+ */
1631
+ public function setProvidedOptions($providedOptions = [])
1632
+ {
1633
+ $this->createOptions();
1634
+
1635
+ $this->providedOptions = $providedOptions;
1636
+
1637
+ if (isset($this->providedOptions['png'])) {
1638
+ if ($this->getMimeTypeOfSource() == 'image/png') {
1639
+ $this->providedOptions = array_merge($this->providedOptions, $this->providedOptions['png']);
1640
+ // $this->logLn(print_r($this->providedOptions, true));
1641
+ unset($this->providedOptions['png']);
1642
+ }
1643
+ }
1644
+
1645
+ if (isset($this->providedOptions['jpeg'])) {
1646
+ if ($this->getMimeTypeOfSource() == 'image/jpeg') {
1647
+ $this->providedOptions = array_merge($this->providedOptions, $this->providedOptions['jpeg']);
1648
+ unset($this->providedOptions['jpeg']);
1649
+ }
1650
+ }
1651
+
1652
+ // merge down converter-prefixed options
1653
+ $converterId = self::getConverterId();
1654
+ $strLen = strlen($converterId);
1655
+ foreach ($this->providedOptions as $optionKey => $optionValue) {
1656
+ if (substr($optionKey, 0, $strLen + 1) == ($converterId . '-')) {
1657
+ $this->providedOptions[substr($optionKey, $strLen + 1)] = $optionValue;
1658
+ }
1659
+ }
1660
+
1661
+ // Create options (Option objects)
1662
+ foreach ($this->providedOptions as $optionId => $optionValue) {
1663
+ $this->options2->setOrCreateOption($optionId, $optionValue);
1664
+ }
1665
+ //$this->logLn(print_r($this->options2->getOptions(), true));
1666
+ //$this->logLn($this->options2->getOption('hello'));
1667
+
1668
+ // Create flat associative array of options
1669
+ $this->options = $this->options2->getOptions();
1670
+
1671
+ // - Merge $defaultOptions into provided options
1672
+ //$this->options = array_merge($this->getDefaultOptions(), $this->providedOptions);
1673
+
1674
+ //$this->logOptions();
1675
+ }
1676
+
1677
+ /**
1678
+ * Get the resulting options after merging provided options with default options.
1679
+ *
1680
+ * Note that the defaults depends on the mime type of the source. For example, the default value for quality
1681
+ * is "auto" for jpegs, and 85 for pngs.
1682
+ *
1683
+ * @return array An associative array of options: ['metadata' => 'none', ...]
1684
+ */
1685
+ public function getOptions()
1686
+ {
1687
+ return $this->options;
1688
+ }
1689
+
1690
+ /**
1691
+ * Change an option specifically.
1692
+ *
1693
+ * This method is probably rarely neeeded. We are using it to change the "encoding" option temporarily
1694
+ * in the EncodingAutoTrait.
1695
+ *
1696
+ * @param string $id Id of option (ie "metadata")
1697
+ * @param mixed $value The new value.
1698
+ * @return void
1699
+ */
1700
+ protected function setOption($id, $value)
1701
+ {
1702
+ $this->options[$id] = $value;
1703
+ $this->options2->setOrCreateOption($id, $value);
1704
+ }
1705
+
1706
+ /**
1707
+ * Check options.
1708
+ *
1709
+ * @throws InvalidOptionTypeException if an option have wrong type
1710
+ * @throws InvalidOptionValueException if an option value is out of range
1711
+ * @throws ConversionSkippedException if 'skip' option is set to true
1712
+ * @return void
1713
+ */
1714
+ protected function checkOptions()
1715
+ {
1716
+ $this->options2->check();
1717
+
1718
+ if ($this->options['skip']) {
1719
+ if (($this->getMimeTypeOfSource() == 'image/png') && isset($this->options['png']['skip'])) {
1720
+ throw new ConversionSkippedException(
1721
+ 'skipped conversion (configured to do so for PNG)'
1722
+ );
1723
+ } else {
1724
+ throw new ConversionSkippedException(
1725
+ 'skipped conversion (configured to do so)'
1726
+ );
1727
+ }
1728
+ }
1729
+ }
1730
+
1731
+ public function logOptions()
1732
+ {
1733
+ $this->logLn('');
1734
+ $this->logLn('Options:');
1735
+ $this->logLn('------------');
1736
+ $this->logLn(
1737
+ 'The following options have been set explicitly. ' .
1738
+ 'Note: it is the resulting options after merging down the "jpeg" and "png" options and any ' .
1739
+ 'converter-prefixed options.'
1740
+ );
1741
+ $this->logLn('- source: ' . $this->source);
1742
+ $this->logLn('- destination: ' . $this->destination);
1743
+
1744
+ $unsupported = $this->getUnsupportedDefaultOptions();
1745
+ //$this->logLn('Unsupported:' . print_r($this->getUnsupportedDefaultOptions(), true));
1746
+ $ignored = [];
1747
+ $implicit = [];
1748
+ foreach ($this->options2->getOptionsMap() as $id => $option) {
1749
+ if (($id == 'png') || ($id == 'jpeg')) {
1750
+ continue;
1751
+ }
1752
+ if ($option->isValueExplicitlySet()) {
1753
+ if (($option instanceof GhostOption) || in_array($id, $unsupported)) {
1754
+ //$this->log(' (note: this option is ignored by this converter)');
1755
+ if (($id != '_skip_input_check') && ($id != '_suppress_success_message')) {
1756
+ $ignored[] = $option;
1757
+ }
1758
+ } else {
1759
+ $this->log('- ' . $id . ': ');
1760
+ $this->log($option->getValueForPrint());
1761
+ $this->logLn('');
1762
+ }
1763
+ } else {
1764
+ if (($option instanceof GhostOption) || in_array($id, $unsupported)) {
1765
+ } else {
1766
+ $implicit[] = $option;
1767
+ }
1768
+ }
1769
+ }
1770
+
1771
+ if (count($implicit) > 0) {
1772
+ $this->logLn('');
1773
+ $this->logLn(
1774
+ 'The following options have not been explicitly set, so using the following defaults:'
1775
+ );
1776
+ foreach ($implicit as $option) {
1777
+ $this->log('- ' . $option->getId() . ': ');
1778
+ $this->log($option->getValueForPrint());
1779
+ $this->logLn('');
1780
+ }
1781
+ }
1782
+ if (count($ignored) > 0) {
1783
+ $this->logLn('');
1784
+ if ($this instanceof Stack) {
1785
+ $this->logLn(
1786
+ 'The following options were supplied and are passed on to the converters in the stack:'
1787
+ );
1788
+ foreach ($ignored as $option) {
1789
+ $this->log('- ' . $option->getId() . ': ');
1790
+ $this->log($option->getValueForPrint());
1791
+ $this->logLn('');
1792
+ }
1793
+ } else {
1794
+ $this->logLn(
1795
+ 'The following options were supplied but are ignored because they are not supported by this ' .
1796
+ 'converter:'
1797
+ );
1798
+ foreach ($ignored as $option) {
1799
+ $this->logLn('- ' . $option->getId());
1800
+ }
1801
+ }
1802
+ }
1803
+ $this->logLn('------------');
1804
+ }
1805
+
1806
+ // to be overridden by converters
1807
+ protected function getUnsupportedDefaultOptions()
1808
+ {
1809
+ return [];
1810
+ }
1811
+ }
1812
+
1813
+ ?><?php
1814
+
1815
+ namespace WebPConvert\Convert\Converters\BaseTraits;
1816
+
1817
+ use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\TargetNotFoundException;
1818
+ use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\InvalidImageTypeException;
1819
+
1820
+ /**
1821
+ * Trait for handling options
1822
+ *
1823
+ * This trait is currently only used in the AbstractConverter class. It has been extracted into a
1824
+ * trait in order to bundle the methods concerning options.
1825
+ *
1826
+ * @package WebPConvert
1827
+ * @author Bjørn Rosell <it@rosell.dk>
1828
+ * @since Class available since Release 2.0.0
1829
+ */
1830
+ trait SourceValidationTrait
1831
+ {
1832
+
1833
+ abstract protected function getMimeTypeOfSource();
1834
+ abstract public function getSource();
1835
+
1836
+ /** @var array Array of allowed mime types for source. */
1837
+ public static $allowedMimeTypes = ['image/jpeg', 'image/png'];
1838
+
1839
+ /**
1840
+ * Check that source file exists.
1841
+ *
1842
+ * Note: As the input validations are only run one time in a stack,
1843
+ * this method is not overridable
1844
+ *
1845
+ * @throws TargetNotFoundException
1846
+ * @return void
1847
+ */
1848
+ private function checkSourceExists()
1849
+ {
1850
+ // Check if source exists
1851
+ if (!@file_exists($this->getSource())) {
1852
+ throw new TargetNotFoundException('File or directory not found: ' . $this->getSource());
1853
+ }
1854
+ }
1855
+
1856
+ /**
1857
+ * Check that source has a valid mime type.
1858
+ *
1859
+ * Note: As the input validations are only run one time in a stack,
1860
+ * this method is not overridable
1861
+ *
1862
+ * @throws InvalidImageTypeException If mime type could not be detected or is unsupported
1863
+ * @return void
1864
+ */
1865
+ private function checkSourceMimeType()
1866
+ {
1867
+ $fileMimeType = $this->getMimeTypeOfSource();
1868
+ if (is_null($fileMimeType)) {
1869
+ throw new InvalidImageTypeException('Image type could not be detected');
1870
+ } elseif ($fileMimeType === false) {
1871
+ throw new InvalidImageTypeException('File seems not to be an image.');
1872
+ } elseif (!in_array($fileMimeType, self::$allowedMimeTypes)) {
1873
+ throw new InvalidImageTypeException('Unsupported mime type: ' . $fileMimeType);
1874
+ }
1875
+ }
1876
+ }
1877
+
1878
+ ?><?php
1879
+
1880
+ namespace WebPConvert\Convert\Converters\BaseTraits;
1881
+
1882
+ /**
1883
+ * Trait for handling warnings (by logging them)
1884
+ *
1885
+ * This trait is currently only used in the AbstractConverter class. It has been extracted into a
1886
+ * trait in order to bundle the methods concerning options.
1887
+ *
1888
+ * @package WebPConvert
1889
+ * @author Bjørn Rosell <it@rosell.dk>
1890
+ * @since Class available since Release 2.0.0
1891
+ */
1892
+ trait WarningLoggerTrait
1893
+ {
1894
+ abstract protected function logLn($msg, $style = '');
1895
+
1896
+ /** @var string|array|null Previous error handler (stored in order to be able pass warnings on) */
1897
+ private $previousErrorHandler;
1898
+
1899
+ /**
1900
+ * Handle warnings and notices during conversion by logging them and passing them on.
1901
+ *
1902
+ * The function is a callback used with "set_error_handler".
1903
+ * It is declared public because it needs to be accessible from the point where the warning happened.
1904
+ *
1905
+ * @param integer $errno
1906
+ * @param string $errstr
1907
+ * @param string $errfile
1908
+ * @param integer $errline
1909
+ *
1910
+ * @return false|null
1911
+ */
1912
+ public function warningHandler($errno, $errstr, $errfile, $errline)
1913
+ {
1914
+ /*
1915
+ We do NOT do the following (even though it is generally recommended):
1916
+
1917
+ if (!(error_reporting() & $errno)) {
1918
+ // This error code is not included in error_reporting, so let it fall
1919
+ // through to the standard PHP error handler
1920
+ return false;
1921
+ }
1922
+
1923
+ - Because we want to log all warnings and errors (also the ones that was suppressed with @)
1924
+ https://secure.php.net/manual/en/language.operators.errorcontrol.php
1925
+ */
1926
+
1927
+ $errorTypes = [
1928
+ E_WARNING => "Warning",
1929
+ E_NOTICE => "Notice",
1930
+ E_STRICT => "Strict Notice",
1931
+ E_DEPRECATED => "Deprecated",
1932
+ E_USER_DEPRECATED => "User Deprecated",
1933
+
1934
+ /*
1935
+ The following can never be catched by a custom error handler:
1936
+ E_PARSE, E_ERROR, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING
1937
+
1938
+ We do do not currently trigger the following:
1939
+ E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE
1940
+
1941
+ But we may want to do that at some point, like this:
1942
+ trigger_error('Your version of Gd is very old', E_USER_WARNING);
1943
+ in that case, remember to add them to this array
1944
+ */
1945
+ ];
1946
+
1947
+ if (isset($errorTypes[$errno])) {
1948
+ $errType = $errorTypes[$errno];
1949
+ } else {
1950
+ $errType = "Unknown error/warning/notice ($errno)";
1951
+ }
1952
+
1953
+ $msg = $errType . ': ' . $errstr . ' in ' . $errfile . ', line ' . $errline . ', PHP ' . PHP_VERSION .
1954
+ ' (' . PHP_OS . ')';
1955
+ $this->logLn('');
1956
+ $this->logLn($msg, 'italic');
1957
+ $this->logLn('');
1958
+
1959
+ //echo 'previously defined handler:' . print_r($this->previousErrorHandler, true);
1960
+
1961
+ if (!is_null($this->previousErrorHandler)) {
1962
+ return call_user_func($this->previousErrorHandler, $errno, $errstr, $errfile, $errline);
1963
+ } else {
1964
+ return false;
1965
+ }
1966
+ }
1967
+
1968
+ /**
1969
+ * Activate warning logger.
1970
+ *
1971
+ * Sets the error handler and stores the previous so our error handler can bubble up warnings
1972
+ *
1973
+ * @return void
1974
+ */
1975
+ protected function activateWarningLogger()
1976
+ {
1977
+ $this->previousErrorHandler = set_error_handler(
1978
+ array($this, "warningHandler"),
1979
+ E_WARNING | E_USER_WARNING | E_NOTICE | E_USER_NOTICE
1980
+ );
1981
+ }
1982
+
1983
+ /**
1984
+ * Deactivate warning logger.
1985
+ *
1986
+ * Restores the previous error handler.
1987
+ *
1988
+ * @return void
1989
+ */
1990
+ protected function deactivateWarningLogger()
1991
+ {
1992
+ restore_error_handler();
1993
+ }
1994
+ }
1995
+
1996
+ ?><?php
1997
+
1998
+ namespace WebPConvert\Convert\Converters\ConverterTraits;
1999
+
2000
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
2001
+ use WebPConvert\Convert\Converters\AbstractConverter;
2002
+ use WebPConvert\Convert\Helpers\PhpIniSizes;
2003
+
2004
+ /**
2005
+ * Trait for converters that works by uploading to a cloud service.
2006
+ *
2007
+ * The trait adds a method for checking against upload limits.
2008
+ *
2009
+ * @package WebPConvert
2010
+ * @author Bjørn Rosell <it@rosell.dk>
2011
+ * @since Class available since Release 2.0.0
2012
+ */
2013
+ trait CloudConverterTrait
2014
+ {
2015
+
2016
+ /**
2017
+ * Test that filesize is below "upload_max_filesize" and "post_max_size" values in php.ini.
2018
+ *
2019
+ * @param string $iniSettingId Id of ini setting (ie "upload_max_filesize")
2020
+ *
2021
+ * @throws ConversionFailedException if filesize is larger than the ini setting
2022
+ * @return void
2023
+ */
2024
+ private function checkFileSizeVsIniSetting($iniSettingId)
2025
+ {
2026
+ $fileSize = @filesize($this->source);
2027
+ if ($fileSize === false) {
2028
+ return;
2029
+ }
2030
+ $sizeInIni = PhpIniSizes::getIniBytes($iniSettingId);
2031
+ if ($sizeInIni === false) {
2032
+ // Not sure if we should throw an exception here, or not...
2033
+ return;
2034
+ }
2035
+ if ($sizeInIni < $fileSize) {
2036
+ throw new ConversionFailedException(
2037
+ 'File is larger than your ' . $iniSettingId . ' (set in your php.ini). File size:' .
2038
+ round($fileSize/1024) . ' kb. ' .
2039
+ $iniSettingId . ' in php.ini: ' . ini_get($iniSettingId) .
2040
+ ' (parsed as ' . round($sizeInIni/1024) . ' kb)'
2041
+ );
2042
+ }
2043
+ }
2044
+
2045
+ /**
2046
+ * Check convertability of cloud converters (that file is not bigger than limits set in php.ini).
2047
+ *
2048
+ * Performs the same as ::Convertability(). It is here so converters that overrides the
2049
+ * ::Convertability() still has a chance to do the checks.
2050
+ *
2051
+ * @throws ConversionFailedException if filesize is larger than "upload_max_filesize" or "post_max_size"
2052
+ * @return void
2053
+ */
2054
+ public function checkConvertabilityCloudConverterTrait()
2055
+ {
2056
+ $this->checkFileSizeVsIniSetting('upload_max_filesize');
2057
+ $this->checkFileSizeVsIniSetting('post_max_size');
2058
+ }
2059
+
2060
+ /**
2061
+ * Check convertability of cloud converters (file upload limits).
2062
+ */
2063
+ public function checkConvertability()
2064
+ {
2065
+ $this->checkConvertabilityCloudConverterTrait();
2066
+ }
2067
+ }
2068
+
2069
+ ?><?php
2070
+
2071
+ namespace WebPConvert\Convert\Converters\ConverterTraits;
2072
+
2073
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
2074
+ use WebPConvert\Convert\Converters\AbstractConverter;
2075
+
2076
+ /**
2077
+ * Trait for converters that works by uploading to a cloud service.
2078
+ *
2079
+ * The trait adds a method for checking against upload limits.
2080
+ *
2081
+ * @package WebPConvert
2082
+ * @author Bjørn Rosell <it@rosell.dk>
2083
+ * @since Class available since Release 2.0.0
2084
+ */
2085
+ trait CurlTrait
2086
+ {
2087
+
2088
+ /**
2089
+ * Check basis operationality for converters relying on curl.
2090
+ *
2091
+ * Performs the same as ::checkOperationality(). It is here so converters that overrides the
2092
+ * ::checkOperationality() still has a chance to do the checks.
2093
+ *
2094
+ * @throws SystemRequirementsNotMetException
2095
+ * @return void
2096
+ */
2097
+ public function checkOperationalityForCurlTrait()
2098
+ {
2099
+ if (!extension_loaded('curl')) {
2100
+ throw new SystemRequirementsNotMetException('Required cURL extension is not available.');
2101
+ }
2102
+
2103
+ if (!function_exists('curl_init')) {
2104
+ throw new SystemRequirementsNotMetException('Required url_init() function is not available.');
2105
+ }
2106
+
2107
+ if (!function_exists('curl_file_create')) {
2108
+ throw new SystemRequirementsNotMetException(
2109
+ 'Required curl_file_create() function is not available (requires PHP > 5.5).'
2110
+ );
2111
+ }
2112
+ }
2113
+
2114
+ /**
2115
+ * Check basis operationality for converters relying on curl
2116
+ *
2117
+ * @throws SystemRequirementsNotMetException
2118
+ * @return void
2119
+ */
2120
+ public function checkOperationality()
2121
+ {
2122
+ $this->checkOperationalityForCurlTrait();
2123
+ }
2124
+
2125
+ /**
2126
+ * Init curl.
2127
+ *
2128
+ * @throws SystemRequirementsNotMetException if curl could not be initialized
2129
+ * @return resource curl handle
2130
+ */
2131
+ protected static function initCurl()
2132
+ {
2133
+ // Get curl handle
2134
+ $ch = curl_init();
2135
+ if ($ch === false) {
2136
+ throw new SystemRequirementsNotMetException('Could not initialise cURL.');
2137
+ }
2138
+ return $ch;
2139
+ }
2140
+ }
2141
+
2142
+ ?><?php
2143
+
2144
+ //namespace WebPConvert\Convert\Converters\BaseTraits;
2145
+ namespace WebPConvert\Convert\Converters\ConverterTraits;
2146
+
2147
+ /**
2148
+ * Trait for converters that supports lossless encoding and thus the "lossless:auto" option.
2149
+ *
2150
+ * @package WebPConvert
2151
+ * @author Bjørn Rosell <it@rosell.dk>
2152
+ * @since Class available since Release 2.0.0
2153
+ */
2154
+ trait EncodingAutoTrait
2155
+ {
2156
+
2157
+ abstract protected function doActualConvert();
2158
+ abstract public function getSource();
2159
+ abstract public function getDestination();
2160
+ abstract public function setDestination($destination);
2161
+ abstract public function getOptions();
2162
+ abstract protected function setOption($optionName, $optionValue);
2163
+ abstract protected function logLn($msg, $style = '');
2164
+ abstract protected function ln();
2165
+
2166
+ public function supportsLossless()
2167
+ {
2168
+ return true;
2169
+ }
2170
+
2171
+ /** Default is to not pass "lossless:auto" on, but implement it.
2172
+ *
2173
+ * The Stack converter passes it on (it does not even use this trait)
2174
+ * WPC currently implements it, but this might be configurable in the future.
2175
+ *
2176
+ */
2177
+ public function passOnEncodingAuto()
2178
+ {
2179
+ return false;
2180
+ }
2181
+
2182
+ private function convertTwoAndSelectSmallest()
2183
+ {
2184
+ $destination = $this->getDestination();
2185
+ $destinationLossless = $destination . '.lossless.webp';
2186
+ $destinationLossy = $destination . '.lossy.webp';
2187
+
2188
+ $this->logLn(
2189
+ 'Encoding is set to auto - converting to both lossless and lossy and selecting the smallest file'
2190
+ );
2191
+
2192
+ $this->ln();
2193
+ $this->logLn('Converting to lossy');
2194
+ $this->setDestination($destinationLossy);
2195
+ $this->setOption('encoding', 'lossy');
2196
+ $this->doActualConvert();
2197
+ $this->log('Reduction: ');
2198
+ $this->logReduction($this->getSource(), $destinationLossy);
2199
+ $this->ln();
2200
+
2201
+ $this->logLn('Converting to lossless');
2202
+ $this->setDestination($destinationLossless);
2203
+ $this->setOption('encoding', 'lossless');
2204
+ $this->doActualConvert();
2205
+ $this->log('Reduction: ');
2206
+ $this->logReduction($this->getSource(), $destinationLossless);
2207
+ $this->ln();
2208
+
2209
+ if (filesize($destinationLossless) > filesize($destinationLossy)) {
2210
+ $this->logLn('Picking lossy');
2211
+ unlink($destinationLossless);
2212
+ rename($destinationLossy, $destination);
2213
+ } else {
2214
+ $this->logLn('Picking lossless');
2215
+ unlink($destinationLossy);
2216
+ rename($destinationLossless, $destination);
2217
+ }
2218
+ $this->setDestination($destination);
2219
+ $this->setOption('encoding', 'auto');
2220
+ }
2221
+
2222
+ protected function runActualConvert()
2223
+ {
2224
+ if (!$this->passOnEncodingAuto() && ($this->getOptions()['encoding'] == 'auto') && $this->supportsLossless()) {
2225
+ $this->convertTwoAndSelectSmallest();
2226
+ } else {
2227
+ $this->doActualConvert();
2228
+ }
2229
+ }
2230
+ }
2231
+
2232
+ ?><?php
2233
+
2234
+ namespace WebPConvert\Convert\Converters\ConverterTraits;
2235
+
2236
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
2237
+
2238
+ /**
2239
+ * Trait for converters that uses exec()
2240
+ *
2241
+ * @package WebPConvert
2242
+ * @author Bjørn Rosell <it@rosell.dk>
2243
+ * @since Class available since Release 2.0.0
2244
+ */
2245
+ trait ExecTrait
2246
+ {
2247
+
2248
+ abstract protected function logLn($msg, $style = '');
2249
+
2250
+ /**
2251
+ * Helper function for examining if "nice" command is available
2252
+ *
2253
+ * @return boolean true if nice is available
2254
+ */
2255
+ protected static function hasNiceSupport()
2256
+ {
2257
+ exec("nice 2>&1", $niceOutput);
2258
+
2259
+ if (is_array($niceOutput) && isset($niceOutput[0])) {
2260
+ if (preg_match('/usage/', $niceOutput[0]) || (preg_match('/^\d+$/', $niceOutput[0]))) {
2261
+ /*
2262
+ * Nice is available - default niceness (+10)
2263
+ * https://www.lifewire.com/uses-of-commands-nice-renice-2201087
2264
+ * https://www.computerhope.com/unix/unice.htm
2265
+ */
2266
+
2267
+ return true;
2268
+ }
2269
+ return false;
2270
+ }
2271
+ }
2272
+
2273
+ /**
2274
+ * Logs output from the exec call.
2275
+ *
2276
+ * @param array $output
2277
+ *
2278
+ * @return void
2279
+ */
2280
+ protected function logExecOutput($output)
2281
+ {
2282
+ if (is_array($output) && count($output) > 0) {
2283
+ $this->logLn('');
2284
+ $this->logLn('Output:', 'italic');
2285
+ foreach ($output as $line) {
2286
+ $this->logLn(print_r($line, true));
2287
+ }
2288
+ $this->logLn('');
2289
+ }
2290
+ }
2291
+
2292
+ /**
2293
+ * Check basic operationality of exec converters (that the "exec" function is available)
2294
+ *
2295
+ * @throws WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException
2296
+ * @return void
2297
+ */
2298
+ public function checkOperationalityExecTrait()
2299
+ {
2300
+ if (!function_exists('exec')) {
2301
+ throw new SystemRequirementsNotMetException('exec() is not enabled.');
2302
+ }
2303
+ }
2304
+ }
2305
+
2306
+ ?><?php
2307
+
2308
+ namespace WebPConvert\Convert\Converters;
2309
+
2310
+ use WebPConvert\Convert\Converters\AbstractConverter;
2311
+ use WebPConvert\Convert\Converters\ConverterTraits\EncodingAutoTrait;
2312
+ use WebPConvert\Convert\Converters\ConverterTraits\ExecTrait;
2313
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
2314
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
2315
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
2316
+ use WebPConvert\Options\BooleanOption;
2317
+ use WebPConvert\Options\SensitiveStringOption;
2318
+ use WebPConvert\Options\StringOption;
2319
+
2320
+ /**
2321
+ * Convert images to webp by calling cwebp binary.
2322
+ *
2323
+ * @package WebPConvert
2324
+ * @author Bjørn Rosell <it@rosell.dk>
2325
+ * @since Class available since Release 2.0.0
2326
+ */
2327
+ class Cwebp extends AbstractConverter
2328
+ {
2329
+
2330
+ use EncodingAutoTrait;
2331
+ use ExecTrait;
2332
+
2333
+ protected function getUnsupportedDefaultOptions()
2334
+ {
2335
+ return [];
2336
+ }
2337
+
2338
+ protected function createOptions()
2339
+ {
2340
+ parent::createOptions();
2341
+
2342
+ $this->options2->addOptions(
2343
+ new StringOption('command-line-options', ''),
2344
+ new SensitiveStringOption('rel-path-to-precompiled-binaries', './Binaries'),
2345
+ new BooleanOption('try-common-system-paths', true),
2346
+ new BooleanOption('try-supplied-binary-for-os', true)
2347
+ );
2348
+ }
2349
+
2350
+ // System paths to look for cwebp binary
2351
+ private static $cwebpDefaultPaths = [
2352
+ 'cwebp',
2353
+ '/usr/bin/cwebp',
2354
+ '/usr/local/bin/cwebp',
2355
+ '/usr/gnu/bin/cwebp',
2356
+ '/usr/syno/bin/cwebp'
2357
+ ];
2358
+
2359
+ // OS-specific binaries included in this library, along with hashes
2360
+ // If other binaries are going to be added, notice that the first argument is what PHP_OS returns.
2361
+ // (possible values, see here: https://stackoverflow.com/questions/738823/possible-values-for-php-os)
2362
+ private static $suppliedBinariesInfo = [
2363
+ 'WINNT' => [ 'cwebp.exe', '49e9cb98db30bfa27936933e6fd94d407e0386802cb192800d9fd824f6476873'],
2364
+ 'Darwin' => [ 'cwebp-mac12', 'a06a3ee436e375c89dbc1b0b2e8bd7729a55139ae072ed3f7bd2e07de0ebb379'],
2365
+ 'SunOS' => [ 'cwebp-sol', '1febaffbb18e52dc2c524cda9eefd00c6db95bc388732868999c0f48deb73b4f'],
2366
+ 'FreeBSD' => [ 'cwebp-fbsd', 'e5cbea11c97fadffe221fdf57c093c19af2737e4bbd2cb3cd5e908de64286573'],
2367
+ 'Linux' => [ 'cwebp-linux', '916623e5e9183237c851374d969aebdb96e0edc0692ab7937b95ea67dc3b2568']
2368
+ ];
2369
+
2370
+ public function checkOperationality()
2371
+ {
2372
+ $this->checkOperationalityExecTrait();
2373
+
2374
+ $options = $this->options;
2375
+ if (!$options['try-supplied-binary-for-os'] && !$options['try-common-system-paths']) {
2376
+ throw new ConverterNotOperationalException(
2377
+ 'Configured to neither look for cweb binaries in common system locations, ' .
2378
+ 'nor to use one of the supplied precompiled binaries. But these are the only ways ' .
2379
+ 'this converter can convert images. No conversion can be made!'
2380
+ );
2381
+ }
2382
+ }
2383
+
2384
+ private function executeBinary($binary, $commandOptions, $useNice)
2385
+ {
2386
+ $command = ($useNice ? 'nice ' : '') . $binary . ' ' . $commandOptions;
2387
+
2388
+ //$logger->logLn('command options:' . $commandOptions);
2389
+ //$logger->logLn('Trying to execute binary:' . $binary);
2390
+ exec($command, $output, $returnCode);
2391
+ $this->logExecOutput($output);
2392
+ /*
2393
+ if ($returnCode == 255) {
2394
+ if (isset($output[0])) {
2395
+ // Could be an error like 'Error! Cannot open output file' or 'Error! ...preset... '
2396
+ $this->logLn(print_r($output[0], true));
2397
+ }
2398
+ }*/
2399
+ //$logger->logLn(self::msgForExitCode($returnCode));
2400
+ return intval($returnCode);
2401
+ }
2402
+
2403
+ /**
2404
+ * Use "escapeshellarg()" on all arguments in a commandline string of options
2405
+ *
2406
+ * For example, passing '-sharpness 5 -crop 10 10 40 40 -low_memory' will result in:
2407
+ * [
2408
+ * "-sharpness '5'"
2409
+ * "-crop '10' '10' '40' '40'"
2410
+ * "-low_memory"
2411
+ * ]
2412
+ * @param string $commandLineOptions string which can contain multiple commandline options
2413
+ * @return array Array of command options
2414
+ */
2415
+ private static function escapeShellArgOnCommandLineOptions($commandLineOptions)
2416
+ {
2417
+ $cmdOptions = [];
2418
+ $arr = explode(' -', ' ' . $commandLineOptions);
2419
+ foreach ($arr as $cmdOption) {
2420
+ $pos = strpos($cmdOption, ' ');
2421
+ $cName = '';
2422
+ if (!$pos) {
2423
+ $cName = $cmdOption;
2424
+ if ($cName == '') {
2425
+ continue;
2426
+ }
2427
+ $cmdOptions[] = '-' . $cName;
2428
+ } else {
2429
+ $cName = substr($cmdOption, 0, $pos);
2430
+ $cValues = substr($cmdOption, $pos + 1);
2431
+ $cValuesArr = explode(' ', $cValues);
2432
+ foreach ($cValuesArr as &$cArg) {
2433
+ $cArg = escapeshellarg($cArg);
2434
+ }
2435
+ $cValues = implode(' ', $cValuesArr);
2436
+ $cmdOptions[] = '-' . $cName . ' ' . $cValues;
2437
+ }
2438
+ }
2439
+ return $cmdOptions;
2440
+ }
2441
+
2442
+ /**
2443
+ * Build command line options
2444
+ *
2445
+ * @return string
2446
+ */
2447
+ private function createCommandLineOptions()
2448
+ {
2449
+ $options = $this->options;
2450
+
2451
+ $cmdOptions = [];
2452
+
2453
+ // Metadata (all, exif, icc, xmp or none (default))
2454
+ // Comma-separated list of existing metadata to copy from input to output
2455
+ $cmdOptions[] = '-metadata ' . $options['metadata'];
2456
+
2457
+ // preset. Appears first in the list as recommended in the docs
2458
+ if (!is_null($options['preset'])) {
2459
+ if ($options['preset'] != 'none') {
2460
+ $cmdOptions[] = '-preset ' . $options['preset'];
2461
+ }
2462
+ }
2463
+
2464
+ // Size
2465
+ $addedSizeOption = false;
2466
+ if (!is_null($options['size-in-percentage'])) {
2467
+ $sizeSource = filesize($this->source);
2468
+ if ($sizeSource !== false) {
2469
+ $targetSize = floor($sizeSource * $options['size-in-percentage'] / 100);
2470
+ $cmdOptions[] = '-size ' . $targetSize;
2471
+ $addedSizeOption = true;
2472
+ }
2473
+ }
2474
+
2475
+ // quality
2476
+ if (!$addedSizeOption) {
2477
+ $cmdOptions[] = '-q ' . $this->getCalculatedQuality();
2478
+ }
2479
+
2480
+ // alpha-quality
2481
+ if ($this->options['alpha-quality'] !== 100) {
2482
+ $cmdOptions[] = '-alpha_q ' . escapeshellarg($this->options['alpha-quality']);
2483
+ }
2484
+
2485
+ // Losless PNG conversion
2486
+ if ($options['encoding'] == 'lossless') {
2487
+ // No need to add -lossless when near-lossless is used
2488
+ if ($options['near-lossless'] === 100) {
2489
+ $cmdOptions[] = '-lossless';
2490
+ }
2491
+ }
2492
+
2493
+ // Near-lossles
2494
+ if ($options['near-lossless'] !== 100) {
2495
+ // We only let near_lossless have effect when encoding is set to "lossless"
2496
+ // otherwise encoding=auto would not work as expected
2497
+ if ($options['encoding'] == 'lossless') {
2498
+ $cmdOptions[] ='-near_lossless ' . $options['near-lossless'];
2499
+ }
2500
+ }
2501
+
2502
+ if ($options['auto-filter'] === true) {
2503
+ $cmdOptions[] = '-af';
2504
+ }
2505
+
2506
+ // Built-in method option
2507
+ $cmdOptions[] = '-m ' . strval($options['method']);
2508
+
2509
+ // Built-in low memory option
2510
+ if ($options['low-memory']) {
2511
+ $cmdOptions[] = '-low_memory';
2512
+ }
2513
+
2514
+ // command-line-options
2515
+ if ($options['command-line-options']) {
2516
+ array_push(
2517
+ $cmdOptions,
2518
+ ...self::escapeShellArgOnCommandLineOptions($options['command-line-options'])
2519
+ );
2520
+ }
2521
+
2522
+ // Source file
2523
+ $cmdOptions[] = escapeshellarg($this->source);
2524
+
2525
+ // Output
2526
+ $cmdOptions[] = '-o ' . escapeshellarg($this->destination);
2527
+
2528
+ // Redirect stderr to same place as stdout
2529
+ // https://www.brianstorti.com/understanding-shell-script-idiom-redirect/
2530
+ $cmdOptions[] = '2>&1';
2531
+
2532
+ $commandOptions = implode(' ', $cmdOptions);
2533
+ $this->logLn('command line options:' . $commandOptions);
2534
+
2535
+ return $commandOptions;
2536
+ }
2537
+
2538
+ /**
2539
+ *
2540
+ *
2541
+ * @return string Error message if failure, empty string if successful
2542
+ */
2543
+ private function composeErrorMessageForCommonSystemPathsFailures($failureCodes)
2544
+ {
2545
+ if (count($failureCodes) == 1) {
2546
+ switch ($failureCodes[0]) {
2547
+ case 126:
2548
+ return 'Permission denied. The user that the command was run with (' .
2549
+ shell_exec('whoami') . ') does not have permission to execute any of the ' .
2550
+ 'cweb binaries found in common system locations. ';
2551
+ case 127:
2552
+ return 'Found no cwebp binaries in any common system locations. ';
2553
+ default:
2554
+ return 'Tried executing cwebp binaries in common system locations. ' .
2555
+ 'All failed (exit code: ' . $failureCodes[0] . '). ';
2556
+ }
2557
+ } else {
2558
+ /**
2559
+ * $failureCodesBesides127 is used to check first position ($failureCodesBesides127[0])
2560
+ * however position can vary as index can be 1 or something else. array_values() would
2561
+ * always start from 0.
2562
+ */
2563
+ $failureCodesBesides127 = array_values(array_diff($failureCodes, [127]));
2564
+
2565
+ if (count($failureCodesBesides127) == 1) {
2566
+ switch ($failureCodesBesides127[0]) {
2567
+ case 126:
2568
+ return 'Permission denied. The user that the command was run with (' .
2569
+ shell_exec('whoami') . ') does not have permission to execute any of the cweb ' .
2570
+ 'binaries found in common system locations. ';
2571
+ break;
2572
+ default:
2573
+ return 'Tried executing cwebp binaries in common system locations. ' .
2574
+ 'All failed (exit code: ' . $failureCodesBesides127[0] . '). ';
2575
+ }
2576
+ } else {
2577
+ return 'None of the cwebp binaries in the common system locations could be executed ' .
2578
+ '(mixed results - got the following exit codes: ' . implode(',', $failureCodes) . '). ';
2579
+ }
2580
+ }
2581
+ }
2582
+
2583
+ /**
2584
+ * Try executing cwebp in common system paths
2585
+ *
2586
+ * @param boolean $useNice Whether to use nice
2587
+ * @param string $commandOptions for the exec call
2588
+ *
2589
+ * @return array Unique failure codes in case of failure, empty array in case of success
2590
+ */
2591
+ private function tryCommonSystemPaths($useNice, $commandOptions)
2592
+ {
2593
+ $failureCodes = [];
2594
+
2595
+ $paths = self::$cwebpDefaultPaths;
2596
+
2597
+ if (!empty(getenv('CWEBP_PATH'))) {
2598
+ array_unshift($paths, getenv('CWEBP_PATH'));
2599
+ }
2600
+
2601
+ // Loop through paths
2602
+ foreach ($paths as $index => $binary) {
2603
+ $returnCode = $this->executeBinary($binary, $commandOptions, $useNice);
2604
+ if ($returnCode == 0) {
2605
+ $this->logLn('Successfully executed binary: ' . $binary);
2606
+ return [];
2607
+ } else {
2608
+ if ($returnCode == 127) {
2609
+ $this->logLn(
2610
+ 'Trying to execute binary: ' . $binary . '. Failed (not found)'
2611
+ );
2612
+ } else {
2613
+ $this->logLn(
2614
+ 'Trying to execute binary: ' . $binary . '. Failed (return code: ' . $returnCode . ')'
2615
+ );
2616
+ }
2617
+ if (!in_array($returnCode, $failureCodes)) {
2618
+ $failureCodes[] = $returnCode;
2619
+ }
2620
+ }
2621
+ }
2622
+ return $failureCodes;
2623
+ }
2624
+
2625
+ /**
2626
+ * Try executing supplied cwebp for PHP_OS.
2627
+ *
2628
+ * @param boolean $useNice Whether to use nice
2629
+ * @param string $commandOptions for the exec call
2630
+ * @param array $failureCodesForCommonSystemPaths Return codes from the other attempt
2631
+ * (in order to produce short error message)
2632
+ *
2633
+ * @return string Error message if failure, empty string if successful
2634
+ */
2635
+ private function trySuppliedBinaryForOS($useNice, $commandOptions, $failureCodesForCommonSystemPaths)
2636
+ {
2637
+ $this->logLn('Trying to execute supplied binary for OS: ' . PHP_OS);
2638
+
2639
+ // Try supplied binary (if available for OS, and hash is correct)
2640
+ $options = $this->options;
2641
+ if (!isset(self::$suppliedBinariesInfo[PHP_OS])) {
2642
+ return 'No supplied binaries found for OS:' . PHP_OS;
2643
+ }
2644
+
2645
+ $info = self::$suppliedBinariesInfo[PHP_OS];
2646
+
2647
+ $file = $info[0];
2648
+ $hash = $info[1];
2649
+
2650
+ $binaryFile = __DIR__ . '/' . $options['rel-path-to-precompiled-binaries'] . '/' . $file;
2651
+
2652
+
2653
+ // The file should exist, but may have been removed manually.
2654
+ if (!file_exists($binaryFile)) {
2655
+ return 'Supplied binary not found! It ought to be here:' . $binaryFile;
2656
+ }
2657
+
2658
+ // File exists, now generate its hash
2659
+
2660
+ // hash_file() is normally available, but it is not always
2661
+ // - https://stackoverflow.com/questions/17382712/php-5-3-20-undefined-function-hash
2662
+ // If available, validate that hash is correct.
2663
+
2664
+ if (function_exists('hash_file')) {
2665
+ $binaryHash = hash_file('sha256', $binaryFile);
2666
+
2667
+ if ($binaryHash != $hash) {
2668
+ return 'Binary checksum of supplied binary is invalid! ' .
2669
+ 'Did you transfer with FTP, but not in binary mode? ' .
2670
+ 'File:' . $binaryFile . '. ' .
2671
+ 'Expected checksum: ' . $hash . '. ' .
2672
+ 'Actual checksum:' . $binaryHash . '.';
2673
+ }
2674
+ }
2675
+
2676
+ $returnCode = $this->executeBinary($binaryFile, $commandOptions, $useNice);
2677
+ if ($returnCode == 0) {
2678
+ // yay!
2679
+ $this->logLn('success!');
2680
+ return '';
2681
+ }
2682
+
2683
+ $errorMsg = 'Tried executing supplied binary for ' . PHP_OS . ', ' .
2684
+ ($options['try-common-system-paths'] ? 'but that failed too' : 'but failed');
2685
+
2686
+
2687
+ if (($options['try-common-system-paths']) && (count($failureCodesForCommonSystemPaths) > 0)) {
2688
+ // check if it was the same error
2689
+ // if it was, simply refer to that with "(same problem)"
2690
+ $majorFailCode = 0;
2691
+ if (count($failureCodesForCommonSystemPaths) == 1) {
2692
+ $majorFailCode = $failureCodesForCommonSystemPaths[0];
2693
+ } else {
2694
+ $failureCodesBesides127 = array_values(array_diff($failureCodesForCommonSystemPaths, [127]));
2695
+ if (count($failureCodesBesides127) == 1) {
2696
+ $majorFailCode = $failureCodesBesides127[0];
2697
+ } else {
2698
+ // it cannot be summarized into a single code
2699
+ }
2700
+ }
2701
+ if ($majorFailCode != 0) {
2702
+ $errorMsg .= ' (same problem)';
2703
+ return $errorMsg;
2704
+ }
2705
+ }
2706
+
2707
+ if ($returnCode > 128) {
2708
+ $errorMsg .= '. The binary did not work (exit code: ' . $returnCode . '). ' .
2709
+ 'Check out https://github.com/rosell-dk/webp-convert/issues/92';
2710
+ } else {
2711
+ switch ($returnCode) {
2712
+ case 0:
2713
+ // success!
2714
+ break;
2715
+ case 126:
2716
+ $errorMsg .= ': Permission denied. The user that the command was run' .
2717
+ ' with (' . shell_exec('whoami') . ') does not have permission to ' .
2718
+ 'execute that binary.';
2719
+ break;
2720
+ case 127:
2721
+ $errorMsg .= '. The binary was not found! ' .
2722
+ 'It ought to be here: ' . $binaryFile;
2723
+ break;
2724
+ default:
2725
+ $errorMsg .= ' (exit code:' . $returnCode . ').';
2726
+ }
2727
+ }
2728
+ return $errorMsg;
2729
+ }
2730
+
2731
+ protected function doActualConvert()
2732
+ {
2733
+ $errorMsg = '';
2734
+ $options = $this->options;
2735
+ $useNice = (($options['use-nice']) && self::hasNiceSupport());
2736
+
2737
+ $commandOptions = $this->createCommandLineOptions();
2738
+
2739
+ // Try all common paths that exists
2740
+ $success = false;
2741
+
2742
+ $failureCodes = [];
2743
+
2744
+ if ($options['try-common-system-paths']) {
2745
+ $failureCodes = $this->tryCommonSystemPaths($useNice, $commandOptions);
2746
+ $success = (count($failureCodes) == 0);
2747
+ $errorMsg = $this->composeErrorMessageForCommonSystemPathsFailures($failureCodes);
2748
+ }
2749
+
2750
+ if (!$success && $options['try-supplied-binary-for-os']) {
2751
+ $errorMsg2 = $this->trySuppliedBinaryForOS($useNice, $commandOptions, $failureCodes);
2752
+ $errorMsg .= $errorMsg2;
2753
+ $success = ($errorMsg2 == '');
2754
+ }
2755
+
2756
+ // cwebp sets file permissions to 664 but instead ..
2757
+ // .. $destination's parent folder's permissions should be used (except executable bits)
2758
+ // (or perhaps the current umask instead? https://www.php.net/umask)
2759
+
2760
+ if ($success) {
2761
+ $destinationParent = dirname($this->destination);
2762
+ $fileStatistics = stat($destinationParent);
2763
+ if ($fileStatistics !== false) {
2764
+ // Apply same permissions as parent folder but strip off the executable bits
2765
+ $permissions = $fileStatistics['mode'] & 0000666;
2766
+ chmod($this->destination, $permissions);
2767
+ }
2768
+ }
2769
+
2770
+ if (!$success) {
2771
+ throw new SystemRequirementsNotMetException($errorMsg);
2772
+ }
2773
+ }
2774
+ }
2775
+
2776
+ ?><?php
2777
+
2778
+ namespace WebPConvert\Convert\Converters;
2779
+
2780
+ use WebPConvert\Convert\Converters\AbstractConverter;
2781
+ use WebPConvert\Convert\Converters\ConverterTraits\CloudConverterTrait;
2782
+ use WebPConvert\Convert\Converters\ConverterTraits\CurlTrait;
2783
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
2784
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
2785
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\InvalidApiKeyException;
2786
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
2787
+ use WebPConvert\Options\SensitiveStringOption;
2788
+
2789
+ /**
2790
+ * Convert images to webp using ewww cloud service.
2791
+ *
2792
+ * @package WebPConvert
2793
+ * @author Bjørn Rosell <it@rosell.dk>
2794
+ * @since Class available since Release 2.0.0
2795
+ */
2796
+ class Ewww extends AbstractConverter
2797
+ {
2798
+ use CloudConverterTrait;
2799
+ use CurlTrait;
2800
+
2801
+ protected function getUnsupportedDefaultOptions()
2802
+ {
2803
+ return [
2804
+ 'alpha-quality',
2805
+ 'auto-filter',
2806
+ 'encoding',
2807
+ 'low-memory',
2808
+ 'use-nice'
2809
+ ];
2810
+ }
2811
+
2812
+ protected function createOptions()
2813
+ {
2814
+ parent::createOptions();
2815
+
2816
+ $this->options2->addOptions(
2817
+ new SensitiveStringOption('api-key', '')
2818
+ );
2819
+ }
2820
+
2821
+ /**
2822
+ * Get api key from options or environment variable
2823
+ *
2824
+ * @return string|false api key or false if none is set
2825
+ */
2826
+ private function getKey()
2827
+ {
2828
+ if (!empty($this->options['api-key'])) {
2829
+ return $this->options['api-key'];
2830
+ }
2831
+ if (!empty(getenv('EWWW_API_KEY'))) {
2832
+ return getenv('EWWW_API_KEY');
2833
+ }
2834
+ return false;
2835
+ }
2836
+
2837
+
2838
+ /**
2839
+ * Check operationality of Ewww converter.
2840
+ *
2841
+ * @throws SystemRequirementsNotMetException if system requirements are not met (curl)
2842
+ * @throws ConverterNotOperationalException if key is missing or invalid, or quota has exceeded
2843
+ */
2844
+ public function checkOperationality()
2845
+ {
2846
+
2847
+ $apiKey = $this->getKey();
2848
+
2849
+ if ($apiKey === false) {
2850
+ if (isset($this->options['key'])) {
2851
+ throw new InvalidApiKeyException(
2852
+ 'The "key" option has been renamed to "api-key" in webp-convert 2.0. ' .
2853
+ 'You must change the configuration accordingly.'
2854
+ );
2855
+ }
2856
+
2857
+ throw new InvalidApiKeyException('Missing API key.');
2858
+ }
2859
+
2860
+ if (strlen($apiKey) < 20) {
2861
+ throw new InvalidApiKeyException(
2862
+ 'Api key is invalid. Api keys are supposed to be 32 characters long - ' .
2863
+ 'the provided api key is much shorter'
2864
+ );
2865
+ }
2866
+
2867
+ // Check for curl requirements
2868
+ $this->checkOperationalityForCurlTrait();
2869
+
2870
+ $keyStatus = self::getKeyStatus($apiKey);
2871
+ switch ($keyStatus) {
2872
+ case 'great':
2873
+ break;
2874
+ case 'exceeded':
2875
+ throw new ConverterNotOperationalException('Quota has exceeded');
2876
+ break;
2877
+ case 'invalid':
2878
+ throw new InvalidApiKeyException('Api key is invalid');
2879
+ break;
2880
+ }
2881
+ }
2882
+
2883
+ /*
2884
+ public function checkConvertability()
2885
+ {
2886
+ // check upload limits
2887
+ $this->checkConvertabilityCloudConverterTrait();
2888
+ }
2889
+ */
2890
+
2891
+ // Although this method is public, do not call directly.
2892
+ // You should rather call the static convert() function, defined in AbstractConverter, which
2893
+ // takes care of preparing stuff before calling doConvert, and validating after.
2894
+ protected function doActualConvert()
2895
+ {
2896
+
2897
+ $options = $this->options;
2898
+
2899
+ $ch = self::initCurl();
2900
+
2901
+ //$this->logLn('api key:' . $this->getKey());
2902
+
2903
+ $postData = [
2904
+ 'api_key' => $this->getKey(),
2905
+ 'webp' => '1',
2906
+ 'file' => curl_file_create($this->source),
2907
+ 'quality' => $this->getCalculatedQuality(),
2908
+ 'metadata' => ($options['metadata'] == 'none' ? '0' : '1')
2909
+ ];
2910
+
2911
+ curl_setopt_array(
2912
+ $ch,
2913
+ [
2914
+ CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
2915
+ CURLOPT_HTTPHEADER => [
2916
+ 'User-Agent: WebPConvert',
2917
+ 'Accept: image/*'
2918
+ ],
2919
+ CURLOPT_POSTFIELDS => $postData,
2920
+ CURLOPT_BINARYTRANSFER => true,
2921
+ CURLOPT_RETURNTRANSFER => true,
2922
+ CURLOPT_HEADER => false,
2923
+ CURLOPT_SSL_VERIFYPEER => false
2924
+ ]
2925
+ );
2926
+
2927
+ $response = curl_exec($ch);
2928
+
2929
+ if (curl_errno($ch)) {
2930
+ throw new ConversionFailedException(curl_error($ch));
2931
+ }
2932
+
2933
+ // The API does not always return images.
2934
+ // For example, it may return a message such as '{"error":"invalid","t":"exceeded"}
2935
+ // Messages has a http content type of ie 'text/html; charset=UTF-8
2936
+ // Images has application/octet-stream.
2937
+ // So verify that we got an image back.
2938
+ if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
2939
+ //echo curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
2940
+ curl_close($ch);
2941
+
2942
+ /* May return this: {"error":"invalid","t":"exceeded"} */
2943
+ $responseObj = json_decode($response);
2944
+ if (isset($responseObj->error)) {
2945
+ //echo 'error:' . $responseObj->error . '<br>';
2946
+ //echo $response;
2947
+ //self::blacklistKey($key);
2948
+ //throw new SystemRequirementsNotMetException('The key is invalid. Blacklisted it!');
2949
+ throw new InvalidApiKeyException('The api key is invalid');
2950
+ }
2951
+
2952
+ throw new ConversionFailedException(
2953
+ 'ewww api did not return an image. It could be that the key is invalid. Response: '
2954
+ . $response
2955
+ );
2956
+ }
2957
+
2958
+ // Not sure this can happen. So just in case
2959
+ if ($response == '') {
2960
+ throw new ConversionFailedException('ewww api did not return anything');
2961
+ }
2962
+
2963
+ $success = file_put_contents($this->destination, $response);
2964
+
2965
+ if (!$success) {
2966
+ throw new ConversionFailedException('Error saving file');
2967
+ }
2968
+ }
2969
+
2970
+ /**
2971
+ * Keep subscription alive by optimizing a jpeg
2972
+ * (ewww closes accounts after 6 months of inactivity - and webp conversions seems not to be counted? )
2973
+ */
2974
+ public static function keepSubscriptionAlive($source, $key)
2975
+ {
2976
+ try {
2977
+ $ch = curl_init();
2978
+ } catch (\Exception $e) {
2979
+ return 'curl is not installed';
2980
+ }
2981
+ if ($ch === false) {
2982
+ return 'curl could not be initialized';
2983
+ }
2984
+ curl_setopt_array(
2985
+ $ch,
2986
+ [
2987
+ CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
2988
+ CURLOPT_HTTPHEADER => [
2989
+ 'User-Agent: WebPConvert',
2990
+ 'Accept: image/*'
2991
+ ],
2992
+ CURLOPT_POSTFIELDS => [
2993
+ 'api_key' => $key,
2994
+ 'webp' => '0',
2995
+ 'file' => curl_file_create($source),
2996
+ 'domain' => $_SERVER['HTTP_HOST'],
2997
+ 'quality' => 60,
2998
+ 'metadata' => 0
2999
+ ],
3000
+ CURLOPT_BINARYTRANSFER => true,
3001
+ CURLOPT_RETURNTRANSFER => true,
3002
+ CURLOPT_HEADER => false,
3003
+ CURLOPT_SSL_VERIFYPEER => false
3004
+ ]
3005
+ );
3006
+
3007
+ $response = curl_exec($ch);
3008
+ if (curl_errno($ch)) {
3009
+ return 'curl error' . curl_error($ch);
3010
+ }
3011
+ if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
3012
+ curl_close($ch);
3013
+
3014
+ /* May return this: {"error":"invalid","t":"exceeded"} */
3015
+ $responseObj = json_decode($response);
3016
+ if (isset($responseObj->error)) {
3017
+ return 'The key is invalid';
3018
+ }
3019
+
3020
+ return 'ewww api did not return an image. It could be that the key is invalid. Response: ' . $response;
3021
+ }
3022
+
3023
+ // Not sure this can happen. So just in case
3024
+ if ($response == '') {
3025
+ return 'ewww api did not return anything';
3026
+ }
3027
+
3028
+ return true;
3029
+ }
3030
+
3031
+ /*
3032
+ public static function blacklistKey($key)
3033
+ {
3034
+ }
3035
+
3036
+ public static function isKeyBlacklisted($key)
3037
+ {
3038
+ }*/
3039
+
3040
+ /**
3041
+ * Return "great", "exceeded" or "invalid"
3042
+ */
3043
+ public static function getKeyStatus($key)
3044
+ {
3045
+ $ch = self::initCurl();
3046
+
3047
+ curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/verify/");
3048
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
3049
+ curl_setopt(
3050
+ $ch,
3051
+ CURLOPT_POSTFIELDS,
3052
+ [
3053
+ 'api_key' => $key
3054
+ ]
3055
+ );
3056
+
3057
+ // The 403 forbidden is avoided with this line.
3058
+ curl_setopt(
3059
+ $ch,
3060
+ CURLOPT_USERAGENT,
3061
+ 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)'
3062
+ );
3063
+
3064
+ $response = curl_exec($ch);
3065
+ // echo $response;
3066
+ if (curl_errno($ch)) {
3067
+ throw new \Exception(curl_error($ch));
3068
+ }
3069
+ curl_close($ch);
3070
+
3071
+ // Possible responses:
3072
+ // “great” = verification successful
3073
+ // “exceeded” = indicates a valid key with no remaining image credits.
3074
+ // an empty response indicates that the key is not valid
3075
+
3076
+ if ($response == '') {
3077
+ return 'invalid';
3078
+ }
3079
+ $responseObj = json_decode($response);
3080
+ if (isset($responseObj->error)) {
3081
+ if ($responseObj->error == 'invalid') {
3082
+ return 'invalid';
3083
+ } else {
3084
+ throw new \Exception('Ewww returned unexpected error: ' . $response);
3085
+ }
3086
+ }
3087
+ if (!isset($responseObj->status)) {
3088
+ throw new \Exception('Ewww returned unexpected response to verify request: ' . $response);
3089
+ }
3090
+ switch ($responseObj->status) {
3091
+ case 'great':
3092
+ case 'exceeded':
3093
+ return $responseObj->status;
3094
+ }
3095
+ throw new \Exception('Ewww returned unexpected status to verify request: "' . $responseObj->status . '"');
3096
+ }
3097
+
3098
+ public static function isWorkingKey($key)
3099
+ {
3100
+ return (self::getKeyStatus($key) == 'great');
3101
+ }
3102
+
3103
+ public static function isValidKey($key)
3104
+ {
3105
+ return (self::getKeyStatus($key) != 'invalid');
3106
+ }
3107
+
3108
+ public static function getQuota($key)
3109
+ {
3110
+ $ch = self::initCurl();
3111
+
3112
+ curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/quota/");
3113
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
3114
+ curl_setopt(
3115
+ $ch,
3116
+ CURLOPT_POSTFIELDS,
3117
+ [
3118
+ 'api_key' => $key
3119
+ ]
3120
+ );
3121
+ curl_setopt(
3122
+ $ch,
3123
+ CURLOPT_USERAGENT,
3124
+ 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)'
3125
+ );
3126
+
3127
+ $response = curl_exec($ch);
3128
+ return $response; // ie -830 23. Seems to return empty for invalid keys
3129
+ // or empty
3130
+ //echo $response;
3131
+ }
3132
+ }
3133
+
3134
+ ?><?php
3135
+
3136
+ namespace WebPConvert\Convert\Converters;
3137
+
3138
+ use WebPConvert\Convert\Converters\AbstractConverter;
3139
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
3140
+ use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInputException;
3141
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
3142
+
3143
+ /**
3144
+ * Convert images to webp using gd extension.
3145
+ *
3146
+ * @package WebPConvert
3147
+ * @author Bjørn Rosell <it@rosell.dk>
3148
+ * @since Class available since Release 2.0.0
3149
+ */
3150
+ class Gd extends AbstractConverter
3151
+ {
3152
+ public function supportsLossless()
3153
+ {
3154
+ return false;
3155
+ }
3156
+
3157
+ protected function getUnsupportedDefaultOptions()
3158
+ {
3159
+ return [
3160
+ 'alpha-quality',
3161
+ 'auto-filter',
3162
+ 'encoding',
3163
+ 'low-memory',
3164
+ 'metadata',
3165
+ 'method',
3166
+ 'near-lossless',
3167
+ 'preset',
3168
+ 'size-in-percentage',
3169
+ 'use-nice'
3170
+ ];
3171
+ }
3172
+
3173
+ private $errorMessageWhileCreating = '';
3174
+ private $errorNumberWhileCreating;
3175
+
3176
+ /**
3177
+ * Check (general) operationality of Gd converter.
3178
+ *
3179
+ * @throws SystemRequirementsNotMetException if system requirements are not met
3180
+ */
3181
+ public function checkOperationality()
3182
+ {
3183
+ if (!extension_loaded('gd')) {
3184
+ throw new SystemRequirementsNotMetException('Required Gd extension is not available.');
3185
+ }
3186
+
3187
+ if (!function_exists('imagewebp')) {
3188
+ throw new SystemRequirementsNotMetException(
3189
+ 'Gd has been compiled without webp support.'
3190
+ );
3191
+ }
3192
+ }
3193
+
3194
+ /**
3195
+ * Check if specific file is convertable with current converter / converter settings.
3196
+ *
3197
+ * @throws SystemRequirementsNotMetException if Gd has been compiled without support for image type
3198
+ */
3199
+ public function checkConvertability()
3200
+ {
3201
+ $mimeType = $this->getMimeTypeOfSource();
3202
+ switch ($mimeType) {
3203
+ case 'image/png':
3204
+ if (!function_exists('imagecreatefrompng')) {
3205
+ throw new SystemRequirementsNotMetException(
3206
+ 'Gd has been compiled without PNG support and can therefore not convert this PNG image.'
3207
+ );
3208
+ }
3209
+ break;
3210
+
3211
+ case 'image/jpeg':
3212
+ if (!function_exists('imagecreatefromjpeg')) {
3213
+ throw new SystemRequirementsNotMetException(
3214
+ 'Gd has been compiled without Jpeg support and can therefore not convert this jpeg image.'
3215
+ );
3216
+ }
3217
+ }
3218
+ }
3219
+
3220
+ /**
3221
+ * Find out if all functions exists.
3222
+ *
3223
+ * @return boolean
3224
+ */
3225
+ private static function functionsExist($functionNamesArr)
3226
+ {
3227
+ foreach ($functionNamesArr as $functionName) {
3228
+ if (!function_exists($functionName)) {
3229
+ return false;
3230
+ }
3231
+ }
3232
+ return true;
3233
+ }
3234
+
3235
+ /**
3236
+ * Try to convert image pallette to true color on older systems that does not have imagepalettetotruecolor().
3237
+ *
3238
+ * The aim is to function as imagepalettetotruecolor, but for older systems.
3239
+ * So, if the image is already rgb, nothing will be done, and true will be returned
3240
+ * PS: Got the workaround here: https://secure.php.net/manual/en/function.imagepalettetotruecolor.php
3241
+ *
3242
+ * @param resource $image
3243
+ * @return boolean TRUE if the convertion was complete, or if the source image already is a true color image,
3244
+ * otherwise FALSE is returned.
3245
+ */
3246
+ private function makeTrueColorUsingWorkaround(&$image)
3247
+ {
3248
+ //return $this->makeTrueColor($image);
3249
+ /*
3250
+ if (function_exists('imageistruecolor') && imageistruecolor($image)) {
3251
+ return true;
3252
+ }*/
3253
+ if (self::functionsExist(['imagecreatetruecolor', 'imagealphablending', 'imagecolorallocatealpha',
3254
+ 'imagefilledrectangle', 'imagecopy', 'imagedestroy', 'imagesx', 'imagesy'])) {
3255
+ $dst = imagecreatetruecolor(imagesx($image), imagesy($image));
3256
+
3257
+ if ($dst === false) {
3258
+ return false;
3259
+ }
3260
+
3261
+ //prevent blending with default black
3262
+ if (imagealphablending($dst, false) === false) {
3263
+ return false;
3264
+ }
3265
+
3266
+ //change the RGB values if you need, but leave alpha at 127
3267
+ $transparent = imagecolorallocatealpha($dst, 255, 255, 255, 127);
3268
+
3269
+ if ($transparent === false) {
3270
+ return false;
3271
+ }
3272
+
3273
+ //simpler than flood fill
3274
+ if (imagefilledrectangle($dst, 0, 0, imagesx($image), imagesy($image), $transparent) === false) {
3275
+ return false;
3276
+ }
3277
+
3278
+ //restore default blending
3279
+ if (imagealphablending($dst, true) === false) {
3280
+ return false;
3281
+ };
3282
+
3283
+ if (imagecopy($dst, $image, 0, 0, 0, 0, imagesx($image), imagesy($image)) === false) {
3284
+ return false;
3285
+ }
3286
+ imagedestroy($image);
3287
+
3288
+ $image = $dst;
3289
+ return true;
3290
+ } else {
3291
+ // The necessary methods for converting color palette are not avalaible
3292
+ return false;
3293
+ }
3294
+ }
3295
+
3296
+ /**
3297
+ * Try to convert image pallette to true color.
3298
+ *
3299
+ * Try to convert image pallette to true color. If imagepalettetotruecolor() exists, that is used (available from
3300
+ * PHP >= 5.5.0). Otherwise using workaround found on the net.
3301
+ *
3302
+ * @param resource $image
3303
+ * @return boolean TRUE if the convertion was complete, or if the source image already is a true color image,
3304
+ * otherwise FALSE is returned.
3305
+ */
3306
+ private function makeTrueColor(&$image)
3307
+ {
3308
+ if (function_exists('imagepalettetotruecolor')) {
3309
+ return imagepalettetotruecolor($image);
3310
+ } else {
3311
+ // imagepalettetotruecolor() is not available on this system. Using custom implementation instead
3312
+ return $this->makeTrueColorUsingWorkaround($image);
3313
+ }
3314
+ }
3315
+
3316
+ /**
3317
+ * Create Gd image resource from source
3318
+ *
3319
+ * @throws InvalidInputException if mime type is unsupported or could not be detected
3320
+ * @throws ConversionFailedException if imagecreatefrompng or imagecreatefromjpeg fails
3321
+ * @return resource $image The created image
3322
+ */
3323
+ private function createImageResource()
3324
+ {
3325
+ // In case of failure, image will be false
3326
+
3327
+ $mimeType = $this->getMimeTypeOfSource();
3328
+
3329
+ if ($mimeType == 'image/png') {
3330
+ $image = imagecreatefrompng($this->source);
3331
+ if ($image === false) {
3332
+ throw new ConversionFailedException(
3333
+ 'Gd failed when trying to load/create image (imagecreatefrompng() failed)'
3334
+ );
3335
+ }
3336
+ return $image;
3337
+ }
3338
+
3339
+ if ($mimeType == 'image/jpeg') {
3340
+ $image = imagecreatefromjpeg($this->source);
3341
+ if ($image === false) {
3342
+ throw new ConversionFailedException(
3343
+ 'Gd failed when trying to load/create image (imagecreatefromjpeg() failed)'
3344
+ );
3345
+ }
3346
+ return $image;
3347
+ }
3348
+
3349
+ /*
3350
+ throw new InvalidInputException(
3351
+ 'Unsupported mime type:' . $mimeType
3352
+ );*/
3353
+ }
3354
+
3355
+ /**
3356
+ * Try to make image resource true color if it is not already.
3357
+ *
3358
+ * @param resource $image The image to work on
3359
+ * @return void
3360
+ */
3361
+ protected function tryToMakeTrueColorIfNot(&$image)
3362
+ {
3363
+ $mustMakeTrueColor = false;
3364
+ if (function_exists('imageistruecolor')) {
3365
+ if (imageistruecolor($image)) {
3366
+ $this->logLn('image is true color');
3367
+ } else {
3368
+ $this->logLn('image is not true color');
3369
+ $mustMakeTrueColor = true;
3370
+ }
3371
+ } else {
3372
+ $this->logLn('It can not be determined if image is true color');
3373
+ $mustMakeTrueColor = true;
3374
+ }
3375
+
3376
+ if ($mustMakeTrueColor) {
3377
+ $this->logLn('converting color palette to true color');
3378
+ $success = $this->makeTrueColor($image);
3379
+ if (!$success) {
3380
+ $this->logLn(
3381
+ 'Warning: FAILED converting color palette to true color. ' .
3382
+ 'Continuing, but this does NOT look good.'
3383
+ );
3384
+ }
3385
+ }
3386
+ }
3387
+
3388
+ /**
3389
+ *
3390
+ * @param resource $image
3391
+ * @return boolean true if alpha blending was set successfully, false otherwise
3392
+ */
3393
+ protected function trySettingAlphaBlending($image)
3394
+ {
3395
+ if (function_exists('imagealphablending')) {
3396
+ if (!imagealphablending($image, true)) {
3397
+ $this->logLn('Warning: imagealphablending() failed');
3398
+ return false;
3399
+ }
3400
+ } else {
3401
+ $this->logLn(
3402
+ 'Warning: imagealphablending() is not available on your system.' .
3403
+ ' Converting PNGs with transparency might fail on some systems'
3404
+ );
3405
+ return false;
3406
+ }
3407
+
3408
+ if (function_exists('imagesavealpha')) {
3409
+ if (!imagesavealpha($image, true)) {
3410
+ $this->logLn('Warning: imagesavealpha() failed');
3411
+ return false;
3412
+ }
3413
+ } else {
3414
+ $this->logLn(
3415
+ 'Warning: imagesavealpha() is not available on your system. ' .
3416
+ 'Converting PNGs with transparency might fail on some systems'
3417
+ );
3418
+ return false;
3419
+ }
3420
+ return true;
3421
+ }
3422
+
3423
+ protected function errorHandlerWhileCreatingWebP($errno, $errstr, $errfile, $errline)
3424
+ {
3425
+ $this->errorNumberWhileCreating = $errno;
3426
+ $this->errorMessageWhileCreating = $errstr . ' in ' . $errfile . ', line ' . $errline .
3427
+ ', PHP ' . PHP_VERSION . ' (' . PHP_OS . ')';
3428
+ //return false;
3429
+ }
3430
+
3431
+ /**
3432
+ *
3433
+ * @param resource $image
3434
+ * @return void
3435
+ */
3436
+ protected function destroyAndRemove($image)
3437
+ {
3438
+ imagedestroy($image);
3439
+ if (file_exists($this->destination)) {
3440
+ unlink($this->destination);
3441
+ }
3442
+ }
3443
+
3444
+ /**
3445
+ *
3446
+ * @param resource $image
3447
+ * @return void
3448
+ */
3449
+ protected function tryConverting($image)
3450
+ {
3451
+
3452
+ // Danger zone!
3453
+ // Using output buffering to generate image.
3454
+ // In this zone, Do NOT do anything that might produce unwanted output
3455
+ // Do NOT call $this->logLn
3456
+ // --------------------------------- (start of danger zone)
3457
+
3458
+ $addedZeroPadding = false;
3459
+ set_error_handler(array($this, "errorHandlerWhileCreatingWebP"));
3460
+
3461
+ // This line may trigger log, so we need to do it BEFORE ob_start() !
3462
+ $q = $this->getCalculatedQuality();
3463
+
3464
+ ob_start();
3465
+
3466
+ //$success = imagewebp($image, $this->destination, $q);
3467
+ $success = imagewebp($image, null, $q);
3468
+
3469
+ if (!$success) {
3470
+ $this->destroyAndRemove($image);
3471
+ ob_end_clean();
3472
+ restore_error_handler();
3473
+ throw new ConversionFailedException(
3474
+ 'Failed creating image. Call to imagewebp() failed.',
3475
+ $this->errorMessageWhileCreating
3476
+ );
3477
+ }
3478
+
3479
+
3480
+ // The following hack solves an `imagewebp` bug
3481
+ // See https://stackoverflow.com/questions/30078090/imagewebp-php-creates-corrupted-webp-files
3482
+ if (ob_get_length() % 2 == 1) {
3483
+ echo "\0";
3484
+ $addedZeroPadding = true;
3485
+ }
3486
+ $output = ob_get_clean();
3487
+ restore_error_handler();
3488
+
3489
+ if ($output == '') {
3490
+ $this->destroyAndRemove($image);
3491
+ throw new ConversionFailedException(
3492
+ 'Gd failed: imagewebp() returned empty string'
3493
+ );
3494
+ }
3495
+
3496
+ // --------------------------------- (end of danger zone).
3497
+
3498
+
3499
+ if ($this->errorMessageWhileCreating != '') {
3500
+ switch ($this->errorNumberWhileCreating) {
3501
+ case E_WARNING:
3502
+ $this->logLn('An warning was produced during conversion: ' . $this->errorMessageWhileCreating);
3503
+ break;
3504
+ case E_NOTICE:
3505
+ $this->logLn('An notice was produced during conversion: ' . $this->errorMessageWhileCreating);
3506
+ break;
3507
+ default:
3508
+ $this->destroyAndRemove($image);
3509
+ throw new ConversionFailedException(
3510
+ 'An error was produced during conversion',
3511
+ $this->errorMessageWhileCreating
3512
+ );
3513
+ break;
3514
+ }
3515
+ }
3516
+
3517
+ if ($addedZeroPadding) {
3518
+ $this->logLn(
3519
+ 'Fixing corrupt webp by adding a zero byte ' .
3520
+ '(older versions of Gd had a bug, but this hack fixes it)'
3521
+ );
3522
+ }
3523
+
3524
+ $success = file_put_contents($this->destination, $output);
3525
+
3526
+ if (!$success) {
3527
+ $this->destroyAndRemove($image);
3528
+ throw new ConversionFailedException(
3529
+ 'Gd failed when trying to save the image. Check file permissions!'
3530
+ );
3531
+ }
3532
+
3533
+ /*
3534
+ Previous code was much simpler, but on a system, the hack was not activated (a file with uneven number of bytes
3535
+ was created). This is puzzeling. And the old code did not provide any insights.
3536
+ Also, perhaps having two subsequent writes to the same file could perhaps cause a problem.
3537
+ In the new code, there is only one write.
3538
+ However, a bad thing about the new code is that the entire webp file is read into memory. This might cause
3539
+ memory overflow with big files.
3540
+ Perhaps we should check the filesize of the original and only use the new code when it is smaller than
3541
+ memory limit set in PHP by a certain factor.
3542
+ Or perhaps only use the new code on older versions of Gd
3543
+ https://wordpress.org/support/topic/images-not-seen-on-chrome/#post-11390284
3544
+
3545
+ Here is the old code:
3546
+
3547
+ $success = imagewebp($image, $this->destination, $this->getCalculatedQuality());
3548
+
3549
+ if (!$success) {
3550
+ throw new ConversionFailedException(
3551
+ 'Gd failed when trying to save the image as webp (call to imagewebp() failed). ' .
3552
+ 'It probably failed writing file. Check file permissions!'
3553
+ );
3554
+ }
3555
+
3556
+
3557
+ // This hack solves an `imagewebp` bug
3558
+ // See https://stackoverflow.com/questions/30078090/imagewebp-php-creates-corrupted-webp-files
3559
+ if (filesize($this->destination) % 2 == 1) {
3560
+ file_put_contents($this->destination, "\0", FILE_APPEND);
3561
+ }
3562
+ */
3563
+ }
3564
+
3565
+ // Although this method is public, do not call directly.
3566
+ // You should rather call the static convert() function, defined in AbstractConverter, which
3567
+ // takes care of preparing stuff before calling doConvert, and validating after.
3568
+ protected function doActualConvert()
3569
+ {
3570
+
3571
+ $this->logLn('GD Version: ' . gd_info()["GD Version"]);
3572
+
3573
+ // Btw: Check out processWebp here:
3574
+ // https://github.com/Intervention/image/blob/master/src/Intervention/Image/Gd/Encoder.php
3575
+
3576
+ // Create image resource
3577
+ $image = $this->createImageResource();
3578
+
3579
+ // Try to convert color palette if it is not true color
3580
+ $this->tryToMakeTrueColorIfNot($image);
3581
+
3582
+
3583
+ if ($this->getMimeTypeOfSource() == 'image/png') {
3584
+ // Try to set alpha blending
3585
+ $this->trySettingAlphaBlending($image);
3586
+ }
3587
+
3588
+ // Try to convert it to webp
3589
+ $this->tryConverting($image);
3590
+
3591
+ // End of story
3592
+ imagedestroy($image);
3593
+ }
3594
+ }
3595
+
3596
+ ?><?php
3597
+
3598
+ namespace WebPConvert\Convert\Converters;
3599
+
3600
+ use WebPConvert\Convert\Converters\AbstractConverter;
3601
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
3602
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
3603
+ use WebPConvert\Convert\Converters\ConverterTraits\EncodingAutoTrait;
3604
+
3605
+ //use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\TargetNotFoundException;
3606
+
3607
+ /**
3608
+ * Convert images to webp using Gmagick extension.
3609
+ *
3610
+ * @package WebPConvert
3611
+ * @author Bjørn Rosell <it@rosell.dk>
3612
+ * @since Class available since Release 2.0.0
3613
+ */
3614
+ class Gmagick extends AbstractConverter
3615
+ {
3616
+ use EncodingAutoTrait;
3617
+
3618
+ protected function getUnsupportedDefaultOptions()
3619
+ {
3620
+ return [
3621
+ 'near-lossless',
3622
+ 'preset',
3623
+ 'size-in-percentage',
3624
+ 'use-nice'
3625
+ ];
3626
+ }
3627
+
3628
+ /**
3629
+ * Check (general) operationality of Gmagick converter.
3630
+ *
3631
+ * Note:
3632
+ * It may be that Gd has been compiled without jpeg support or png support.
3633
+ * We do not check for this here, as the converter could still be used for the other.
3634
+ *
3635
+ * @throws SystemRequirementsNotMetException if system requirements are not met
3636
+ */
3637
+ public function checkOperationality()
3638
+ {
3639
+ if (!extension_loaded('Gmagick')) {
3640
+ throw new SystemRequirementsNotMetException('Required Gmagick extension is not available.');
3641
+ }
3642
+
3643
+ if (!class_exists('Gmagick')) {
3644
+ throw new SystemRequirementsNotMetException(
3645
+ 'Gmagick is installed, but not correctly. The class Gmagick is not available'
3646
+ );
3647
+ }
3648
+
3649
+ $im = new \Gmagick($this->source);
3650
+
3651
+ if (!in_array('WEBP', $im->queryformats())) {
3652
+ throw new SystemRequirementsNotMetException('Gmagick was compiled without WebP support.');
3653
+ }
3654
+ }
3655
+
3656
+ /**
3657
+ * Check if specific file is convertable with current converter / converter settings.
3658
+ *
3659
+ * @throws SystemRequirementsNotMetException if Gmagick does not support image type
3660
+ */
3661
+ public function checkConvertability()
3662
+ {
3663
+ $im = new \Gmagick();
3664
+ $mimeType = $this->getMimeTypeOfSource();
3665
+ switch ($mimeType) {
3666
+ case 'image/png':
3667
+ if (!in_array('PNG', $im->queryFormats())) {
3668
+ throw new SystemRequirementsNotMetException(
3669
+ 'Gmagick has been compiled without PNG support and can therefore not convert this PNG image.'
3670
+ );
3671
+ }
3672
+ break;
3673
+ case 'image/jpeg':
3674
+ if (!in_array('JPEG', $im->queryFormats())) {
3675
+ throw new SystemRequirementsNotMetException(
3676
+ 'Gmagick has been compiled without Jpeg support and can therefore not convert this Jpeg image.'
3677
+ );
3678
+ }
3679
+ break;
3680
+ }
3681
+ }
3682
+
3683
+ // Although this method is public, do not call directly.
3684
+ // You should rather call the static convert() function, defined in AbstractConverter, which
3685
+ // takes care of preparing stuff before calling doConvert, and validating after.
3686
+ protected function doActualConvert()
3687
+ {
3688
+
3689
+ $options = $this->options;
3690
+
3691
+ try {
3692
+ $im = new \Gmagick($this->source);
3693
+ } catch (\Exception $e) {
3694
+ throw new ConversionFailedException(
3695
+ 'Failed creating Gmagick object of file',
3696
+ 'Failed creating Gmagick object of file: "' . $this->source . '" - Gmagick threw an exception.',
3697
+ $e
3698
+ );
3699
+ }
3700
+
3701
+ $im->setimageformat('WEBP');
3702
+
3703
+ // Not completely sure if setimageoption() has always been there, so lets check first. #169
3704
+ if (method_exists($im, 'setimageoption')) {
3705
+ // Finally cracked setting webp options.
3706
+ // See #167
3707
+ // - and https://stackoverflow.com/questions/47294962/how-to-write-lossless-webp-files-with-perlmagick
3708
+ $im->setimageoption('webp', 'method', $options['method']);
3709
+ $im->setimageoption('webp', 'lossless', $options['encoding'] == 'lossless' ? 'true' : 'false');
3710
+ $im->setimageoption('webp', 'alpha-quality', $options['alpha-quality']);
3711
+
3712
+ if ($options['auto-filter'] === true) {
3713
+ $im->setimageoption('webp', 'auto-filter', 'true');
3714
+ }
3715
+ }
3716
+
3717
+ /*
3718
+ low-memory seems not to be supported:
3719
+ $im->setimageoption('webp', 'low-memory', $options['low-memory'] ? true : false);
3720
+ */
3721
+
3722
+ if ($options['metadata'] == 'none') {
3723
+ // Strip metadata and profiles
3724
+ $im->stripImage();
3725
+ }
3726
+
3727
+ // Ps: Imagick automatically uses same quality as source, when no quality is set
3728
+ // This feature is however not present in Gmagick
3729
+ // TODO: However, it might be possible after all - see #91
3730
+ $im->setcompressionquality($this->getCalculatedQuality());
3731
+
3732
+ // We call getImageBlob().
3733
+ // That method is undocumented, but it is there!
3734
+ // - just like it is in imagick, as pointed out here:
3735
+ // https://www.php.net/manual/ru/gmagick.readimageblob.php
3736
+
3737
+ /** @scrutinizer ignore-call */
3738
+ $imageBlob = $im->getImageBlob();
3739
+
3740
+ $success = @file_put_contents($this->destination, $imageBlob);
3741
+
3742
+ if (!$success) {
3743
+ throw new ConversionFailedException('Failed writing file');
3744
+ }
3745
+ }
3746
+ }
3747
+
3748
+ ?><?php
3749
+
3750
+ namespace WebPConvert\Convert\Converters;
3751
+
3752
+ use WebPConvert\Convert\Converters\AbstractConverter;
3753
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
3754
+
3755
+ /**
3756
+ * Non-functional converter, just here to tell you that it has been renamed.
3757
+ *
3758
+ * @package WebPConvert
3759
+ * @author Bjørn Rosell <it@rosell.dk>
3760
+ * @since Class available since Release 2.0.0
3761
+ */
3762
+ class GmagickBinary extends AbstractConverter
3763
+ {
3764
+ public function checkOperationality()
3765
+ {
3766
+ throw new ConversionFailedException(
3767
+ 'This converter has changed ID from "gmagickbinary" to "graphicsmagick". You need to change!'
3768
+ );
3769
+ }
3770
+
3771
+ protected function doActualConvert()
3772
+ {
3773
+ $this->checkOperationality();
3774
+ }
3775
+ }
3776
+
3777
+ ?><?php
3778
+
3779
+ namespace WebPConvert\Convert\Converters;
3780
+
3781
+ use WebPConvert\Convert\Converters\AbstractConverter;
3782
+ use WebPConvert\Convert\Converters\ConverterTraits\EncodingAutoTrait;
3783
+ use WebPConvert\Convert\Converters\ConverterTraits\ExecTrait;
3784
+
3785
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
3786
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
3787
+
3788
+ //use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\TargetNotFoundException;
3789
+
3790
+ /**
3791
+ * Convert images to webp by calling gmagick binary (gm).
3792
+ *
3793
+ * @package WebPConvert
3794
+ * @author Bjørn Rosell <it@rosell.dk>
3795
+ * @since Class available since Release 2.0.0
3796
+ */
3797
+ class GraphicsMagick extends AbstractConverter
3798
+ {
3799
+ use ExecTrait;
3800
+ use EncodingAutoTrait;
3801
+
3802
+ protected function getUnsupportedDefaultOptions()
3803
+ {
3804
+ return [
3805
+ 'auto-filter',
3806
+ 'near-lossless',
3807
+ 'preset',
3808
+ 'size-in-percentage',
3809
+ ];
3810
+ }
3811
+
3812
+ private function getPath()
3813
+ {
3814
+ if (empty(getenv('GRAPHICSMAGICK_PATH'))) {
3815
+ return 'gm';
3816
+ } else {
3817
+ return getenv('GRAPHICSMAGICK_PATH');
3818
+ }
3819
+ }
3820
+
3821
+ public function isInstalled()
3822
+ {
3823
+ exec($this->getPath() . ' -version', $output, $returnCode);
3824
+ return ($returnCode == 0);
3825
+ }
3826
+
3827
+ public function getVersion()
3828
+ {
3829
+ exec($this->getPath() . ' -version', $output, $returnCode);
3830
+ if (($returnCode == 0) && isset($output[0])) {
3831
+ return preg_replace('#http.*#', '', $output[0]);
3832
+ }
3833
+ return 'unknown';
3834
+ }
3835
+
3836
+ // Check if webp delegate is installed
3837
+ public function isWebPDelegateInstalled()
3838
+ {
3839
+ exec($this->getPath() . ' -version', $output, $returnCode);
3840
+ foreach ($output as $line) {
3841
+ if (preg_match('#WebP.*yes#i', $line)) {
3842
+ return true;
3843
+ }
3844
+ }
3845
+ return false;
3846
+ }
3847
+
3848
+ /**
3849
+ * Check (general) operationality of imagack converter executable
3850
+ *
3851
+ * @throws SystemRequirementsNotMetException if system requirements are not met
3852
+ */
3853
+ public function checkOperationality()
3854
+ {
3855
+ $this->checkOperationalityExecTrait();
3856
+
3857
+ if (!$this->isInstalled()) {
3858
+ throw new SystemRequirementsNotMetException('gmagick is not installed');
3859
+ }
3860
+ if (!$this->isWebPDelegateInstalled()) {
3861
+ throw new SystemRequirementsNotMetException('webp delegate missing');
3862
+ }
3863
+ }
3864
+
3865
+ /**
3866
+ * Build command line options
3867
+ *
3868
+ * @return string
3869
+ */
3870
+ private function createCommandLineOptions()
3871
+ {
3872
+ $commandArguments = [];
3873
+
3874
+ // Unlike imagick binary, it seems gmagick binary uses a fixed
3875
+ // quality (75) when quality is omitted
3876
+ $commandArguments[] = '-quality ' . escapeshellarg($this->getCalculatedQuality());
3877
+
3878
+ // encoding
3879
+ if ($this->options['encoding'] == 'lossless') {
3880
+ // Btw:
3881
+ // I am not sure if we should set "quality" for lossless.
3882
+ // Quality should not apply to lossless, but my tests shows that it does in some way for gmagick
3883
+ // setting it low, you get bad visual quality and small filesize. Setting it high, you get the opposite
3884
+ // Some claim it is a bad idea to set quality, but I'm not so sure.
3885
+ // https://stackoverflow.com/questions/4228027/
3886
+ // First, I do not just get bigger images when setting quality, as toc777 does.
3887
+ // Secondly, the answer is very old and that bad behaviour is probably fixed by now.
3888
+ $commandArguments[] = '-define webp:lossless=true';
3889
+ } else {
3890
+ $commandArguments[] = '-define webp:lossless=false';
3891
+ }
3892
+
3893
+ if ($this->options['alpha-quality'] !== 100) {
3894
+ $commandArguments[] = '-define webp:alpha-quality=' . strval($this->options['alpha-quality']);
3895
+ }
3896
+
3897
+ if ($this->options['low-memory']) {
3898
+ $commandArguments[] = '-define webp:low-memory=true';
3899
+ }
3900
+
3901
+ if ($this->options['metadata'] == 'none') {
3902
+ $commandArguments[] = '-strip';
3903
+ }
3904
+
3905
+ $commandArguments[] = '-define webp:method=' . $this->options['method'];
3906
+
3907
+ $commandArguments[] = escapeshellarg($this->source);
3908
+ $commandArguments[] = escapeshellarg('webp:' . $this->destination);
3909
+
3910
+ return implode(' ', $commandArguments);
3911
+ }
3912
+
3913
+ protected function doActualConvert()
3914
+ {
3915
+ //$this->logLn('Using quality:' . $this->getCalculatedQuality());
3916
+
3917
+ $this->logLn('Version: ' . $this->getVersion());
3918
+
3919
+ $command = $this->getPath() . ' convert ' . $this->createCommandLineOptions();
3920
+
3921
+ $useNice = (($this->options['use-nice']) && self::hasNiceSupport()) ? true : false;
3922
+ if ($useNice) {
3923
+ $this->logLn('using nice');
3924
+ $command = 'nice ' . $command;
3925
+ }
3926
+ $this->logLn('Executing command: ' . $command);
3927
+ exec($command, $output, $returnCode);
3928
+
3929
+ $this->logExecOutput($output);
3930
+ if ($returnCode == 0) {
3931
+ $this->logLn('success');
3932
+ } else {
3933
+ $this->logLn('return code: ' . $returnCode);
3934
+ }
3935
+
3936
+ if ($returnCode == 127) {
3937
+ throw new SystemRequirementsNotMetException('gmagick is not installed');
3938
+ }
3939
+ if ($returnCode != 0) {
3940
+ $this->logLn('command:' . $command);
3941
+ $this->logLn('return code:' . $returnCode);
3942
+ $this->logLn('output:' . print_r(implode("\n", $output), true));
3943
+ throw new SystemRequirementsNotMetException('The exec call failed');
3944
+ }
3945
+ }
3946
+ }
3947
+
3948
+ ?><?php
3949
+
3950
+ namespace WebPConvert\Convert\Converters;
3951
+
3952
+ use WebPConvert\Convert\Converters\AbstractConverter;
3953
+ use WebPConvert\Convert\Converters\ConverterTraits\ExecTrait;
3954
+ use WebPConvert\Convert\Converters\ConverterTraits\EncodingAutoTrait;
3955
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
3956
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
3957
+
3958
+ //use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\TargetNotFoundException;
3959
+
3960
+ /**
3961
+ * Convert images to webp by calling imagemagick binary.
3962
+ *
3963
+ * @package WebPConvert
3964
+ * @author Bjørn Rosell <it@rosell.dk>
3965
+ * @since Class available since Release 2.0.0
3966
+ */
3967
+ class ImageMagick extends AbstractConverter
3968
+ {
3969
+ use ExecTrait;
3970
+ use EncodingAutoTrait;
3971
+
3972
+ protected function getUnsupportedDefaultOptions()
3973
+ {
3974
+ return [
3975
+ 'near-lossless',
3976
+ 'preset',
3977
+ 'size-in-percentage',
3978
+ ];
3979
+ }
3980
+
3981
+ // To futher improve this converter, I could check out:
3982
+ // https://github.com/Orbitale/ImageMagickPHP
3983
+
3984
+ private function getPath()
3985
+ {
3986
+ // Should we use "magick" or "convert" command?
3987
+ // It seems they do the same. But which is best supported? Which is mostly available (whitelisted)?
3988
+ // Should we perhaps try both?
3989
+ // For now, we just go with "convert"
3990
+
3991
+ if (!empty(getenv('IMAGEMAGICK_PATH'))) {
3992
+ return getenv('IMAGEMAGICK_PATH');
3993
+ } else {
3994
+ return 'convert';
3995
+ }
3996
+ }
3997
+
3998
+ private function getVersion()
3999
+ {
4000
+ exec($this->getPath() . ' -version', $output, $returnCode);
4001
+ if (($returnCode == 0) && isset($output[0])) {
4002
+ return $output[0];
4003
+ } else {
4004
+ return 'unknown';
4005
+ }
4006
+ }
4007
+
4008
+ public function isInstalled()
4009
+ {
4010
+ exec($this->getPath() . ' -version', $output, $returnCode);
4011
+ return ($returnCode == 0);
4012
+ }
4013
+
4014
+ // Check if webp delegate is installed
4015
+ public function isWebPDelegateInstalled()
4016
+ {
4017
+
4018
+ exec('convert -list delegate', $output, $returnCode);
4019
+ foreach ($output as $line) {
4020
+ if (preg_match('#webp\\s*=#i', $line)) {
4021
+ return true;
4022
+ }
4023
+ }
4024
+
4025
+ // try other command
4026
+ exec('convert -list configure', $output, $returnCode);
4027
+ foreach ($output as $line) {
4028
+ if (preg_match('#DELEGATE.*webp#i', $line)) {
4029
+ return true;
4030
+ }
4031
+ }
4032
+
4033
+ return false;
4034
+
4035
+ // PS, convert -version does not output delegates on travis, so it is not reliable
4036
+ }
4037
+
4038
+ /**
4039
+ * Check (general) operationality of imagack converter executable
4040
+ *
4041
+ * @throws SystemRequirementsNotMetException if system requirements are not met
4042
+ */
4043
+ public function checkOperationality()
4044
+ {
4045
+ $this->checkOperationalityExecTrait();
4046
+
4047
+ if (!$this->isInstalled()) {
4048
+ throw new SystemRequirementsNotMetException(
4049
+ 'imagemagick is not installed (cannot execute: "' . $this->getPath() . '")'
4050
+ );
4051
+ }
4052
+ if (!$this->isWebPDelegateInstalled()) {
4053
+ throw new SystemRequirementsNotMetException('webp delegate missing');
4054
+ }
4055
+ }
4056
+
4057
+ /**
4058
+ * Build command line options
4059
+ *
4060
+ * @return string
4061
+ */
4062
+ private function createCommandLineOptions()
4063
+ {
4064
+ // PS: Available webp options for imagemagick are documented here:
4065
+ // https://imagemagick.org/script/webp.php
4066
+
4067
+ $commandArguments = [];
4068
+ if ($this->isQualityDetectionRequiredButFailing()) {
4069
+ // quality:auto was specified, but could not be determined.
4070
+ // we cannot apply the max-quality logic, but we can provide auto quality
4071
+ // simply by not specifying the quality option.
4072
+ } else {
4073
+ $commandArguments[] = '-quality ' . escapeshellarg($this->getCalculatedQuality());
4074
+ }
4075
+ if ($this->options['encoding'] == 'lossless') {
4076
+ $commandArguments[] = '-define webp:lossless=true';
4077
+ }
4078
+ if ($this->options['low-memory']) {
4079
+ $commandArguments[] = '-define webp:low-memory=true';
4080
+ }
4081
+ if ($this->options['auto-filter'] === true) {
4082
+ $commandArguments[] = '-define webp:auto-filter=true';
4083
+ }
4084
+ if ($this->options['metadata'] == 'none') {
4085
+ $commandArguments[] = '-strip';
4086
+ }
4087
+ if ($this->options['alpha-quality'] !== 100) {
4088
+ $commandArguments[] = '-define webp:alpha-quality=' . strval($this->options['alpha-quality']);
4089
+ }
4090
+
4091
+ // Unfortunately, near-lossless does not seem to be supported.
4092
+ // it does have a "preprocessing" option, which may be doing something similar
4093
+
4094
+ $commandArguments[] = '-define webp:method=' . $this->options['method'];
4095
+
4096
+ $commandArguments[] = escapeshellarg($this->source);
4097
+ $commandArguments[] = escapeshellarg('webp:' . $this->destination);
4098
+
4099
+ return implode(' ', $commandArguments);
4100
+ }
4101
+
4102
+ protected function doActualConvert()
4103
+ {
4104
+ $this->logLn($this->getVersion());
4105
+
4106
+ $command = $this->getPath() . ' ' . $this->createCommandLineOptions();
4107
+
4108
+ $useNice = (($this->options['use-nice']) && self::hasNiceSupport()) ? true : false;
4109
+ if ($useNice) {
4110
+ $this->logLn('using nice');
4111
+ $command = 'nice ' . $command;
4112
+ }
4113
+ $this->logLn('Executing command: ' . $command);
4114
+ exec($command, $output, $returnCode);
4115
+
4116
+ $this->logExecOutput($output);
4117
+ if ($returnCode == 0) {
4118
+ $this->logLn('success');
4119
+ } else {
4120
+ $this->logLn('return code: ' . $returnCode);
4121
+ }
4122
+
4123
+ if ($returnCode == 127) {
4124
+ throw new SystemRequirementsNotMetException('imagemagick is not installed');
4125
+ }
4126
+ if ($returnCode != 0) {
4127
+ throw new SystemRequirementsNotMetException('The exec call failed');
4128
+ }
4129
+ }
4130
+ }
4131
+
4132
+ ?><?php
4133
+
4134
+ namespace WebPConvert\Convert\Converters;
4135
+
4136
+ use WebPConvert\Convert\Converters\AbstractConverter;
4137
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
4138
+ use WebPConvert\Convert\Exceptions\ConversionFailed\FileSystemProblems\CreateDestinationFileException;
4139
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
4140
+ use WebPConvert\Convert\Converters\ConverterTraits\EncodingAutoTrait;
4141
+
4142
+ //use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\TargetNotFoundException;
4143
+
4144
+ /**
4145
+ * Convert images to webp using Imagick extension.
4146
+ *
4147
+ * @package WebPConvert
4148
+ * @author Bjørn Rosell <it@rosell.dk>
4149
+ * @since Class available since Release 2.0.0
4150
+ */
4151
+ class Imagick extends AbstractConverter
4152
+ {
4153
+ use EncodingAutoTrait;
4154
+
4155
+ protected function getUnsupportedDefaultOptions()
4156
+ {
4157
+ return [
4158
+ 'near-lossless',
4159
+ 'preset',
4160
+ 'size-in-percentage',
4161
+ 'use-nice'
4162
+ ];
4163
+ }
4164
+
4165
+ /**
4166
+ * Check operationality of Imagick converter.
4167
+ *
4168
+ * Note:
4169
+ * It may be that Gd has been compiled without jpeg support or png support.
4170
+ * We do not check for this here, as the converter could still be used for the other.
4171
+ *
4172
+ * @throws SystemRequirementsNotMetException if system requirements are not met
4173
+ * @return void
4174
+ */
4175
+ public function checkOperationality()
4176
+ {
4177
+ if (!extension_loaded('imagick')) {
4178
+ throw new SystemRequirementsNotMetException('Required iMagick extension is not available.');
4179
+ }
4180
+
4181
+ if (!class_exists('\\Imagick')) {
4182
+ throw new SystemRequirementsNotMetException(
4183
+ 'iMagick is installed, but not correctly. The class Imagick is not available'
4184
+ );
4185
+ }
4186
+
4187
+ $im = new \Imagick();
4188
+ if (!in_array('WEBP', $im->queryFormats('WEBP'))) {
4189
+ throw new SystemRequirementsNotMetException('iMagick was compiled without WebP support.');
4190
+ }
4191
+ }
4192
+
4193
+ /**
4194
+ * Check if specific file is convertable with current converter / converter settings.
4195
+ *
4196
+ * @throws SystemRequirementsNotMetException if Imagick does not support image type
4197
+ */
4198
+ public function checkConvertability()
4199
+ {
4200
+ $im = new \Imagick();
4201
+ $mimeType = $this->getMimeTypeOfSource();
4202
+ switch ($mimeType) {
4203
+ case 'image/png':
4204
+ if (!in_array('PNG', $im->queryFormats('PNG'))) {
4205
+ throw new SystemRequirementsNotMetException(
4206
+ 'Imagick has been compiled without PNG support and can therefore not convert this PNG image.'
4207
+ );
4208
+ }
4209
+ break;
4210
+ case 'image/jpeg':
4211
+ if (!in_array('JPEG', $im->queryFormats('JPEG'))) {
4212
+ throw new SystemRequirementsNotMetException(
4213
+ 'Imagick has been compiled without Jpeg support and can therefore not convert this Jpeg image.'
4214
+ );
4215
+ }
4216
+ break;
4217
+ }
4218
+ }
4219
+
4220
+ /**
4221
+ *
4222
+ * It may also throw an ImagickException if imagick throws an exception
4223
+ * @throws CreateDestinationFileException if imageblob could not be saved to file
4224
+ */
4225
+ protected function doActualConvert()
4226
+ {
4227
+ /*
4228
+ * More about iMagick's WebP options:
4229
+ * - Inspect source code: https://github.com/ImageMagick/ImageMagick/blob/master/coders/webp.c#L559
4230
+ * (search for "webp:")
4231
+ * - http://www.imagemagick.org/script/webp.php
4232
+ * - https://developers.google.com/speed/webp/docs/cwebp
4233
+ * - https://stackoverflow.com/questions/37711492/imagemagick-specific-webp-calls-in-php
4234
+ */
4235
+
4236
+ $options = $this->options;
4237
+
4238
+ // This might throw - we let it!
4239
+ $im = new \Imagick($this->source);
4240
+
4241
+ //$im = new \Imagick();
4242
+ //$im->pingImage($this->source);
4243
+ //$im->readImage($this->source);
4244
+
4245
+ $im->setImageFormat('WEBP');
4246
+
4247
+ $im->setOption('webp:method', $options['method']);
4248
+ $im->setOption('webp:lossless', $options['encoding'] == 'lossless' ? 'true' : 'false');
4249
+ $im->setOption('webp:low-memory', $options['low-memory'] ? 'true' : 'false');
4250
+ $im->setOption('webp:alpha-quality', $options['alpha-quality']);
4251
+
4252
+ if ($options['auto-filter'] === true) {
4253
+ $im->setOption('webp:auto-filter', 'true');
4254
+ }
4255
+
4256
+ if ($options['metadata'] == 'none') {
4257
+ // Strip metadata and profiles
4258
+ $im->stripImage();
4259
+ }
4260
+
4261
+ if ($this->isQualityDetectionRequiredButFailing()) {
4262
+ // Luckily imagick is a big boy, and automatically converts with same quality as
4263
+ // source, when the quality isn't set.
4264
+ // So we simply do not set quality.
4265
+ // This actually kills the max-quality functionality. But I deem that this is more important
4266
+ // because setting image quality to something higher than source generates bigger files,
4267
+ // but gets you no extra quality. When failing to limit quality, you at least get something
4268
+ // out of it
4269
+ $this->logLn('Converting without setting quality in order to achieve auto quality');
4270
+ } else {
4271
+ $im->setImageCompressionQuality($this->getCalculatedQuality());
4272
+ }
4273
+
4274
+ // https://stackoverflow.com/questions/29171248/php-imagick-jpeg-optimization
4275
+ // setImageFormat
4276
+
4277
+ // TODO: Read up on
4278
+ // https://www.smashingmagazine.com/2015/06/efficient-image-resizing-with-imagemagick/
4279
+ // https://github.com/nwtn/php-respimg
4280
+
4281
+ // TODO:
4282
+ // Should we set alpha channel for PNG's like suggested here:
4283
+ // https://gauntface.com/blog/2014/09/02/webp-support-with-imagemagick-and-php ??
4284
+ // It seems that alpha channel works without... (at least I see completely transparerent pixels)
4285
+
4286
+ // We used to use writeImageFile() method. But we now use getImageBlob(). See issue #43
4287
+
4288
+ // This might throw - we let it!
4289
+ $imageBlob = $im->getImageBlob();
4290
+
4291
+ $success = file_put_contents($this->destination, $imageBlob);
4292
+
4293
+ if (!$success) {
4294
+ throw new CreateDestinationFileException('Failed writing file');
4295
+ }
4296
+
4297
+ // Btw: check out processWebp() method here:
4298
+ // https://github.com/Intervention/image/blob/master/src/Intervention/Image/Imagick/Encoder.php
4299
+ }
4300
+ }
4301
+
4302
+ ?><?php
4303
+
4304
+ namespace WebPConvert\Convert\Converters;
4305
+
4306
+ use WebPConvert\Convert\Converters\AbstractConverter;
4307
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
4308
+
4309
+ /**
4310
+ * Non-functional converter, just here to tell you that it has been renamed.
4311
+ *
4312
+ * @package WebPConvert
4313
+ * @author Bjørn Rosell <it@rosell.dk>
4314
+ * @since Class available since Release 2.0.0
4315
+ */
4316
+ class ImagickBinary extends AbstractConverter
4317
+ {
4318
+ public function checkOperationality()
4319
+ {
4320
+ throw new ConversionFailedException(
4321
+ 'This converter has changed ID from "imagickbinary" to "imagemagick". You need to change!'
4322
+ );
4323
+ }
4324
+
4325
+ protected function doActualConvert()
4326
+ {
4327
+ $this->checkOperationality();
4328
+ }
4329
+ }
4330
+
4331
+ ?><?php
4332
+
4333
+ namespace WebPConvert\Convert\Converters;
4334
+
4335
+ use WebPConvert\Convert\ConverterFactory;
4336
+ use WebPConvert\Convert\Converters\AbstractConverter;
4337
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
4338
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
4339
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
4340
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConversionSkippedException;
4341
+ use WebPConvert\Options\BooleanOption;
4342
+ use WebPConvert\Options\ArrayOption;
4343
+ use WebPConvert\Options\GhostOption;
4344
+ use WebPConvert\Options\SensitiveArrayOption;
4345
+
4346
+ //use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\TargetNotFoundException;
4347
+
4348
+ /**
4349
+ * Convert images to webp by trying a stack of converters until success.
4350
+ *
4351
+ * @package WebPConvert
4352
+ * @author Bjørn Rosell <it@rosell.dk>
4353
+ * @since Class available since Release 2.0.0
4354
+ */
4355
+ class Stack extends AbstractConverter
4356
+ {
4357
+
4358
+ protected function getUnsupportedDefaultOptions()
4359
+ {
4360
+ return [
4361
+ 'alpha-quality',
4362
+ 'auto-filter',
4363
+ 'encoding',
4364
+ 'low-memory',
4365
+ 'metadata',
4366
+ 'method',
4367
+ 'near-lossless',
4368
+ 'preset',
4369
+ 'size-in-percentage',
4370
+ 'use-nice',
4371
+ 'skip',
4372
+ 'default-quality',
4373
+ 'quality',
4374
+ 'max-quality',
4375
+ ];
4376
+ }
4377
+
4378
+ protected function createOptions()
4379
+ {
4380
+ parent::createOptions();
4381
+
4382
+ $this->options2->addOptions(
4383
+ new SensitiveArrayOption('converters', self::getAvailableConverters()),
4384
+ new SensitiveArrayOption('converter-options', []),
4385
+ new BooleanOption('shuffle', false),
4386
+ new ArrayOption('preferred-converters', []),
4387
+ new SensitiveArrayOption('extra-converters', [])
4388
+ );
4389
+ }
4390
+
4391
+ /**
4392
+ * Get available converters (ids) - ordered by awesomeness.
4393
+ *
4394
+ * @return array An array of ids of converters that comes with this library
4395
+ */
4396
+ public static function getAvailableConverters()
4397
+ {
4398
+ return [
4399
+ 'cwebp', 'vips', 'imagick', 'gmagick', 'imagemagick', 'graphicsmagick', 'wpc', 'ewww', 'gd'
4400
+ ];
4401
+ }
4402
+
4403
+ /**
4404
+ * Check (general) operationality of imagack converter executable
4405
+ *
4406
+ * @throws SystemRequirementsNotMetException if system requirements are not met
4407
+ */
4408
+ public function checkOperationality()
4409
+ {
4410
+ if (count($this->options['converters']) == 0) {
4411
+ throw new ConverterNotOperationalException(
4412
+ 'Converter stack is empty! - no converters to try, no conversion can be made!'
4413
+ );
4414
+ }
4415
+
4416
+ // TODO: We should test if all converters are found in order to detect problems early
4417
+
4418
+ //$this->logLn('Stack converter ignited');
4419
+ }
4420
+
4421
+ protected function doActualConvert()
4422
+ {
4423
+ $options = $this->options;
4424
+
4425
+ $beginTimeStack = microtime(true);
4426
+
4427
+ $anyRuntimeErrors = false;
4428
+
4429
+ $converters = $options['converters'];
4430
+ if (count($options['extra-converters']) > 0) {
4431
+ $converters = array_merge($converters, $options['extra-converters']);
4432
+ /*foreach ($options['extra-converters'] as $extra) {
4433
+ $converters[] = $extra;
4434
+ }*/
4435
+ }
4436
+
4437
+ // preferred-converters
4438
+ if (count($options['preferred-converters']) > 0) {
4439
+ foreach (array_reverse($options['preferred-converters']) as $prioritizedConverter) {
4440
+ foreach ($converters as $i => $converter) {
4441
+ if (is_array($converter)) {
4442
+ $converterId = $converter['converter'];
4443
+ } else {
4444
+ $converterId = $converter;
4445
+ }
4446
+ if ($converterId == $prioritizedConverter) {
4447
+ unset($converters[$i]);
4448
+ array_unshift($converters, $converter);
4449
+ break;
4450
+ }
4451
+ }
4452
+ }
4453
+ // perhaps write the order to the log? (without options) - but this requires some effort
4454
+ }
4455
+
4456
+ // shuffle
4457
+ if ($options['shuffle']) {
4458
+ shuffle($converters);
4459
+ }
4460
+
4461
+ //$this->logLn(print_r($converters));
4462
+ //$options['converters'] = $converters;
4463
+ //$defaultConverterOptions = $options;
4464
+ $defaultConverterOptions = [];
4465
+
4466
+ foreach ($this->options2->getOptionsMap() as $id => $option) {
4467
+ if ($option->isValueExplicitlySet() && ! ($option instanceof GhostOption)) {
4468
+ //$this->logLn('hi' . $id);
4469
+ $defaultConverterOptions[$id] = $option->getValue();
4470
+ }
4471
+ }
4472
+
4473
+ //unset($defaultConverterOptions['converters']);
4474
+ //unset($defaultConverterOptions['converter-options']);
4475
+ $defaultConverterOptions['_skip_input_check'] = true;
4476
+ $defaultConverterOptions['_suppress_success_message'] = true;
4477
+ unset($defaultConverterOptions['converters']);
4478
+ unset($defaultConverterOptions['extra-converters']);
4479
+ unset($defaultConverterOptions['converter-options']);
4480
+ unset($defaultConverterOptions['preferred-converters']);
4481
+ unset($defaultConverterOptions['shuffle']);
4482
+
4483
+ // $this->logLn('converters: ' . print_r($converters, true));
4484
+
4485
+ //return;
4486
+ foreach ($converters as $converter) {
4487
+ if (is_array($converter)) {
4488
+ $converterId = $converter['converter'];
4489
+ $converterOptions = isset($converter['options']) ? $converter['options'] : [];
4490
+ } else {
4491
+ $converterId = $converter;
4492
+ $converterOptions = [];
4493
+ if (isset($options['converter-options'][$converterId])) {
4494
+ // Note: right now, converter-options are not meant to be used,
4495
+ // when you have several converters of the same type
4496
+ $converterOptions = $options['converter-options'][$converterId];
4497
+ }
4498
+ }
4499
+ $converterOptions = array_merge($defaultConverterOptions, $converterOptions);
4500
+ /*
4501
+ if ($converterId != 'stack') {
4502
+ //unset($converterOptions['converters']);
4503
+ //unset($converterOptions['converter-options']);
4504
+ } else {
4505
+ //$converterOptions['converter-options'] =
4506
+ $this->logLn('STACK');
4507
+ $this->logLn('converterOptions: ' . print_r($converterOptions, true));
4508
+ }*/
4509
+
4510
+ $beginTime = microtime(true);
4511
+
4512
+ $this->ln();
4513
+ $this->logLn('Trying: ' . $converterId, 'italic');
4514
+
4515
+ $converter = ConverterFactory::makeConverter(
4516
+ $converterId,
4517
+ $this->source,
4518
+ $this->destination,
4519
+ $converterOptions,
4520
+ $this->logger
4521
+ );
4522
+
4523
+ try {
4524
+ $converter->doConvert();
4525
+
4526
+ //self::runConverterWithTiming($converterId, $source, $destination, $converterOptions, false, $logger);
4527
+
4528
+ $this->logLn($converterId . ' succeeded :)');
4529
+ //throw new ConverterNotOperationalException('...');
4530
+ return;
4531
+ } catch (ConverterNotOperationalException $e) {
4532
+ $this->logLn($e->getMessage());
4533
+ } catch (ConversionFailedException $e) {
4534
+ $this->logLn($e->getMessage(), 'italic');
4535
+ $prev = $e->getPrevious();
4536
+ if (!is_null($prev)) {
4537
+ $this->logLn($prev->getMessage(), 'italic');
4538
+ $this->logLn(' in ' . $prev->getFile() . ', line ' . $prev->getLine(), 'italic');
4539
+ $this->ln();
4540
+ }
4541
+ //$this->logLn($e->getTraceAsString());
4542
+ $anyRuntimeErrors = true;
4543
+ } catch (ConversionSkippedException $e) {
4544
+ $this->logLn($e->getMessage());
4545
+ }
4546
+
4547
+ $this->logLn($converterId . ' failed in ' . round((microtime(true) - $beginTime) * 1000) . ' ms');
4548
+ }
4549
+
4550
+ $this->ln();
4551
+ $this->logLn('Stack failed in ' . round((microtime(true) - $beginTimeStack) * 1000) . ' ms');
4552
+
4553
+ if ($anyRuntimeErrors) {
4554
+ // At least one converter failed
4555
+ throw new ConversionFailedException(
4556
+ 'None of the converters in the stack could convert the image. ' .
4557
+ 'At least one failed, even though its requirements seemed to be met.'
4558
+ );
4559
+ } else {
4560
+ // All converters threw a SystemRequirementsNotMetException
4561
+ throw new ConverterNotOperationalException('None of the converters in the stack are operational');
4562
+ }
4563
+ }
4564
+ }
4565
+
4566
+ ?><?php
4567
+
4568
+ namespace WebPConvert\Convert\Converters;
4569
+
4570
+ use WebPConvert\Convert\Converters\AbstractConverter;
4571
+ use WebPConvert\Convert\Converters\ConverterTraits\EncodingAutoTrait;
4572
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
4573
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
4574
+ use WebPConvert\Options\BooleanOption;
4575
+
4576
+ //require '/home/rosell/.composer/vendor/autoload.php';
4577
+
4578
+ /**
4579
+ * Convert images to webp using Vips extension.
4580
+ *
4581
+ * @package WebPConvert
4582
+ * @author Bjørn Rosell <it@rosell.dk>
4583
+ * @since Class available since Release 2.0.0
4584
+ */
4585
+ class Vips extends AbstractConverter
4586
+ {
4587
+ use EncodingAutoTrait;
4588
+
4589
+ protected function getUnsupportedDefaultOptions()
4590
+ {
4591
+ return [
4592
+ 'size-in-percentage',
4593
+ 'use-nice'
4594
+ ];
4595
+ }
4596
+
4597
+ protected function createOptions()
4598
+ {
4599
+ parent::createOptions();
4600
+
4601
+ $this->options2->addOptions(
4602
+ new BooleanOption('smart-subsample', false)
4603
+ );
4604
+ }
4605
+
4606
+ /**
4607
+ * Check operationality of Vips converter.
4608
+ *
4609
+ * @throws SystemRequirementsNotMetException if system requirements are not met
4610
+ */
4611
+ public function checkOperationality()
4612
+ {
4613
+ if (!extension_loaded('vips')) {
4614
+ throw new SystemRequirementsNotMetException('Required Vips extension is not available.');
4615
+ }
4616
+
4617
+ if (!function_exists('vips_image_new_from_file')) {
4618
+ throw new SystemRequirementsNotMetException(
4619
+ 'Vips extension seems to be installed, however something is not right: ' .
4620
+ 'the function "vips_image_new_from_file" is not available.'
4621
+ );
4622
+ }
4623
+
4624
+ // TODO: Should we also test if webp is available? (It seems not to be neccessary - it seems
4625
+ // that webp be well intergrated part of vips)
4626
+ }
4627
+
4628
+ /**
4629
+ * Check if specific file is convertable with current converter / converter settings.
4630
+ *
4631
+ * @throws SystemRequirementsNotMetException if Vips does not support image type
4632
+ */
4633
+ public function checkConvertability()
4634
+ {
4635
+ // It seems that png and jpeg are always supported by Vips
4636
+ // - so nothing needs to be done here
4637
+
4638
+ if (function_exists('vips_version')) {
4639
+ $this->logLn('vipslib version: ' . vips_version());
4640
+ }
4641
+ $this->logLn('vips extension version: ' . phpversion('vips'));
4642
+ }
4643
+
4644
+ /**
4645
+ * Create vips image resource from source file
4646
+ *
4647
+ * @throws ConversionFailedException if image resource cannot be created
4648
+ * @return resource vips image resource
4649
+ */
4650
+ private function createImageResource()
4651
+ {
4652
+ // We are currently using vips_image_new_from_file(), but we could consider
4653
+ // calling vips_jpegload / vips_pngload instead
4654
+ $result = /** @scrutinizer ignore-call */ vips_image_new_from_file($this->source, []);
4655
+ if ($result === -1) {
4656
+ /*throw new ConversionFailedException(
4657
+ 'Failed creating new vips image from file: ' . $this->source
4658
+ );*/
4659
+ $message = /** @scrutinizer ignore-call */ vips_error_buffer();
4660
+ throw new ConversionFailedException($message);
4661
+ }
4662
+
4663
+ if (!is_array($result)) {
4664
+ throw new ConversionFailedException(
4665
+ 'vips_image_new_from_file did not return an array, which we expected'
4666
+ );
4667
+ }
4668
+
4669
+ if (count($result) != 1) {
4670
+ throw new ConversionFailedException(
4671
+ 'vips_image_new_from_file did not return an array of length 1 as we expected ' .
4672
+ '- length was: ' . count($result)
4673
+ );
4674
+ }
4675
+
4676
+ $im = array_shift($result);
4677
+ return $im;
4678
+ }
4679
+
4680
+ /**
4681
+ * Create parameters for webpsave
4682
+ *
4683
+ * @return array the parameters as an array
4684
+ */
4685
+ private function createParamsForVipsWebPSave()
4686
+ {
4687
+ // webpsave options are described here:
4688
+ // v 8.8.0: https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-webpsave
4689
+ // v ?.?.?: https://jcupitt.github.io/libvips/API/current/VipsForeignSave.html#vips-webpsave
4690
+ // near_lossless option is described here: https://github.com/libvips/libvips/pull/430
4691
+
4692
+ // Note that "method" is currently not supported (27 may 2019)
4693
+
4694
+ $options = [
4695
+ "Q" => $this->getCalculatedQuality(),
4696
+ 'lossless' => ($this->options['encoding'] == 'lossless'),
4697
+ 'strip' => $this->options['metadata'] == 'none',
4698
+ ];
4699
+
4700
+ // Only set the following options if they differ from the default of vipslib
4701
+ // This ensures we do not get warning if that property isn't supported
4702
+ if ($this->options['smart-subsample'] !== false) {
4703
+ $options['smart_subsample'] = $this->options['smart-subsample'];
4704
+ }
4705
+ if ($this->options['alpha-quality'] !== 100) {
4706
+ $options['alpha_q'] = $this->options['alpha-quality'];
4707
+ }
4708
+
4709
+ if (!is_null($this->options['preset']) && ($this->options['preset'] != 'none')) {
4710
+ // preset. 0:default, 1:picture, 2:photo, 3:drawing, 4:icon, 5:text, 6:last
4711
+ $options['preset'] = array_search(
4712
+ $this->options['preset'],
4713
+ ['default', 'picture', 'photo', 'drawing', 'icon', 'text']
4714
+ );
4715
+ }
4716
+ if ($this->options['near-lossless'] !== 100) {
4717
+ if ($this->options['encoding'] == 'lossless') {
4718
+ // We only let near_lossless have effect when encoding is set to lossless
4719
+ // otherwise encoding=auto would not work as expected
4720
+ // Available in https://github.com/libvips/libvips/pull/430, merged 1 may 2016
4721
+ // seems it corresponds to release 8.4.2
4722
+ $options['near_lossless'] = true;
4723
+
4724
+ // In Vips, the near-lossless value is controlled by Q.
4725
+ // this differs from how it is done in cwebp, where it is an integer.
4726
+ // We have chosen same option syntax as cwebp
4727
+ $options['Q'] = $this->options['near-lossless'];
4728
+ }
4729
+ }
4730
+
4731
+ return $options;
4732
+ }
4733
+
4734
+ /**
4735
+ * Convert with vips extension.
4736
+ *
4737
+ * Tries to create image resource and save it as webp using the calculated options.
4738
+ * Vips fails when a parameter is not supported, but we detect this and unset that parameter and try again
4739
+ * (recursively call itself until there is no more of these kind of errors).
4740
+ *
4741
+ * @param resource $im A vips image resource to save
4742
+ * @throws ConversionFailedException if conversion fails.
4743
+ */
4744
+ private function webpsave($im, $options)
4745
+ {
4746
+ $result = /** @scrutinizer ignore-call */ vips_call('webpsave', $im, $this->destination, $options);
4747
+
4748
+ //trigger_error('test-warning', E_USER_WARNING);
4749
+ if ($result === -1) {
4750
+ $message = /** @scrutinizer ignore-call */ vips_error_buffer();
4751
+
4752
+ $nameOfPropertyNotFound = '';
4753
+ if (preg_match("#no property named .(.*).#", $message, $matches)) {
4754
+ $nameOfPropertyNotFound = $matches[1];
4755
+ } elseif (preg_match("#(.*)\\sunsupported$#", $message, $matches)) {
4756
+ // Actually, I am not quite sure if this ever happens.
4757
+ // I got a "near_lossless unsupported" error message in a build, but perhaps it rather a warning
4758
+ if (in_array($matches[1], ['lossless', 'alpha_q', 'near_lossless', 'smart_subsample'])) {
4759
+ $nameOfPropertyNotFound = $matches[1];
4760
+ }
4761
+ }
4762
+
4763
+ if ($nameOfPropertyNotFound != '') {
4764
+ $this->logLn(
4765
+ 'Your version of vipslib does not support the "' . $nameOfPropertyNotFound . '" property. ' .
4766
+ 'The option is ignored.'
4767
+ );
4768
+ unset($options[$nameOfPropertyNotFound]);
4769
+ $this->webpsave($im, $options);
4770
+ } else {
4771
+ throw new ConversionFailedException($message);
4772
+ }
4773
+ }
4774
+ }
4775
+
4776
+ /**
4777
+ * Convert with vips extension.
4778
+ *
4779
+ * Tries to create image resource and save it as webp using the calculated options.
4780
+ * Vips fails when a parameter is not supported, but we detect this and unset that parameter and try again
4781
+ * (repeat until success).
4782
+ *
4783
+ * @throws ConversionFailedException if conversion fails.
4784
+ */
4785
+ protected function doActualConvert()
4786
+ {
4787
+ /*
4788
+ $im = \Jcupitt\Vips\Image::newFromFile($this->source);
4789
+ //$im->writeToFile(__DIR__ . '/images/small-vips.webp', ["Q" => 10]);
4790
+
4791
+ $im->webpsave($this->destination, [
4792
+ "Q" => 80,
4793
+ //'near_lossless' => true
4794
+ ]);
4795
+ return;*/
4796
+
4797
+ $im = $this->createImageResource();
4798
+ $options = $this->createParamsForVipsWebPSave();
4799
+ $this->webpsave($im, $options);
4800
+ }
4801
+ }
4802
+
4803
+ ?><?php
4804
+
4805
+ namespace WebPConvert\Convert\Converters;
4806
+
4807
+ use WebPConvert\Convert\Converters\AbstractConverter;
4808
+ use WebPConvert\Convert\Converters\ConverterTraits\CloudConverterTrait;
4809
+ use WebPConvert\Convert\Converters\ConverterTraits\CurlTrait;
4810
+ use WebPConvert\Convert\Converters\ConverterTraits\EncodingAutoTrait;
4811
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
4812
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
4813
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
4814
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\InvalidApiKeyException;
4815
+ use WebPConvert\Options\BooleanOption;
4816
+ use WebPConvert\Options\IntegerOption;
4817
+ use WebPConvert\Options\SensitiveStringOption;
4818
+
4819
+ /**
4820
+ * Convert images to webp using Wpc (a cloud converter based on WebP Convert).
4821
+ *
4822
+ * @package WebPConvert
4823
+ * @author Bjørn Rosell <it@rosell.dk>
4824
+ * @since Class available since Release 2.0.0
4825
+ */
4826
+ class Wpc extends AbstractConverter
4827
+ {
4828
+ use CloudConverterTrait;
4829
+ use CurlTrait;
4830
+ use EncodingAutoTrait;
4831
+
4832
+ protected function getUnsupportedDefaultOptions()
4833
+ {
4834
+ return [];
4835
+ }
4836
+
4837
+ public function supportsLossless()
4838
+ {
4839
+ return ($this->options['api-version'] >= 2);
4840
+ }
4841
+
4842
+ public function passOnEncodingAuto()
4843
+ {
4844
+ // We could make this configurable. But I guess passing it on is always to be preferred
4845
+ // for api >= 2.
4846
+ return ($this->options['api-version'] >= 2);
4847
+ }
4848
+
4849
+ protected function createOptions()
4850
+ {
4851
+ parent::createOptions();
4852
+
4853
+ $this->options2->addOptions(
4854
+ new SensitiveStringOption('api-key', ''), /* for communicating with wpc api v.1+ */
4855
+ new SensitiveStringOption('secret', ''), /* for communicating with wpc api v.0 */
4856
+ new SensitiveStringOption('api-url', ''),
4857
+ new SensitiveStringOption('url', ''), /* DO NOT USE. Only here to keep the protection */
4858
+ new IntegerOption('api-version', 2, 0, 2),
4859
+ new BooleanOption('crypt-api-key-in-transfer', false) /* new in api v.1 */
4860
+ );
4861
+ }
4862
+
4863
+ private static function createRandomSaltForBlowfish()
4864
+ {
4865
+ $salt = '';
4866
+ $validCharsForSalt = array_merge(
4867
+ range('A', 'Z'),
4868
+ range('a', 'z'),
4869
+ range('0', '9'),
4870
+ ['.', '/']
4871
+ );
4872
+
4873
+ for ($i=0; $i<22; $i++) {
4874
+ $salt .= $validCharsForSalt[array_rand($validCharsForSalt)];
4875
+ }
4876
+ return $salt;
4877
+ }
4878
+
4879
+ /**
4880
+ * Get api key from options or environment variable
4881
+ *
4882
+ * @return string api key or empty string if none is set
4883
+ */
4884
+ private function getApiKey()
4885
+ {
4886
+ if ($this->options['api-version'] == 0) {
4887
+ if (!empty($this->options['secret'])) {
4888
+ return $this->options['secret'];
4889
+ }
4890
+ } elseif ($this->options['api-version'] >= 1) {
4891
+ if (!empty($this->options['api-key'])) {
4892
+ return $this->options['api-key'];
4893
+ }
4894
+ }
4895
+ if (!empty(getenv('WPC_API_KEY'))) {
4896
+ return getenv('WPC_API_KEY');
4897
+ }
4898
+ return '';
4899
+ }
4900
+
4901
+ /**
4902
+ * Get url from options or environment variable
4903
+ *
4904
+ * @return string URL to WPC or empty string if none is set
4905
+ */
4906
+ private function getApiUrl()
4907
+ {
4908
+ if (!empty($this->options['api-url'])) {
4909
+ return $this->options['api-url'];
4910
+ }
4911
+ if (!empty(getenv('WPC_API_URL'))) {
4912
+ return getenv('WPC_API_URL');
4913
+ }
4914
+ return '';
4915
+ }
4916
+
4917
+
4918
+ /**
4919
+ * Check operationality of Wpc converter.
4920
+ *
4921
+ * @throws SystemRequirementsNotMetException if system requirements are not met (curl)
4922
+ * @throws ConverterNotOperationalException if key is missing or invalid, or quota has exceeded
4923
+ */
4924
+ public function checkOperationality()
4925
+ {
4926
+
4927
+ $options = $this->options;
4928
+
4929
+ $apiVersion = $options['api-version'];
4930
+
4931
+ if ($this->getApiUrl() == '') {
4932
+ if (isset($this->options['url']) && ($this->options['url'] != '')) {
4933
+ throw new ConverterNotOperationalException(
4934
+ 'The "url" option has been renamed to "api-url" in webp-convert 2.0. ' .
4935
+ 'You must change the configuration accordingly.'
4936
+ );
4937
+ }
4938
+ throw new ConverterNotOperationalException(
4939
+ 'Missing URL. You must install Webp Convert Cloud Service on a server, ' .
4940
+ 'or the WebP Express plugin for Wordpress - and supply the url.'
4941
+ );
4942
+ }
4943
+
4944
+ if ($apiVersion == 0) {
4945
+ if (!empty($this->getApiKey())) {
4946
+ // if secret is set, we need md5() and md5_file() functions
4947
+ if (!function_exists('md5')) {
4948
+ throw new ConverterNotOperationalException(
4949
+ 'A secret has been set, which requires us to create a md5 hash from the secret and the file ' .
4950
+ 'contents. ' .
4951
+ 'But the required md5() PHP function is not available.'
4952
+ );
4953
+ }
4954
+ if (!function_exists('md5_file')) {
4955
+ throw new ConverterNotOperationalException(
4956
+ 'A secret has been set, which requires us to create a md5 hash from the secret and the file ' .
4957
+ 'contents. But the required md5_file() PHP function is not available.'
4958
+ );
4959
+ }
4960
+ }
4961
+ } elseif ($apiVersion >= 1) {
4962
+ if ($options['crypt-api-key-in-transfer']) {
4963
+ if (!function_exists('crypt')) {
4964
+ throw new ConverterNotOperationalException(
4965
+ 'Configured to crypt the api-key, but crypt() function is not available.'
4966
+ );
4967
+ }
4968
+
4969
+ if (!defined('CRYPT_BLOWFISH')) {
4970
+ throw new ConverterNotOperationalException(
4971
+ 'Configured to crypt the api-key. ' .
4972
+ 'That requires Blowfish encryption, which is not available on your current setup.'
4973
+ );
4974
+ }
4975
+ }
4976
+ }
4977
+
4978
+ // Check for curl requirements
4979
+ $this->checkOperationalityForCurlTrait();
4980
+ }
4981
+
4982
+ /*
4983
+ public function checkConvertability()
4984
+ {
4985
+ // check upload limits
4986
+ $this->checkConvertabilityCloudConverterTrait();
4987
+
4988
+ // TODO: some from below can be moved up here
4989
+ }
4990
+ */
4991
+
4992
+ private function createOptionsToSend()
4993
+ {
4994
+ $optionsToSend = $this->options;
4995
+
4996
+ if ($this->isQualityDetectionRequiredButFailing()) {
4997
+ // quality was set to "auto", but we could not meassure the quality of the jpeg locally
4998
+ // Ask the cloud service to do it, rather than using what we came up with.
4999
+ $optionsToSend['quality'] = 'auto';
5000
+ } else {
5001
+ $optionsToSend['quality'] = $this->getCalculatedQuality();
5002
+ }
5003
+
5004
+ // The following are unset for security reasons.
5005
+ unset($optionsToSend['converters']);
5006
+ unset($optionsToSend['secret']);
5007
+ unset($optionsToSend['api-key']);
5008
+ unset($optionsToSend['api-url']);
5009
+
5010
+ $apiVersion = $optionsToSend['api-version'];
5011
+
5012
+ if ($apiVersion == 1) {
5013
+ // Lossless can be "auto" in api 2, but in api 1 "auto" is not supported
5014
+ //unset($optionsToSend['lossless']);
5015
+ } elseif ($apiVersion == 2) {
5016
+ //unset($optionsToSend['png']);
5017
+ //unset($optionsToSend['jpeg']);
5018
+
5019
+ // The following are unset for security reasons.
5020
+ unset($optionsToSend['cwebp-command-line-options']);
5021
+ unset($optionsToSend['command-line-options']);
5022
+ }
5023
+
5024
+ return $optionsToSend;
5025
+ }
5026
+
5027
+ private function createPostData()
5028
+ {
5029
+ $options = $this->options;
5030
+
5031
+ $postData = [
5032
+ 'file' => curl_file_create($this->source),
5033
+ 'options' => json_encode($this->createOptionsToSend()),
5034
+ 'servername' => (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '')
5035
+ ];
5036
+
5037
+ $apiVersion = $options['api-version'];
5038
+
5039
+ $apiKey = $this->getApiKey();
5040
+
5041
+ if ($apiVersion == 0) {
5042
+ $postData['hash'] = md5(md5_file($this->source) . $apiKey);
5043
+ } elseif ($apiVersion == 1) {
5044
+ //$this->logLn('api key: ' . $apiKey);
5045
+
5046
+ if ($options['crypt-api-key-in-transfer']) {
5047
+ $salt = self::createRandomSaltForBlowfish();
5048
+ $postData['salt'] = $salt;
5049
+
5050
+ // Strip off the first 28 characters (the first 6 are always "$2y$10$". The next 22 is the salt)
5051
+ $postData['api-key-crypted'] = substr(crypt($apiKey, '$2y$10$' . $salt . '$'), 28);
5052
+ } else {
5053
+ $postData['api-key'] = $apiKey;
5054
+ }
5055
+ }
5056
+ return $postData;
5057
+ }
5058
+
5059
+ protected function doActualConvert()
5060
+ {
5061
+ $ch = self::initCurl();
5062
+
5063
+ //$this->logLn('api url: ' . $this->getApiUrl());
5064
+
5065
+ curl_setopt_array($ch, [
5066
+ CURLOPT_URL => $this->getApiUrl(),
5067
+ CURLOPT_POST => 1,
5068
+ CURLOPT_POSTFIELDS => $this->createPostData(),
5069
+ CURLOPT_BINARYTRANSFER => true,
5070
+ CURLOPT_RETURNTRANSFER => true,
5071
+ CURLOPT_HEADER => false,
5072
+ CURLOPT_SSL_VERIFYPEER => false
5073
+ ]);
5074
+
5075
+ $response = curl_exec($ch);
5076
+ if (curl_errno($ch)) {
5077
+ $this->logLn('Curl error: ', 'bold');
5078
+ $this->logLn(curl_error($ch));
5079
+ throw new ConverterNotOperationalException('Curl error:');
5080
+ }
5081
+
5082
+ // Check if we got a 404
5083
+ $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
5084
+ if ($httpCode == 404) {
5085
+ curl_close($ch);
5086
+ throw new ConversionFailedException(
5087
+ 'WPC was not found at the specified URL - we got a 404 response.'
5088
+ );
5089
+ }
5090
+
5091
+ // Check for empty response
5092
+ if (empty($response)) {
5093
+ throw new ConversionFailedException(
5094
+ 'Error: Unexpected result. We got nothing back. ' .
5095
+ 'HTTP CODE: ' . $httpCode . '. ' .
5096
+ 'Content type:' . curl_getinfo($ch, CURLINFO_CONTENT_TYPE)
5097
+ );
5098
+ };
5099
+
5100
+ // The WPC cloud service either returns an image or an error message
5101
+ // Images has application/octet-stream.
5102
+ // Verify that we got an image back.
5103
+ if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
5104
+ curl_close($ch);
5105
+
5106
+ if (substr($response, 0, 1) == '{') {
5107
+ $responseObj = json_decode($response, true);
5108
+ if (isset($responseObj['errorCode'])) {
5109
+ switch ($responseObj['errorCode']) {
5110
+ case 0:
5111
+ throw new ConverterNotOperationalException(
5112
+ 'There are problems with the server setup: "' .
5113
+ $responseObj['errorMessage'] . '"'
5114
+ );
5115
+ case 1:
5116
+ throw new InvalidApiKeyException(
5117
+ 'Access denied. ' . $responseObj['errorMessage']
5118
+ );
5119
+ default:
5120
+ throw new ConversionFailedException(
5121
+ 'Conversion failed: "' . $responseObj['errorMessage'] . '"'
5122
+ );
5123
+ }
5124
+ }
5125
+ }
5126
+
5127
+ // WPC 0.1 returns 'failed![error messag]' when conversion fails. Handle that.
5128
+ if (substr($response, 0, 7) == 'failed!') {
5129
+ throw new ConversionFailedException(
5130
+ 'WPC failed converting image: "' . substr($response, 7) . '"'
5131
+ );
5132
+ }
5133
+
5134
+ $this->logLn('Bummer, we did not receive an image');
5135
+ $this->log('What we received starts with: "');
5136
+ $this->logLn(
5137
+ str_replace("\r", '', str_replace("\n", '', htmlentities(substr($response, 0, 400)))) . '..."'
5138
+ );
5139
+
5140
+ throw new ConversionFailedException('Unexpected result. We did not receive an image but something else.');
5141
+ //throw new ConverterNotOperationalException($response);
5142
+ }
5143
+
5144
+ $success = file_put_contents($this->destination, $response);
5145
+ curl_close($ch);
5146
+
5147
+ if (!$success) {
5148
+ throw new ConversionFailedException('Error saving file. Check file permissions');
5149
+ }
5150
+ }
5151
+ }
5152
+
5153
+ ?><?php
5154
+
5155
+ namespace WebPConvert\Convert\Exceptions\ConversionFailed;
5156
+
5157
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
5158
+
5159
+ class ConversionSkippedException extends ConversionFailedException
5160
+ {
5161
+ public $description = 'The converter declined converting';
5162
+ }
5163
+
5164
+ ?><?php
5165
+
5166
+ namespace WebPConvert\Convert\Exceptions\ConversionFailed;
5167
+
5168
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
5169
+
5170
+ class ConverterNotOperationalException extends ConversionFailedException
5171
+ {
5172
+ public $description = 'The converter is not operational';
5173
+ }
5174
+
5175
+ ?><?php
5176
+
5177
+ namespace WebPConvert\Convert\Exceptions\ConversionFailed;
5178
+
5179
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
5180
+
5181
+ class FileSystemProblemsException extends ConversionFailedException
5182
+ {
5183
+ public $description = 'Filesystem problems';
5184
+ }
5185
+
5186
+ ?><?php
5187
+
5188
+ namespace WebPConvert\Convert\Exceptions\ConversionFailed;
5189
+
5190
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
5191
+
5192
+ class InvalidInputException extends ConversionFailedException
5193
+ {
5194
+ public $description = 'Invalid input';
5195
+ }
5196
+
5197
+ ?><?php
5198
+
5199
+ namespace WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational;
5200
+
5201
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
5202
+
5203
+ class InvalidApiKeyException extends ConverterNotOperationalException
5204
+ {
5205
+ public $description = 'The converter is not operational (access denied)';
5206
+ }
5207
+
5208
+ ?><?php
5209
+
5210
+ namespace WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational;
5211
+
5212
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
5213
+
5214
+ class SystemRequirementsNotMetException extends ConverterNotOperationalException
5215
+ {
5216
+ public $description = 'The converter is not operational (system requirements not met)';
5217
+ }
5218
+
5219
+ ?><?php
5220
+
5221
+ namespace WebPConvert\Convert\Exceptions\ConversionFailed\FileSystemProblems;
5222
+
5223
+ use WebPConvert\Convert\Exceptions\ConversionFailed\FileSystemProblemsException;
5224
+
5225
+ class CreateDestinationFileException extends FileSystemProblemsException
5226
+ {
5227
+ public $description = 'The converter could not create destination file. Check file permisions!';
5228
+ }
5229
+
5230
+ ?><?php
5231
+
5232
+ namespace WebPConvert\Convert\Exceptions\ConversionFailed\FileSystemProblems;
5233
+
5234
+ use WebPConvert\Convert\Exceptions\ConversionFailed\FileSystemProblemsException;
5235
+
5236
+ class CreateDestinationFolderException extends FileSystemProblemsException
5237
+ {
5238
+ public $description = 'The converter could not create destination folder. Check file permisions!';
5239
+ }
5240
+
5241
+ ?><?php
5242
+
5243
+ namespace WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput;
5244
+
5245
+ use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInputException;
5246
+
5247
+ class ConverterNotFoundException extends InvalidInputException
5248
+ {
5249
+ public $description = 'The converter does not exist.';
5250
+ }
5251
+
5252
+ ?><?php
5253
+
5254
+ namespace WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput;
5255
+
5256
+ use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInputException;
5257
+
5258
+ class InvalidImageTypeException extends InvalidInputException
5259
+ {
5260
+ public $description = 'The converter does not handle the supplied image type';
5261
+ }
5262
+
5263
+ ?><?php
5264
+
5265
+ namespace WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput;
5266
+
5267
+ use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInputException;
5268
+
5269
+ class TargetNotFoundException extends InvalidInputException
5270
+ {
5271
+ public $description = 'The converter could not locate source file';
5272
+ }
5273
+
5274
+ ?><?php
5275
+
5276
+ namespace WebPConvert\Convert\Helpers;
5277
+
5278
+ /**
5279
+ * Try to detect quality of a jpeg image using various tools.
5280
+ *
5281
+ * @package WebPConvert
5282
+ * @author Bjørn Rosell <it@rosell.dk>
5283
+ * @since Class available since Release 2.0.0
5284
+ */
5285
+ class JpegQualityDetector
5286
+ {
5287
+
5288
+ /**
5289
+ * Try to detect quality of jpeg using imagick extension
5290
+ *
5291
+ * @param string $filename A complete file path to file to be examined
5292
+ * @return int|null Quality, or null if it was not possible to detect quality
5293
+ */
5294
+ private static function detectQualityOfJpgUsingImagick($filename)
5295
+ {
5296
+ if (extension_loaded('imagick') && class_exists('\\Imagick')) {
5297
+ try {
5298
+ $img = new \Imagick($filename);
5299
+
5300
+ // The required function is available as from PECL imagick v2.2.2
5301
+ if (method_exists($img, 'getImageCompressionQuality')) {
5302
+ return $img->getImageCompressionQuality();
5303
+ }
5304
+ } catch (\Exception $e) {
5305
+ // Well well, it just didn't work out.
5306
+ // - But perhaps next method will work...
5307
+ }
5308
+ }
5309
+ return null;
5310
+ }
5311
+
5312
+
5313
+ /**
5314
+ * Try to detect quality of jpeg using imagick binary
5315
+ *
5316
+ * @param string $filename A complete file path to file to be examined
5317
+ * @return int|null Quality, or null if it was not possible to detect quality
5318
+ */
5319
+ private static function detectQualityOfJpgUsingImageMagick($filename)
5320
+ {
5321
+ if (function_exists('exec')) {
5322
+ // Try Imagick using exec, and routing stderr to stdout (the "2>$1" magic)
5323
+ exec("identify -format '%Q' " . escapeshellarg($filename) . " 2>&1", $output, $returnCode);
5324
+ //echo 'out:' . print_r($output, true);
5325
+ if ((intval($returnCode) == 0) && (is_array($output)) && (count($output) == 1)) {
5326
+ return intval($output[0]);
5327
+ }
5328
+ }
5329
+ return null;
5330
+ }
5331
+
5332
+
5333
+ /**
5334
+ * Try to detect quality of jpeg using gmagick binary
5335
+ *
5336
+ * @param string $filename A complete file path to file to be examined
5337
+ * @return int|null Quality, or null if it was not possible to detect quality
5338
+ */
5339
+ private static function detectQualityOfJpgUsingGraphicsMagick($filename)
5340
+ {
5341
+ if (function_exists('exec')) {
5342
+ // Try GraphicsMagick
5343
+ exec("gm identify -format '%Q' " . escapeshellarg($filename) . " 2>&1", $output, $returnCode);
5344
+ if ((intval($returnCode) == 0) && (is_array($output)) && (count($output) == 1)) {
5345
+ return intval($output[0]);
5346
+ }
5347
+ }
5348
+ return null;
5349
+ }
5350
+
5351
+
5352
+ /**
5353
+ * Try to detect quality of jpeg.
5354
+ *
5355
+ * Note: This method does not throw errors, but might dispatch warnings.
5356
+ * You can use the WarningsIntoExceptions class if it is critical to you that nothing gets "printed"
5357
+ *
5358
+ * @param string $filename A complete file path to file to be examined
5359
+ * @return int|null Quality, or null if it was not possible to detect quality
5360
+ */
5361
+ public static function detectQualityOfJpg($filename)
5362
+ {
5363
+
5364
+ //trigger_error('warning test', E_USER_WARNING);
5365
+
5366
+ // Test that file exists in order not to break things.
5367
+ if (!file_exists($filename)) {
5368
+ // One could argue that it would be better to throw an Exception...?
5369
+ return null;
5370
+ }
5371
+
5372
+ // Try Imagick extension, if available
5373
+ $quality = self::detectQualityOfJpgUsingImagick($filename);
5374
+
5375
+ if (is_null($quality)) {
5376
+ $quality = self::detectQualityOfJpgUsingImageMagick($filename);
5377
+ }
5378
+
5379
+ if (is_null($quality)) {
5380
+ $quality = self::detectQualityOfJpgUsingGraphicsMagick($filename);
5381
+ }
5382
+
5383
+ return $quality;
5384
+ }
5385
+ }
5386
+
5387
+ ?><?php
5388
+
5389
+ namespace WebPConvert\Convert\Helpers;
5390
+
5391
+ /**
5392
+ * Get/parse shorthandsize strings from php.ini as bytes.
5393
+ *
5394
+ * Parse strings like "1k" into bytes (1024).
5395
+ *
5396
+ * @package WebPConvert
5397
+ * @author Bjørn Rosell <it@rosell.dk>
5398
+ * @since Class available since Release 2.0.0
5399
+ */
5400
+ class PhpIniSizes
5401
+ {
5402
+
5403
+ /**
5404
+ * Parse a shordhandsize string as the ones returned by ini_get()
5405
+ *
5406
+ * Parse a shorthandsize string having the syntax allowed in php.ini and returned by ini_get().
5407
+ * Ie "1K" => 1024.
5408
+ * Strings without units are also accepted.
5409
+ * The shorthandbytes syntax is described here: https://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes
5410
+ *
5411
+ * @param string $shortHandSize A size string of the type returned by ini_get()
5412
+ * @return float|false The parsed size (beware: it is float, do not check high numbers for equality),
5413
+ * or false if parse error
5414
+ */
5415
+ public static function parseShortHandSize($shortHandSize)
5416
+ {
5417
+
5418
+ $result = preg_match("#^\\s*(\\d+(?:\\.\\d+)?)([bkmgtpezy]?)\\s*$#i", $shortHandSize, $matches);
5419
+ if ($result !== 1) {
5420
+ return false;
5421
+ }
5422
+
5423
+ // Truncate, because that is what php does.
5424
+ $digitsValue = floor($matches[1]);
5425
+
5426
+ if ((count($matches) >= 3) && ($matches[2] != '')) {
5427
+ $unit = $matches[2];
5428
+
5429
+ // Find the position of the unit in the ordered string which is the power
5430
+ // of magnitude to multiply a kilobyte by.
5431
+ $position = stripos('bkmgtpezy', $unit);
5432
+
5433
+ return floatval($digitsValue * pow(1024, $position));
5434
+ } else {
5435
+ return $digitsValue;
5436
+ }
5437
+ }
5438
+
5439
+ /*
5440
+ * Get the size of an php.ini option.
5441
+ *
5442
+ * Calls ini_get() and parses the size to a number.
5443
+ * If the configuration option is null, does not exist, or cannot be parsed as a shorthandsize, false is returned
5444
+ *
5445
+ * @param string $varname The configuration option name.
5446
+ * @return float|false The parsed size or false if the configuration option does not exist
5447
+ */
5448
+ public static function getIniBytes($iniVarName)
5449
+ {
5450
+ $iniVarValue = ini_get($iniVarName);
5451
+ if (($iniVarValue == '') || $iniVarValue === false) {
5452
+ return false;
5453
+ }
5454
+ return self::parseShortHandSize($iniVarValue);
5455
+ }
5456
+ }
5457
+
5458
+ ?><?php
5459
+
5460
+ namespace WebPConvert\Convert;
5461
+
5462
+ use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\ConverterNotFoundException;
5463
+ use WebPConvert\Convert\Converters\AbstractConverter;
5464
+
5465
+ /**
5466
+ * Make converters from their ids.
5467
+ *
5468
+ * @package WebPConvert
5469
+ * @author Bjørn Rosell <it@rosell.dk>
5470
+ * @since Class available since Release 2.0.0
5471
+ */
5472
+ class ConverterFactory
5473
+ {
5474
+ /**
5475
+ * Get classname of a converter (by id)
5476
+ *
5477
+ * @param string $converterId Id of converter (ie "cwebp")
5478
+ *
5479
+ * @throws ConverterNotFoundException If there is no converter with that id.
5480
+ * @return string Fully qualified class name of converter
5481
+ */
5482
+ public static function converterIdToClassname($converterId)
5483
+ {
5484
+ switch ($converterId) {
5485
+ case 'imagickbinary':
5486
+ $classNameShort = 'ImagickBinary';
5487
+ break;
5488
+ case 'imagemagick':
5489
+ $classNameShort = 'ImageMagick';
5490
+ break;
5491
+ case 'gmagickbinary':
5492
+ $classNameShort = 'GmagickBinary';
5493
+ break;
5494
+ case 'graphicsmagick':
5495
+ $classNameShort = 'GraphicsMagick';
5496
+ break;
5497
+ default:
5498
+ $classNameShort = ucfirst($converterId);
5499
+ }
5500
+ $className = 'WebPConvert\\Convert\\Converters\\' . $classNameShort;
5501
+ if (is_callable([$className, 'convert'])) {
5502
+ return $className;
5503
+ } else {
5504
+ throw new ConverterNotFoundException('There is no converter with id:' . $converterId);
5505
+ }
5506
+ }
5507
+
5508
+ /**
5509
+ * Make a converter instance by class name.
5510
+ *
5511
+ * @param string $converterClassName Fully qualified class name
5512
+ * @param string $source The path to the file to convert
5513
+ * @param string $destination The path to save the converted file to
5514
+ * @param array $options (optional)
5515
+ * @param \WebPConvert\Loggers\BaseLogger $logger (optional)
5516
+ *
5517
+ * @throws ConverterNotFoundException If the specified converter class isn't found
5518
+ * @return AbstractConverter An instance of the specified converter
5519
+ */
5520
+ public static function makeConverterFromClassname(
5521
+ $converterClassName,
5522
+ $source,
5523
+ $destination,
5524
+ $options = [],
5525
+ $logger = null
5526
+ ) {
5527
+ if (!is_callable([$converterClassName, 'convert'])) {
5528
+ throw new ConverterNotFoundException(
5529
+ 'There is no converter with class name:' . $converterClassName . ' (or it is not a converter)'
5530
+ );
5531
+ }
5532
+ //$converter = new $converterClassName($source, $destination, $options, $logger);
5533
+
5534
+ return call_user_func(
5535
+ [$converterClassName, 'createInstance'],
5536
+ $source,
5537
+ $destination,
5538
+ $options,
5539
+ $logger
5540
+ );
5541
+ }
5542
+
5543
+ /**
5544
+ * Make a converter instance by either id or class name.
5545
+ *
5546
+ * @param string $converterIdOrClassName Either a converter ID or a fully qualified class name
5547
+ * @param string $source The path to the file to convert
5548
+ * @param string $destination The path to save the converted file to
5549
+ * @param array $options (optional)
5550
+ * @param \WebPConvert\Loggers\BaseLogger $logger (optional)
5551
+ *
5552
+ * @throws ConverterNotFoundException If the specified converter class isn't found
5553
+ * @return AbstractConverter An instance of the specified converter
5554
+ */
5555
+ public static function makeConverter($converterIdOrClassName, $source, $destination, $options = [], $logger = null)
5556
+ {
5557
+ // We take it that all lowercase means it is an id rather than a class name
5558
+ if (strtolower($converterIdOrClassName) == $converterIdOrClassName) {
5559
+ $converterClassName = self::converterIdToClassname($converterIdOrClassName);
5560
+ } else {
5561
+ $converterClassName = $converterIdOrClassName;
5562
+ }
5563
+
5564
+ return self::makeConverterFromClassname($converterClassName, $source, $destination, $options, $logger);
5565
+ }
5566
+ }
5567
+
5568
+ ?><?php
5569
+
5570
+ namespace WebPConvert\Loggers;
5571
+
5572
+ /**
5573
+ * Base for all logger classes.
5574
+ *
5575
+ * WebPConvert can provide insights into the conversion process by means of accepting a logger which
5576
+ * extends this class.
5577
+ *
5578
+ * @package WebPConvert
5579
+ * @author Bjørn Rosell <it@rosell.dk>
5580
+ * @since Class available since Release 2.0.0
5581
+ */
5582
+ abstract class BaseLogger
5583
+ {
5584
+ /**
5585
+ * Write a message to the log
5586
+ *
5587
+ * @param string $msg message to log
5588
+ * @param string $style style (null | bold | italic)
5589
+ * @return void
5590
+ */
5591
+ abstract public function log($msg, $style = '');
5592
+
5593
+ /**
5594
+ * Add new line to the log
5595
+ * @return void
5596
+ */
5597
+ abstract public function ln();
5598
+
5599
+ /**
5600
+ * Write a line to the log
5601
+ *
5602
+ * @param string $msg message to log
5603
+ * @param string $style style (null | bold | italic)
5604
+ * @return void
5605
+ */
5606
+ public function logLn($msg, $style = '')
5607
+ {
5608
+ $this->log($msg, $style);
5609
+ $this->ln();
5610
+ }
5611
+ }
5612
+
5613
+ ?><?php
5614
+
5615
+ namespace WebPConvert\Loggers;
5616
+
5617
+ use WebPConvert\Loggers\BaseLogger;
5618
+
5619
+ /**
5620
+ * Collect the logging and retrieve it later in HTML or plain text format.
5621
+ *
5622
+ * @package WebPConvert
5623
+ * @author Bjørn Rosell <it@rosell.dk>
5624
+ * @since Class available since Release 2.0.0
5625
+ */
5626
+ class BufferLogger extends BaseLogger
5627
+ {
5628
+ public $entries = array();
5629
+
5630
+ /**
5631
+ * Write a message to the buffer - all entries can later be retrieved with getText() or getHtlm().
5632
+ *
5633
+ * @param string $msg message to log
5634
+ * @param string $style style (null | bold | italic)
5635
+ * @return void
5636
+ */
5637
+ public function log($msg, $style = '')
5638
+ {
5639
+ $this->entries[] = [$msg, $style];
5640
+ }
5641
+
5642
+ /**
5643
+ * Write a new line to the buffer.
5644
+ *
5645
+ * @return void
5646
+ */
5647
+ public function ln()
5648
+ {
5649
+ $this->entries[] = '';
5650
+ }
5651
+
5652
+ /**
5653
+ * Get everything logged - as HTML.
5654
+ *
5655
+ * @return string The log, formatted as HTML.
5656
+ */
5657
+ public function getHtml()
5658
+ {
5659
+ $html = '';
5660
+ foreach ($this->entries as $entry) {
5661
+ if ($entry == '') {
5662
+ $html .= '<br>';
5663
+ } else {
5664
+ list($msg, $style) = $entry;
5665
+ $msg = htmlspecialchars($msg);
5666
+ if ($style == 'bold') {
5667
+ $html .= '<b>' . $msg . '</b>';
5668
+ } elseif ($style == 'italic') {
5669
+ $html .= '<i>' . $msg . '</i>';
5670
+ } else {
5671
+ $html .= $msg;
5672
+ }
5673
+ }
5674
+ }
5675
+ return $html;
5676
+ }
5677
+
5678
+ /**
5679
+ * Get everything logged - as markdown.
5680
+ *
5681
+ * @return string The log, formatted as MarkDown.
5682
+ */
5683
+ public function getMarkDown($newLineChar = "\n\r")
5684
+ {
5685
+ $md = '';
5686
+ foreach ($this->entries as $entry) {
5687
+ if ($entry == '') {
5688
+ $md .= $newLineChar;
5689
+ } else {
5690
+ list($msg, $style) = $entry;
5691
+ if ($style == 'bold') {
5692
+ $md .= '**' . $msg . '** ';
5693
+ } elseif ($style == 'italic') {
5694
+ $md .= '*' . $msg . '* ';
5695
+ } else {
5696
+ $md .= $msg;
5697
+ }
5698
+ }
5699
+ }
5700
+ return $md;
5701
+ }
5702
+
5703
+ /**
5704
+ * Get everything logged - as plain text.
5705
+ *
5706
+ * @param string $newLineChar. The character used for new lines.
5707
+ * @return string The log, formatted as plain text.
5708
+ */
5709
+ public function getText($newLineChar = ' ')
5710
+ {
5711
+ $text = '';
5712
+ foreach ($this->entries as $entry) {
5713
+ if ($entry == '') { // empty string means new line
5714
+ if (substr($text, -2) != '.' . $newLineChar) {
5715
+ $text .= '.' . $newLineChar;
5716
+ }
5717
+ } else {
5718
+ list($msg, $style) = $entry;
5719
+ $text .= $msg;
5720
+ }
5721
+ }
5722
+
5723
+ return $text;
5724
+ }
5725
+ }
5726
+
5727
+ ?><?php
5728
+
5729
+ namespace WebPConvert\Loggers;
5730
+
5731
+ /**
5732
+ * Echo the logs immediately (in HTML)
5733
+ *
5734
+ * @package WebPConvert
5735
+ * @author Bjørn Rosell <it@rosell.dk>
5736
+ * @since Class available since Release 2.0.0
5737
+ */
5738
+ class EchoLogger extends BaseLogger
5739
+ {
5740
+
5741
+ /**
5742
+ * Handle log() by echoing the message.
5743
+ *
5744
+ * @param string $msg message to log
5745
+ * @param string $style style (null | bold | italic)
5746
+ * @return void
5747
+ */
5748
+ public function log($msg, $style = '')
5749
+ {
5750
+ $msg = htmlspecialchars($msg);
5751
+ if ($style == 'bold') {
5752
+ echo '<b>' . $msg . '</b>';
5753
+ } elseif ($style == 'italic') {
5754
+ echo '<i>' . $msg . '</i>';
5755
+ } else {
5756
+ echo $msg;
5757
+ }
5758
+ }
5759
+
5760
+ /**
5761
+ * Handle ln by echoing a <br> tag.
5762
+ *
5763
+ * @return void
5764
+ */
5765
+ public function ln()
5766
+ {
5767
+ echo '<br>';
5768
+ }
5769
+ }
5770
+
5771
+ ?><?php
5772
+ namespace WebPConvert\Serve;
5773
+
5774
+ /**
5775
+ * Add / Set HTTP header.
5776
+ *
5777
+ * This class does nothing more than adding two convenience functions for calling the "header" function.
5778
+ *
5779
+ * @package WebPConvert
5780
+ * @author Bjørn Rosell <it@rosell.dk>
5781
+ * @since Class available since Release 2.0.0
5782
+ */
5783
+ class Header
5784
+ {
5785
+ /**
5786
+ * Convenience function for adding header (append).
5787
+ *
5788
+ * @param string $header The header to add.
5789
+ * @return void
5790
+ */
5791
+ public static function addHeader($header)
5792
+ {
5793
+ header($header, false);
5794
+ }
5795
+
5796
+ /**
5797
+ * Convenience function for replacing header.
5798
+ *
5799
+ * @param string $header The header to set.
5800
+ * @return void
5801
+ */
5802
+ public static function setHeader($header)
5803
+ {
5804
+ header($header, true);
5805
+ }
5806
+
5807
+ /**
5808
+ * Add log header and optionally send it to a logger as well.
5809
+ *
5810
+ * @param string $msg Message to add to "X-WebP-Convert-Log" header
5811
+ * @param \WebPConvert\Loggers\BaseLogger $logger (optional)
5812
+ * @return void
5813
+ */
5814
+ public static function addLogHeader($msg, $logger = null)
5815
+ {
5816
+ self::addHeader('X-WebP-Convert-Log: ' . $msg);
5817
+ if (!is_null($logger)) {
5818
+ $logger->logLn($msg);
5819
+ }
5820
+ }
5821
+ }
5822
+
5823
+ ?><?php
5824
+ namespace WebPConvert\Serve;
5825
+
5826
+ use WebPConvert\WebPConvert;
5827
+ use WebPConvert\Loggers\EchoLogger;
5828
+
5829
+ /**
5830
+ * Class for generating a HTML report of converting an image.
5831
+ *
5832
+ * @package WebPConvert
5833
+ * @author Bjørn Rosell <it@rosell.dk>
5834
+ * @since Class available since Release 2.0.0
5835
+ */
5836
+ class Report
5837
+ {
5838
+ public static function convertAndReport($source, $destination, $options)
5839
+ {
5840
+ ?>
5841
+ <html>
5842
+ <head>
5843
+ <style>td {vertical-align: top} table {color: #666}</style>
5844
+ <script>
5845
+ function showOptions(elToHide) {
5846
+ document.getElementById('options').style.display='block';
5847
+ elToHide.style.display='none';
5848
+ }
5849
+ </script>
5850
+ </head>
5851
+ <body>
5852
+ <table>
5853
+ <tr><td><i>source:</i></td><td><?php echo $source ?></td></tr>
5854
+ <tr><td><i>destination:</i></td><td><?php echo $destination ?><td></tr>
5855
+ </table>
5856
+ <br>
5857
+ <?php
5858
+ try {
5859
+ $echoLogger = new EchoLogger();
5860
+ $options['log-call-arguments'] = true;
5861
+ WebPConvert::convert($source, $destination, $options, $echoLogger);
5862
+ } catch (\Exception $e) {
5863
+ $success = false;
5864
+
5865
+ $msg = $e->getMessage();
5866
+
5867
+ echo '<b>' . $msg . '</b>';
5868
+
5869
+ //echo '<p>Rethrowing exception for your convenience</p>';
5870
+ //throw ($e);
5871
+ }
5872
+ ?>
5873
+ </body>
5874
+ </html>
5875
+ <?php
5876
+ }
5877
+ }
5878
+
5879
+ ?><?php
5880
+ namespace WebPConvert\Serve;
5881
+
5882
+ use ImageMimeTypeGuesser\ImageMimeTypeGuesser;
5883
+
5884
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
5885
+ use WebPConvert\Serve\Exceptions\ServeFailedException;
5886
+ use WebPConvert\Serve\Header;
5887
+ use WebPConvert\Serve\Report;
5888
+ use WebPConvert\Serve\ServeFile;
5889
+ use WebPConvert\Options\ArrayOption;
5890
+ use WebPConvert\Options\BooleanOption;
5891
+ use WebPConvert\Options\Options;
5892
+ use WebPConvert\Options\SensitiveArrayOption;
5893
+ use WebPConvert\Options\Exceptions\InvalidOptionTypeException;
5894
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
5895
+ use WebPConvert\WebPConvert;
5896
+
5897
+ /**
5898
+ * Serve a converted webp image.
5899
+ *
5900
+ * The webp that is served might end up being one of these:
5901
+ * - a fresh convertion
5902
+ * - the destionation
5903
+ * - the original
5904
+ *
5905
+ * Exactly which is a decision based upon options, file sizes and file modification dates
5906
+ * (see the serve method of this class for details)
5907
+ *
5908
+ * @package WebPConvert
5909
+ * @author Bjørn Rosell <it@rosell.dk>
5910
+ * @since Class available since Release 2.0.0
5911
+ */
5912
+ class ServeConvertedWebP
5913
+ {
5914
+
5915
+ /**
5916
+ * Process options.
5917
+ *
5918
+ * @throws \WebPConvert\Options\Exceptions\InvalidOptionTypeException If the type of an option is invalid
5919
+ * @throws \WebPConvert\Options\Exceptions\InvalidOptionValueException If the value of an option is invalid
5920
+ * @param array $options
5921
+ */
5922
+ private static function processOptions($options)
5923
+ {
5924
+ $options2 = new Options();
5925
+ $options2->addOptions(
5926
+ new BooleanOption('reconvert', false),
5927
+ new BooleanOption('serve-original', false),
5928
+ new BooleanOption('show-report', false),
5929
+ new BooleanOption('suppress-warnings', true),
5930
+ new ArrayOption('serve-image', []),
5931
+ new SensitiveArrayOption('convert', [])
5932
+ );
5933
+ foreach ($options as $optionId => $optionValue) {
5934
+ $options2->setOrCreateOption($optionId, $optionValue);
5935
+ }
5936
+ $options2->check();
5937
+ return $options2->getOptions();
5938
+ }
5939
+
5940
+ /**
5941
+ * Serve original file (source).
5942
+ *
5943
+ * @param string $source path to source file
5944
+ * @param array $serveImageOptions (optional) options for serving an image
5945
+ * Supported options:
5946
+ * - All options supported by ServeFile::serve()
5947
+ * @throws ServeFailedException if source is not an image or mime type cannot be determined
5948
+ * @return void
5949
+ */
5950
+ public static function serveOriginal($source, $serveImageOptions = [])
5951
+ {
5952
+ $contentType = ImageMimeTypeGuesser::lenientGuess($source);
5953
+ if (is_null($contentType)) {
5954
+ throw new ServeFailedException('Rejecting to serve original (mime type cannot be determined)');
5955
+ } elseif ($contentType === false) {
5956
+ throw new ServeFailedException('Rejecting to serve original (it is not an image)');
5957
+ } else {
5958
+ ServeFile::serve($source, $contentType, $serveImageOptions);
5959
+ }
5960
+ }
5961
+
5962
+ /**
5963
+ * Serve destination file.
5964
+ *
5965
+ * @param string $destination path to destination file
5966
+ * @param array $serveImageOptions (optional) options for serving (such as which headers to add)
5967
+ * Supported options:
5968
+ * - All options supported by ServeFile::serve()
5969
+ * @return void
5970
+ */
5971
+ public static function serveDestination($destination, $serveImageOptions = [])
5972
+ {
5973
+ ServeFile::serve($destination, 'image/webp', $serveImageOptions);
5974
+ }
5975
+
5976
+
5977
+ public static function warningHandler()
5978
+ {
5979
+ // do nothing! - as we do not return anything, the warning is suppressed
5980
+ }
5981
+
5982
+ /**
5983
+ * Serve converted webp.
5984
+ *
5985
+ * Serve a converted webp. If a file already exists at the destination, that is served (unless it is
5986
+ * older than the source - in that case a fresh conversion will be made, or the file at the destination
5987
+ * is larger than the source - in that case the source is served). Some options may alter this logic.
5988
+ * In case no file exists at the destination, a fresh conversion is made and served.
5989
+ *
5990
+ * @param string $source path to source file
5991
+ * @param string $destination path to destination
5992
+ * @param array $options (optional) options for serving/converting
5993
+ * Supported options:
5994
+ * 'show-report' => (boolean) If true, the decision will always be 'report'
5995
+ * 'serve-original' => (boolean) If true, the decision will be 'source' (unless above option is set)
5996
+ * 'reconvert ' => (boolean) If true, the decision will be 'fresh-conversion' (unless one of the
5997
+ * above options is set)
5998
+ * - All options supported by WebPConvert::convert()
5999
+ * - All options supported by ServeFile::serve()
6000
+ * @param \WebPConvert\Loggers\BaseLogger $serveLogger (optional)
6001
+ * @param \WebPConvert\Loggers\BaseLogger $convertLogger (optional)
6002
+ *
6003
+ * @throws \WebPConvert\Exceptions\WebPConvertException If something went wrong.
6004
+ * @return void
6005
+ */
6006
+ public static function serve($source, $destination, $options = [], $serveLogger = null, $convertLogger = null)
6007
+ {
6008
+
6009
+ if (empty($source)) {
6010
+ throw new ServeFailedException('Source argument missing');
6011
+ }
6012
+ if (empty($destination)) {
6013
+ throw new ServeFailedException('Destination argument missing');
6014
+ }
6015
+ if (@!file_exists($source)) {
6016
+ throw new ServeFailedException('Source file was not found');
6017
+ }
6018
+
6019
+ $options = self::processOptions($options);
6020
+
6021
+ if ($options['suppress-warnings']) {
6022
+ set_error_handler(
6023
+ array('\\WebPConvert\\Serve\\ServeConvertedWebP', "warningHandler"),
6024
+ E_WARNING | E_USER_WARNING | E_NOTICE | E_USER_NOTICE
6025
+ );
6026
+ }
6027
+
6028
+
6029
+ //$options = array_merge(self::$defaultOptions, $options);
6030
+
6031
+ // Step 1: Is there a file at the destination? If not, trigger conversion
6032
+ // However 1: if "show-report" option is set, serve the report instead
6033
+ // However 2: "reconvert" option should also trigger conversion
6034
+ if ($options['show-report']) {
6035
+ Header::addLogHeader('Showing report', $serveLogger);
6036
+ Report::convertAndReport($source, $destination, $options);
6037
+ return;
6038
+ }
6039
+
6040
+ if (!@file_exists($destination)) {
6041
+ Header::addLogHeader('Converting (there were no file at destination)', $serveLogger);
6042
+ WebPConvert::convert($source, $destination, $options['convert'], $convertLogger);
6043
+ } elseif ($options['reconvert']) {
6044
+ Header::addLogHeader('Converting (told to reconvert)', $serveLogger);
6045
+ WebPConvert::convert($source, $destination, $options['convert'], $convertLogger);
6046
+ } else {
6047
+ // Step 2: Is the destination older than the source?
6048
+ // If yes, trigger conversion (deleting destination is implicit)
6049
+ $timestampSource = @filemtime($source);
6050
+ $timestampDestination = @filemtime($destination);
6051
+ if (($timestampSource !== false) &&
6052
+ ($timestampDestination !== false) &&
6053
+ ($timestampSource > $timestampDestination)) {
6054
+ Header::addLogHeader('Converting (destination was older than the source)', $serveLogger);
6055
+ WebPConvert::convert($source, $destination, $options['convert'], $convertLogger);
6056
+ }
6057
+ }
6058
+
6059
+ // Step 3: Serve the smallest file (destination or source)
6060
+ // However, first check if 'serve-original' is set
6061
+ if ($options['serve-original']) {
6062
+ Header::addLogHeader('Serving original (told to)', $serveLogger);
6063
+ self::serveOriginal($source, $options['serve-image']);
6064
+ }
6065
+
6066
+ $filesizeDestination = @filesize($destination);
6067
+ $filesizeSource = @filesize($source);
6068
+ if (($filesizeSource !== false) &&
6069
+ ($filesizeDestination !== false) &&
6070
+ ($filesizeDestination > $filesizeSource)) {
6071
+ Header::addLogHeader('Serving original (it is smaller)', $serveLogger);
6072
+ self::serveOriginal($source, $options['serve-image']);
6073
+ }
6074
+
6075
+ Header::addLogHeader('Serving converted file', $serveLogger);
6076
+ self::serveDestination($destination, $options['serve-image']);
6077
+ }
6078
+ }
6079
+
6080
+ ?><?php
6081
+ namespace WebPConvert\Serve;
6082
+
6083
+ use WebPConvert\Options\Options;
6084
+ use WebPConvert\Options\StringOption;
6085
+ use WebPConvert\Serve\Header;
6086
+ use WebPConvert\Serve\Report;
6087
+ use WebPConvert\Serve\ServeConvertedWeb;
6088
+ use WebPConvert\Serve\Exceptions\ServeFailedException;
6089
+ use WebPConvert\Exceptions\WebPConvertException;
6090
+
6091
+ /**
6092
+ * Serve a converted webp image and handle errors.
6093
+ *
6094
+ * @package WebPConvert
6095
+ * @author Bjørn Rosell <it@rosell.dk>
6096
+ * @since Class available since Release 2.0.0
6097
+ */
6098
+ class ServeConvertedWebPWithErrorHandling
6099
+ {
6100
+
6101
+ /**
6102
+ * Process options.
6103
+ *
6104
+ * @throws \WebPConvert\Options\Exceptions\InvalidOptionTypeException If the type of an option is invalid
6105
+ * @throws \WebPConvert\Options\Exceptions\InvalidOptionValueException If the value of an option is invalid
6106
+ * @param array $options
6107
+ */
6108
+ private static function processOptions($options)
6109
+ {
6110
+ $options2 = new Options();
6111
+ $options2->addOptions(
6112
+ new StringOption('fail', 'original', ['original', '404', 'throw', 'report']),
6113
+ new StringOption('fail-when-fail-fails', 'throw', ['original', '404', 'throw', 'report'])
6114
+ );
6115
+ foreach ($options as $optionId => $optionValue) {
6116
+ $options2->setOrCreateOption($optionId, $optionValue);
6117
+ }
6118
+ $options2->check();
6119
+ return $options2->getOptions();
6120
+ }
6121
+
6122
+ /**
6123
+ * Add headers for preventing caching.
6124
+ *
6125
+ * @return void
6126
+ */
6127
+ private static function addHeadersPreventingCaching()
6128
+ {
6129
+ Header::setHeader("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
6130
+ Header::addHeader("Cache-Control: post-check=0, pre-check=0");
6131
+ Header::setHeader("Pragma: no-cache");
6132
+ }
6133
+
6134
+ /**
6135
+ * Perform fail action.
6136
+ *
6137
+ * @param string $fail Action to perform (original | 404 | report)
6138
+ * @param string $failIfFailFails Action to perform if $fail action fails
6139
+ * @param string $source path to source file
6140
+ * @param string $destination path to destination
6141
+ * @param array $options (optional) options for serving/converting
6142
+ * @param \Exception $e exception that was thrown when trying to serve
6143
+ * @param string $serveClass (optional) Full class name to a class that has a serveOriginal() method
6144
+ * @return void
6145
+ */
6146
+ public static function performFailAction($fail, $failIfFailFails, $source, $destination, $options, $e, $serveClass)
6147
+ {
6148
+ self::addHeadersPreventingCaching();
6149
+
6150
+ //Header::addLogHeader('Failure');
6151
+ Header::addLogHeader('Performing fail action: ' . $fail);
6152
+
6153
+ switch ($fail) {
6154
+ case 'original':
6155
+ try {
6156
+ //ServeConvertedWebP::serveOriginal($source, $options);
6157
+ call_user_func($serveClass . '::serveOriginal', $source, $options);
6158
+ } catch (\Exception $e) {
6159
+ self::performFailAction($failIfFailFails, '404', $source, $destination, $options, $e, $serveClass);
6160
+ }
6161
+ break;
6162
+
6163
+ case '404':
6164
+ $protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : 'HTTP/1.0';
6165
+ Header::setHeader($protocol . " 404 Not Found");
6166
+ break;
6167
+
6168
+ case 'report':
6169
+ $options['show-report'] = true;
6170
+ Report::convertAndReport($source, $destination, $options);
6171
+ break;
6172
+
6173
+ case 'throw':
6174
+ throw $e;
6175
+ break;
6176
+
6177
+ case 'report-as-image':
6178
+ // TODO: Implement or discard ?
6179
+ break;
6180
+ }
6181
+ }
6182
+
6183
+ /**
6184
+ * Serve webp image and handle errors as specified in the 'fail' option.
6185
+ *
6186
+ * This method basically wraps ServeConvertedWebP:serve in order to provide exception handling.
6187
+ * The error handling is set with the 'fail' option and can be either '404', 'original' or 'report'.
6188
+ * If set to '404', errors results in 404 Not Found headers being issued. If set to 'original', an
6189
+ * error results in the original being served.
6190
+ * Look up the ServeConvertedWebP:serve method to learn more.
6191
+ *
6192
+ * @param string $source path to source file
6193
+ * @param string $destination path to destination
6194
+ * @param array $options (optional) options for serving/converting
6195
+ * Supported options:
6196
+ * - 'fail' => (string) Action to take on failure (404 | original | report | throw).
6197
+ * "404" or "throw" is recommended for development and "original" is recommended for production.
6198
+ * Default: 'original'.
6199
+ * - 'fail-when-fail-fails' => (string) Action to take if fail action also fails. Default: '404'.
6200
+ * - All options supported by WebPConvert::convert()
6201
+ * - All options supported by ServeFile::serve()
6202
+ * - All options supported by DecideWhatToServe::decide)
6203
+ * @param \WebPConvert\Loggers\BaseLogger $serveLogger (optional)
6204
+ * @param \WebPConvert\Loggers\BaseLogger $convertLogger (optional)
6205
+ * @param string $serveClass (optional) Full class name to a class that has a serve() method and a
6206
+ * serveOriginal() method
6207
+ * @return void
6208
+ */
6209
+ public static function serve(
6210
+ $source,
6211
+ $destination,
6212
+ $options = [],
6213
+ $serveLogger = null,
6214
+ $convertLogger = null,
6215
+ $serveClass = '\\WebPConvert\\Serve\\ServeConvertedWebP'
6216
+ ) {
6217
+ $options = self::processOptions($options);
6218
+
6219
+ try {
6220
+ //ServeConvertedWebP::serve($source, $destination, $options, $serveLogger);
6221
+ call_user_func($serveClass . '::serve', $source, $destination, $options, $serveLogger, $convertLogger);
6222
+ } catch (\Exception $e) {
6223
+ if ($e instanceof \WebPConvert\Exceptions\WebPConvertException) {
6224
+ Header::addLogHeader($e->getShortMessage(), $serveLogger);
6225
+ }
6226
+
6227
+ self::performFailAction(
6228
+ $options['fail'],
6229
+ $options['fail-when-fail-fails'],
6230
+ $source,
6231
+ $destination,
6232
+ $options,
6233
+ $e,
6234
+ $serveClass
6235
+ );
6236
+ }
6237
+ }
6238
+ }
6239
+
6240
+ ?><?php
6241
+ namespace WebPConvert\Serve;
6242
+
6243
+ //use WebPConvert\Serve\Report;
6244
+ use WebPConvert\Options\ArrayOption;
6245
+ use WebPConvert\Options\BooleanOption;
6246
+ use WebPConvert\Options\Options;
6247
+ use WebPConvert\Options\StringOption;
6248
+ use WebPConvert\Serve\Header;
6249
+ use WebPConvert\Serve\Exceptions\ServeFailedException;
6250
+
6251
+ /**
6252
+ * Serve a file (send to standard output)
6253
+ *
6254
+ * @package WebPConvert
6255
+ * @author Bjørn Rosell <it@rosell.dk>
6256
+ * @since Class available since Release 2.0.0
6257
+ */
6258
+ class ServeFile
6259
+ {
6260
+
6261
+ /**
6262
+ * Process options.
6263
+ *
6264
+ * @throws \WebPConvert\Options\Exceptions\InvalidOptionTypeException If the type of an option is invalid
6265
+ * @throws \WebPConvert\Options\Exceptions\InvalidOptionValueException If the value of an option is invalid
6266
+ * @param array $options
6267
+ */
6268
+ private static function processOptions($options)
6269
+ {
6270
+ $options2 = new Options();
6271
+ $options2->addOptions(
6272
+ new ArrayOption('headers', []),
6273
+ new StringOption('cache-control-header', 'public, max-age=31536000')
6274
+ );
6275
+ foreach ($options as $optionId => $optionValue) {
6276
+ $options2->setOrCreateOption($optionId, $optionValue);
6277
+ }
6278
+ $options2->check();
6279
+ $options = $options2->getOptions();
6280
+
6281
+ // headers option
6282
+ // --------------
6283
+
6284
+ $headerOptions = new Options();
6285
+ $headerOptions->addOptions(
6286
+ new BooleanOption('cache-control', false),
6287
+ new BooleanOption('content-length', true),
6288
+ new BooleanOption('content-type', true),
6289
+ new BooleanOption('expires', false),
6290
+ new BooleanOption('last-modified', true),
6291
+ new BooleanOption('vary-accept', false)
6292
+ );
6293
+ foreach ($options['headers'] as $optionId => $optionValue) {
6294
+ $headerOptions->setOrCreateOption($optionId, $optionValue);
6295
+ }
6296
+ $options['headers'] = $headerOptions->getOptions();
6297
+ return $options;
6298
+ }
6299
+
6300
+ /**
6301
+ * Serve existing file.
6302
+ *
6303
+ * @param string $filename File to serve (absolute path)
6304
+ * @param string $contentType Content-type (used to set header).
6305
+ * Only used when the "set-content-type-header" option is set.
6306
+ * Set to ie "image/jpeg" for serving jpeg file.
6307
+ * @param array $options Array of named options (optional).
6308
+ * Supported options:
6309
+ * 'add-vary-accept-header' => (boolean) Whether to add *Vary: Accept* header or not. Default: true.
6310
+ * 'set-content-type-header' => (boolean) Whether to set *Content-Type* header or not. Default: true.
6311
+ * 'set-last-modified-header' => (boolean) Whether to set *Last-Modified* header or not. Default: true.
6312
+ * 'set-cache-control-header' => (boolean) Whether to set *Cache-Control* header or not. Default: true.
6313
+ * 'cache-control-header' => string Cache control header. Default: "public, max-age=86400"
6314
+ *
6315
+ * @throws ServeFailedException if serving failed
6316
+ * @return void
6317
+ */
6318
+ public static function serve($filename, $contentType, $options = [])
6319
+ {
6320
+ if (!file_exists($filename)) {
6321
+ Header::addHeader('X-WebP-Convert-Error: Could not read file');
6322
+ throw new ServeFailedException('Could not read file');
6323
+ }
6324
+
6325
+ $options = self::processOptions($options);
6326
+
6327
+ if ($options['headers']['last-modified']) {
6328
+ Header::setHeader("Last-Modified: " . gmdate("D, d M Y H:i:s", @filemtime($filename)) ." GMT");
6329
+ }
6330
+
6331
+ if ($options['headers']['content-type']) {
6332
+ Header::setHeader('Content-Type: ' . $contentType);
6333
+ }
6334
+
6335
+ if ($options['headers']['vary-accept']) {
6336
+ Header::addHeader('Vary: Accept');
6337
+ }
6338
+
6339
+ if (!empty($options['cache-control-header'])) {
6340
+ if ($options['headers']['cache-control']) {
6341
+ Header::setHeader('Cache-Control: ' . $options['cache-control-header']);
6342
+ }
6343
+ if ($options['headers']['expires']) {
6344
+ // Add exprires header too (#126)
6345
+ // Check string for something like this: max-age:86400
6346
+ if (preg_match('#max-age\\s*=\\s*(\\d*)#', $options['cache-control-header'], $matches)) {
6347
+ $seconds = $matches[1];
6348
+ Header::setHeader('Expires: '. gmdate('D, d M Y H:i:s \G\M\T', time() + intval($seconds)));
6349
+ }
6350
+ }
6351
+ }
6352
+
6353
+ if ($options['headers']['content-length']) {
6354
+ Header::setHeader('Content-Length: ' . filesize($filename));
6355
+ }
6356
+
6357
+ if (@readfile($filename) === false) {
6358
+ Header::addHeader('X-WebP-Convert-Error: Could not read file');
6359
+ throw new ServeFailedException('Could not read file');
6360
+ }
6361
+ }
6362
+ }
6363
+
6364
+ ?><?php
6365
+
6366
+ namespace WebPConvert\Serve\Exceptions;
6367
+
6368
+ use WebPConvert\Exceptions\WebPConvertException;
6369
+
6370
+ class ServeFailedException extends WebPConvertException
6371
+ {
6372
+ public $description = 'Failed serving';
6373
+ }
6374
+
6375
+ ?><?php
6376
+
6377
+ namespace ImageMimeTypeGuesser\Detectors;
6378
+
6379
+ use ImageMimeTypeGuesser\Detectors\AbstractDetector;
6380
+
6381
+ abstract class AbstractDetector
6382
+ {
6383
+ /**
6384
+ * Try to detect mime type of image
6385
+ *
6386
+ * Returns:
6387
+ * - mime type (string) (if it is in fact an image, and type could be determined)
6388
+ * - false (if it is not an image type that the server knowns about)
6389
+ * - null (if nothing can be determined)
6390
+ *
6391
+ * @param string $filePath The path to the file
6392
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
6393
+ * false (if it is not an image type that the server knowns about)
6394
+ * or null (if nothing can be determined)
6395
+ */
6396
+ abstract protected function doDetect($filePath);
6397
+
6398
+ /**
6399
+ * Create an instance of this class
6400
+ *
6401
+ * @param string $filePath The path to the file
6402
+ * @return static
6403
+ */
6404
+ public static function createInstance()
6405
+ {
6406
+ return new static();
6407
+ }
6408
+
6409
+ /**
6410
+ * Detect mime type of file (for images only)
6411
+ *
6412
+ * Returns:
6413
+ * - mime type (string) (if it is in fact an image, and type could be determined)
6414
+ * - false (if it is not an image type that the server knowns about)
6415
+ * - null (if nothing can be determined)
6416
+ *
6417
+ * @param string $filePath The path to the file
6418
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
6419
+ * false (if it is not an image type that the server knowns about)
6420
+ * or null (if nothing can be determined)
6421
+ */
6422
+ public static function detect($filePath)
6423
+ {
6424
+ if (!@file_exists($filePath)) {
6425
+ return false;
6426
+ }
6427
+ return self::createInstance()->doDetect($filePath);
6428
+ }
6429
+ }
6430
+
6431
+ ?><?php
6432
+
6433
+ /**
6434
+ * ImageMimeTypeGuesser - Detect / guess mime type of an image
6435
+ *
6436
+ * @link https://github.com/rosell-dk/image-mime-type-guesser
6437
+ * @license MIT
6438
+ */
6439
+
6440
+ namespace ImageMimeTypeGuesser;
6441
+
6442
+ class GuessFromExtension
6443
+ {
6444
+
6445
+
6446
+ /**
6447
+ * Make a wild guess based on file extension.
6448
+ *
6449
+ * - and I mean wild!
6450
+ *
6451
+ * Only most popular image types are recognized.
6452
+ * Many are not. See this list: https://www.iana.org/assignments/media-types/media-types.xhtml
6453
+ * - and the constants here: https://secure.php.net/manual/en/function.exif-imagetype.php
6454
+ *
6455
+ * If no mapping found, nothing is returned
6456
+ *
6457
+ * TODO: jp2, jpx, ...
6458
+ * Returns:
6459
+ * - mimetype (if file extension could be mapped to an image type),
6460
+ * - false (if file extension could be mapped to a type known not to be an image type)
6461
+ * - null (if file extension could not be mapped to any mime type, using our little list)
6462
+ *
6463
+ * @param string $filePath The path to the file
6464
+ * @return string|false|null mimetype (if file extension could be mapped to an image type),
6465
+ * false (if file extension could be mapped to a type known not to be an image type)
6466
+ * or null (if file extension could not be mapped to any mime type, using our little list)
6467
+ */
6468
+ public static function guess($filePath)
6469
+ {
6470
+ if (!@file_exists($filePath)) {
6471
+ return false;
6472
+ }
6473
+ /*
6474
+ Not using pathinfo, as it is locale aware, and I'm not sure if that could lead to problems
6475
+
6476
+ if (!function_exists('pathinfo')) {
6477
+ // This is really a just in case! - We do not expect this to happen.
6478
+ // - in fact we have a test case asserting that this does not happen.
6479
+ return null;
6480
+ //
6481
+ $fileExtension = pathinfo($filePath, PATHINFO_EXTENSION);
6482
+ $fileExtension = strtolower($fileExtension);
6483
+ }*/
6484
+
6485
+ $result = preg_match('#\\.([^.]*)$#', $filePath, $matches);
6486
+ if ($result !== 1) {
6487
+ return null;
6488
+ }
6489
+ $fileExtension = $matches[1];
6490
+
6491
+ // Trivial image mime types
6492
+ if (in_array($fileExtension, ['bmp', 'gif', 'jpeg', 'png', 'tiff', 'webp'])) {
6493
+ return 'image/' . $fileExtension;
6494
+ }
6495
+
6496
+ // Common extensions that are definitely not images
6497
+ if (in_array($fileExtension, ['txt', 'doc', 'zip', 'gz', 'exe'])) {
6498
+ return false;
6499
+ }
6500
+
6501
+ // Non-trivial image mime types
6502
+ switch ($fileExtension) {
6503
+ case 'ico':
6504
+ return 'image/vnd.microsoft.icon'; // or perhaps 'x-icon' ?
6505
+
6506
+ case 'jpg':
6507
+ return 'image/jpeg';
6508
+
6509
+ case 'svg':
6510
+ return 'image/svg+xml';
6511
+
6512
+ case 'tif':
6513
+ return 'image/tiff';
6514
+ }
6515
+
6516
+ // We do not know this extension, return null
6517
+ return null;
6518
+ }
6519
+
6520
+ }
6521
+
6522
+ ?><?php
6523
+
6524
+ /**
6525
+ * ImageMimeTypeGuesser - Detect / guess mime type of an image
6526
+ *
6527
+ * The library is born out of a discussion here:
6528
+ * https://github.com/rosell-dk/webp-convert/issues/98
6529
+ *
6530
+ * @link https://github.com/rosell-dk/image-mime-type-guesser
6531
+ * @license MIT
6532
+ */
6533
+
6534
+ namespace ImageMimeTypeGuesser;
6535
+
6536
+ use \ImageMimeTypeGuesser\Detectors\Stack;
6537
+
6538
+ class ImageMimeTypeGuesser
6539
+ {
6540
+
6541
+
6542
+ /**
6543
+ * Try to detect mime type of image using all available detectors (the "stack" detector).
6544
+ *
6545
+ * Returns:
6546
+ * - mime type (string) (if it is in fact an image, and type could be determined)
6547
+ * - false (if it is not an image type that the server knowns about)
6548
+ * - null (if nothing can be determined)
6549
+ *
6550
+ * @param string $filePath The path to the file
6551
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
6552
+ * false (if it is not an image type that the server knowns about)
6553
+ * or null (if nothing can be determined)
6554
+ */
6555
+ public static function detect($filePath)
6556
+ {
6557
+ return Stack::detect($filePath);
6558
+ }
6559
+
6560
+ /**
6561
+ * Try to detect mime type of image. If that fails, make a guess based on the file extension.
6562
+ *
6563
+ * Try to detect mime type of image using "stack" detector (all available methods, until one succeeds)
6564
+ * If that fails (null), fall back to wild west guessing based solely on file extension.
6565
+ *
6566
+ * Returns:
6567
+ * - mime type (string) (if it is an image, and type could be determined / mapped from file extension))
6568
+ * - false (if it is not an image type that the server knowns about)
6569
+ * - null (if nothing can be determined)
6570
+ *
6571
+ * @param string $filePath The path to the file
6572
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
6573
+ * false (if it is not an image type that the server knowns about)
6574
+ * or null (if nothing can be determined)
6575
+ */
6576
+ public static function guess($filePath)
6577
+ {
6578
+ $detectionResult = self::detect($filePath);
6579
+ if (!is_null($detectionResult)) {
6580
+ return $detectionResult;
6581
+ }
6582
+
6583
+ // fall back to the wild west method
6584
+ return GuessFromExtension::guess($filePath);
6585
+ }
6586
+
6587
+ /**
6588
+ * Try to detect mime type of image. If that fails, make a guess based on the file extension.
6589
+ *
6590
+ * Try to detect mime type of image using "stack" detector (all available methods, until one succeeds)
6591
+ * If that fails (false or null), fall back to wild west guessing based solely on file extension.
6592
+ *
6593
+ * Returns:
6594
+ * - mime type (string) (if it is an image, and type could be determined / mapped from file extension)
6595
+ * - false (if it is not an image type that the server knowns about)
6596
+ * - null (if nothing can be determined)
6597
+ *
6598
+ * @param string $filePath The path to the file
6599
+ * @return string|false|null mimetype (if it is an image, and type could be determined / guessed),
6600
+ * false (if it is not an image type that the server knowns about)
6601
+ * or null (if nothing can be determined)
6602
+ */
6603
+ public static function lenientGuess($filePath)
6604
+ {
6605
+ $detectResult = self::detect($filePath);
6606
+ if ($detectResult === false) {
6607
+ // The server does not recognize this image type.
6608
+ // - but perhaps it is because it does not know about this image type.
6609
+ // - so we turn to mapping the file extension
6610
+ return GuessFromExtension::guess($filePath);
6611
+ } elseif (is_null($detectResult)) {
6612
+ // the mime type could not be determined
6613
+ // perhaps we also in this case want to turn to mapping the file extension
6614
+ return GuessFromExtension::guess($filePath);
6615
+ }
6616
+ return $detectResult;
6617
+ }
6618
+
6619
+
6620
+ /**
6621
+ * Check if the *detected* mime type is in a list of accepted mime types.
6622
+ *
6623
+ * @param string $filePath The path to the file
6624
+ * @param string[] $mimeTypes Mime types to accept
6625
+ * @return bool Whether the detected mime type is in the $mimeTypes array or not
6626
+ */
6627
+ public static function detectIsIn($filePath, $mimeTypes)
6628
+ {
6629
+ return in_array(self::detect($filePath), $mimeTypes);
6630
+ }
6631
+
6632
+ /**
6633
+ * Check if the *guessed* mime type is in a list of accepted mime types.
6634
+ *
6635
+ * @param string $filePath The path to the file
6636
+ * @param string[] $mimeTypes Mime types to accept
6637
+ * @return bool Whether the detected / guessed mime type is in the $mimeTypes array or not
6638
+ */
6639
+ public static function guessIsIn($filePath, $mimeTypes)
6640
+ {
6641
+ return in_array(self::guess($filePath), $mimeTypes);
6642
+ }
6643
+
6644
+ /**
6645
+ * Check if the *leniently guessed* mime type is in a list of accepted mime types.
6646
+ *
6647
+ * @param string $filePath The path to the file
6648
+ * @param string[] $mimeTypes Mime types to accept
6649
+ * @return bool Whether the detected / leniently guessed mime type is in the $mimeTypes array or not
6650
+ */
6651
+ public static function lenientGuessIsIn($filePath, $mimeTypes)
6652
+ {
6653
+ return in_array(self::lenientGuess($filePath), $mimeTypes);
6654
+ }
6655
+ }
6656
+
6657
+ ?><?php
6658
+
6659
+ namespace ImageMimeTypeGuesser\Detectors;
6660
+
6661
+ use \ImageMimeTypeGuesser\Detectors\AbstractDetector;
6662
+
6663
+ class ExifImageType extends AbstractDetector
6664
+ {
6665
+
6666
+ /**
6667
+ * Try to detect mime type of image using *exif_imagetype*.
6668
+ *
6669
+ * Returns:
6670
+ * - mime type (string) (if it is in fact an image, and type could be determined)
6671
+ * - false (if it is not an image type that the server knowns about)
6672
+ * - null (if nothing can be determined)
6673
+ *
6674
+ * @param string $filePath The path to the file
6675
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
6676
+ * false (if it is not an image type that the server knowns about)
6677
+ * or null (if nothing can be determined)
6678
+ */
6679
+ protected function doDetect($filePath)
6680
+ {
6681
+ // exif_imagetype is fast, however not available on all systems,
6682
+ // It may return false. In that case we can rely on that the file is not an image (and return false)
6683
+ if (function_exists('exif_imagetype')) {
6684
+ try {
6685
+ $imageType = exif_imagetype($filePath);
6686
+ return ($imageType ? image_type_to_mime_type($imageType) : false);
6687
+ } catch (\Exception $e) {
6688
+ // Might for example get "Read error!"
6689
+ // well well, don't let this stop us
6690
+ //echo $e->getMessage();
6691
+ // throw($e);
6692
+ }
6693
+ }
6694
+ return null;
6695
+ }
6696
+ }
6697
+
6698
+ ?><?php
6699
+
6700
+ namespace ImageMimeTypeGuesser\Detectors;
6701
+
6702
+ class FInfo extends AbstractDetector
6703
+ {
6704
+
6705
+ /**
6706
+ * Try to detect mime type of image using *finfo* class.
6707
+ *
6708
+ * Returns:
6709
+ * - mime type (string) (if it is in fact an image, and type could be determined)
6710
+ * - false (if it is not an image type that the server knowns about)
6711
+ * - null (if nothing can be determined)
6712
+ *
6713
+ * @param string $filePath The path to the file
6714
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
6715
+ * false (if it is not an image type that the server knowns about)
6716
+ * or null (if nothing can be determined)
6717
+ */
6718
+ protected function doDetect($filePath)
6719
+ {
6720
+
6721
+ if (class_exists('finfo')) {
6722
+ // phpcs:ignore PHPCompatibility.PHP.NewClasses.finfoFound
6723
+ $finfo = new \finfo(FILEINFO_MIME);
6724
+ $mime = explode('; ', $finfo->file($filePath));
6725
+ $result = $mime[0];
6726
+
6727
+ if (strpos($result, 'image/') === 0) {
6728
+ return $result;
6729
+ } else {
6730
+ return false;
6731
+ }
6732
+ }
6733
+ return null;
6734
+ }
6735
+ }
6736
+
6737
+ ?><?php
6738
+
6739
+ namespace ImageMimeTypeGuesser\Detectors;
6740
+
6741
+ class GetImageSize extends AbstractDetector
6742
+ {
6743
+
6744
+ /**
6745
+ * Try to detect mime type of image using *getimagesize()*.
6746
+ *
6747
+ * Returns:
6748
+ * - mime type (string) (if it is in fact an image, and type could be determined)
6749
+ * - false (if it is not an image type that the server knowns about)
6750
+ * - null (if nothing can be determined)
6751
+ *
6752
+ * @param string $filePath The path to the file
6753
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
6754
+ * false (if it is not an image type that the server knowns about)
6755
+ * or null (if nothing can be determined)
6756
+ */
6757
+ protected function doDetect($filePath)
6758
+ {
6759
+ // getimagesize is slower than exif_imagetype
6760
+ // It may not return "mime". In that case we can rely on that the file is not an image (and return false)
6761
+ if (function_exists('getimagesize')) {
6762
+ try {
6763
+ $imageSize = getimagesize($filePath);
6764
+ return (isset($imageSize['mime']) ? $imageSize['mime'] : false);
6765
+ } catch (\Exception $e) {
6766
+ // well well, don't let this stop us either
6767
+ return null;
6768
+ }
6769
+ }
6770
+ return null;
6771
+ }
6772
+ }
6773
+
6774
+ ?><?php
6775
+
6776
+ namespace ImageMimeTypeGuesser\Detectors;
6777
+
6778
+ class MimeContentType extends AbstractDetector
6779
+ {
6780
+
6781
+ /**
6782
+ * Try to detect mime type of image using *mime_content_type()*.
6783
+ *
6784
+ * Returns:
6785
+ * - mime type (string) (if it is in fact an image, and type could be determined)
6786
+ * - false (if it is not an image type that the server knowns about)
6787
+ * - null (if nothing can be determined)
6788
+ *
6789
+ * @param string $filePath The path to the file
6790
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
6791
+ * false (if it is not an image type that the server knowns about)
6792
+ * or null (if nothing can be determined)
6793
+ */
6794
+ protected function doDetect($filePath)
6795
+ {
6796
+ // mime_content_type supposedly used to be deprecated, but it seems it isn't anymore
6797
+ // it may return false on failure.
6798
+ if (function_exists('mime_content_type')) {
6799
+ try {
6800
+ $result = mime_content_type($filePath);
6801
+ if ($result !== false) {
6802
+ if (strpos($result, 'image/') === 0) {
6803
+ return $result;
6804
+ } else {
6805
+ return false;
6806
+ }
6807
+ }
6808
+ } catch (\Exception $e) {
6809
+ // we are unstoppable!
6810
+ }
6811
+ }
6812
+ return null;
6813
+ }
6814
+ }
6815
+
6816
+ ?><?php
6817
+
6818
+ namespace ImageMimeTypeGuesser\Detectors;
6819
+
6820
+ use \ImageMimeTypeGuesser\Detectors\AbstractDetector;
6821
+
6822
+ class SniffFirstFourBytes extends AbstractDetector
6823
+ {
6824
+
6825
+ /**
6826
+ * Try to detect mime type by sniffing the first four bytes.
6827
+ *
6828
+ * Credits: Based on the code here: http://phil.lavin.me.uk/2011/12/php-accurately-detecting-the-type-of-a-file/
6829
+ *
6830
+ * Returns:
6831
+ * - mime type (string) (if it is in fact an image, and type could be determined)
6832
+ * - false (if it is not an image type that the server knowns about)
6833
+ * - null (if nothing can be determined)
6834
+ *
6835
+ * @param string $filePath The path to the file
6836
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
6837
+ * false (if it is not an image type that the server knowns about)
6838
+ * or null (if nothing can be determined)
6839
+ */
6840
+ protected function doDetect($filePath)
6841
+ {
6842
+ // PNG, GIF, JFIF JPEG, EXIF JPEF (respectively)
6843
+ $known = [
6844
+ '89504E47' => 'image/png',
6845
+ '47494638' => 'image/gif',
6846
+ 'FFD8FFE0' => 'image/jpeg', // JFIF JPEG
6847
+ 'FFD8FFE1' => 'image/jpeg', // EXIF JPEG
6848
+ ];
6849
+
6850
+ $handle = @fopen($filePath, 'r');
6851
+ if ($handle === false) {
6852
+ return null;
6853
+ }
6854
+ $firstFour = @fread($handle, 4);
6855
+ if ($firstFour === false) {
6856
+ return null;
6857
+ }
6858
+ $key = strtoupper(bin2hex($firstFour));
6859
+ if (isset($known[$key])) {
6860
+ return $known[$key];
6861
+ }
6862
+ }
6863
+ }
6864
+
6865
+ ?><?php
6866
+
6867
+ namespace ImageMimeTypeGuesser\Detectors;
6868
+
6869
+ class Stack extends AbstractDetector
6870
+ {
6871
+ /**
6872
+ * Try to detect mime type of image using all available detectors.
6873
+ *
6874
+ * Returns:
6875
+ * - mime type (string) (if it is in fact an image, and type could be determined)
6876
+ * - false (if it is not an image type that the server knowns about)
6877
+ * - null (if nothing can be determined)
6878
+ *
6879
+ * @param string $filePath The path to the file
6880
+ * @return string|false|null mimetype (if it is an image, and type could be determined),
6881
+ * false (if it is not an image type that the server knowns about)
6882
+ * or null (if nothing can be determined)
6883
+ */
6884
+ protected function doDetect($filePath)
6885
+ {
6886
+ $detectors = [
6887
+ 'ExifImageType',
6888
+ 'FInfo',
6889
+ 'SniffFirstFourBytes',
6890
+ 'GetImageSize',
6891
+ 'MimeContentType',
6892
+ ];
6893
+
6894
+ foreach ($detectors as $className) {
6895
+ $result = call_user_func(
6896
+ array("\\ImageMimeTypeGuesser\\Detectors\\" . $className, 'detect'),
6897
+ $filePath
6898
+ );
6899
+ if (!is_null($result)) {
6900
+ return $result;
6901
+ }
6902
+ }
6903
+
6904
+ return null; // undetermined
6905
+ }
6906
+ }
6907
+
vendor/rosell-dk/webp-convert/src-build/webp-on-demand-1.inc ADDED
@@ -0,0 +1,616 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ ?><?php
3
+ namespace WebPConvert\Serve;
4
+
5
+ use ImageMimeTypeGuesser\ImageMimeTypeGuesser;
6
+
7
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
8
+ use WebPConvert\Serve\Exceptions\ServeFailedException;
9
+ use WebPConvert\Serve\Header;
10
+ use WebPConvert\Serve\Report;
11
+ use WebPConvert\Serve\ServeFile;
12
+ use WebPConvert\Options\ArrayOption;
13
+ use WebPConvert\Options\BooleanOption;
14
+ use WebPConvert\Options\Options;
15
+ use WebPConvert\Options\SensitiveArrayOption;
16
+ use WebPConvert\Options\Exceptions\InvalidOptionTypeException;
17
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
18
+ use WebPConvert\WebPConvert;
19
+
20
+ /**
21
+ * Serve a converted webp image.
22
+ *
23
+ * The webp that is served might end up being one of these:
24
+ * - a fresh convertion
25
+ * - the destionation
26
+ * - the original
27
+ *
28
+ * Exactly which is a decision based upon options, file sizes and file modification dates
29
+ * (see the serve method of this class for details)
30
+ *
31
+ * @package WebPConvert
32
+ * @author Bjørn Rosell <it@rosell.dk>
33
+ * @since Class available since Release 2.0.0
34
+ */
35
+ class ServeConvertedWebP
36
+ {
37
+
38
+ /**
39
+ * Process options.
40
+ *
41
+ * @throws \WebPConvert\Options\Exceptions\InvalidOptionTypeException If the type of an option is invalid
42
+ * @throws \WebPConvert\Options\Exceptions\InvalidOptionValueException If the value of an option is invalid
43
+ * @param array $options
44
+ */
45
+ private static function processOptions($options)
46
+ {
47
+ $options2 = new Options();
48
+ $options2->addOptions(
49
+ new BooleanOption('reconvert', false),
50
+ new BooleanOption('serve-original', false),
51
+ new BooleanOption('show-report', false),
52
+ new BooleanOption('suppress-warnings', true),
53
+ new ArrayOption('serve-image', []),
54
+ new SensitiveArrayOption('convert', [])
55
+ );
56
+ foreach ($options as $optionId => $optionValue) {
57
+ $options2->setOrCreateOption($optionId, $optionValue);
58
+ }
59
+ $options2->check();
60
+ return $options2->getOptions();
61
+ }
62
+
63
+ /**
64
+ * Serve original file (source).
65
+ *
66
+ * @param string $source path to source file
67
+ * @param array $serveImageOptions (optional) options for serving an image
68
+ * Supported options:
69
+ * - All options supported by ServeFile::serve()
70
+ * @throws ServeFailedException if source is not an image or mime type cannot be determined
71
+ * @return void
72
+ */
73
+ public static function serveOriginal($source, $serveImageOptions = [])
74
+ {
75
+ $contentType = ImageMimeTypeGuesser::lenientGuess($source);
76
+ if (is_null($contentType)) {
77
+ throw new ServeFailedException('Rejecting to serve original (mime type cannot be determined)');
78
+ } elseif ($contentType === false) {
79
+ throw new ServeFailedException('Rejecting to serve original (it is not an image)');
80
+ } else {
81
+ ServeFile::serve($source, $contentType, $serveImageOptions);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Serve destination file.
87
+ *
88
+ * @param string $destination path to destination file
89
+ * @param array $serveImageOptions (optional) options for serving (such as which headers to add)
90
+ * Supported options:
91
+ * - All options supported by ServeFile::serve()
92
+ * @return void
93
+ */
94
+ public static function serveDestination($destination, $serveImageOptions = [])
95
+ {
96
+ ServeFile::serve($destination, 'image/webp', $serveImageOptions);
97
+ }
98
+
99
+
100
+ public static function warningHandler()
101
+ {
102
+ // do nothing! - as we do not return anything, the warning is suppressed
103
+ }
104
+
105
+ /**
106
+ * Serve converted webp.
107
+ *
108
+ * Serve a converted webp. If a file already exists at the destination, that is served (unless it is
109
+ * older than the source - in that case a fresh conversion will be made, or the file at the destination
110
+ * is larger than the source - in that case the source is served). Some options may alter this logic.
111
+ * In case no file exists at the destination, a fresh conversion is made and served.
112
+ *
113
+ * @param string $source path to source file
114
+ * @param string $destination path to destination
115
+ * @param array $options (optional) options for serving/converting
116
+ * Supported options:
117
+ * 'show-report' => (boolean) If true, the decision will always be 'report'
118
+ * 'serve-original' => (boolean) If true, the decision will be 'source' (unless above option is set)
119
+ * 'reconvert ' => (boolean) If true, the decision will be 'fresh-conversion' (unless one of the
120
+ * above options is set)
121
+ * - All options supported by WebPConvert::convert()
122
+ * - All options supported by ServeFile::serve()
123
+ * @param \WebPConvert\Loggers\BaseLogger $serveLogger (optional)
124
+ * @param \WebPConvert\Loggers\BaseLogger $convertLogger (optional)
125
+ *
126
+ * @throws \WebPConvert\Exceptions\WebPConvertException If something went wrong.
127
+ * @return void
128
+ */
129
+ public static function serve($source, $destination, $options = [], $serveLogger = null, $convertLogger = null)
130
+ {
131
+
132
+ if (empty($source)) {
133
+ throw new ServeFailedException('Source argument missing');
134
+ }
135
+ if (empty($destination)) {
136
+ throw new ServeFailedException('Destination argument missing');
137
+ }
138
+ if (@!file_exists($source)) {
139
+ throw new ServeFailedException('Source file was not found');
140
+ }
141
+
142
+ $options = self::processOptions($options);
143
+
144
+ if ($options['suppress-warnings']) {
145
+ set_error_handler(
146
+ array('\\WebPConvert\\Serve\\ServeConvertedWebP', "warningHandler"),
147
+ E_WARNING | E_USER_WARNING | E_NOTICE | E_USER_NOTICE
148
+ );
149
+ }
150
+
151
+
152
+ //$options = array_merge(self::$defaultOptions, $options);
153
+
154
+ // Step 1: Is there a file at the destination? If not, trigger conversion
155
+ // However 1: if "show-report" option is set, serve the report instead
156
+ // However 2: "reconvert" option should also trigger conversion
157
+ if ($options['show-report']) {
158
+ Header::addLogHeader('Showing report', $serveLogger);
159
+ Report::convertAndReport($source, $destination, $options);
160
+ return;
161
+ }
162
+
163
+ if (!@file_exists($destination)) {
164
+ Header::addLogHeader('Converting (there were no file at destination)', $serveLogger);
165
+ WebPConvert::convert($source, $destination, $options['convert'], $convertLogger);
166
+ } elseif ($options['reconvert']) {
167
+ Header::addLogHeader('Converting (told to reconvert)', $serveLogger);
168
+ WebPConvert::convert($source, $destination, $options['convert'], $convertLogger);
169
+ } else {
170
+ // Step 2: Is the destination older than the source?
171
+ // If yes, trigger conversion (deleting destination is implicit)
172
+ $timestampSource = @filemtime($source);
173
+ $timestampDestination = @filemtime($destination);
174
+ if (($timestampSource !== false) &&
175
+ ($timestampDestination !== false) &&
176
+ ($timestampSource > $timestampDestination)) {
177
+ Header::addLogHeader('Converting (destination was older than the source)', $serveLogger);
178
+ WebPConvert::convert($source, $destination, $options['convert'], $convertLogger);
179
+ }
180
+ }
181
+
182
+ // Step 3: Serve the smallest file (destination or source)
183
+ // However, first check if 'serve-original' is set
184
+ if ($options['serve-original']) {
185
+ Header::addLogHeader('Serving original (told to)', $serveLogger);
186
+ self::serveOriginal($source, $options['serve-image']);
187
+ }
188
+
189
+ $filesizeDestination = @filesize($destination);
190
+ $filesizeSource = @filesize($source);
191
+ if (($filesizeSource !== false) &&
192
+ ($filesizeDestination !== false) &&
193
+ ($filesizeDestination > $filesizeSource)) {
194
+ Header::addLogHeader('Serving original (it is smaller)', $serveLogger);
195
+ self::serveOriginal($source, $options['serve-image']);
196
+ }
197
+
198
+ Header::addLogHeader('Serving converted file', $serveLogger);
199
+ self::serveDestination($destination, $options['serve-image']);
200
+ }
201
+ }
202
+
203
+ ?><?php
204
+ namespace WebPConvert\Serve;
205
+
206
+ use WebPConvert\Options\Options;
207
+ use WebPConvert\Options\StringOption;
208
+ use WebPConvert\Serve\Header;
209
+ use WebPConvert\Serve\Report;
210
+ use WebPConvert\Serve\ServeConvertedWeb;
211
+ use WebPConvert\Serve\Exceptions\ServeFailedException;
212
+ use WebPConvert\Exceptions\WebPConvertException;
213
+
214
+ /**
215
+ * Serve a converted webp image and handle errors.
216
+ *
217
+ * @package WebPConvert
218
+ * @author Bjørn Rosell <it@rosell.dk>
219
+ * @since Class available since Release 2.0.0
220
+ */
221
+ class ServeConvertedWebPWithErrorHandling
222
+ {
223
+
224
+ /**
225
+ * Process options.
226
+ *
227
+ * @throws \WebPConvert\Options\Exceptions\InvalidOptionTypeException If the type of an option is invalid
228
+ * @throws \WebPConvert\Options\Exceptions\InvalidOptionValueException If the value of an option is invalid
229
+ * @param array $options
230
+ */
231
+ private static function processOptions($options)
232
+ {
233
+ $options2 = new Options();
234
+ $options2->addOptions(
235
+ new StringOption('fail', 'original', ['original', '404', 'throw', 'report']),
236
+ new StringOption('fail-when-fail-fails', 'throw', ['original', '404', 'throw', 'report'])
237
+ );
238
+ foreach ($options as $optionId => $optionValue) {
239
+ $options2->setOrCreateOption($optionId, $optionValue);
240
+ }
241
+ $options2->check();
242
+ return $options2->getOptions();
243
+ }
244
+
245
+ /**
246
+ * Add headers for preventing caching.
247
+ *
248
+ * @return void
249
+ */
250
+ private static function addHeadersPreventingCaching()
251
+ {
252
+ Header::setHeader("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
253
+ Header::addHeader("Cache-Control: post-check=0, pre-check=0");
254
+ Header::setHeader("Pragma: no-cache");
255
+ }
256
+
257
+ /**
258
+ * Perform fail action.
259
+ *
260
+ * @param string $fail Action to perform (original | 404 | report)
261
+ * @param string $failIfFailFails Action to perform if $fail action fails
262
+ * @param string $source path to source file
263
+ * @param string $destination path to destination
264
+ * @param array $options (optional) options for serving/converting
265
+ * @param \Exception $e exception that was thrown when trying to serve
266
+ * @param string $serveClass (optional) Full class name to a class that has a serveOriginal() method
267
+ * @return void
268
+ */
269
+ public static function performFailAction($fail, $failIfFailFails, $source, $destination, $options, $e, $serveClass)
270
+ {
271
+ self::addHeadersPreventingCaching();
272
+
273
+ //Header::addLogHeader('Failure');
274
+ Header::addLogHeader('Performing fail action: ' . $fail);
275
+
276
+ switch ($fail) {
277
+ case 'original':
278
+ try {
279
+ //ServeConvertedWebP::serveOriginal($source, $options);
280
+ call_user_func($serveClass . '::serveOriginal', $source, $options);
281
+ } catch (\Exception $e) {
282
+ self::performFailAction($failIfFailFails, '404', $source, $destination, $options, $e, $serveClass);
283
+ }
284
+ break;
285
+
286
+ case '404':
287
+ $protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : 'HTTP/1.0';
288
+ Header::setHeader($protocol . " 404 Not Found");
289
+ break;
290
+
291
+ case 'report':
292
+ $options['show-report'] = true;
293
+ Report::convertAndReport($source, $destination, $options);
294
+ break;
295
+
296
+ case 'throw':
297
+ throw $e;
298
+ break;
299
+
300
+ case 'report-as-image':
301
+ // TODO: Implement or discard ?
302
+ break;
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Serve webp image and handle errors as specified in the 'fail' option.
308
+ *
309
+ * This method basically wraps ServeConvertedWebP:serve in order to provide exception handling.
310
+ * The error handling is set with the 'fail' option and can be either '404', 'original' or 'report'.
311
+ * If set to '404', errors results in 404 Not Found headers being issued. If set to 'original', an
312
+ * error results in the original being served.
313
+ * Look up the ServeConvertedWebP:serve method to learn more.
314
+ *
315
+ * @param string $source path to source file
316
+ * @param string $destination path to destination
317
+ * @param array $options (optional) options for serving/converting
318
+ * Supported options:
319
+ * - 'fail' => (string) Action to take on failure (404 | original | report | throw).
320
+ * "404" or "throw" is recommended for development and "original" is recommended for production.
321
+ * Default: 'original'.
322
+ * - 'fail-when-fail-fails' => (string) Action to take if fail action also fails. Default: '404'.
323
+ * - All options supported by WebPConvert::convert()
324
+ * - All options supported by ServeFile::serve()
325
+ * - All options supported by DecideWhatToServe::decide)
326
+ * @param \WebPConvert\Loggers\BaseLogger $serveLogger (optional)
327
+ * @param \WebPConvert\Loggers\BaseLogger $convertLogger (optional)
328
+ * @param string $serveClass (optional) Full class name to a class that has a serve() method and a
329
+ * serveOriginal() method
330
+ * @return void
331
+ */
332
+ public static function serve(
333
+ $source,
334
+ $destination,
335
+ $options = [],
336
+ $serveLogger = null,
337
+ $convertLogger = null,
338
+ $serveClass = '\\WebPConvert\\Serve\\ServeConvertedWebP'
339
+ ) {
340
+ $options = self::processOptions($options);
341
+
342
+ try {
343
+ //ServeConvertedWebP::serve($source, $destination, $options, $serveLogger);
344
+ call_user_func($serveClass . '::serve', $source, $destination, $options, $serveLogger, $convertLogger);
345
+ } catch (\Exception $e) {
346
+ if ($e instanceof \WebPConvert\Exceptions\WebPConvertException) {
347
+ Header::addLogHeader($e->getShortMessage(), $serveLogger);
348
+ }
349
+
350
+ self::performFailAction(
351
+ $options['fail'],
352
+ $options['fail-when-fail-fails'],
353
+ $source,
354
+ $destination,
355
+ $options,
356
+ $e,
357
+ $serveClass
358
+ );
359
+ }
360
+ }
361
+ }
362
+
363
+ ?><?php
364
+ namespace WebPConvert\Serve;
365
+
366
+ //use WebPConvert\Serve\Report;
367
+ use WebPConvert\Options\ArrayOption;
368
+ use WebPConvert\Options\BooleanOption;
369
+ use WebPConvert\Options\Options;
370
+ use WebPConvert\Options\StringOption;
371
+ use WebPConvert\Serve\Header;
372
+ use WebPConvert\Serve\Exceptions\ServeFailedException;
373
+
374
+ /**
375
+ * Serve a file (send to standard output)
376
+ *
377
+ * @package WebPConvert
378
+ * @author Bjørn Rosell <it@rosell.dk>
379
+ * @since Class available since Release 2.0.0
380
+ */
381
+ class ServeFile
382
+ {
383
+
384
+ /**
385
+ * Process options.
386
+ *
387
+ * @throws \WebPConvert\Options\Exceptions\InvalidOptionTypeException If the type of an option is invalid
388
+ * @throws \WebPConvert\Options\Exceptions\InvalidOptionValueException If the value of an option is invalid
389
+ * @param array $options
390
+ */
391
+ private static function processOptions($options)
392
+ {
393
+ $options2 = new Options();
394
+ $options2->addOptions(
395
+ new ArrayOption('headers', []),
396
+ new StringOption('cache-control-header', 'public, max-age=31536000')
397
+ );
398
+ foreach ($options as $optionId => $optionValue) {
399
+ $options2->setOrCreateOption($optionId, $optionValue);
400
+ }
401
+ $options2->check();
402
+ $options = $options2->getOptions();
403
+
404
+ // headers option
405
+ // --------------
406
+
407
+ $headerOptions = new Options();
408
+ $headerOptions->addOptions(
409
+ new BooleanOption('cache-control', false),
410
+ new BooleanOption('content-length', true),
411
+ new BooleanOption('content-type', true),
412
+ new BooleanOption('expires', false),
413
+ new BooleanOption('last-modified', true),
414
+ new BooleanOption('vary-accept', false)
415
+ );
416
+ foreach ($options['headers'] as $optionId => $optionValue) {
417
+ $headerOptions->setOrCreateOption($optionId, $optionValue);
418
+ }
419
+ $options['headers'] = $headerOptions->getOptions();
420
+ return $options;
421
+ }
422
+
423
+ /**
424
+ * Serve existing file.
425
+ *
426
+ * @param string $filename File to serve (absolute path)
427
+ * @param string $contentType Content-type (used to set header).
428
+ * Only used when the "set-content-type-header" option is set.
429
+ * Set to ie "image/jpeg" for serving jpeg file.
430
+ * @param array $options Array of named options (optional).
431
+ * Supported options:
432
+ * 'add-vary-accept-header' => (boolean) Whether to add *Vary: Accept* header or not. Default: true.
433
+ * 'set-content-type-header' => (boolean) Whether to set *Content-Type* header or not. Default: true.
434
+ * 'set-last-modified-header' => (boolean) Whether to set *Last-Modified* header or not. Default: true.
435
+ * 'set-cache-control-header' => (boolean) Whether to set *Cache-Control* header or not. Default: true.
436
+ * 'cache-control-header' => string Cache control header. Default: "public, max-age=86400"
437
+ *
438
+ * @throws ServeFailedException if serving failed
439
+ * @return void
440
+ */
441
+ public static function serve($filename, $contentType, $options = [])
442
+ {
443
+ if (!file_exists($filename)) {
444
+ Header::addHeader('X-WebP-Convert-Error: Could not read file');
445
+ throw new ServeFailedException('Could not read file');
446
+ }
447
+
448
+ $options = self::processOptions($options);
449
+
450
+ if ($options['headers']['last-modified']) {
451
+ Header::setHeader("Last-Modified: " . gmdate("D, d M Y H:i:s", @filemtime($filename)) ." GMT");
452
+ }
453
+
454
+ if ($options['headers']['content-type']) {
455
+ Header::setHeader('Content-Type: ' . $contentType);
456
+ }
457
+
458
+ if ($options['headers']['vary-accept']) {
459
+ Header::addHeader('Vary: Accept');
460
+ }
461
+
462
+ if (!empty($options['cache-control-header'])) {
463
+ if ($options['headers']['cache-control']) {
464
+ Header::setHeader('Cache-Control: ' . $options['cache-control-header']);
465
+ }
466
+ if ($options['headers']['expires']) {
467
+ // Add exprires header too (#126)
468
+ // Check string for something like this: max-age:86400
469
+ if (preg_match('#max-age\\s*=\\s*(\\d*)#', $options['cache-control-header'], $matches)) {
470
+ $seconds = $matches[1];
471
+ Header::setHeader('Expires: '. gmdate('D, d M Y H:i:s \G\M\T', time() + intval($seconds)));
472
+ }
473
+ }
474
+ }
475
+
476
+ if ($options['headers']['content-length']) {
477
+ Header::setHeader('Content-Length: ' . filesize($filename));
478
+ }
479
+
480
+ if (@readfile($filename) === false) {
481
+ Header::addHeader('X-WebP-Convert-Error: Could not read file');
482
+ throw new ServeFailedException('Could not read file');
483
+ }
484
+ }
485
+ }
486
+
487
+ ?><?php
488
+ namespace WebPConvert\Serve;
489
+
490
+ /**
491
+ * Add / Set HTTP header.
492
+ *
493
+ * This class does nothing more than adding two convenience functions for calling the "header" function.
494
+ *
495
+ * @package WebPConvert
496
+ * @author Bjørn Rosell <it@rosell.dk>
497
+ * @since Class available since Release 2.0.0
498
+ */
499
+ class Header
500
+ {
501
+ /**
502
+ * Convenience function for adding header (append).
503
+ *
504
+ * @param string $header The header to add.
505
+ * @return void
506
+ */
507
+ public static function addHeader($header)
508
+ {
509
+ header($header, false);
510
+ }
511
+
512
+ /**
513
+ * Convenience function for replacing header.
514
+ *
515
+ * @param string $header The header to set.
516
+ * @return void
517
+ */
518
+ public static function setHeader($header)
519
+ {
520
+ header($header, true);
521
+ }
522
+
523
+ /**
524
+ * Add log header and optionally send it to a logger as well.
525
+ *
526
+ * @param string $msg Message to add to "X-WebP-Convert-Log" header
527
+ * @param \WebPConvert\Loggers\BaseLogger $logger (optional)
528
+ * @return void
529
+ */
530
+ public static function addLogHeader($msg, $logger = null)
531
+ {
532
+ self::addHeader('X-WebP-Convert-Log: ' . $msg);
533
+ if (!is_null($logger)) {
534
+ $logger->logLn($msg);
535
+ }
536
+ }
537
+ }
538
+
539
+ ?><?php
540
+
541
+ namespace WebPConvert;
542
+
543
+ //use WebPConvert\Convert\Converters\ConverterHelper;
544
+ use WebPConvert\Convert\Converters\Stack;
545
+ //use WebPConvert\Serve\ServeExistingOrHandOver;
546
+ use WebPConvert\Serve\ServeConvertedWebP;
547
+ use WebPConvert\Serve\ServeConvertedWebPWithErrorHandling;
548
+
549
+ /**
550
+ * Convert images to webp and/or serve them.
551
+ *
552
+ * This class is just a couple of convenience methods for doing conversion and/or
553
+ * serving.
554
+ *
555
+ * @package WebPConvert
556
+ * @author Bjørn Rosell <it@rosell.dk>
557
+ * @since Class available since Release 2.0.0
558
+ */
559
+ class WebPConvert
560
+ {
561
+
562
+ /**
563
+ * Convert jpeg or png into webp
564
+ *
565
+ * Convenience method for calling Stack::convert.
566
+ *
567
+ * @param string $source The image to convert (absolute,no backslashes)
568
+ * Image must be jpeg or png.
569
+ * @param string $destination Where to store the converted file (absolute path, no backslashes).
570
+ * @param array $options (optional) Array of named options
571
+ * The options are documented here:
572
+ * https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/converting/options.md
573
+ * @param \WebPConvert\Loggers\BaseLogger $logger (optional)
574
+ *
575
+ * @throws \WebPConvert\Convert\Exceptions\ConversionFailedException in case conversion fails
576
+ * @return void
577
+ */
578
+ public static function convert($source, $destination, $options = [], $logger = null)
579
+ {
580
+ Stack::convert($source, $destination, $options, $logger);
581
+ }
582
+
583
+ /**
584
+ * Serve webp image, converting first if neccessary.
585
+ *
586
+ * If an image already exists, it will be served, unless it is older or larger than the source. (If it is larger,
587
+ * the original is served, if it is older, the existing webp will be deleted and a fresh conversion will be made
588
+ * and served). In case of error, the action indicated in the 'fail' option will be triggered (default is to serve
589
+ * the original). Look up the ServeConvertedWebP:serve() and the ServeConvertedWebPWithErrorHandling::serve()
590
+ * methods to learn more.
591
+ *
592
+ * @param string $source path to source file
593
+ * @param string $destination path to destination
594
+ * @param array $options (optional) options for serving/converting. The options are documented in the
595
+ * ServeConvertedWebPWithErrorHandling::serve() method
596
+ * @param \WebPConvert\Loggers\BaseLogger $serveLogger (optional)
597
+ * @param \WebPConvert\Loggers\BaseLogger $convertLogger (optional)
598
+ * @return void
599
+ */
600
+ public static function serveConverted(
601
+ $source,
602
+ $destination,
603
+ $options = [],
604
+ $serveLogger = null,
605
+ $convertLogger = null
606
+ ) {
607
+ //return ServeExistingOrHandOver::serveConverted($source, $destination, $options);
608
+ //if (isset($options['handle-errors']) && $options['handle-errors'] === true) {
609
+ if (isset($options['fail']) && ($options['fail'] != 'throw')) {
610
+ ServeConvertedWebPWithErrorHandling::serve($source, $destination, $options, $serveLogger, $convertLogger);
611
+ } else {
612
+ ServeConvertedWebP::serve($source, $destination, $options, $serveLogger, $convertLogger);
613
+ }
614
+ }
615
+ }
616
+
vendor/rosell-dk/webp-convert/src-build/webp-on-demand-2.inc ADDED
@@ -0,0 +1,6292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ ?><?php
3
+
4
+ namespace WebPConvert\Options;
5
+
6
+ use WebPConvert\Options\Exceptions\InvalidOptionTypeException;
7
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
8
+
9
+ /**
10
+ * (base) option class.
11
+ *
12
+ * @package WebPConvert
13
+ * @author Bjørn Rosell <it@rosell.dk>
14
+ * @since Class available since Release 2.0.0
15
+ */
16
+ class Option
17
+ {
18
+ /** @var string The id of the option */
19
+ protected $id;
20
+
21
+ /** @var mixed The default value of the option */
22
+ protected $defaultValue;
23
+
24
+ /** @var mixed The value of the option */
25
+ protected $value;
26
+
27
+ /** @var boolean Whether the value has been explicitly set */
28
+ protected $isExplicitlySet = false;
29
+
30
+ /**
31
+ * Constructor.
32
+ *
33
+ * @param string $id id of the option
34
+ * @param mixed $defaultValue default value for the option
35
+ * @throws InvalidOptionValueException if the default value cannot pass the check
36
+ * @throws InvalidOptionTypeException if the default value is wrong type
37
+ * @return void
38
+ */
39
+ public function __construct($id, $defaultValue)
40
+ {
41
+ $this->id = $id;
42
+ $this->defaultValue = $defaultValue;
43
+
44
+ // Check that default value is ok
45
+ $this->check();
46
+ }
47
+
48
+ /**
49
+ * Get Id.
50
+ *
51
+ * @return string The id of the option
52
+ */
53
+ public function getId()
54
+ {
55
+ return $this->id;
56
+ }
57
+
58
+ /**
59
+ * Get default value.
60
+ *
61
+ * @return mixed The default value for the option
62
+ */
63
+ public function getDefaultValue()
64
+ {
65
+ return $this->defaultValue;
66
+ }
67
+
68
+
69
+ /**
70
+ * Get value, or default value if value has not been explicitly set.
71
+ *
72
+ * @return mixed The value/default value
73
+ */
74
+ public function getValue()
75
+ {
76
+ if (!$this->isExplicitlySet) {
77
+ return $this->defaultValue;
78
+ } else {
79
+ return $this->value;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Get to know if value has been explicitly set.
85
+ *
86
+ * @return boolean Whether or not the value has been set explicitly
87
+ */
88
+ public function isValueExplicitlySet()
89
+ {
90
+ return $this->isExplicitlySet;
91
+ }
92
+
93
+ /**
94
+ * Set value
95
+ *
96
+ * @param mixed $value The value
97
+ * @return void
98
+ */
99
+ public function setValue($value)
100
+ {
101
+ $this->isExplicitlySet = true;
102
+ $this->value = $value;
103
+ }
104
+
105
+ /**
106
+ * Check if the value is valid.
107
+ *
108
+ * This base class does no checking, but this method is overridden by most other options.
109
+ * @return void
110
+ */
111
+ public function check()
112
+ {
113
+ }
114
+
115
+ /**
116
+ * Helpful function for checking type - used by subclasses.
117
+ *
118
+ * @param string $expectedType The expected type, ie 'string'
119
+ * @throws InvalidOptionTypeException If the type is invalid
120
+ * @return void
121
+ */
122
+ protected function checkType($expectedType)
123
+ {
124
+ if (gettype($this->getValue()) != $expectedType) {
125
+ throw new InvalidOptionTypeException(
126
+ 'The "' . $this->id . '" option must be a ' . $expectedType .
127
+ ' (you provided a ' . gettype($this->getValue()) . ')'
128
+ );
129
+ }
130
+ }
131
+
132
+ public function getValueForPrint()
133
+ {
134
+ return print_r($this->getValue(), true);
135
+ }
136
+ }
137
+
138
+ ?><?php
139
+
140
+ // TODO:
141
+ // Read this: https://sourcemaking.com/design_patterns/strategy
142
+
143
+ namespace WebPConvert\Convert\Converters;
144
+
145
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
146
+ use WebPConvert\Exceptions\WebPConvertException;
147
+ use WebPConvert\Convert\Converters\BaseTraits\AutoQualityTrait;
148
+ use WebPConvert\Convert\Converters\BaseTraits\DestinationPreparationTrait;
149
+ use WebPConvert\Convert\Converters\BaseTraits\LoggerTrait;
150
+ use WebPConvert\Convert\Converters\BaseTraits\OptionsTrait;
151
+ use WebPConvert\Convert\Converters\BaseTraits\SourceValidationTrait;
152
+ use WebPConvert\Convert\Converters\BaseTraits\WarningLoggerTrait;
153
+ use WebPConvert\Loggers\BaseLogger;
154
+
155
+ use ImageMimeTypeGuesser\ImageMimeTypeGuesser;
156
+
157
+ /**
158
+ * Base for all converter classes.
159
+ *
160
+ * @package WebPConvert
161
+ * @author Bjørn Rosell <it@rosell.dk>
162
+ * @since Class available since Release 2.0.0
163
+ */
164
+ abstract class AbstractConverter
165
+ {
166
+ use AutoQualityTrait;
167
+ use OptionsTrait;
168
+ use WarningLoggerTrait;
169
+ use DestinationPreparationTrait;
170
+ use SourceValidationTrait;
171
+ use LoggerTrait;
172
+
173
+ /**
174
+ * The actual conversion is be done by a concrete converter extending this class.
175
+ *
176
+ * At the stage this method is called, the abstract converter has taken preparational steps.
177
+ * - It has created the destination folder (if neccesary)
178
+ * - It has checked the input (valid mime type)
179
+ * - It has set up an error handler, mostly in order to catch and log warnings during the doConvert fase
180
+ *
181
+ * Note: This method is not meant to be called from the outside. Use the static *convert* method for converting
182
+ * or, if you wish, create an instance with ::createInstance() and then call ::doConvert()
183
+ *
184
+ * @throws ConversionFailedException in case conversion failed in an antipiciated way (or subclass)
185
+ * @throws \Exception in case conversion failed in an unantipiciated way
186
+ */
187
+ abstract protected function doActualConvert();
188
+
189
+ /**
190
+ * Whether or not the converter supports lossless encoding (even for jpegs)
191
+ *
192
+ * PS: Converters that supports lossless encoding all use the EncodingAutoTrait, which
193
+ * overrides this function.
194
+ *
195
+ * @return boolean Whether the converter supports lossless encoding (even for jpegs).
196
+ */
197
+ public function supportsLossless()
198
+ {
199
+ return false;
200
+ }
201
+
202
+ /** @var string The filename of the image to convert (complete path) */
203
+ protected $source;
204
+
205
+ /** @var string Where to save the webp (complete path) */
206
+ protected $destination;
207
+
208
+ /** @var string|false|null Where to save the webp (complete path) */
209
+ private $sourceMimeType;
210
+
211
+ /**
212
+ * Check basis operationality
213
+ *
214
+ * Converters may override this method for the purpose of performing basic operationaly checks. It is for
215
+ * running general operation checks for a conversion method.
216
+ * If some requirement is not met, it should throw a ConverterNotOperationalException (or subtype)
217
+ *
218
+ * The method is called internally right before calling doActualConvert() method.
219
+ * - It SHOULD take options into account when relevant. For example, a missing api key for a
220
+ * cloud converter should be detected here
221
+ * - It should NOT take the actual filename into consideration, as the purpose is *general*
222
+ * For that pupose, converters should override checkConvertability
223
+ * Also note that doConvert method is allowed to throw ConverterNotOperationalException too.
224
+ *
225
+ * @return void
226
+ */
227
+ public function checkOperationality()
228
+ {
229
+ }
230
+
231
+ /**
232
+ * Converters may override this for the purpose of performing checks on the concrete file.
233
+ *
234
+ * This can for example be used for rejecting big uploads in cloud converters or rejecting unsupported
235
+ * image types.
236
+ *
237
+ * @return void
238
+ */
239
+ public function checkConvertability()
240
+ {
241
+ }
242
+
243
+ /**
244
+ * Constructor.
245
+ *
246
+ * @param string $source path to source file
247
+ * @param string $destination path to destination
248
+ * @param array $options (optional) options for conversion
249
+ * @param BaseLogger $logger (optional)
250
+ */
251
+ public function __construct($source, $destination, $options = [], $logger = null)
252
+ {
253
+ $this->source = $source;
254
+ $this->destination = $destination;
255
+
256
+ $this->setLogger($logger);
257
+ $this->setProvidedOptions($options);
258
+
259
+ if (!isset($this->options['_skip_input_check'])) {
260
+ $this->log('WebP Convert 2.0.0', 'italic');
261
+ $this->logLn(' ignited.');
262
+ $this->logLn('- PHP version: ' . phpversion());
263
+ if (isset($_SERVER['SERVER_SOFTWARE'])) {
264
+ $this->logLn('- Server software: ' . $_SERVER['SERVER_SOFTWARE']);
265
+ }
266
+ $this->logLn('');
267
+ $this->logLn(self::getConverterDisplayName() . ' converter ignited');
268
+ }
269
+
270
+ $this->checkSourceExists();
271
+ $this->checkSourceMimeType();
272
+ }
273
+
274
+ /**
275
+ * Get source.
276
+ *
277
+ * @return string The source.
278
+ */
279
+ public function getSource()
280
+ {
281
+ return $this->source;
282
+ }
283
+
284
+ /**
285
+ * Get destination.
286
+ *
287
+ * @return string The destination.
288
+ */
289
+ public function getDestination()
290
+ {
291
+ return $this->destination;
292
+ }
293
+
294
+ /**
295
+ * Set destination.
296
+ *
297
+ * @param string $destination path to destination
298
+ * @return string The destination.
299
+ */
300
+ public function setDestination($destination)
301
+ {
302
+ $this->destination = $destination;
303
+ }
304
+
305
+
306
+ /**
307
+ * Get converter name for display (defaults to the class name (short)).
308
+ *
309
+ * Converters can override this.
310
+ *
311
+ * @return string A display name, ie "Gd"
312
+ */
313
+ protected static function getConverterDisplayName()
314
+ {
315
+ // https://stackoverflow.com/questions/19901850/how-do-i-get-an-objects-unqualified-short-class-name/25308464
316
+ return substr(strrchr('\\' . static::class, '\\'), 1);
317
+ }
318
+
319
+
320
+ /**
321
+ * Get converter id (defaults to the class name lowercased)
322
+ *
323
+ * Converters can override this.
324
+ *
325
+ * @return string A display name, ie "Gd"
326
+ */
327
+ protected static function getConverterId()
328
+ {
329
+ return strtolower(self::getConverterDisplayName());
330
+ }
331
+
332
+
333
+ /**
334
+ * Create an instance of this class
335
+ *
336
+ * @param string $source The path to the file to convert
337
+ * @param string $destination The path to save the converted file to
338
+ * @param array $options (optional)
339
+ * @param \WebPConvert\Loggers\BaseLogger $logger (optional)
340
+ *
341
+ * @return static
342
+ */
343
+ public static function createInstance($source, $destination, $options = [], $logger = null)
344
+ {
345
+
346
+ return new static($source, $destination, $options, $logger);
347
+ }
348
+
349
+ protected function logReduction($source, $destination)
350
+ {
351
+ $sourceSize = filesize($source);
352
+ $destSize = filesize($destination);
353
+ $this->log(round(($sourceSize - $destSize)/$sourceSize * 100) . '% ');
354
+ if ($sourceSize < 10000) {
355
+ $this->logLn('(went from ' . strval($sourceSize) . ' bytes to '. strval($destSize) . ' bytes)');
356
+ } else {
357
+ $this->logLn('(went from ' . round($sourceSize/1024) . ' kb to ' . round($destSize/1024) . ' kb)');
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Run conversion.
363
+ *
364
+ * @return void
365
+ */
366
+ private function doConvertImplementation()
367
+ {
368
+ $beginTime = microtime(true);
369
+
370
+ $this->activateWarningLogger();
371
+
372
+ $this->checkOptions();
373
+
374
+ // Prepare destination folder
375
+ $this->createWritableDestinationFolder();
376
+ $this->removeExistingDestinationIfExists();
377
+
378
+ if (!isset($this->options['_skip_input_check'])) {
379
+ // Run basic input validations (if source exists and if file extension is valid)
380
+ $this->checkSourceExists();
381
+ $this->checkSourceMimeType();
382
+
383
+ // Check that a file can be written to destination
384
+ $this->checkDestinationWritable();
385
+ }
386
+
387
+ $this->checkOperationality();
388
+ $this->checkConvertability();
389
+
390
+ if ($this->options['log-call-arguments']) {
391
+ $this->logOptions();
392
+ $this->logLn('');
393
+ }
394
+
395
+ $this->runActualConvert();
396
+
397
+ $source = $this->source;
398
+ $destination = $this->destination;
399
+
400
+ if (!@file_exists($destination)) {
401
+ throw new ConversionFailedException('Destination file is not there: ' . $destination);
402
+ } elseif (@filesize($destination) === 0) {
403
+ unlink($destination);
404
+ throw new ConversionFailedException('Destination file was completely empty');
405
+ } else {
406
+ if (!isset($this->options['_suppress_success_message'])) {
407
+ $this->ln();
408
+ $this->log('Converted image in ' . round((microtime(true) - $beginTime) * 1000) . ' ms');
409
+
410
+ $sourceSize = @filesize($source);
411
+ if ($sourceSize !== false) {
412
+ $this->log(', reducing file size with ');
413
+ $this->logReduction($source, $destination);
414
+ }
415
+ }
416
+ }
417
+
418
+ $this->deactivateWarningLogger();
419
+ }
420
+
421
+ //private function logEx
422
+ /**
423
+ * Start conversion.
424
+ *
425
+ * Usually you would rather call the static convert method, but alternatively you can call
426
+ * call ::createInstance to get an instance and then ::doConvert().
427
+ *
428
+ * @return void
429
+ */
430
+ public function doConvert()
431
+ {
432
+ try {
433
+ //trigger_error('hello', E_USER_ERROR);
434
+ $this->doConvertImplementation();
435
+ } catch (WebPConvertException $e) {
436
+ $this->logLn('');
437
+ /*
438
+ if (isset($e->description) && ($e->description != '')) {
439
+ $this->log('Error: ' . $e->description . '. ', 'bold');
440
+ } else {
441
+ $this->log('Error: ', 'bold');
442
+ }
443
+ */
444
+ $this->log('Error: ', 'bold');
445
+ $this->logLn($e->getMessage(), 'bold');
446
+ throw $e;
447
+ } catch (\Exception $e) {
448
+ $className = get_class($e);
449
+
450
+ $classNameParts = explode("\\", $className);
451
+ $shortClassName = array_pop($classNameParts);
452
+
453
+ $this->logLn('');
454
+ $this->logLn($shortClassName . ' thrown in ' . $e->getFile() . ':' . $e->getLine(), 'bold');
455
+ $this->logLn('Message: "' . $e->getMessage() . '"', 'bold');
456
+ //$this->logLn('Exception class: ' . $className);
457
+
458
+ $this->logLn('Trace:');
459
+ foreach ($e->getTrace() as $trace) {
460
+ //$this->logLn(print_r($trace, true));
461
+ if (isset($trace['file']) && isset($trace['line'])) {
462
+ $this->logLn(
463
+ $trace['file'] . ':' . $trace['line']
464
+ );
465
+ }
466
+ }
467
+ throw $e;
468
+ } /*catch (\Error $e) {
469
+ $this->logLn('ERROR');
470
+ }*/
471
+ }
472
+
473
+ /**
474
+ * Runs the actual conversion (after setup and checks)
475
+ * Simply calls the doActualConvert() of the actual converter.
476
+ * However, in the EncodingAutoTrait, this method is overridden to make two conversions
477
+ * and select the smallest.
478
+ *
479
+ * @return void
480
+ */
481
+ protected function runActualConvert()
482
+ {
483
+ $this->doActualConvert();
484
+ }
485
+
486
+ /**
487
+ * Convert an image to webp.
488
+ *
489
+ * @param string $source path to source file
490
+ * @param string $destination path to destination
491
+ * @param array $options (optional) options for conversion
492
+ * @param BaseLogger $logger (optional)
493
+ *
494
+ * @throws ConversionFailedException in case conversion fails in an antipiciated way
495
+ * @throws \Exception in case conversion fails in an unantipiciated way
496
+ * @return void
497
+ */
498
+ public static function convert($source, $destination, $options = [], $logger = null)
499
+ {
500
+ $c = self::createInstance($source, $destination, $options, $logger);
501
+ $c->doConvert();
502
+ //echo $instance->id;
503
+ }
504
+
505
+ /**
506
+ * Get mime type for image (best guess).
507
+ *
508
+ * It falls back to using file extension. If that fails too, false is returned
509
+ *
510
+ * PS: Is it a security risk to fall back on file extension?
511
+ * - By setting file extension to "jpg", one can lure our library into trying to convert a file, which isn't a jpg.
512
+ * hmm, seems very unlikely, though not unthinkable that one of the converters could be exploited
513
+ *
514
+ * @return string|false|null mimetype (if it is an image, and type could be determined / guessed),
515
+ * false (if it is not an image type that the server knowns about)
516
+ * or null (if nothing can be determined)
517
+ */
518
+ public function getMimeTypeOfSource()
519
+ {
520
+ if (!isset($this->sourceMimeType)) {
521
+ $this->sourceMimeType = ImageMimeTypeGuesser::lenientGuess($this->source);
522
+ }
523
+ return $this->sourceMimeType;
524
+ }
525
+ }
526
+
527
+ ?><?php
528
+
529
+ namespace WebPConvert\Exceptions;
530
+
531
+ /**
532
+ * WebPConvertException is the base exception for all exceptions in this library.
533
+ *
534
+ * Note that the parameters for the constructor differs from that of the Exception class.
535
+ * We do not use exception code here, but are instead allowing two version of the error message:
536
+ * a short version and a long version.
537
+ * The short version may not contain special characters or dynamic content.
538
+ * The detailed version may.
539
+ * If the detailed version isn't provided, getDetailedMessage will return the short version.
540
+ *
541
+ */
542
+ class WebPConvertException extends \Exception
543
+ {
544
+ public $description = '';
545
+ protected $detailedMessage;
546
+ protected $shortMessage;
547
+
548
+ public function getDetailedMessage()
549
+ {
550
+ return $this->detailedMessage;
551
+ }
552
+
553
+ public function getShortMessage()
554
+ {
555
+ return $this->shortMessage;
556
+ }
557
+
558
+ public function __construct($shortMessage = "", $detailedMessage = "", $previous = null)
559
+ {
560
+ $detailedMessage = ($detailedMessage != '') ? $detailedMessage : $shortMessage;
561
+ $this->detailedMessage = $detailedMessage;
562
+ $this->shortMessage = $shortMessage;
563
+
564
+ parent::__construct(
565
+ $detailedMessage,
566
+ 0,
567
+ $previous
568
+ );
569
+ }
570
+ }
571
+
572
+ ?><?php
573
+
574
+ namespace WebPConvert\Convert\Exceptions;
575
+
576
+ use WebPConvert\Exceptions\WebPConvertException;
577
+
578
+ /**
579
+ * ConversionFailedException is the base exception in the hierarchy for conversion errors.
580
+ *
581
+ * Exception hierarchy from here:
582
+ *
583
+ * WebpConvertException
584
+ * ConversionFailedException
585
+ * ConversionSkippedException
586
+ * ConverterNotOperationalException
587
+ * InvalidApiKeyException
588
+ * SystemRequirementsNotMetException
589
+ * FileSystemProblemsException
590
+ * CreateDestinationFileException
591
+ * CreateDestinationFolderException
592
+ * InvalidInputException
593
+ * ConverterNotFoundException
594
+ * InvalidImageTypeException
595
+ * InvalidOptionValueException
596
+ * TargetNotFoundException
597
+ */
598
+ class ConversionFailedException extends WebPConvertException
599
+ {
600
+ //public $description = 'Conversion failed';
601
+ public $description = '';
602
+ }
603
+
604
+ ?><?php
605
+
606
+ namespace WebPConvert\Options;
607
+
608
+ use WebPConvert\Options\Option;
609
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
610
+
611
+ /**
612
+ * Abstract option class
613
+ *
614
+ * @package WebPConvert
615
+ * @author Bjørn Rosell <it@rosell.dk>
616
+ * @since Class available since Release 2.0.0
617
+ */
618
+ class ArrayOption extends Option
619
+ {
620
+
621
+ public function check()
622
+ {
623
+ $this->checkType('array');
624
+ }
625
+
626
+ public function getValueForPrint()
627
+ {
628
+ if (count($this->getValue()) == 0) {
629
+ return '(empty array)';
630
+ } else {
631
+ return parent::getValueForPrint();
632
+ }
633
+ }
634
+ }
635
+
636
+ ?><?php
637
+
638
+ namespace WebPConvert\Options;
639
+
640
+ use WebPConvert\Options\Option;
641
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
642
+
643
+ /**
644
+ * Boolean option
645
+ *
646
+ * @package WebPConvert
647
+ * @author Bjørn Rosell <it@rosell.dk>
648
+ * @since Class available since Release 2.0.0
649
+ */
650
+ class BooleanOption extends Option
651
+ {
652
+
653
+ public function check()
654
+ {
655
+ $this->checkType('boolean');
656
+ }
657
+
658
+ public function getValueForPrint()
659
+ {
660
+ return ($this->getValue() === true ? 'true' : 'false');
661
+ }
662
+ }
663
+
664
+ ?><?php
665
+
666
+ namespace WebPConvert\Options;
667
+
668
+ use WebPConvert\Options\Option;
669
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
670
+
671
+ /**
672
+ * Ghost option
673
+ *
674
+ * @package WebPConvert
675
+ * @author Bjørn Rosell <it@rosell.dk>
676
+ * @since Class available since Release 2.0.0
677
+ */
678
+ class GhostOption extends Option
679
+ {
680
+
681
+ public function getValueForPrint()
682
+ {
683
+ return '(not defined for this converter)';
684
+ }
685
+ }
686
+
687
+ ?><?php
688
+
689
+ namespace WebPConvert\Options;
690
+
691
+ use WebPConvert\Options\Option;
692
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
693
+
694
+ /**
695
+ * Abstract option class
696
+ *
697
+ * @package WebPConvert
698
+ * @author Bjørn Rosell <it@rosell.dk>
699
+ * @since Class available since Release 2.0.0
700
+ */
701
+ class IntegerOption extends Option
702
+ {
703
+
704
+ protected $minValue;
705
+ protected $maxValue;
706
+
707
+ /**
708
+ * Constructor.
709
+ *
710
+ * @param string $id id of the option
711
+ * @param integer $defaultValue default value for the option
712
+ * @throws InvalidOptionValueException if the default value cannot pass the check
713
+ * @return void
714
+ */
715
+ public function __construct($id, $defaultValue, $minValue = null, $maxValue = null)
716
+ {
717
+ $this->minValue = $minValue;
718
+ $this->maxValue = $maxValue;
719
+ parent::__construct($id, $defaultValue);
720
+ }
721
+
722
+ protected function checkMin()
723
+ {
724
+ if (!is_null($this->minValue) && $this->getValue() < $this->minValue) {
725
+ throw new InvalidOptionValueException(
726
+ '"' . $this->id . '" option must be set to minimum ' . $this->minValue . '. ' .
727
+ 'It was however set to: ' . $this->getValue()
728
+ );
729
+ }
730
+ }
731
+
732
+ protected function checkMax()
733
+ {
734
+ if (!is_null($this->maxValue) && $this->getValue() > $this->maxValue) {
735
+ throw new InvalidOptionValueException(
736
+ '"' . $this->id . '" option must be set to max ' . $this->maxValue . '. ' .
737
+ 'It was however set to: ' . $this->getValue()
738
+ );
739
+ }
740
+ }
741
+
742
+ protected function checkMinMax()
743
+ {
744
+ $this->checkMin();
745
+ $this->checkMax();
746
+ }
747
+
748
+ public function check()
749
+ {
750
+ $this->checkType('integer');
751
+ $this->checkMinMax();
752
+ }
753
+ }
754
+
755
+ ?><?php
756
+
757
+ namespace WebPConvert\Options;
758
+
759
+ use WebPConvert\Options\IntegerOption;
760
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
761
+
762
+ /**
763
+ * Abstract option class
764
+ *
765
+ * @package WebPConvert
766
+ * @author Bjørn Rosell <it@rosell.dk>
767
+ * @since Class available since Release 2.0.0
768
+ */
769
+ class IntegerOrNullOption extends IntegerOption
770
+ {
771
+
772
+ public function __construct($id, $defaultValue, $minValue = null, $maxValue = null)
773
+ {
774
+ parent::__construct($id, $defaultValue, $minValue, $maxValue);
775
+ }
776
+
777
+ public function check()
778
+ {
779
+ $this->checkMinMax();
780
+
781
+ $valueType = gettype($this->getValue());
782
+ if (!in_array($valueType, ['integer', 'NULL'])) {
783
+ throw new InvalidOptionValueException(
784
+ 'The "' . $this->id . '" option must be either integer or NULL. ' .
785
+ 'You however provided a value of type: ' . $valueType
786
+ );
787
+ }
788
+ }
789
+
790
+ public function getValueForPrint()
791
+ {
792
+ if (gettype($this->getValue() == 'NULL')) {
793
+ return 'null (not set)';
794
+ }
795
+ return parent::getValueForPrint();
796
+ }
797
+ }
798
+
799
+ ?><?php
800
+
801
+ namespace WebPConvert\Options;
802
+
803
+ use WebPConvert\Options\StringOption;
804
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
805
+
806
+ /**
807
+ * Metadata option. A Comma-separated list ('all', 'none', 'exif', 'icc', 'xmp')
808
+ *
809
+ * @package WebPConvert
810
+ * @author Bjørn Rosell <it@rosell.dk>
811
+ * @since Class available since Release 2.0.0
812
+ */
813
+ class MetadataOption extends StringOption
814
+ {
815
+
816
+ public function __construct($id, $defaultValue)
817
+ {
818
+ parent::__construct($id, $defaultValue);
819
+ }
820
+
821
+ public function check()
822
+ {
823
+ parent::check();
824
+
825
+ $value = $this->getValue();
826
+
827
+ if (($value == 'all') || ($value == 'none')) {
828
+ return;
829
+ }
830
+
831
+ foreach (explode(',', $value) as $item) {
832
+ if (!in_array($value, ['exif', 'icc', 'xmp'])) {
833
+ throw new InvalidOptionValueException(
834
+ '"metadata" option must be "all", "none" or a comma-separated list of "exif", "icc" or "xmp". ' .
835
+ 'It was however set to: "' . $value . '"'
836
+ );
837
+ }
838
+ }
839
+
840
+ //$this->checkType('string');
841
+ }
842
+ }
843
+
844
+ ?><?php
845
+
846
+ namespace WebPConvert\Options;
847
+
848
+ use WebPConvert\Options\Option;
849
+ use WebPConvert\Options\Exceptions\OptionNotFoundException;
850
+
851
+ /**
852
+ * Handles a collection of options.
853
+ *
854
+ * @package WebPConvert
855
+ * @author Bjørn Rosell <it@rosell.dk>
856
+ * @since Class available since Release 2.0.0
857
+ */
858
+ class Options
859
+ {
860
+
861
+ /** @var array A map of options, keyed by their id */
862
+ private $options = [];
863
+
864
+ /**
865
+ * Add option.
866
+ *
867
+ * @param Option $option The option object to add to collection.
868
+ * @return void
869
+ */
870
+ public function addOption($option)
871
+ {
872
+ $this->options[$option->getId()] = $option;
873
+ }
874
+
875
+ /**
876
+ * Add options.
877
+ *
878
+ * Conveniently add several options in one call.
879
+ *
880
+ * @param Option[] ...$options Array of options objects to add
881
+ * @return void
882
+ */
883
+ public function addOptions(...$options)
884
+ {
885
+ foreach ($options as $option) {
886
+ $this->addOption($option);
887
+ }
888
+ }
889
+
890
+ /**
891
+ * Set the value of an option.
892
+ *
893
+ * @param string $id Id of the option
894
+ * @param mixed $value Value of the option
895
+ * @return void
896
+ */
897
+ public function setOption($id, $value)
898
+ {
899
+ if (!isset($this->options[$id])) {
900
+ throw new OptionNotFoundException(
901
+ 'Could not set option. There is no option called "' . $id . '" in the collection.'
902
+ );
903
+ }
904
+ $option = $this->options[$id];
905
+ $option->setValue($value);
906
+ }
907
+
908
+ /**
909
+ * Set option, or create a new, if no such option exists.
910
+ *
911
+ * @param string $id Id of option to set/create
912
+ * @param mixed $value Value of option
913
+ * @return void
914
+ */
915
+ public function setOrCreateOption($id, $value)
916
+ {
917
+ if (!isset($this->options[$id])) {
918
+ $newOption = new GhostOption($id, null);
919
+ $newOption->setValue($value);
920
+ //$newOption = new Option($id, $value);
921
+ $this->addOption($newOption);
922
+ } else {
923
+ $this->setOption($id, $value);
924
+ }
925
+ }
926
+
927
+ /**
928
+ * Get the value of an option in the collection - by id.
929
+ *
930
+ * @param string $id Id of the option to get
931
+ * @throws OptionNotFoundException if the option is not in the collection
932
+ * @return mixed The value of the option
933
+ */
934
+ public function getOption($id)
935
+ {
936
+ if (!isset($this->options[$id])) {
937
+ throw new OptionNotFoundException(
938
+ 'There is no option called "' . $id . '" in the collection.'
939
+ );
940
+ }
941
+ $option = $this->options[$id];
942
+ return $option->getValue();
943
+ }
944
+
945
+ /**
946
+ * Return map of option objects.
947
+ *
948
+ * @return array map of option objects
949
+ */
950
+ public function getOptionsMap()
951
+ {
952
+ return $this->options;
953
+ }
954
+
955
+ /**
956
+ * Return flat associative array of options.
957
+ *
958
+ * @return array associative array of options
959
+ */
960
+ public function getOptions()
961
+ {
962
+ $values = [];
963
+ foreach ($this->options as $id => $option) {
964
+ $values[$id] = $option->getValue();
965
+ }
966
+ return $values;
967
+ }
968
+
969
+ /**
970
+ * Check all options in the collection.
971
+ */
972
+ public function check()
973
+ {
974
+ foreach ($this->options as $id => $option) {
975
+ $option->check();
976
+ }
977
+ }
978
+ }
979
+
980
+ ?><?php
981
+
982
+ namespace WebPConvert\Options;
983
+
984
+ use WebPConvert\Options\Option;
985
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
986
+
987
+ /**
988
+ * Quality option.
989
+ *
990
+ * Quality can be a number between 0-100 or "auto"
991
+ *
992
+ * @package WebPConvert
993
+ * @author Bjørn Rosell <it@rosell.dk>
994
+ * @since Class available since Release 2.0.0
995
+ */
996
+ class QualityOption extends Option
997
+ {
998
+
999
+ public function __construct($id, $defaultValue)
1000
+ {
1001
+ parent::__construct($id, $defaultValue);
1002
+ }
1003
+
1004
+ public function check()
1005
+ {
1006
+ $value = $this->getValue();
1007
+ if (gettype($value) == 'string') {
1008
+ if ($value != 'auto') {
1009
+ throw new InvalidOptionValueException(
1010
+ 'The "quality" option must be either "auto" or a number between 0-100. ' .
1011
+ 'A string, different from "auto" was given'
1012
+ );
1013
+ }
1014
+ } elseif (gettype($value) == 'integer') {
1015
+ if (($value < 0) || ($value > 100)) {
1016
+ throw new InvalidOptionValueException(
1017
+ 'The "quality" option must be either "auto" or a number between 0-100. ' .
1018
+ 'The number you provided (' . strval($value) . ') is out of range.'
1019
+ );
1020
+ }
1021
+ } else {
1022
+ throw new InvalidOptionValueException(
1023
+ 'The "quality" option must be either "auto" or an integer. ' .
1024
+ 'You however provided a value of type: ' . gettype($value)
1025
+ );
1026
+ }
1027
+ }
1028
+
1029
+ public function getValueForPrint()
1030
+ {
1031
+ if (gettype($this->getValue()) == 'string') {
1032
+ return '"' . $this->getValue() . '"';
1033
+ }
1034
+ return $this->getValue();
1035
+ }
1036
+ }
1037
+
1038
+ ?><?php
1039
+
1040
+ namespace WebPConvert\Options;
1041
+
1042
+ use WebPConvert\Options\StringOption;
1043
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
1044
+
1045
+ /**
1046
+ * Abstract option class
1047
+ *
1048
+ * @package WebPConvert
1049
+ * @author Bjørn Rosell <it@rosell.dk>
1050
+ * @since Class available since Release 2.0.0
1051
+ */
1052
+ class SensitiveArrayOption extends ArrayOption
1053
+ {
1054
+
1055
+ public function check()
1056
+ {
1057
+ parent::check();
1058
+ }
1059
+
1060
+ public function getValueForPrint()
1061
+ {
1062
+ if (count($this->getValue()) == 0) {
1063
+ return '(empty array)';
1064
+ } else {
1065
+ return '(array of ' . count($this->getValue()) . ' items)';
1066
+ }
1067
+ //return '*****';
1068
+ }
1069
+ }
1070
+
1071
+ ?><?php
1072
+
1073
+ namespace WebPConvert\Options;
1074
+
1075
+ use WebPConvert\Options\StringOption;
1076
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
1077
+
1078
+ /**
1079
+ * Abstract option class
1080
+ *
1081
+ * @package WebPConvert
1082
+ * @author Bjørn Rosell <it@rosell.dk>
1083
+ * @since Class available since Release 2.0.0
1084
+ */
1085
+ class SensitiveStringOption extends StringOption
1086
+ {
1087
+
1088
+ public function __construct($id, $defaultValue, $allowedValues = null)
1089
+ {
1090
+ parent::__construct($id, $defaultValue, $allowedValues);
1091
+ }
1092
+
1093
+ public function check()
1094
+ {
1095
+ parent::check();
1096
+ }
1097
+
1098
+ public function getValueForPrint()
1099
+ {
1100
+ if (strlen($this->getValue()) == 0) {
1101
+ return '""';
1102
+ }
1103
+ return '*****';
1104
+ }
1105
+ }
1106
+
1107
+ ?><?php
1108
+
1109
+ namespace WebPConvert\Options;
1110
+
1111
+ use WebPConvert\Options\Option;
1112
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
1113
+
1114
+ /**
1115
+ * Abstract option class
1116
+ *
1117
+ * @package WebPConvert
1118
+ * @author Bjørn Rosell <it@rosell.dk>
1119
+ * @since Class available since Release 2.0.0
1120
+ */
1121
+ class StringOption extends Option
1122
+ {
1123
+
1124
+ public $allowedValues;
1125
+
1126
+ public function __construct($id, $defaultValue, $allowedValues = null)
1127
+ {
1128
+ $this->allowedValues = $allowedValues;
1129
+ parent::__construct($id, $defaultValue);
1130
+ }
1131
+
1132
+ public function check()
1133
+ {
1134
+ $this->checkType('string');
1135
+
1136
+ if (!is_null($this->allowedValues) && (!in_array($this->getValue(), $this->allowedValues))) {
1137
+ throw new InvalidOptionValueException(
1138
+ '"' . $this->id . '" option must be on of these values: ' .
1139
+ '[' . implode(', ', $this->allowedValues) . ']. ' .
1140
+ 'It was however set to: "' . $this->getValue() . '"'
1141
+ );
1142
+ }
1143
+ }
1144
+
1145
+ public function getValueForPrint()
1146
+ {
1147
+ return '"' . $this->getValue() . '"';
1148
+ }
1149
+ }
1150
+
1151
+ ?><?php
1152
+
1153
+ namespace WebPConvert\Convert\Converters\BaseTraits;
1154
+
1155
+ use WebPConvert\Convert\Helpers\JpegQualityDetector;
1156
+
1157
+ /**
1158
+ * Trait for handling the "quality:auto" option.
1159
+ *
1160
+ * This trait is only used in the AbstractConverter class. It has been extracted into a
1161
+ * trait in order to bundle the methods concerning auto quality.
1162
+ *
1163
+ * @package WebPConvert
1164
+ * @author Bjørn Rosell <it@rosell.dk>
1165
+ * @since Class available since Release 2.0.0
1166
+ */
1167
+ trait AutoQualityTrait
1168
+ {
1169
+
1170
+ abstract public function logLn($msg, $style = '');
1171
+ abstract public function getMimeTypeOfSource();
1172
+
1173
+ /** @var boolean Whether the quality option has been processed or not */
1174
+ private $processed = false;
1175
+
1176
+ /** @var boolean Whether the quality of the source could be detected or not (set upon processing) */
1177
+ private $qualityCouldNotBeDetected = false;
1178
+
1179
+ /** @var integer The calculated quality (set upon processing - on successful detection) */
1180
+ private $calculatedQuality;
1181
+
1182
+
1183
+ /**
1184
+ * Determine if quality detection is required but failing.
1185
+ *
1186
+ * It is considered "required" when:
1187
+ * - Mime type is "image/jpeg"
1188
+ * - Quality is set to "auto"
1189
+ *
1190
+ * If quality option hasn't been proccessed yet, it is triggered.
1191
+ *
1192
+ * @return boolean
1193
+ */
1194
+ public function isQualityDetectionRequiredButFailing()
1195
+ {
1196
+ $this->processQualityOptionIfNotAlready();
1197
+ return $this->qualityCouldNotBeDetected;
1198
+ }
1199
+
1200
+ /**
1201
+ * Get calculated quality.
1202
+ *
1203
+ * If the "quality" option is a number, that number is returned.
1204
+ * If mime type of source is something else than "image/jpeg", the "default-quality" option is returned
1205
+ * If quality is "auto" and source is a jpeg image, it will be attempted to detect jpeg quality.
1206
+ * In case of failure, the value of the "default-quality" option is returned.
1207
+ * In case of success, the detected quality is returned, or the value of the "max-quality" if that is lower.
1208
+ *
1209
+ * @return int
1210
+ */
1211
+ public function getCalculatedQuality()
1212
+ {
1213
+ $this->processQualityOptionIfNotAlready();
1214
+ return $this->calculatedQuality;
1215
+ }
1216
+
1217
+ /**
1218
+ * Process the quality option if it is not already processed.
1219
+ *
1220
+ * @return void
1221
+ */
1222
+ private function processQualityOptionIfNotAlready()
1223
+ {
1224
+ if (!$this->processed) {
1225
+ $this->processed = true;
1226
+ $this->processQualityOption();
1227
+ }
1228
+ }
1229
+
1230
+ /**
1231
+ * Process the quality option.
1232
+ *
1233
+ * Sets the private property "calculatedQuality" according to the description for the getCalculatedQuality
1234
+ * function.
1235
+ * In case quality detection was attempted and failed, the private property "qualityCouldNotBeDetected" is set
1236
+ * to true. This is used by the "isQualityDetectionRequiredButFailing" (and documented there too).
1237
+ *
1238
+ * @return void
1239
+ */
1240
+ private function processQualityOption()
1241
+ {
1242
+ $options = $this->options;
1243
+ $source = $this->source;
1244
+
1245
+ $q = $options['quality'];
1246
+ if ($q == 'auto') {
1247
+ if (($this->/** @scrutinizer ignore-call */getMimeTypeOfSource() == 'image/jpeg')) {
1248
+ $q = JpegQualityDetector::detectQualityOfJpg($source);
1249
+ if (is_null($q)) {
1250
+ $q = $options['default-quality'];
1251
+ $this->/** @scrutinizer ignore-call */logLn(
1252
+ 'Quality of source could not be established (Imagick or GraphicsMagick is required)' .
1253
+ ' - Using default instead (' . $options['default-quality'] . ').'
1254
+ );
1255
+
1256
+ $this->qualityCouldNotBeDetected = true;
1257
+ } else {
1258
+ if ($q > $options['max-quality']) {
1259
+ $this->logLn(
1260
+ 'Quality of source is ' . $q . '. ' .
1261
+ 'This is higher than max-quality, so using max-quality instead (' .
1262
+ $options['max-quality'] . ')'
1263
+ );
1264
+ } else {
1265
+ $this->logLn('Quality set to same as source: ' . $q);
1266
+ }
1267
+ }
1268
+ $q = min($q, $options['max-quality']);
1269
+ } else {
1270
+ //$q = $options['default-quality'];
1271
+ $q = min($options['default-quality'], $options['max-quality']);
1272
+ $this->logLn('Quality: ' . $q . '. ');
1273
+ }
1274
+ } else {
1275
+ $this->logLn(
1276
+ 'Quality: ' . $q . '. '
1277
+ );
1278
+ if (($this->getMimeTypeOfSource() == 'image/jpeg')) {
1279
+ $this->logLn(
1280
+ 'Consider setting quality to "auto" instead. It is generally a better idea'
1281
+ );
1282
+ }
1283
+ }
1284
+ $this->calculatedQuality = $q;
1285
+ }
1286
+ }
1287
+
1288
+ ?><?php
1289
+
1290
+ namespace WebPConvert\Convert\Converters\BaseTraits;
1291
+
1292
+ use WebPConvert\Convert\Exceptions\ConversionFailed\FileSystemProblems\CreateDestinationFileException;
1293
+ use WebPConvert\Convert\Exceptions\ConversionFailed\FileSystemProblems\CreateDestinationFolderException;
1294
+
1295
+ /**
1296
+ * Trait for handling options
1297
+ *
1298
+ * This trait is currently only used in the AbstractConverter class. It has been extracted into a
1299
+ * trait in order to bundle the methods concerning options.
1300
+ *
1301
+ * @package WebPConvert
1302
+ * @author Bjørn Rosell <it@rosell.dk>
1303
+ * @since Class available since Release 2.0.0
1304
+ */
1305
+ trait DestinationPreparationTrait
1306
+ {
1307
+
1308
+ abstract public function getDestination();
1309
+ abstract public function logLn($msg, $style = '');
1310
+
1311
+ /**
1312
+ * Create writable folder in provided path (if it does not exist already)
1313
+ *
1314
+ * @throws CreateDestinationFolderException if folder cannot be removed
1315
+ * @return void
1316
+ */
1317
+ private function createWritableDestinationFolder()
1318
+ {
1319
+ $destination = $this->getDestination();
1320
+
1321
+ $folder = dirname($destination);
1322
+ if (!file_exists($folder)) {
1323
+ $this->logLn('Destination folder does not exist. Creating folder: ' . $folder);
1324
+ // TODO: what if this is outside open basedir?
1325
+ // see http://php.net/manual/en/ini.core.php#ini.open-basedir
1326
+
1327
+ // Trying to create the given folder (recursively)
1328
+ if (!mkdir($folder, 0777, true)) {
1329
+ throw new CreateDestinationFolderException(
1330
+ 'Failed creating folder. Check the permissions!',
1331
+ 'Failed creating folder: ' . $folder . '. Check permissions!'
1332
+ );
1333
+ }
1334
+ }
1335
+ }
1336
+
1337
+ /**
1338
+ * Check that we can write file at destination.
1339
+ *
1340
+ * It is assumed that the folder already exists (that ::createWritableDestinationFolder() was called first)
1341
+ *
1342
+ * @throws CreateDestinationFileException if file cannot be created at destination
1343
+ * @return void
1344
+ */
1345
+ private function checkDestinationWritable()
1346
+ {
1347
+ $destination = $this->getDestination();
1348
+ $dirName = dirname($destination);
1349
+
1350
+ if (@is_writable($dirName) && @is_executable($dirName)) {
1351
+ // all is well
1352
+ return;
1353
+ }
1354
+
1355
+ // The above might fail on Windows, even though dir is writable
1356
+ // So, to be absolute sure that we cannot write, we make an actual write test (writing a dummy file)
1357
+ // No harm in doing that for non-Windows systems either.
1358
+ if (file_put_contents($destination, 'dummy') !== false) {
1359
+ // all is well, after all
1360
+ unlink($destination);
1361
+ return;
1362
+ }
1363
+
1364
+ throw new CreateDestinationFileException(
1365
+ 'Cannot create file: ' . basename($destination) . ' in dir:' . dirname($destination)
1366
+ );
1367
+ }
1368
+
1369
+ /**
1370
+ * Remove existing destination.
1371
+ *
1372
+ * @throws CreateDestinationFileException if file cannot be removed
1373
+ * @return void
1374
+ */
1375
+ private function removeExistingDestinationIfExists()
1376
+ {
1377
+ $destination = $this->getDestination();
1378
+ if (file_exists($destination)) {
1379
+ // A file already exists in this folder...
1380
+ // We delete it, to make way for a new webp
1381
+ if (!unlink($destination)) {
1382
+ throw new CreateDestinationFileException(
1383
+ 'Existing file cannot be removed: ' . basename($destination)
1384
+ );
1385
+ }
1386
+ }
1387
+ }
1388
+ }
1389
+
1390
+ ?><?php
1391
+
1392
+ namespace WebPConvert\Convert\Converters\BaseTraits;
1393
+
1394
+ /**
1395
+ * Trait for providing logging capabilities.
1396
+ *
1397
+ * This trait is currently only used in the AbstractConverter class. It has been extracted into a
1398
+ * trait in order to bundle the methods concerning logging.
1399
+ *
1400
+ * @package WebPConvert
1401
+ * @author Bjørn Rosell <it@rosell.dk>
1402
+ * @since Class available since Release 2.0.0
1403
+ */
1404
+ trait LoggerTrait
1405
+ {
1406
+
1407
+ /** @var \WebPConvert\Loggers\BaseLogger The logger (or null if not set) */
1408
+ protected $logger;
1409
+
1410
+ /**
1411
+ * Set logger
1412
+ *
1413
+ * @param \WebPConvert\Loggers\BaseLogger $logger (optional) $logger
1414
+ * @return void
1415
+ */
1416
+ public function setLogger($logger = null)
1417
+ {
1418
+ $this->logger = $logger;
1419
+ }
1420
+
1421
+ /**
1422
+ * Write a line to the logger.
1423
+ *
1424
+ * @param string $msg The line to write.
1425
+ * @param string $style (optional) Ie "italic" or "bold"
1426
+ * @return void
1427
+ */
1428
+ protected function logLn($msg, $style = '')
1429
+ {
1430
+ if (isset($this->logger)) {
1431
+ $this->logger->logLn($msg, $style);
1432
+ }
1433
+ }
1434
+
1435
+ /**
1436
+ * New line
1437
+ *
1438
+ * @return void
1439
+ */
1440
+ protected function ln()
1441
+ {
1442
+ if (isset($this->logger)) {
1443
+ $this->logger->ln();
1444
+ }
1445
+ }
1446
+
1447
+ /**
1448
+ * Write to the logger, without newline
1449
+ *
1450
+ * @param string $msg What to write.
1451
+ * @param string $style (optional) Ie "italic" or "bold"
1452
+ * @return void
1453
+ */
1454
+ protected function log($msg, $style = '')
1455
+ {
1456
+ if (isset($this->logger)) {
1457
+ $this->logger->log($msg, $style);
1458
+ }
1459
+ }
1460
+ }
1461
+
1462
+ ?><?php
1463
+
1464
+ namespace WebPConvert\Convert\Converters\BaseTraits;
1465
+
1466
+ use WebPConvert\Convert\Converters\Stack;
1467
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConversionSkippedException;
1468
+ use WebPConvert\Options\Exceptions\InvalidOptionValueException;
1469
+ use WebPConvert\Options\Exceptions\InvalidOptionTypeException;
1470
+
1471
+ use WebPConvert\Options\ArrayOption;
1472
+ use WebPConvert\Options\BooleanOption;
1473
+ use WebPConvert\Options\GhostOption;
1474
+ use WebPConvert\Options\IntegerOption;
1475
+ use WebPConvert\Options\IntegerOrNullOption;
1476
+ use WebPConvert\Options\MetadataOption;
1477
+ use WebPConvert\Options\Options;
1478
+ use WebPConvert\Options\StringOption;
1479
+ use WebPConvert\Options\QualityOption;
1480
+
1481
+ /**
1482
+ * Trait for handling options
1483
+ *
1484
+ * This trait is currently only used in the AbstractConverter class. It has been extracted into a
1485
+ * trait in order to bundle the methods concerning options.
1486
+ *
1487
+ * @package WebPConvert
1488
+ * @author Bjørn Rosell <it@rosell.dk>
1489
+ * @since Class available since Release 2.0.0
1490
+ */
1491
+ trait OptionsTrait
1492
+ {
1493
+
1494
+ abstract public function log($msg, $style = '');
1495
+ abstract public function logLn($msg, $style = '');
1496
+ abstract protected function getMimeTypeOfSource();
1497
+
1498
+ /** @var array Provided conversion options */
1499
+ public $providedOptions;
1500
+
1501
+ /** @var array Calculated conversion options (merge of default options and provided options)*/
1502
+ protected $options;
1503
+
1504
+ /** @var Options */
1505
+ protected $options2;
1506
+
1507
+
1508
+ /**
1509
+ * Create options.
1510
+ *
1511
+ * The options created here will be available to all converters.
1512
+ * Individual converters may add options by overriding this method.
1513
+ *
1514
+ * @return void
1515
+ */
1516
+ protected function createOptions()
1517
+ {
1518
+ $isPng = ($this->getMimeTypeOfSource() == 'image/png');
1519
+
1520
+ $this->options2 = new Options();
1521
+ $this->options2->addOptions(
1522
+ new IntegerOption('alpha-quality', 85, 0, 100),
1523
+ new BooleanOption('auto-filter', false),
1524
+ new IntegerOption('default-quality', ($isPng ? 85 : 75), 0, 100),
1525
+ new StringOption('encoding', 'auto', ['lossy', 'lossless', 'auto']),
1526
+ new BooleanOption('low-memory', false),
1527
+ new BooleanOption('log-call-arguments', false),
1528
+ new IntegerOption('max-quality', 85, 0, 100),
1529
+ new MetadataOption('metadata', 'none'),
1530
+ new IntegerOption('method', 6, 0, 6),
1531
+ new IntegerOption('near-lossless', 60, 0, 100),
1532
+ new StringOption('preset', 'none', ['none', 'default', 'photo', 'picture', 'drawing', 'icon', 'text']),
1533
+ new QualityOption('quality', ($isPng ? 85 : 'auto')),
1534
+ new IntegerOrNullOption('size-in-percentage', null, 0, 100),
1535
+ new BooleanOption('skip', false),
1536
+ new BooleanOption('use-nice', false),
1537
+ new ArrayOption('jpeg', []),
1538
+ new ArrayOption('png', [])
1539
+ );
1540
+ }
1541
+
1542
+ /**
1543
+ * Set "provided options" (options provided by the user when calling convert().
1544
+ *
1545
+ * This also calculates the protected options array, by merging in the default options, merging
1546
+ * jpeg and png options and merging prefixed options (such as 'vips-quality').
1547
+ * The resulting options array are set in the protected property $this->options and can be
1548
+ * retrieved using the public ::getOptions() function.
1549
+ *
1550
+ * @param array $providedOptions (optional)
1551
+ * @return void
1552
+ */
1553
+ public function setProvidedOptions($providedOptions = [])
1554
+ {
1555
+ $this->createOptions();
1556
+
1557
+ $this->providedOptions = $providedOptions;
1558
+
1559
+ if (isset($this->providedOptions['png'])) {
1560
+ if ($this->getMimeTypeOfSource() == 'image/png') {
1561
+ $this->providedOptions = array_merge($this->providedOptions, $this->providedOptions['png']);
1562
+ // $this->logLn(print_r($this->providedOptions, true));
1563
+ unset($this->providedOptions['png']);
1564
+ }
1565
+ }
1566
+
1567
+ if (isset($this->providedOptions['jpeg'])) {
1568
+ if ($this->getMimeTypeOfSource() == 'image/jpeg') {
1569
+ $this->providedOptions = array_merge($this->providedOptions, $this->providedOptions['jpeg']);
1570
+ unset($this->providedOptions['jpeg']);
1571
+ }
1572
+ }
1573
+
1574
+ // merge down converter-prefixed options
1575
+ $converterId = self::getConverterId();
1576
+ $strLen = strlen($converterId);
1577
+ foreach ($this->providedOptions as $optionKey => $optionValue) {
1578
+ if (substr($optionKey, 0, $strLen + 1) == ($converterId . '-')) {
1579
+ $this->providedOptions[substr($optionKey, $strLen + 1)] = $optionValue;
1580
+ }
1581
+ }
1582
+
1583
+ // Create options (Option objects)
1584
+ foreach ($this->providedOptions as $optionId => $optionValue) {
1585
+ $this->options2->setOrCreateOption($optionId, $optionValue);
1586
+ }
1587
+ //$this->logLn(print_r($this->options2->getOptions(), true));
1588
+ //$this->logLn($this->options2->getOption('hello'));
1589
+
1590
+ // Create flat associative array of options
1591
+ $this->options = $this->options2->getOptions();
1592
+
1593
+ // - Merge $defaultOptions into provided options
1594
+ //$this->options = array_merge($this->getDefaultOptions(), $this->providedOptions);
1595
+
1596
+ //$this->logOptions();
1597
+ }
1598
+
1599
+ /**
1600
+ * Get the resulting options after merging provided options with default options.
1601
+ *
1602
+ * Note that the defaults depends on the mime type of the source. For example, the default value for quality
1603
+ * is "auto" for jpegs, and 85 for pngs.
1604
+ *
1605
+ * @return array An associative array of options: ['metadata' => 'none', ...]
1606
+ */
1607
+ public function getOptions()
1608
+ {
1609
+ return $this->options;
1610
+ }
1611
+
1612
+ /**
1613
+ * Change an option specifically.
1614
+ *
1615
+ * This method is probably rarely neeeded. We are using it to change the "encoding" option temporarily
1616
+ * in the EncodingAutoTrait.
1617
+ *
1618
+ * @param string $id Id of option (ie "metadata")
1619
+ * @param mixed $value The new value.
1620
+ * @return void
1621
+ */
1622
+ protected function setOption($id, $value)
1623
+ {
1624
+ $this->options[$id] = $value;
1625
+ $this->options2->setOrCreateOption($id, $value);
1626
+ }
1627
+
1628
+ /**
1629
+ * Check options.
1630
+ *
1631
+ * @throws InvalidOptionTypeException if an option have wrong type
1632
+ * @throws InvalidOptionValueException if an option value is out of range
1633
+ * @throws ConversionSkippedException if 'skip' option is set to true
1634
+ * @return void
1635
+ */
1636
+ protected function checkOptions()
1637
+ {
1638
+ $this->options2->check();
1639
+
1640
+ if ($this->options['skip']) {
1641
+ if (($this->getMimeTypeOfSource() == 'image/png') && isset($this->options['png']['skip'])) {
1642
+ throw new ConversionSkippedException(
1643
+ 'skipped conversion (configured to do so for PNG)'
1644
+ );
1645
+ } else {
1646
+ throw new ConversionSkippedException(
1647
+ 'skipped conversion (configured to do so)'
1648
+ );
1649
+ }
1650
+ }
1651
+ }
1652
+
1653
+ public function logOptions()
1654
+ {
1655
+ $this->logLn('');
1656
+ $this->logLn('Options:');
1657
+ $this->logLn('------------');
1658
+ $this->logLn(
1659
+ 'The following options have been set explicitly. ' .
1660
+ 'Note: it is the resulting options after merging down the "jpeg" and "png" options and any ' .
1661
+ 'converter-prefixed options.'
1662
+ );
1663
+ $this->logLn('- source: ' . $this->source);
1664
+ $this->logLn('- destination: ' . $this->destination);
1665
+
1666
+ $unsupported = $this->getUnsupportedDefaultOptions();
1667
+ //$this->logLn('Unsupported:' . print_r($this->getUnsupportedDefaultOptions(), true));
1668
+ $ignored = [];
1669
+ $implicit = [];
1670
+ foreach ($this->options2->getOptionsMap() as $id => $option) {
1671
+ if (($id == 'png') || ($id == 'jpeg')) {
1672
+ continue;
1673
+ }
1674
+ if ($option->isValueExplicitlySet()) {
1675
+ if (($option instanceof GhostOption) || in_array($id, $unsupported)) {
1676
+ //$this->log(' (note: this option is ignored by this converter)');
1677
+ if (($id != '_skip_input_check') && ($id != '_suppress_success_message')) {
1678
+ $ignored[] = $option;
1679
+ }
1680
+ } else {
1681
+ $this->log('- ' . $id . ': ');
1682
+ $this->log($option->getValueForPrint());
1683
+ $this->logLn('');
1684
+ }
1685
+ } else {
1686
+ if (($option instanceof GhostOption) || in_array($id, $unsupported)) {
1687
+ } else {
1688
+ $implicit[] = $option;
1689
+ }
1690
+ }
1691
+ }
1692
+
1693
+ if (count($implicit) > 0) {
1694
+ $this->logLn('');
1695
+ $this->logLn(
1696
+ 'The following options have not been explicitly set, so using the following defaults:'
1697
+ );
1698
+ foreach ($implicit as $option) {
1699
+ $this->log('- ' . $option->getId() . ': ');
1700
+ $this->log($option->getValueForPrint());
1701
+ $this->logLn('');
1702
+ }
1703
+ }
1704
+ if (count($ignored) > 0) {
1705
+ $this->logLn('');
1706
+ if ($this instanceof Stack) {
1707
+ $this->logLn(
1708
+ 'The following options were supplied and are passed on to the converters in the stack:'
1709
+ );
1710
+ foreach ($ignored as $option) {
1711
+ $this->log('- ' . $option->getId() . ': ');
1712
+ $this->log($option->getValueForPrint());
1713
+ $this->logLn('');
1714
+ }
1715
+ } else {
1716
+ $this->logLn(
1717
+ 'The following options were supplied but are ignored because they are not supported by this ' .
1718
+ 'converter:'
1719
+ );
1720
+ foreach ($ignored as $option) {
1721
+ $this->logLn('- ' . $option->getId());
1722
+ }
1723
+ }
1724
+ }
1725
+ $this->logLn('------------');
1726
+ }
1727
+
1728
+ // to be overridden by converters
1729
+ protected function getUnsupportedDefaultOptions()
1730
+ {
1731
+ return [];
1732
+ }
1733
+ }
1734
+
1735
+ ?><?php
1736
+
1737
+ namespace WebPConvert\Convert\Converters\BaseTraits;
1738
+
1739
+ use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\TargetNotFoundException;
1740
+ use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\InvalidImageTypeException;
1741
+
1742
+ /**
1743
+ * Trait for handling options
1744
+ *
1745
+ * This trait is currently only used in the AbstractConverter class. It has been extracted into a
1746
+ * trait in order to bundle the methods concerning options.
1747
+ *
1748
+ * @package WebPConvert
1749
+ * @author Bjørn Rosell <it@rosell.dk>
1750
+ * @since Class available since Release 2.0.0
1751
+ */
1752
+ trait SourceValidationTrait
1753
+ {
1754
+
1755
+ abstract protected function getMimeTypeOfSource();
1756
+ abstract public function getSource();
1757
+
1758
+ /** @var array Array of allowed mime types for source. */
1759
+ public static $allowedMimeTypes = ['image/jpeg', 'image/png'];
1760
+
1761
+ /**
1762
+ * Check that source file exists.
1763
+ *
1764
+ * Note: As the input validations are only run one time in a stack,
1765
+ * this method is not overridable
1766
+ *
1767
+ * @throws TargetNotFoundException
1768
+ * @return void
1769
+ */
1770
+ private function checkSourceExists()
1771
+ {
1772
+ // Check if source exists
1773
+ if (!@file_exists($this->getSource())) {
1774
+ throw new TargetNotFoundException('File or directory not found: ' . $this->getSource());
1775
+ }
1776
+ }
1777
+
1778
+ /**
1779
+ * Check that source has a valid mime type.
1780
+ *
1781
+ * Note: As the input validations are only run one time in a stack,
1782
+ * this method is not overridable
1783
+ *
1784
+ * @throws InvalidImageTypeException If mime type could not be detected or is unsupported
1785
+ * @return void
1786
+ */
1787
+ private function checkSourceMimeType()
1788
+ {
1789
+ $fileMimeType = $this->getMimeTypeOfSource();
1790
+ if (is_null($fileMimeType)) {
1791
+ throw new InvalidImageTypeException('Image type could not be detected');
1792
+ } elseif ($fileMimeType === false) {
1793
+ throw new InvalidImageTypeException('File seems not to be an image.');
1794
+ } elseif (!in_array($fileMimeType, self::$allowedMimeTypes)) {
1795
+ throw new InvalidImageTypeException('Unsupported mime type: ' . $fileMimeType);
1796
+ }
1797
+ }
1798
+ }
1799
+
1800
+ ?><?php
1801
+
1802
+ namespace WebPConvert\Convert\Converters\BaseTraits;
1803
+
1804
+ /**
1805
+ * Trait for handling warnings (by logging them)
1806
+ *
1807
+ * This trait is currently only used in the AbstractConverter class. It has been extracted into a
1808
+ * trait in order to bundle the methods concerning options.
1809
+ *
1810
+ * @package WebPConvert
1811
+ * @author Bjørn Rosell <it@rosell.dk>
1812
+ * @since Class available since Release 2.0.0
1813
+ */
1814
+ trait WarningLoggerTrait
1815
+ {
1816
+ abstract protected function logLn($msg, $style = '');
1817
+
1818
+ /** @var string|array|null Previous error handler (stored in order to be able pass warnings on) */
1819
+ private $previousErrorHandler;
1820
+
1821
+ /**
1822
+ * Handle warnings and notices during conversion by logging them and passing them on.
1823
+ *
1824
+ * The function is a callback used with "set_error_handler".
1825
+ * It is declared public because it needs to be accessible from the point where the warning happened.
1826
+ *
1827
+ * @param integer $errno
1828
+ * @param string $errstr
1829
+ * @param string $errfile
1830
+ * @param integer $errline
1831
+ *
1832
+ * @return false|null
1833
+ */
1834
+ public function warningHandler($errno, $errstr, $errfile, $errline)
1835
+ {
1836
+ /*
1837
+ We do NOT do the following (even though it is generally recommended):
1838
+
1839
+ if (!(error_reporting() & $errno)) {
1840
+ // This error code is not included in error_reporting, so let it fall
1841
+ // through to the standard PHP error handler
1842
+ return false;
1843
+ }
1844
+
1845
+ - Because we want to log all warnings and errors (also the ones that was suppressed with @)
1846
+ https://secure.php.net/manual/en/language.operators.errorcontrol.php
1847
+ */
1848
+
1849
+ $errorTypes = [
1850
+ E_WARNING => "Warning",
1851
+ E_NOTICE => "Notice",
1852
+ E_STRICT => "Strict Notice",
1853
+ E_DEPRECATED => "Deprecated",
1854
+ E_USER_DEPRECATED => "User Deprecated",
1855
+
1856
+ /*
1857
+ The following can never be catched by a custom error handler:
1858
+ E_PARSE, E_ERROR, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING
1859
+
1860
+ We do do not currently trigger the following:
1861
+ E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE
1862
+
1863
+ But we may want to do that at some point, like this:
1864
+ trigger_error('Your version of Gd is very old', E_USER_WARNING);
1865
+ in that case, remember to add them to this array
1866
+ */
1867
+ ];
1868
+
1869
+ if (isset($errorTypes[$errno])) {
1870
+ $errType = $errorTypes[$errno];
1871
+ } else {
1872
+ $errType = "Unknown error/warning/notice ($errno)";
1873
+ }
1874
+
1875
+ $msg = $errType . ': ' . $errstr . ' in ' . $errfile . ', line ' . $errline . ', PHP ' . PHP_VERSION .
1876
+ ' (' . PHP_OS . ')';
1877
+ $this->logLn('');
1878
+ $this->logLn($msg, 'italic');
1879
+ $this->logLn('');
1880
+
1881
+ //echo 'previously defined handler:' . print_r($this->previousErrorHandler, true);
1882
+
1883
+ if (!is_null($this->previousErrorHandler)) {
1884
+ return call_user_func($this->previousErrorHandler, $errno, $errstr, $errfile, $errline);
1885
+ } else {
1886
+ return false;
1887
+ }
1888
+ }
1889
+
1890
+ /**
1891
+ * Activate warning logger.
1892
+ *
1893
+ * Sets the error handler and stores the previous so our error handler can bubble up warnings
1894
+ *
1895
+ * @return void
1896
+ */
1897
+ protected function activateWarningLogger()
1898
+ {
1899
+ $this->previousErrorHandler = set_error_handler(
1900
+ array($this, "warningHandler"),
1901
+ E_WARNING | E_USER_WARNING | E_NOTICE | E_USER_NOTICE
1902
+ );
1903
+ }
1904
+
1905
+ /**
1906
+ * Deactivate warning logger.
1907
+ *
1908
+ * Restores the previous error handler.
1909
+ *
1910
+ * @return void
1911
+ */
1912
+ protected function deactivateWarningLogger()
1913
+ {
1914
+ restore_error_handler();
1915
+ }
1916
+ }
1917
+
1918
+ ?><?php
1919
+
1920
+ namespace WebPConvert\Convert\Converters\ConverterTraits;
1921
+
1922
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
1923
+ use WebPConvert\Convert\Converters\AbstractConverter;
1924
+ use WebPConvert\Convert\Helpers\PhpIniSizes;
1925
+
1926
+ /**
1927
+ * Trait for converters that works by uploading to a cloud service.
1928
+ *
1929
+ * The trait adds a method for checking against upload limits.
1930
+ *
1931
+ * @package WebPConvert
1932
+ * @author Bjørn Rosell <it@rosell.dk>
1933
+ * @since Class available since Release 2.0.0
1934
+ */
1935
+ trait CloudConverterTrait
1936
+ {
1937
+
1938
+ /**
1939
+ * Test that filesize is below "upload_max_filesize" and "post_max_size" values in php.ini.
1940
+ *
1941
+ * @param string $iniSettingId Id of ini setting (ie "upload_max_filesize")
1942
+ *
1943
+ * @throws ConversionFailedException if filesize is larger than the ini setting
1944
+ * @return void
1945
+ */
1946
+ private function checkFileSizeVsIniSetting($iniSettingId)
1947
+ {
1948
+ $fileSize = @filesize($this->source);
1949
+ if ($fileSize === false) {
1950
+ return;
1951
+ }
1952
+ $sizeInIni = PhpIniSizes::getIniBytes($iniSettingId);
1953
+ if ($sizeInIni === false) {
1954
+ // Not sure if we should throw an exception here, or not...
1955
+ return;
1956
+ }
1957
+ if ($sizeInIni < $fileSize) {
1958
+ throw new ConversionFailedException(
1959
+ 'File is larger than your ' . $iniSettingId . ' (set in your php.ini). File size:' .
1960
+ round($fileSize/1024) . ' kb. ' .
1961
+ $iniSettingId . ' in php.ini: ' . ini_get($iniSettingId) .
1962
+ ' (parsed as ' . round($sizeInIni/1024) . ' kb)'
1963
+ );
1964
+ }
1965
+ }
1966
+
1967
+ /**
1968
+ * Check convertability of cloud converters (that file is not bigger than limits set in php.ini).
1969
+ *
1970
+ * Performs the same as ::Convertability(). It is here so converters that overrides the
1971
+ * ::Convertability() still has a chance to do the checks.
1972
+ *
1973
+ * @throws ConversionFailedException if filesize is larger than "upload_max_filesize" or "post_max_size"
1974
+ * @return void
1975
+ */
1976
+ public function checkConvertabilityCloudConverterTrait()
1977
+ {
1978
+ $this->checkFileSizeVsIniSetting('upload_max_filesize');
1979
+ $this->checkFileSizeVsIniSetting('post_max_size');
1980
+ }
1981
+
1982
+ /**
1983
+ * Check convertability of cloud converters (file upload limits).
1984
+ */
1985
+ public function checkConvertability()
1986
+ {
1987
+ $this->checkConvertabilityCloudConverterTrait();
1988
+ }
1989
+ }
1990
+
1991
+ ?><?php
1992
+
1993
+ namespace WebPConvert\Convert\Converters\ConverterTraits;
1994
+
1995
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
1996
+ use WebPConvert\Convert\Converters\AbstractConverter;
1997
+
1998
+ /**
1999
+ * Trait for converters that works by uploading to a cloud service.
2000
+ *
2001
+ * The trait adds a method for checking against upload limits.
2002
+ *
2003
+ * @package WebPConvert
2004
+ * @author Bjørn Rosell <it@rosell.dk>
2005
+ * @since Class available since Release 2.0.0
2006
+ */
2007
+ trait CurlTrait
2008
+ {
2009
+
2010
+ /**
2011
+ * Check basis operationality for converters relying on curl.
2012
+ *
2013
+ * Performs the same as ::checkOperationality(). It is here so converters that overrides the
2014
+ * ::checkOperationality() still has a chance to do the checks.
2015
+ *
2016
+ * @throws SystemRequirementsNotMetException
2017
+ * @return void
2018
+ */
2019
+ public function checkOperationalityForCurlTrait()
2020
+ {
2021
+ if (!extension_loaded('curl')) {
2022
+ throw new SystemRequirementsNotMetException('Required cURL extension is not available.');
2023
+ }
2024
+
2025
+ if (!function_exists('curl_init')) {
2026
+ throw new SystemRequirementsNotMetException('Required url_init() function is not available.');
2027
+ }
2028
+
2029
+ if (!function_exists('curl_file_create')) {
2030
+ throw new SystemRequirementsNotMetException(
2031
+ 'Required curl_file_create() function is not available (requires PHP > 5.5).'
2032
+ );
2033
+ }
2034
+ }
2035
+
2036
+ /**
2037
+ * Check basis operationality for converters relying on curl
2038
+ *
2039
+ * @throws SystemRequirementsNotMetException
2040
+ * @return void
2041
+ */
2042
+ public function checkOperationality()
2043
+ {
2044
+ $this->checkOperationalityForCurlTrait();
2045
+ }
2046
+
2047
+ /**
2048
+ * Init curl.
2049
+ *
2050
+ * @throws SystemRequirementsNotMetException if curl could not be initialized
2051
+ * @return resource curl handle
2052
+ */
2053
+ protected static function initCurl()
2054
+ {
2055
+ // Get curl handle
2056
+ $ch = curl_init();
2057
+ if ($ch === false) {
2058
+ throw new SystemRequirementsNotMetException('Could not initialise cURL.');
2059
+ }
2060
+ return $ch;
2061
+ }
2062
+ }
2063
+
2064
+ ?><?php
2065
+
2066
+ //namespace WebPConvert\Convert\Converters\BaseTraits;
2067
+ namespace WebPConvert\Convert\Converters\ConverterTraits;
2068
+
2069
+ /**
2070
+ * Trait for converters that supports lossless encoding and thus the "lossless:auto" option.
2071
+ *
2072
+ * @package WebPConvert
2073
+ * @author Bjørn Rosell <it@rosell.dk>
2074
+ * @since Class available since Release 2.0.0
2075
+ */
2076
+ trait EncodingAutoTrait
2077
+ {
2078
+
2079
+ abstract protected function doActualConvert();
2080
+ abstract public function getSource();
2081
+ abstract public function getDestination();
2082
+ abstract public function setDestination($destination);
2083
+ abstract public function getOptions();
2084
+ abstract protected function setOption($optionName, $optionValue);
2085
+ abstract protected function logLn($msg, $style = '');
2086
+ abstract protected function ln();
2087
+
2088
+ public function supportsLossless()
2089
+ {
2090
+ return true;
2091
+ }
2092
+
2093
+ /** Default is to not pass "lossless:auto" on, but implement it.
2094
+ *
2095
+ * The Stack converter passes it on (it does not even use this trait)
2096
+ * WPC currently implements it, but this might be configurable in the future.
2097
+ *
2098
+ */
2099
+ public function passOnEncodingAuto()
2100
+ {
2101
+ return false;
2102
+ }
2103
+
2104
+ private function convertTwoAndSelectSmallest()
2105
+ {
2106
+ $destination = $this->getDestination();
2107
+ $destinationLossless = $destination . '.lossless.webp';
2108
+ $destinationLossy = $destination . '.lossy.webp';
2109
+
2110
+ $this->logLn(
2111
+ 'Encoding is set to auto - converting to both lossless and lossy and selecting the smallest file'
2112
+ );
2113
+
2114
+ $this->ln();
2115
+ $this->logLn('Converting to lossy');
2116
+ $this->setDestination($destinationLossy);
2117
+ $this->setOption('encoding', 'lossy');
2118
+ $this->doActualConvert();
2119
+ $this->log('Reduction: ');
2120
+ $this->logReduction($this->getSource(), $destinationLossy);
2121
+ $this->ln();
2122
+
2123
+ $this->logLn('Converting to lossless');
2124
+ $this->setDestination($destinationLossless);
2125
+ $this->setOption('encoding', 'lossless');
2126
+ $this->doActualConvert();
2127
+ $this->log('Reduction: ');
2128
+ $this->logReduction($this->getSource(), $destinationLossless);
2129
+ $this->ln();
2130
+
2131
+ if (filesize($destinationLossless) > filesize($destinationLossy)) {
2132
+ $this->logLn('Picking lossy');
2133
+ unlink($destinationLossless);
2134
+ rename($destinationLossy, $destination);
2135
+ } else {
2136
+ $this->logLn('Picking lossless');
2137
+ unlink($destinationLossy);
2138
+ rename($destinationLossless, $destination);
2139
+ }
2140
+ $this->setDestination($destination);
2141
+ $this->setOption('encoding', 'auto');
2142
+ }
2143
+
2144
+ protected function runActualConvert()
2145
+ {
2146
+ if (!$this->passOnEncodingAuto() && ($this->getOptions()['encoding'] == 'auto') && $this->supportsLossless()) {
2147
+ $this->convertTwoAndSelectSmallest();
2148
+ } else {
2149
+ $this->doActualConvert();
2150
+ }
2151
+ }
2152
+ }
2153
+
2154
+ ?><?php
2155
+
2156
+ namespace WebPConvert\Convert\Converters\ConverterTraits;
2157
+
2158
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
2159
+
2160
+ /**
2161
+ * Trait for converters that uses exec()
2162
+ *
2163
+ * @package WebPConvert
2164
+ * @author Bjørn Rosell <it@rosell.dk>
2165
+ * @since Class available since Release 2.0.0
2166
+ */
2167
+ trait ExecTrait
2168
+ {
2169
+
2170
+ abstract protected function logLn($msg, $style = '');
2171
+
2172
+ /**
2173
+ * Helper function for examining if "nice" command is available
2174
+ *
2175
+ * @return boolean true if nice is available
2176
+ */
2177
+ protected static function hasNiceSupport()
2178
+ {
2179
+ exec("nice 2>&1", $niceOutput);
2180
+
2181
+ if (is_array($niceOutput) && isset($niceOutput[0])) {
2182
+ if (preg_match('/usage/', $niceOutput[0]) || (preg_match('/^\d+$/', $niceOutput[0]))) {
2183
+ /*
2184
+ * Nice is available - default niceness (+10)
2185
+ * https://www.lifewire.com/uses-of-commands-nice-renice-2201087
2186
+ * https://www.computerhope.com/unix/unice.htm
2187
+ */
2188
+
2189
+ return true;
2190
+ }
2191
+ return false;
2192
+ }
2193
+ }
2194
+
2195
+ /**
2196
+ * Logs output from the exec call.
2197
+ *
2198
+ * @param array $output
2199
+ *
2200
+ * @return void
2201
+ */
2202
+ protected function logExecOutput($output)
2203
+ {
2204
+ if (is_array($output) && count($output) > 0) {
2205
+ $this->logLn('');
2206
+ $this->logLn('Output:', 'italic');
2207
+ foreach ($output as $line) {
2208
+ $this->logLn(print_r($line, true));
2209
+ }
2210
+ $this->logLn('');
2211
+ }
2212
+ }
2213
+
2214
+ /**
2215
+ * Check basic operationality of exec converters (that the "exec" function is available)
2216
+ *
2217
+ * @throws WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException
2218
+ * @return void
2219
+ */
2220
+ public function checkOperationalityExecTrait()
2221
+ {
2222
+ if (!function_exists('exec')) {
2223
+ throw new SystemRequirementsNotMetException('exec() is not enabled.');
2224
+ }
2225
+ }
2226
+ }
2227
+
2228
+ ?><?php
2229
+
2230
+ namespace WebPConvert\Convert\Converters;
2231
+
2232
+ use WebPConvert\Convert\Converters\AbstractConverter;
2233
+ use WebPConvert\Convert\Converters\ConverterTraits\EncodingAutoTrait;
2234
+ use WebPConvert\Convert\Converters\ConverterTraits\ExecTrait;
2235
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
2236
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
2237
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
2238
+ use WebPConvert\Options\BooleanOption;
2239
+ use WebPConvert\Options\SensitiveStringOption;
2240
+ use WebPConvert\Options\StringOption;
2241
+
2242
+ /**
2243
+ * Convert images to webp by calling cwebp binary.
2244
+ *
2245
+ * @package WebPConvert
2246
+ * @author Bjørn Rosell <it@rosell.dk>
2247
+ * @since Class available since Release 2.0.0
2248
+ */
2249
+ class Cwebp extends AbstractConverter
2250
+ {
2251
+
2252
+ use EncodingAutoTrait;
2253
+ use ExecTrait;
2254
+
2255
+ protected function getUnsupportedDefaultOptions()
2256
+ {
2257
+ return [];
2258
+ }
2259
+
2260
+ protected function createOptions()
2261
+ {
2262
+ parent::createOptions();
2263
+
2264
+ $this->options2->addOptions(
2265
+ new StringOption('command-line-options', ''),
2266
+ new SensitiveStringOption('rel-path-to-precompiled-binaries', './Binaries'),
2267
+ new BooleanOption('try-common-system-paths', true),
2268
+ new BooleanOption('try-supplied-binary-for-os', true)
2269
+ );
2270
+ }
2271
+
2272
+ // System paths to look for cwebp binary
2273
+ private static $cwebpDefaultPaths = [
2274
+ 'cwebp',
2275
+ '/usr/bin/cwebp',
2276
+ '/usr/local/bin/cwebp',
2277
+ '/usr/gnu/bin/cwebp',
2278
+ '/usr/syno/bin/cwebp'
2279
+ ];
2280
+
2281
+ // OS-specific binaries included in this library, along with hashes
2282
+ // If other binaries are going to be added, notice that the first argument is what PHP_OS returns.
2283
+ // (possible values, see here: https://stackoverflow.com/questions/738823/possible-values-for-php-os)
2284
+ private static $suppliedBinariesInfo = [
2285
+ 'WINNT' => [ 'cwebp.exe', '49e9cb98db30bfa27936933e6fd94d407e0386802cb192800d9fd824f6476873'],
2286
+ 'Darwin' => [ 'cwebp-mac12', 'a06a3ee436e375c89dbc1b0b2e8bd7729a55139ae072ed3f7bd2e07de0ebb379'],
2287
+ 'SunOS' => [ 'cwebp-sol', '1febaffbb18e52dc2c524cda9eefd00c6db95bc388732868999c0f48deb73b4f'],
2288
+ 'FreeBSD' => [ 'cwebp-fbsd', 'e5cbea11c97fadffe221fdf57c093c19af2737e4bbd2cb3cd5e908de64286573'],
2289
+ 'Linux' => [ 'cwebp-linux', '916623e5e9183237c851374d969aebdb96e0edc0692ab7937b95ea67dc3b2568']
2290
+ ];
2291
+
2292
+ public function checkOperationality()
2293
+ {
2294
+ $this->checkOperationalityExecTrait();
2295
+
2296
+ $options = $this->options;
2297
+ if (!$options['try-supplied-binary-for-os'] && !$options['try-common-system-paths']) {
2298
+ throw new ConverterNotOperationalException(
2299
+ 'Configured to neither look for cweb binaries in common system locations, ' .
2300
+ 'nor to use one of the supplied precompiled binaries. But these are the only ways ' .
2301
+ 'this converter can convert images. No conversion can be made!'
2302
+ );
2303
+ }
2304
+ }
2305
+
2306
+ private function executeBinary($binary, $commandOptions, $useNice)
2307
+ {
2308
+ $command = ($useNice ? 'nice ' : '') . $binary . ' ' . $commandOptions;
2309
+
2310
+ //$logger->logLn('command options:' . $commandOptions);
2311
+ //$logger->logLn('Trying to execute binary:' . $binary);
2312
+ exec($command, $output, $returnCode);
2313
+ $this->logExecOutput($output);
2314
+ /*
2315
+ if ($returnCode == 255) {
2316
+ if (isset($output[0])) {
2317
+ // Could be an error like 'Error! Cannot open output file' or 'Error! ...preset... '
2318
+ $this->logLn(print_r($output[0], true));
2319
+ }
2320
+ }*/
2321
+ //$logger->logLn(self::msgForExitCode($returnCode));
2322
+ return intval($returnCode);
2323
+ }
2324
+
2325
+ /**
2326
+ * Use "escapeshellarg()" on all arguments in a commandline string of options
2327
+ *
2328
+ * For example, passing '-sharpness 5 -crop 10 10 40 40 -low_memory' will result in:
2329
+ * [
2330
+ * "-sharpness '5'"
2331
+ * "-crop '10' '10' '40' '40'"
2332
+ * "-low_memory"
2333
+ * ]
2334
+ * @param string $commandLineOptions string which can contain multiple commandline options
2335
+ * @return array Array of command options
2336
+ */
2337
+ private static function escapeShellArgOnCommandLineOptions($commandLineOptions)
2338
+ {
2339
+ $cmdOptions = [];
2340
+ $arr = explode(' -', ' ' . $commandLineOptions);
2341
+ foreach ($arr as $cmdOption) {
2342
+ $pos = strpos($cmdOption, ' ');
2343
+ $cName = '';
2344
+ if (!$pos) {
2345
+ $cName = $cmdOption;
2346
+ if ($cName == '') {
2347
+ continue;
2348
+ }
2349
+ $cmdOptions[] = '-' . $cName;
2350
+ } else {
2351
+ $cName = substr($cmdOption, 0, $pos);
2352
+ $cValues = substr($cmdOption, $pos + 1);
2353
+ $cValuesArr = explode(' ', $cValues);
2354
+ foreach ($cValuesArr as &$cArg) {
2355
+ $cArg = escapeshellarg($cArg);
2356
+ }
2357
+ $cValues = implode(' ', $cValuesArr);
2358
+ $cmdOptions[] = '-' . $cName . ' ' . $cValues;
2359
+ }
2360
+ }
2361
+ return $cmdOptions;
2362
+ }
2363
+
2364
+ /**
2365
+ * Build command line options
2366
+ *
2367
+ * @return string
2368
+ */
2369
+ private function createCommandLineOptions()
2370
+ {
2371
+ $options = $this->options;
2372
+
2373
+ $cmdOptions = [];
2374
+
2375
+ // Metadata (all, exif, icc, xmp or none (default))
2376
+ // Comma-separated list of existing metadata to copy from input to output
2377
+ $cmdOptions[] = '-metadata ' . $options['metadata'];
2378
+
2379
+ // preset. Appears first in the list as recommended in the docs
2380
+ if (!is_null($options['preset'])) {
2381
+ if ($options['preset'] != 'none') {
2382
+ $cmdOptions[] = '-preset ' . $options['preset'];
2383
+ }
2384
+ }
2385
+
2386
+ // Size
2387
+ $addedSizeOption = false;
2388
+ if (!is_null($options['size-in-percentage'])) {
2389
+ $sizeSource = filesize($this->source);
2390
+ if ($sizeSource !== false) {
2391
+ $targetSize = floor($sizeSource * $options['size-in-percentage'] / 100);
2392
+ $cmdOptions[] = '-size ' . $targetSize;
2393
+ $addedSizeOption = true;
2394
+ }
2395
+ }
2396
+
2397
+ // quality
2398
+ if (!$addedSizeOption) {
2399
+ $cmdOptions[] = '-q ' . $this->getCalculatedQuality();
2400
+ }
2401
+
2402
+ // alpha-quality
2403
+ if ($this->options['alpha-quality'] !== 100) {
2404
+ $cmdOptions[] = '-alpha_q ' . escapeshellarg($this->options['alpha-quality']);
2405
+ }
2406
+
2407
+ // Losless PNG conversion
2408
+ if ($options['encoding'] == 'lossless') {
2409
+ // No need to add -lossless when near-lossless is used
2410
+ if ($options['near-lossless'] === 100) {
2411
+ $cmdOptions[] = '-lossless';
2412
+ }
2413
+ }
2414
+
2415
+ // Near-lossles
2416
+ if ($options['near-lossless'] !== 100) {
2417
+ // We only let near_lossless have effect when encoding is set to "lossless"
2418
+ // otherwise encoding=auto would not work as expected
2419
+ if ($options['encoding'] == 'lossless') {
2420
+ $cmdOptions[] ='-near_lossless ' . $options['near-lossless'];
2421
+ }
2422
+ }
2423
+
2424
+ if ($options['auto-filter'] === true) {
2425
+ $cmdOptions[] = '-af';
2426
+ }
2427
+
2428
+ // Built-in method option
2429
+ $cmdOptions[] = '-m ' . strval($options['method']);
2430
+
2431
+ // Built-in low memory option
2432
+ if ($options['low-memory']) {
2433
+ $cmdOptions[] = '-low_memory';
2434
+ }
2435
+
2436
+ // command-line-options
2437
+ if ($options['command-line-options']) {
2438
+ array_push(
2439
+ $cmdOptions,
2440
+ ...self::escapeShellArgOnCommandLineOptions($options['command-line-options'])
2441
+ );
2442
+ }
2443
+
2444
+ // Source file
2445
+ $cmdOptions[] = escapeshellarg($this->source);
2446
+
2447
+ // Output
2448
+ $cmdOptions[] = '-o ' . escapeshellarg($this->destination);
2449
+
2450
+ // Redirect stderr to same place as stdout
2451
+ // https://www.brianstorti.com/understanding-shell-script-idiom-redirect/
2452
+ $cmdOptions[] = '2>&1';
2453
+
2454
+ $commandOptions = implode(' ', $cmdOptions);
2455
+ $this->logLn('command line options:' . $commandOptions);
2456
+
2457
+ return $commandOptions;
2458
+ }
2459
+
2460
+ /**
2461
+ *
2462
+ *
2463
+ * @return string Error message if failure, empty string if successful
2464
+ */
2465
+ private function composeErrorMessageForCommonSystemPathsFailures($failureCodes)
2466
+ {
2467
+ if (count($failureCodes) == 1) {
2468
+ switch ($failureCodes[0]) {
2469
+ case 126:
2470
+ return 'Permission denied. The user that the command was run with (' .
2471
+ shell_exec('whoami') . ') does not have permission to execute any of the ' .
2472
+ 'cweb binaries found in common system locations. ';
2473
+ case 127:
2474
+ return 'Found no cwebp binaries in any common system locations. ';
2475
+ default:
2476
+ return 'Tried executing cwebp binaries in common system locations. ' .
2477
+ 'All failed (exit code: ' . $failureCodes[0] . '). ';
2478
+ }
2479
+ } else {
2480
+ /**
2481
+ * $failureCodesBesides127 is used to check first position ($failureCodesBesides127[0])
2482
+ * however position can vary as index can be 1 or something else. array_values() would
2483
+ * always start from 0.
2484
+ */
2485
+ $failureCodesBesides127 = array_values(array_diff($failureCodes, [127]));
2486
+
2487
+ if (count($failureCodesBesides127) == 1) {
2488
+ switch ($failureCodesBesides127[0]) {
2489
+ case 126:
2490
+ return 'Permission denied. The user that the command was run with (' .
2491
+ shell_exec('whoami') . ') does not have permission to execute any of the cweb ' .
2492
+ 'binaries found in common system locations. ';
2493
+ break;
2494
+ default:
2495
+ return 'Tried executing cwebp binaries in common system locations. ' .
2496
+ 'All failed (exit code: ' . $failureCodesBesides127[0] . '). ';
2497
+ }
2498
+ } else {
2499
+ return 'None of the cwebp binaries in the common system locations could be executed ' .
2500
+ '(mixed results - got the following exit codes: ' . implode(',', $failureCodes) . '). ';
2501
+ }
2502
+ }
2503
+ }
2504
+
2505
+ /**
2506
+ * Try executing cwebp in common system paths
2507
+ *
2508
+ * @param boolean $useNice Whether to use nice
2509
+ * @param string $commandOptions for the exec call
2510
+ *
2511
+ * @return array Unique failure codes in case of failure, empty array in case of success
2512
+ */
2513
+ private function tryCommonSystemPaths($useNice, $commandOptions)
2514
+ {
2515
+ $failureCodes = [];
2516
+
2517
+ $paths = self::$cwebpDefaultPaths;
2518
+
2519
+ if (!empty(getenv('CWEBP_PATH'))) {
2520
+ array_unshift($paths, getenv('CWEBP_PATH'));
2521
+ }
2522
+
2523
+ // Loop through paths
2524
+ foreach ($paths as $index => $binary) {
2525
+ $returnCode = $this->executeBinary($binary, $commandOptions, $useNice);
2526
+ if ($returnCode == 0) {
2527
+ $this->logLn('Successfully executed binary: ' . $binary);
2528
+ return [];
2529
+ } else {
2530
+ if ($returnCode == 127) {
2531
+ $this->logLn(
2532
+ 'Trying to execute binary: ' . $binary . '. Failed (not found)'
2533
+ );
2534
+ } else {
2535
+ $this->logLn(
2536
+ 'Trying to execute binary: ' . $binary . '. Failed (return code: ' . $returnCode . ')'
2537
+ );
2538
+ }
2539
+ if (!in_array($returnCode, $failureCodes)) {
2540
+ $failureCodes[] = $returnCode;
2541
+ }
2542
+ }
2543
+ }
2544
+ return $failureCodes;
2545
+ }
2546
+
2547
+ /**
2548
+ * Try executing supplied cwebp for PHP_OS.
2549
+ *
2550
+ * @param boolean $useNice Whether to use nice
2551
+ * @param string $commandOptions for the exec call
2552
+ * @param array $failureCodesForCommonSystemPaths Return codes from the other attempt
2553
+ * (in order to produce short error message)
2554
+ *
2555
+ * @return string Error message if failure, empty string if successful
2556
+ */
2557
+ private function trySuppliedBinaryForOS($useNice, $commandOptions, $failureCodesForCommonSystemPaths)
2558
+ {
2559
+ $this->logLn('Trying to execute supplied binary for OS: ' . PHP_OS);
2560
+
2561
+ // Try supplied binary (if available for OS, and hash is correct)
2562
+ $options = $this->options;
2563
+ if (!isset(self::$suppliedBinariesInfo[PHP_OS])) {
2564
+ return 'No supplied binaries found for OS:' . PHP_OS;
2565
+ }
2566
+
2567
+ $info = self::$suppliedBinariesInfo[PHP_OS];
2568
+
2569
+ $file = $info[0];
2570
+ $hash = $info[1];
2571
+
2572
+ $binaryFile = __DIR__ . '/' . $options['rel-path-to-precompiled-binaries'] . '/' . $file;
2573
+
2574
+
2575
+ // The file should exist, but may have been removed manually.
2576
+ if (!file_exists($binaryFile)) {
2577
+ return 'Supplied binary not found! It ought to be here:' . $binaryFile;
2578
+ }
2579
+
2580
+ // File exists, now generate its hash
2581
+
2582
+ // hash_file() is normally available, but it is not always
2583
+ // - https://stackoverflow.com/questions/17382712/php-5-3-20-undefined-function-hash
2584
+ // If available, validate that hash is correct.
2585
+
2586
+ if (function_exists('hash_file')) {
2587
+ $binaryHash = hash_file('sha256', $binaryFile);
2588
+
2589
+ if ($binaryHash != $hash) {
2590
+ return 'Binary checksum of supplied binary is invalid! ' .
2591
+ 'Did you transfer with FTP, but not in binary mode? ' .
2592
+ 'File:' . $binaryFile . '. ' .
2593
+ 'Expected checksum: ' . $hash . '. ' .
2594
+ 'Actual checksum:' . $binaryHash . '.';
2595
+ }
2596
+ }
2597
+
2598
+ $returnCode = $this->executeBinary($binaryFile, $commandOptions, $useNice);
2599
+ if ($returnCode == 0) {
2600
+ // yay!
2601
+ $this->logLn('success!');
2602
+ return '';
2603
+ }
2604
+
2605
+ $errorMsg = 'Tried executing supplied binary for ' . PHP_OS . ', ' .
2606
+ ($options['try-common-system-paths'] ? 'but that failed too' : 'but failed');
2607
+
2608
+
2609
+ if (($options['try-common-system-paths']) && (count($failureCodesForCommonSystemPaths) > 0)) {
2610
+ // check if it was the same error
2611
+ // if it was, simply refer to that with "(same problem)"
2612
+ $majorFailCode = 0;
2613
+ if (count($failureCodesForCommonSystemPaths) == 1) {
2614
+ $majorFailCode = $failureCodesForCommonSystemPaths[0];
2615
+ } else {
2616
+ $failureCodesBesides127 = array_values(array_diff($failureCodesForCommonSystemPaths, [127]));
2617
+ if (count($failureCodesBesides127) == 1) {
2618
+ $majorFailCode = $failureCodesBesides127[0];
2619
+ } else {
2620
+ // it cannot be summarized into a single code
2621
+ }
2622
+ }
2623
+ if ($majorFailCode != 0) {
2624
+ $errorMsg .= ' (same problem)';
2625
+ return $errorMsg;
2626
+ }
2627
+ }
2628
+
2629
+ if ($returnCode > 128) {
2630
+ $errorMsg .= '. The binary did not work (exit code: ' . $returnCode . '). ' .
2631
+ 'Check out https://github.com/rosell-dk/webp-convert/issues/92';
2632
+ } else {
2633
+ switch ($returnCode) {
2634
+ case 0:
2635
+ // success!
2636
+ break;
2637
+ case 126:
2638
+ $errorMsg .= ': Permission denied. The user that the command was run' .
2639
+ ' with (' . shell_exec('whoami') . ') does not have permission to ' .
2640
+ 'execute that binary.';
2641
+ break;
2642
+ case 127:
2643
+ $errorMsg .= '. The binary was not found! ' .
2644
+ 'It ought to be here: ' . $binaryFile;
2645
+ break;
2646
+ default:
2647
+ $errorMsg .= ' (exit code:' . $returnCode . ').';
2648
+ }
2649
+ }
2650
+ return $errorMsg;
2651
+ }
2652
+
2653
+ protected function doActualConvert()
2654
+ {
2655
+ $errorMsg = '';
2656
+ $options = $this->options;
2657
+ $useNice = (($options['use-nice']) && self::hasNiceSupport());
2658
+
2659
+ $commandOptions = $this->createCommandLineOptions();
2660
+
2661
+ // Try all common paths that exists
2662
+ $success = false;
2663
+
2664
+ $failureCodes = [];
2665
+
2666
+ if ($options['try-common-system-paths']) {
2667
+ $failureCodes = $this->tryCommonSystemPaths($useNice, $commandOptions);
2668
+ $success = (count($failureCodes) == 0);
2669
+ $errorMsg = $this->composeErrorMessageForCommonSystemPathsFailures($failureCodes);
2670
+ }
2671
+
2672
+ if (!$success && $options['try-supplied-binary-for-os']) {
2673
+ $errorMsg2 = $this->trySuppliedBinaryForOS($useNice, $commandOptions, $failureCodes);
2674
+ $errorMsg .= $errorMsg2;
2675
+ $success = ($errorMsg2 == '');
2676
+ }
2677
+
2678
+ // cwebp sets file permissions to 664 but instead ..
2679
+ // .. $destination's parent folder's permissions should be used (except executable bits)
2680
+ // (or perhaps the current umask instead? https://www.php.net/umask)
2681
+
2682
+ if ($success) {
2683
+ $destinationParent = dirname($this->destination);
2684
+ $fileStatistics = stat($destinationParent);
2685
+ if ($fileStatistics !== false) {
2686
+ // Apply same permissions as parent folder but strip off the executable bits
2687
+ $permissions = $fileStatistics['mode'] & 0000666;
2688
+ chmod($this->destination, $permissions);
2689
+ }
2690
+ }
2691
+
2692
+ if (!$success) {
2693
+ throw new SystemRequirementsNotMetException($errorMsg);
2694
+ }
2695
+ }
2696
+ }
2697
+
2698
+ ?><?php
2699
+
2700
+ namespace WebPConvert\Convert\Converters;
2701
+
2702
+ use WebPConvert\Convert\Converters\AbstractConverter;
2703
+ use WebPConvert\Convert\Converters\ConverterTraits\CloudConverterTrait;
2704
+ use WebPConvert\Convert\Converters\ConverterTraits\CurlTrait;
2705
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
2706
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
2707
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\InvalidApiKeyException;
2708
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
2709
+ use WebPConvert\Options\SensitiveStringOption;
2710
+
2711
+ /**
2712
+ * Convert images to webp using ewww cloud service.
2713
+ *
2714
+ * @package WebPConvert
2715
+ * @author Bjørn Rosell <it@rosell.dk>
2716
+ * @since Class available since Release 2.0.0
2717
+ */
2718
+ class Ewww extends AbstractConverter
2719
+ {
2720
+ use CloudConverterTrait;
2721
+ use CurlTrait;
2722
+
2723
+ protected function getUnsupportedDefaultOptions()
2724
+ {
2725
+ return [
2726
+ 'alpha-quality',
2727
+ 'auto-filter',
2728
+ 'encoding',
2729
+ 'low-memory',
2730
+ 'use-nice'
2731
+ ];
2732
+ }
2733
+
2734
+ protected function createOptions()
2735
+ {
2736
+ parent::createOptions();
2737
+
2738
+ $this->options2->addOptions(
2739
+ new SensitiveStringOption('api-key', '')
2740
+ );
2741
+ }
2742
+
2743
+ /**
2744
+ * Get api key from options or environment variable
2745
+ *
2746
+ * @return string|false api key or false if none is set
2747
+ */
2748
+ private function getKey()
2749
+ {
2750
+ if (!empty($this->options['api-key'])) {
2751
+ return $this->options['api-key'];
2752
+ }
2753
+ if (!empty(getenv('EWWW_API_KEY'))) {
2754
+ return getenv('EWWW_API_KEY');
2755
+ }
2756
+ return false;
2757
+ }
2758
+
2759
+
2760
+ /**
2761
+ * Check operationality of Ewww converter.
2762
+ *
2763
+ * @throws SystemRequirementsNotMetException if system requirements are not met (curl)
2764
+ * @throws ConverterNotOperationalException if key is missing or invalid, or quota has exceeded
2765
+ */
2766
+ public function checkOperationality()
2767
+ {
2768
+
2769
+ $apiKey = $this->getKey();
2770
+
2771
+ if ($apiKey === false) {
2772
+ if (isset($this->options['key'])) {
2773
+ throw new InvalidApiKeyException(
2774
+ 'The "key" option has been renamed to "api-key" in webp-convert 2.0. ' .
2775
+ 'You must change the configuration accordingly.'
2776
+ );
2777
+ }
2778
+
2779
+ throw new InvalidApiKeyException('Missing API key.');
2780
+ }
2781
+
2782
+ if (strlen($apiKey) < 20) {
2783
+ throw new InvalidApiKeyException(
2784
+ 'Api key is invalid. Api keys are supposed to be 32 characters long - ' .
2785
+ 'the provided api key is much shorter'
2786
+ );
2787
+ }
2788
+
2789
+ // Check for curl requirements
2790
+ $this->checkOperationalityForCurlTrait();
2791
+
2792
+ $keyStatus = self::getKeyStatus($apiKey);
2793
+ switch ($keyStatus) {
2794
+ case 'great':
2795
+ break;
2796
+ case 'exceeded':
2797
+ throw new ConverterNotOperationalException('Quota has exceeded');
2798
+ break;
2799
+ case 'invalid':
2800
+ throw new InvalidApiKeyException('Api key is invalid');
2801
+ break;
2802
+ }
2803
+ }
2804
+
2805
+ /*
2806
+ public function checkConvertability()
2807
+ {
2808
+ // check upload limits
2809
+ $this->checkConvertabilityCloudConverterTrait();
2810
+ }
2811
+ */
2812
+
2813
+ // Although this method is public, do not call directly.
2814
+ // You should rather call the static convert() function, defined in AbstractConverter, which
2815
+ // takes care of preparing stuff before calling doConvert, and validating after.
2816
+ protected function doActualConvert()
2817
+ {
2818
+
2819
+ $options = $this->options;
2820
+
2821
+ $ch = self::initCurl();
2822
+
2823
+ //$this->logLn('api key:' . $this->getKey());
2824
+
2825
+ $postData = [
2826
+ 'api_key' => $this->getKey(),
2827
+ 'webp' => '1',
2828
+ 'file' => curl_file_create($this->source),
2829
+ 'quality' => $this->getCalculatedQuality(),
2830
+ 'metadata' => ($options['metadata'] == 'none' ? '0' : '1')
2831
+ ];
2832
+
2833
+ curl_setopt_array(
2834
+ $ch,
2835
+ [
2836
+ CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
2837
+ CURLOPT_HTTPHEADER => [
2838
+ 'User-Agent: WebPConvert',
2839
+ 'Accept: image/*'
2840
+ ],
2841
+ CURLOPT_POSTFIELDS => $postData,
2842
+ CURLOPT_BINARYTRANSFER => true,
2843
+ CURLOPT_RETURNTRANSFER => true,
2844
+ CURLOPT_HEADER => false,
2845
+ CURLOPT_SSL_VERIFYPEER => false
2846
+ ]
2847
+ );
2848
+
2849
+ $response = curl_exec($ch);
2850
+
2851
+ if (curl_errno($ch)) {
2852
+ throw new ConversionFailedException(curl_error($ch));
2853
+ }
2854
+
2855
+ // The API does not always return images.
2856
+ // For example, it may return a message such as '{"error":"invalid","t":"exceeded"}
2857
+ // Messages has a http content type of ie 'text/html; charset=UTF-8
2858
+ // Images has application/octet-stream.
2859
+ // So verify that we got an image back.
2860
+ if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
2861
+ //echo curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
2862
+ curl_close($ch);
2863
+
2864
+ /* May return this: {"error":"invalid","t":"exceeded"} */
2865
+ $responseObj = json_decode($response);
2866
+ if (isset($responseObj->error)) {
2867
+ //echo 'error:' . $responseObj->error . '<br>';
2868
+ //echo $response;
2869
+ //self::blacklistKey($key);
2870
+ //throw new SystemRequirementsNotMetException('The key is invalid. Blacklisted it!');
2871
+ throw new InvalidApiKeyException('The api key is invalid');
2872
+ }
2873
+
2874
+ throw new ConversionFailedException(
2875
+ 'ewww api did not return an image. It could be that the key is invalid. Response: '
2876
+ . $response
2877
+ );
2878
+ }
2879
+
2880
+ // Not sure this can happen. So just in case
2881
+ if ($response == '') {
2882
+ throw new ConversionFailedException('ewww api did not return anything');
2883
+ }
2884
+
2885
+ $success = file_put_contents($this->destination, $response);
2886
+
2887
+ if (!$success) {
2888
+ throw new ConversionFailedException('Error saving file');
2889
+ }
2890
+ }
2891
+
2892
+ /**
2893
+ * Keep subscription alive by optimizing a jpeg
2894
+ * (ewww closes accounts after 6 months of inactivity - and webp conversions seems not to be counted? )
2895
+ */
2896
+ public static function keepSubscriptionAlive($source, $key)
2897
+ {
2898
+ try {
2899
+ $ch = curl_init();
2900
+ } catch (\Exception $e) {
2901
+ return 'curl is not installed';
2902
+ }
2903
+ if ($ch === false) {
2904
+ return 'curl could not be initialized';
2905
+ }
2906
+ curl_setopt_array(
2907
+ $ch,
2908
+ [
2909
+ CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
2910
+ CURLOPT_HTTPHEADER => [
2911
+ 'User-Agent: WebPConvert',
2912
+ 'Accept: image/*'
2913
+ ],
2914
+ CURLOPT_POSTFIELDS => [
2915
+ 'api_key' => $key,
2916
+ 'webp' => '0',
2917
+ 'file' => curl_file_create($source),
2918
+ 'domain' => $_SERVER['HTTP_HOST'],
2919
+ 'quality' => 60,
2920
+ 'metadata' => 0
2921
+ ],
2922
+ CURLOPT_BINARYTRANSFER => true,
2923
+ CURLOPT_RETURNTRANSFER => true,
2924
+ CURLOPT_HEADER => false,
2925
+ CURLOPT_SSL_VERIFYPEER => false
2926
+ ]
2927
+ );
2928
+
2929
+ $response = curl_exec($ch);
2930
+ if (curl_errno($ch)) {
2931
+ return 'curl error' . curl_error($ch);
2932
+ }
2933
+ if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
2934
+ curl_close($ch);
2935
+
2936
+ /* May return this: {"error":"invalid","t":"exceeded"} */
2937
+ $responseObj = json_decode($response);
2938
+ if (isset($responseObj->error)) {
2939
+ return 'The key is invalid';
2940
+ }
2941
+
2942
+ return 'ewww api did not return an image. It could be that the key is invalid. Response: ' . $response;
2943
+ }
2944
+
2945
+ // Not sure this can happen. So just in case
2946
+ if ($response == '') {
2947
+ return 'ewww api did not return anything';
2948
+ }
2949
+
2950
+ return true;
2951
+ }
2952
+
2953
+ /*
2954
+ public static function blacklistKey($key)
2955
+ {
2956
+ }
2957
+
2958
+ public static function isKeyBlacklisted($key)
2959
+ {
2960
+ }*/
2961
+
2962
+ /**
2963
+ * Return "great", "exceeded" or "invalid"
2964
+ */
2965
+ public static function getKeyStatus($key)
2966
+ {
2967
+ $ch = self::initCurl();
2968
+
2969
+ curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/verify/");
2970
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
2971
+ curl_setopt(
2972
+ $ch,
2973
+ CURLOPT_POSTFIELDS,
2974
+ [
2975
+ 'api_key' => $key
2976
+ ]
2977
+ );
2978
+
2979
+ // The 403 forbidden is avoided with this line.
2980
+ curl_setopt(
2981
+ $ch,
2982
+ CURLOPT_USERAGENT,
2983
+ 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)'
2984
+ );
2985
+
2986
+ $response = curl_exec($ch);
2987
+ // echo $response;
2988
+ if (curl_errno($ch)) {
2989
+ throw new \Exception(curl_error($ch));
2990
+ }
2991
+ curl_close($ch);
2992
+
2993
+ // Possible responses:
2994
+ // “great” = verification successful
2995
+ // “exceeded” = indicates a valid key with no remaining image credits.
2996
+ // an empty response indicates that the key is not valid
2997
+
2998
+ if ($response == '') {
2999
+ return 'invalid';
3000
+ }
3001
+ $responseObj = json_decode($response);
3002
+ if (isset($responseObj->error)) {
3003
+ if ($responseObj->error == 'invalid') {
3004
+ return 'invalid';
3005
+ } else {
3006
+ throw new \Exception('Ewww returned unexpected error: ' . $response);
3007
+ }
3008
+ }
3009
+ if (!isset($responseObj->status)) {
3010
+ throw new \Exception('Ewww returned unexpected response to verify request: ' . $response);
3011
+ }
3012
+ switch ($responseObj->status) {
3013
+ case 'great':
3014
+ case 'exceeded':
3015
+ return $responseObj->status;
3016
+ }
3017
+ throw new \Exception('Ewww returned unexpected status to verify request: "' . $responseObj->status . '"');
3018
+ }
3019
+
3020
+ public static function isWorkingKey($key)
3021
+ {
3022
+ return (self::getKeyStatus($key) == 'great');
3023
+ }
3024
+
3025
+ public static function isValidKey($key)
3026
+ {
3027
+ return (self::getKeyStatus($key) != 'invalid');
3028
+ }
3029
+
3030
+ public static function getQuota($key)
3031
+ {
3032
+ $ch = self::initCurl();
3033
+
3034
+ curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/quota/");
3035
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
3036
+ curl_setopt(
3037
+ $ch,
3038
+ CURLOPT_POSTFIELDS,
3039
+ [
3040
+ 'api_key' => $key
3041
+ ]
3042
+ );
3043
+ curl_setopt(
3044
+ $ch,
3045
+ CURLOPT_USERAGENT,
3046
+ 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)'
3047
+ );
3048
+
3049
+ $response = curl_exec($ch);
3050
+ return $response; // ie -830 23. Seems to return empty for invalid keys
3051
+ // or empty
3052
+ //echo $response;
3053
+ }
3054
+ }
3055
+
3056
+ ?><?php
3057
+
3058
+ namespace WebPConvert\Convert\Converters;
3059
+
3060
+ use WebPConvert\Convert\Converters\AbstractConverter;
3061
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
3062
+ use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInputException;
3063
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
3064
+
3065
+ /**
3066
+ * Convert images to webp using gd extension.
3067
+ *
3068
+ * @package WebPConvert
3069
+ * @author Bjørn Rosell <it@rosell.dk>
3070
+ * @since Class available since Release 2.0.0
3071
+ */
3072
+ class Gd extends AbstractConverter
3073
+ {
3074
+ public function supportsLossless()
3075
+ {
3076
+ return false;
3077
+ }
3078
+
3079
+ protected function getUnsupportedDefaultOptions()
3080
+ {
3081
+ return [
3082
+ 'alpha-quality',
3083
+ 'auto-filter',
3084
+ 'encoding',
3085
+ 'low-memory',
3086
+ 'metadata',
3087
+ 'method',
3088
+ 'near-lossless',
3089
+ 'preset',
3090
+ 'size-in-percentage',
3091
+ 'use-nice'
3092
+ ];
3093
+ }
3094
+
3095
+ private $errorMessageWhileCreating = '';
3096
+ private $errorNumberWhileCreating;
3097
+
3098
+ /**
3099
+ * Check (general) operationality of Gd converter.
3100
+ *
3101
+ * @throws SystemRequirementsNotMetException if system requirements are not met
3102
+ */
3103
+ public function checkOperationality()
3104
+ {
3105
+ if (!extension_loaded('gd')) {
3106
+ throw new SystemRequirementsNotMetException('Required Gd extension is not available.');
3107
+ }
3108
+
3109
+ if (!function_exists('imagewebp')) {
3110
+ throw new SystemRequirementsNotMetException(
3111
+ 'Gd has been compiled without webp support.'
3112
+ );
3113
+ }
3114
+ }
3115
+
3116
+ /**
3117
+ * Check if specific file is convertable with current converter / converter settings.
3118
+ *
3119
+ * @throws SystemRequirementsNotMetException if Gd has been compiled without support for image type
3120
+ */
3121
+ public function checkConvertability()
3122
+ {
3123
+ $mimeType = $this->getMimeTypeOfSource();
3124
+ switch ($mimeType) {
3125
+ case 'image/png':
3126
+ if (!function_exists('imagecreatefrompng')) {
3127
+ throw new SystemRequirementsNotMetException(
3128
+ 'Gd has been compiled without PNG support and can therefore not convert this PNG image.'
3129
+ );
3130
+ }
3131
+ break;
3132
+
3133
+ case 'image/jpeg':
3134
+ if (!function_exists('imagecreatefromjpeg')) {
3135
+ throw new SystemRequirementsNotMetException(
3136
+ 'Gd has been compiled without Jpeg support and can therefore not convert this jpeg image.'
3137
+ );
3138
+ }
3139
+ }
3140
+ }
3141
+
3142
+ /**
3143
+ * Find out if all functions exists.
3144
+ *
3145
+ * @return boolean
3146
+ */
3147
+ private static function functionsExist($functionNamesArr)
3148
+ {
3149
+ foreach ($functionNamesArr as $functionName) {
3150
+ if (!function_exists($functionName)) {
3151
+ return false;
3152
+ }
3153
+ }
3154
+ return true;
3155
+ }
3156
+
3157
+ /**
3158
+ * Try to convert image pallette to true color on older systems that does not have imagepalettetotruecolor().
3159
+ *
3160
+ * The aim is to function as imagepalettetotruecolor, but for older systems.
3161
+ * So, if the image is already rgb, nothing will be done, and true will be returned
3162
+ * PS: Got the workaround here: https://secure.php.net/manual/en/function.imagepalettetotruecolor.php
3163
+ *
3164
+ * @param resource $image
3165
+ * @return boolean TRUE if the convertion was complete, or if the source image already is a true color image,
3166
+ * otherwise FALSE is returned.
3167
+ */
3168
+ private function makeTrueColorUsingWorkaround(&$image)
3169
+ {
3170
+ //return $this->makeTrueColor($image);
3171
+ /*
3172
+ if (function_exists('imageistruecolor') && imageistruecolor($image)) {
3173
+ return true;
3174
+ }*/
3175
+ if (self::functionsExist(['imagecreatetruecolor', 'imagealphablending', 'imagecolorallocatealpha',
3176
+ 'imagefilledrectangle', 'imagecopy', 'imagedestroy', 'imagesx', 'imagesy'])) {
3177
+ $dst = imagecreatetruecolor(imagesx($image), imagesy($image));
3178
+
3179
+ if ($dst === false) {
3180
+ return false;
3181
+ }
3182
+
3183
+ //prevent blending with default black
3184
+ if (imagealphablending($dst, false) === false) {
3185
+ return false;
3186
+ }
3187
+
3188
+ //change the RGB values if you need, but leave alpha at 127
3189
+ $transparent = imagecolorallocatealpha($dst, 255, 255, 255, 127);
3190
+
3191
+ if ($transparent === false) {
3192
+ return false;
3193
+ }
3194
+
3195
+ //simpler than flood fill
3196
+ if (imagefilledrectangle($dst, 0, 0, imagesx($image), imagesy($image), $transparent) === false) {
3197
+ return false;
3198
+ }
3199
+
3200
+ //restore default blending
3201
+ if (imagealphablending($dst, true) === false) {
3202
+ return false;
3203
+ };
3204
+
3205
+ if (imagecopy($dst, $image, 0, 0, 0, 0, imagesx($image), imagesy($image)) === false) {
3206
+ return false;
3207
+ }
3208
+ imagedestroy($image);
3209
+
3210
+ $image = $dst;
3211
+ return true;
3212
+ } else {
3213
+ // The necessary methods for converting color palette are not avalaible
3214
+ return false;
3215
+ }
3216
+ }
3217
+
3218
+ /**
3219
+ * Try to convert image pallette to true color.
3220
+ *
3221
+ * Try to convert image pallette to true color. If imagepalettetotruecolor() exists, that is used (available from
3222
+ * PHP >= 5.5.0). Otherwise using workaround found on the net.
3223
+ *
3224
+ * @param resource $image
3225
+ * @return boolean TRUE if the convertion was complete, or if the source image already is a true color image,
3226
+ * otherwise FALSE is returned.
3227
+ */
3228
+ private function makeTrueColor(&$image)
3229
+ {
3230
+ if (function_exists('imagepalettetotruecolor')) {
3231
+ return imagepalettetotruecolor($image);
3232
+ } else {
3233
+ // imagepalettetotruecolor() is not available on this system. Using custom implementation instead
3234
+ return $this->makeTrueColorUsingWorkaround($image);
3235
+ }
3236
+ }
3237
+
3238
+ /**
3239
+ * Create Gd image resource from source
3240
+ *
3241
+ * @throws InvalidInputException if mime type is unsupported or could not be detected
3242
+ * @throws ConversionFailedException if imagecreatefrompng or imagecreatefromjpeg fails
3243
+ * @return resource $image The created image
3244
+ */
3245
+ private function createImageResource()
3246
+ {
3247
+ // In case of failure, image will be false
3248
+
3249
+ $mimeType = $this->getMimeTypeOfSource();
3250
+
3251
+ if ($mimeType == 'image/png') {
3252
+ $image = imagecreatefrompng($this->source);
3253
+ if ($image === false) {
3254
+ throw new ConversionFailedException(
3255
+ 'Gd failed when trying to load/create image (imagecreatefrompng() failed)'
3256
+ );
3257
+ }
3258
+ return $image;
3259
+ }
3260
+
3261
+ if ($mimeType == 'image/jpeg') {
3262
+ $image = imagecreatefromjpeg($this->source);
3263
+ if ($image === false) {
3264
+ throw new ConversionFailedException(
3265
+ 'Gd failed when trying to load/create image (imagecreatefromjpeg() failed)'
3266
+ );
3267
+ }
3268
+ return $image;
3269
+ }
3270
+
3271
+ /*
3272
+ throw new InvalidInputException(
3273
+ 'Unsupported mime type:' . $mimeType
3274
+ );*/
3275
+ }
3276
+
3277
+ /**
3278
+ * Try to make image resource true color if it is not already.
3279
+ *
3280
+ * @param resource $image The image to work on
3281
+ * @return void
3282
+ */
3283
+ protected function tryToMakeTrueColorIfNot(&$image)
3284
+ {
3285
+ $mustMakeTrueColor = false;
3286
+ if (function_exists('imageistruecolor')) {
3287
+ if (imageistruecolor($image)) {
3288
+ $this->logLn('image is true color');
3289
+ } else {
3290
+ $this->logLn('image is not true color');
3291
+ $mustMakeTrueColor = true;
3292
+ }
3293
+ } else {
3294
+ $this->logLn('It can not be determined if image is true color');
3295
+ $mustMakeTrueColor = true;
3296
+ }
3297
+
3298
+ if ($mustMakeTrueColor) {
3299
+ $this->logLn('converting color palette to true color');
3300
+ $success = $this->makeTrueColor($image);
3301
+ if (!$success) {
3302
+ $this->logLn(
3303
+ 'Warning: FAILED converting color palette to true color. ' .
3304
+ 'Continuing, but this does NOT look good.'
3305
+ );
3306
+ }
3307
+ }
3308
+ }
3309
+
3310
+ /**
3311
+ *
3312
+ * @param resource $image
3313
+ * @return boolean true if alpha blending was set successfully, false otherwise
3314
+ */
3315
+ protected function trySettingAlphaBlending($image)
3316
+ {
3317
+ if (function_exists('imagealphablending')) {
3318
+ if (!imagealphablending($image, true)) {
3319
+ $this->logLn('Warning: imagealphablending() failed');
3320
+ return false;
3321
+ }
3322
+ } else {
3323
+ $this->logLn(
3324
+ 'Warning: imagealphablending() is not available on your system.' .
3325
+ ' Converting PNGs with transparency might fail on some systems'
3326
+ );
3327
+ return false;
3328
+ }
3329
+
3330
+ if (function_exists('imagesavealpha')) {
3331
+ if (!imagesavealpha($image, true)) {
3332
+ $this->logLn('Warning: imagesavealpha() failed');
3333
+ return false;
3334
+ }
3335
+ } else {
3336
+ $this->logLn(
3337
+ 'Warning: imagesavealpha() is not available on your system. ' .
3338
+ 'Converting PNGs with transparency might fail on some systems'
3339
+ );
3340
+ return false;
3341
+ }
3342
+ return true;
3343
+ }
3344
+
3345
+ protected function errorHandlerWhileCreatingWebP($errno, $errstr, $errfile, $errline)
3346
+ {
3347
+ $this->errorNumberWhileCreating = $errno;
3348
+ $this->errorMessageWhileCreating = $errstr . ' in ' . $errfile . ', line ' . $errline .
3349
+ ', PHP ' . PHP_VERSION . ' (' . PHP_OS . ')';
3350
+ //return false;
3351
+ }
3352
+
3353
+ /**
3354
+ *
3355
+ * @param resource $image
3356
+ * @return void
3357
+ */
3358
+ protected function destroyAndRemove($image)
3359
+ {
3360
+ imagedestroy($image);
3361
+ if (file_exists($this->destination)) {
3362
+ unlink($this->destination);
3363
+ }
3364
+ }
3365
+
3366
+ /**
3367
+ *
3368
+ * @param resource $image
3369
+ * @return void
3370
+ */
3371
+ protected function tryConverting($image)
3372
+ {
3373
+
3374
+ // Danger zone!
3375
+ // Using output buffering to generate image.
3376
+ // In this zone, Do NOT do anything that might produce unwanted output
3377
+ // Do NOT call $this->logLn
3378
+ // --------------------------------- (start of danger zone)
3379
+
3380
+ $addedZeroPadding = false;
3381
+ set_error_handler(array($this, "errorHandlerWhileCreatingWebP"));
3382
+
3383
+ // This line may trigger log, so we need to do it BEFORE ob_start() !
3384
+ $q = $this->getCalculatedQuality();
3385
+
3386
+ ob_start();
3387
+
3388
+ //$success = imagewebp($image, $this->destination, $q);
3389
+ $success = imagewebp($image, null, $q);
3390
+
3391
+ if (!$success) {
3392
+ $this->destroyAndRemove($image);
3393
+ ob_end_clean();
3394
+ restore_error_handler();
3395
+ throw new ConversionFailedException(
3396
+ 'Failed creating image. Call to imagewebp() failed.',
3397
+ $this->errorMessageWhileCreating
3398
+ );
3399
+ }
3400
+
3401
+
3402
+ // The following hack solves an `imagewebp` bug
3403
+ // See https://stackoverflow.com/questions/30078090/imagewebp-php-creates-corrupted-webp-files
3404
+ if (ob_get_length() % 2 == 1) {
3405
+ echo "\0";
3406
+ $addedZeroPadding = true;
3407
+ }
3408
+ $output = ob_get_clean();
3409
+ restore_error_handler();
3410
+
3411
+ if ($output == '') {
3412
+ $this->destroyAndRemove($image);
3413
+ throw new ConversionFailedException(
3414
+ 'Gd failed: imagewebp() returned empty string'
3415
+ );
3416
+ }
3417
+
3418
+ // --------------------------------- (end of danger zone).
3419
+
3420
+
3421
+ if ($this->errorMessageWhileCreating != '') {
3422
+ switch ($this->errorNumberWhileCreating) {
3423
+ case E_WARNING:
3424
+ $this->logLn('An warning was produced during conversion: ' . $this->errorMessageWhileCreating);
3425
+ break;
3426
+ case E_NOTICE:
3427
+ $this->logLn('An notice was produced during conversion: ' . $this->errorMessageWhileCreating);
3428
+ break;
3429
+ default:
3430
+ $this->destroyAndRemove($image);
3431
+ throw new ConversionFailedException(
3432
+ 'An error was produced during conversion',
3433
+ $this->errorMessageWhileCreating
3434
+ );
3435
+ break;
3436
+ }
3437
+ }
3438
+
3439
+ if ($addedZeroPadding) {
3440
+ $this->logLn(
3441
+ 'Fixing corrupt webp by adding a zero byte ' .
3442
+ '(older versions of Gd had a bug, but this hack fixes it)'
3443
+ );
3444
+ }
3445
+
3446
+ $success = file_put_contents($this->destination, $output);
3447
+
3448
+ if (!$success) {
3449
+ $this->destroyAndRemove($image);
3450
+ throw new ConversionFailedException(
3451
+ 'Gd failed when trying to save the image. Check file permissions!'
3452
+ );
3453
+ }
3454
+
3455
+ /*
3456
+ Previous code was much simpler, but on a system, the hack was not activated (a file with uneven number of bytes
3457
+ was created). This is puzzeling. And the old code did not provide any insights.
3458
+ Also, perhaps having two subsequent writes to the same file could perhaps cause a problem.
3459
+ In the new code, there is only one write.
3460
+ However, a bad thing about the new code is that the entire webp file is read into memory. This might cause
3461
+ memory overflow with big files.
3462
+ Perhaps we should check the filesize of the original and only use the new code when it is smaller than
3463
+ memory limit set in PHP by a certain factor.
3464
+ Or perhaps only use the new code on older versions of Gd
3465
+ https://wordpress.org/support/topic/images-not-seen-on-chrome/#post-11390284
3466
+
3467
+ Here is the old code:
3468
+
3469
+ $success = imagewebp($image, $this->destination, $this->getCalculatedQuality());
3470
+
3471
+ if (!$success) {
3472
+ throw new ConversionFailedException(
3473
+ 'Gd failed when trying to save the image as webp (call to imagewebp() failed). ' .
3474
+ 'It probably failed writing file. Check file permissions!'
3475
+ );
3476
+ }
3477
+
3478
+
3479
+ // This hack solves an `imagewebp` bug
3480
+ // See https://stackoverflow.com/questions/30078090/imagewebp-php-creates-corrupted-webp-files
3481
+ if (filesize($this->destination) % 2 == 1) {
3482
+ file_put_contents($this->destination, "\0", FILE_APPEND);
3483
+ }
3484
+ */
3485
+ }
3486
+
3487
+ // Although this method is public, do not call directly.
3488
+ // You should rather call the static convert() function, defined in AbstractConverter, which
3489
+ // takes care of preparing stuff before calling doConvert, and validating after.
3490
+ protected function doActualConvert()
3491
+ {
3492
+
3493
+ $this->logLn('GD Version: ' . gd_info()["GD Version"]);
3494
+
3495
+ // Btw: Check out processWebp here:
3496
+ // https://github.com/Intervention/image/blob/master/src/Intervention/Image/Gd/Encoder.php
3497
+
3498
+ // Create image resource
3499
+ $image = $this->createImageResource();
3500
+
3501
+ // Try to convert color palette if it is not true color
3502
+ $this->tryToMakeTrueColorIfNot($image);
3503
+
3504
+
3505
+ if ($this->getMimeTypeOfSource() == 'image/png') {
3506
+ // Try to set alpha blending
3507
+ $this->trySettingAlphaBlending($image);
3508
+ }
3509
+
3510
+ // Try to convert it to webp
3511
+ $this->tryConverting($image);
3512
+
3513
+ // End of story
3514
+ imagedestroy($image);
3515
+ }
3516
+ }
3517
+
3518
+ ?><?php
3519
+
3520
+ namespace WebPConvert\Convert\Converters;
3521
+
3522
+ use WebPConvert\Convert\Converters\AbstractConverter;
3523
+ use WebPConvert\Convert\Exceptions\ConversionFailedException;
3524
+ use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
3525
+ use WebPConvert\Convert\Converters\ConverterTraits\EncodingAutoTrait;
3526
+
3527
+ //use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\TargetNotFoundException;
3528
+
3529
+ /**
3530
+ * Convert images to webp using Gmagick extension.
3531
+ *
3532
+ * @package WebPConvert
3533
+ * @author Bjørn Rosell <it@rosell.dk>
3534
+ * @since Class available since Release 2.0.0
3535
+ */
3536
+ class Gmagick extends AbstractConverter
3537
+ {
3538
+ use EncodingAutoTrait;
3539
+
3540
+ protected function getUnsupportedDefaultOptions()
3541
+ {
3542
+ return [
3543
+ 'near-lossless',
3544
+ 'preset',
3545
+ 'size-in-percentage',
3546
+ 'use-nice'
3547
+ ];
3548
+ }
3549
+
3550
+ /**
3551
+ * Check (general) operationality of Gmagick converter.
3552
+ *
3553
+ * Note:
3554
+ * It may be that Gd has been compiled without jpeg support or png support.
3555
+ * We do not check for this here, as the converter could still be used for the other.
3556
+ *
3557
+ * @throws SystemRequirementsNotMetException if system requirements are not met
3558
+ */
3559
+ public function checkOperationality()
3560
+ {
3561
+ if (!extension_loaded('Gmagick')) {
3562
+ throw new SystemRequirementsNotMetException('Required Gmagick extension is not available.');
3563
+ }
3564
+
3565
+ if (!class_exists('Gmagick')) {
3566
+ throw new SystemRequirementsNotMetException(
3567
+ 'Gmagick is installed, but not correctly. The class Gmagick is not available'
3568
+ );
3569
+ }
3570
+
3571
+ $im = new \Gmagick($this->source);
3572
+
3573
+ if (!in_array('WEBP', $im->queryformats())) {
3574
+ throw new SystemRequirementsNotMetException('Gmagick was compiled without WebP support.');
3575
+ }
3576
+ }
3577
+
3578
+ /**
3579
+ * Check if specific file is convertable with current converter / converter settings.
3580
+ *
3581
+ * @throws SystemRequirementsNotMetException if Gmagick does not support image type
3582
+ */
3583
+ public function checkConvertability()
3584
+ {
3585
+ $im = new \Gmagick();
3586
+ $mimeType = $this->getMimeTypeOfSource();
3587
+ switch ($mimeType) {
3588
+ case 'image/png':
3589
+ if (!in_array('PNG', $im->queryFormats())) {
3590
+ throw new SystemRequirementsNotMetException(
3591
+ 'Gmagick has been compiled without PNG support and can therefore not convert this PNG image.'
3592
+ );
3593
+ }
3594
+ break;
3595
+ case 'image/jpeg':
3596
+ if (!in_array('JPEG', $im->queryFormats())) {
3597
+ throw new SystemRequirementsNotMetException(
3598
+ 'Gmagick has been compiled without Jpeg support and can therefore not convert this Jpeg image.'
3599
+ );
3600
+ }
3601
+ break;
3602
+ }
3603
+ }
3604
+
3605
+ // Although this method is public, do not call directly.
3606
+ // You should rather call the static convert() function, defined in AbstractConverter, which
3607
+ // takes care of preparing stuff before calling doConvert, and validating after.
3608
+ protected function doActualConvert()
3609
+ {
3610
+
3611
+ $options = $this->options;
3612
+
3613
+ try {
3614
+ $im = new \Gmagick($this->source);
3615
+ } catch (\Exception $e) {
3616
+ throw new ConversionFailedException(
3617
+ 'Failed creating Gmagick object of file',
3618
+ 'Failed creating Gmagick object of file: "' . $this->source . '" - Gmagick threw an exception.',
3619
+ $e
3620
+ );
3621
+ }
3622
+
3623
+ $im->setimageformat('WEBP');
3624
+
3625
+ // Not completely sure if setimageoption() has always been there, so lets check first. #169
3626
+